Author: assaf
Date: Fri Aug 15 17:46:30 2008
New Revision: 686424
URL: http://svn.apache.org/viewvc?rev=686424&view=rev
Log:
Fixed authentication mechanism:
* HTTP Basic allowed in all cases, and takes precedence over sessions
* Access key parameter allowed on all Atom/iCal GET requests
* When all else fails, fall on session (HTML) or HTTP Basic
Modified:
ode/sandbox/singleshot/app/controllers/activity_controller.rb
ode/sandbox/singleshot/app/controllers/application.rb
ode/sandbox/singleshot/app/controllers/sparklines_controller.rb
ode/sandbox/singleshot/app/controllers/tasks_controller.rb
ode/sandbox/singleshot/app/models/activity.rb
ode/sandbox/singleshot/app/models/task.rb
ode/sandbox/singleshot/app/views/tasks/tasks.atom.builder
ode/sandbox/singleshot/spec/controllers/authentication_spec.rb
Modified: ode/sandbox/singleshot/app/controllers/activity_controller.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/activity_controller.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/activity_controller.rb (original)
+++ ode/sandbox/singleshot/app/controllers/activity_controller.rb Fri Aug 15
17:46:30 2008
@@ -16,8 +16,6 @@
class ActivityController < ApplicationController
- access_key_authentication
-
def index
@title = 'Activities'
@subtitle = 'Track activity in tasks you participate in or observe.'
Modified: ode/sandbox/singleshot/app/controllers/application.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/application.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/application.rb (original)
+++ ode/sandbox/singleshot/app/controllers/application.rb Fri Aug 15 17:46:30
2008
@@ -5,21 +5,18 @@
helper :all # include all helpers, all the time
- # See ActionController::Base for details
- # Uncomment this to filter the contents of submitted sensitive data
parameters
- # from your application log (in this case, all fields with names like
"password").
- filter_parameter_logging :password
-
def index
redirect_to tasks_url
end
+protected
- # --- Authentication ---
+ # --- Authentication/Security ---
- # Turn sessions off for everything but HTML and AJAX. This also forces HTTP
Basic or access key
- # authentication on all other content types (JSON, iCal, etc).
- session :off, :if=>lambda { |req| !(req.format.html? || req.xhr?) }
+ # See ActionController::Base for details
+ # Uncomment this to filter the contents of submitted sensitive data
parameters
+ # from your application log (in this case, all fields with names like
"password").
+ filter_parameter_logging :password
# See ActionController::RequestForgeryProtection for details
# Uncomment the :secret if you're not using the cookie session store
@@ -27,46 +24,39 @@
before_filter :authenticate
- # Authentication filter, added by default on all actions in all controllers.
- # Uses HTTP Basic Authentication or, when processing HTML/AJAX requests,
sessions.
+ # Authentication filter enabled by default since most resources are guarded.
def authenticate
- return if @authenticated
- if ActionController::HttpAuthentication::Basic.authorization(request) ||
!session_enabled?
- authenticate_or_request_with_http_basic request.domain do |login,
password|
- @authenticated = Person.authenticate(login, password)
- end
+ # Good luck using HTTP Basic/sessions with feed readers and calendar apps.
+ # Instead we use a query parameter tacked to the URL to authenticate, and
+ # given the lax security, only for these resources and only for GET
requests.
+ if params[:access_key] && (request.format.atom? || request.format.ics?)
+ raise ActionController::MethodNotAllowed, 'GET' unless request.get?
+ session.data.clear # don't send back cookies
+ @authenticated = Person.find_by_access_key(params[:access_key])
+ head :forbidden unless @authenticated
else
- @authenticated ||= Person.find(session[:person_id]) rescue nil
- unless @authenticated
- flash[:return_to] = request.url
- redirect_to session_url
+ # Favoring HTTP Basic over sessions makes my debugging life easier.
+ if ActionController::HttpAuthentication::Basic.authorization(request)
+ @authenticated = authenticate_or_request_with_http_basic(request.host)
{ |login, password| Person.authenticate(login, password) }
+ session.data.clear
+ else
+ @authenticated = Person.find(session[:person_id]) rescue nil
+ unless @authenticated
+ # Browsers respond favorably to this test, so we use it to detect
browsers
+ # and redirect the use to a login page. Otherwise we assume dumb
machine and
+ # insist on HTTP Basic.
+ if request.format.html?
+ flash[:return_to] = request.url
+ redirect_to session_url
+ else
+ session.data.clear
+ request_http_basic_authentication
+ end
+ end
end
end
end
-
- def self.access_key_authentication(options = {})
- formats = options[:formats] || ['atom', 'ics']
- options[:if] ||= lambda { |controller|
formats.include?(controller.request.format) }
- prepend_before_filter :authenticate_with_access_key, options
- end
-
- # Access key authentication, used for feeds, iCal and other type of requets
that
- # do not support HTTP authentication or sessions. Can only be used for GET
requests.
- #
- # To apply as a filter (must come before authenticate):
- # prepend_before_filter :authenticate_with_access_key, :only=>[:feed]
- def authenticate_with_access_key
- raise ActionController::MethodNotAllowed, 'GET' unless request.get?
- @authenticated = Person.find_by_access_key(params[:access_key]) or raise
ActiveRecord::RecordNotFound
- end
-
-=begin
- # Raise to return 403 (Forbidden) with optional error message.
- class NotAuthorized < Exception
- end
- rescue_responses[NotAuthorized.name] = :forbidden
-=end
-
+
# --- Authenticated user ---
Modified: ode/sandbox/singleshot/app/controllers/sparklines_controller.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/sparklines_controller.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/sparklines_controller.rb (original)
+++ ode/sandbox/singleshot/app/controllers/sparklines_controller.rb Fri Aug 15
17:46:30 2008
@@ -2,7 +2,7 @@
# Copyright (c) 2005 Geoffrey Grosenbach [EMAIL PROTECTED]
class Sparklines
-
+
## Creates a deadline sparkline.
#
# A deadline accepts pre-deadline (negative) and post-deadline (positive
@@ -89,6 +89,7 @@
class SparklinesController < ApplicationController
skip_before_filter :authenticate
+ session :off
COLOR_SCHEME = { :above_color=>'#65a0e4', :below_color=>'#a8c0d8',
:target=>50, :target_color=>'#ffffd0', :background_color=>'transparent' }
Modified: ode/sandbox/singleshot/app/controllers/tasks_controller.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/controllers/tasks_controller.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/controllers/tasks_controller.rb (original)
+++ ode/sandbox/singleshot/app/controllers/tasks_controller.rb Fri Aug 15
17:46:30 2008
@@ -1,7 +1,5 @@
class TasksController < ApplicationController
- access_key_authentication :only=>[:index, :completed, :following, :search,
:show]
-
verify :params=>:task, :only=>:update, :render=>{:text=>'Missing task',
:status=>:bad_request}
before_filter :set_task, :only=>[:show, :update, :complete, :destroy]
skip_filter :authenticate, :only=>[:opensearch]
Modified: ode/sandbox/singleshot/app/models/activity.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/activity.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/activity.rb (original)
+++ ode/sandbox/singleshot/app/models/activity.rb Fri Aug 15 17:46:30 2008
@@ -1,3 +1,15 @@
+# == Schema Information
+# Schema version: 20080621023051
+#
+# Table name: activities
+#
+# id :integer not null, primary key
+# person_id :integer
+# task_id :integer not null
+# name :string(255) not null
+# created_at :datetime not null
+#
+
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with this
# work for additional information regarding copyright ownership. The ASF
@@ -14,18 +26,6 @@
# the License.
-# == Schema Information
-# Schema version: 20080621023051
-#
-# Table name: activities
-#
-# id :integer not null, primary key
-# person_id :integer
-# task_id :integer not null
-# action :string(255) not null
-# created_at :datetime not null
-#
-
class Activity < ActiveRecord::Base
belongs_to :person
Modified: ode/sandbox/singleshot/app/models/task.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/task.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/task.rb (original)
+++ ode/sandbox/singleshot/app/models/task.rb Fri Aug 15 17:46:30 2008
@@ -8,9 +8,11 @@
# description :string(255) not null
# priority :integer(1) not null
# due_on :date
+# start_by :date
# status :string(255) not null
# perform_url :string(255)
# details_url :string(255)
+# instructions :string(255)
# integrated_ui :boolean
# outcome_url :string(255)
# outcome_type :string(255)
Modified: ode/sandbox/singleshot/app/views/tasks/tasks.atom.builder
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/views/tasks/tasks.atom.builder?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/views/tasks/tasks.atom.builder (original)
+++ ode/sandbox/singleshot/app/views/tasks/tasks.atom.builder Fri Aug 15
17:46:30 2008
@@ -2,6 +2,8 @@
feed.title "Singleshot: [EMAIL PROTECTED]"
feed.subtitle @subtitle
feed.updated @tasks.map(&:updated_at).max
+ feed.link :href=>@next, :rel=>'next', :type=>Mime::ATOM if @next
+ feed.link :href=>@previous, :rel=>'previous', :type=>Mime::ATOM if @previous
feed.link :href=>@alternate[Mime::ICS], :rel=>'alternate', :type=>Mime::ICS
if @alternate[Mime::ICS]
feed.generator 'Singleshot', :version=>Singleshot::VERSION
Modified: ode/sandbox/singleshot/spec/controllers/authentication_spec.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/spec/controllers/authentication_spec.rb?rev=686424&r1=686423&r2=686424&view=diff
==============================================================================
--- ode/sandbox/singleshot/spec/controllers/authentication_spec.rb (original)
+++ ode/sandbox/singleshot/spec/controllers/authentication_spec.rb Fri Aug 15
17:46:30 2008
@@ -1,7 +1,6 @@
require File.dirname(__FILE__) + '/../spec_helper'
class AuthenticationTestController < ApplicationController
- prepend_before_filter :authenticate_with_access_key, :only=>['feed']
def index
end
@@ -11,121 +10,116 @@
end
-share_examples_for 'AuthenticationTest' do
+describe 'Authentication' do
controller_name :authentication_test
-
+
before :all do
@person = Person.create(:email=>'[EMAIL PROTECTED]', :password=>'secret')
end
-
- after :all do
- Person.delete_all
- end
-end
-
-
-describe 'Unauthenticated request' do
- controller_name :authentication_test
-
- it 'should redirect to login page when requesting HTML' do
- get 'index'
- response.should redirect_to('/session')
- end
-
- it 'should pass request URL in return_to session parameter' do
- get 'index'
- flash[:return_to].should
eql(controller.url_for(:controller=>'authentication_test', :action=>'index'))
- end
-
- it 'should return 401 when requesting XML document' do
- get 'index', :format=>'xml'
- response.should be(:unauthorized)
- end
-
- it 'should return 401 when requesting JSON document' do
- get 'index', :format=>'json'
- response.should be(:unauthorized)
- end
-end
-
-
-describe 'Session authentication' do
- it_should_behave_like 'AuthenticationTest'
-
- it 'should redirect to login page if person does not exist' do
- get 'index', nil, :person_id=>0
- response.should redirect_to('/session')
- end
-
- it 'should return response if user account exists' do
- get 'index', nil, :person_id=>@person.id
- response.should be(:ok)
- end
-
- it 'should assign authenticated user in controller' do
- get 'index', nil, :person_id=>@person.id
- assigns[:authenticated].should == @person
- end
-end
-
-
-describe 'Query param authentication' do
- it_should_behave_like 'AuthenticationTest'
-
- it 'should have no affect unless applied to action' do
- get 'index'
- response.should redirect_to('/session')
+
+ describe 'Unauthenticated request' do
+ it 'should redirect to login page when requesting HTML' do
+ get 'index'
+ response.should redirect_to('/session')
+ end
+
+ it 'should redirect to login page with request URL in return_to session
value' do
+ get 'index'
+ flash[:return_to].should
eql(controller.url_for(:controller=>'authentication_test', :action=>'index'))
+ end
+
+ it 'should return 401 when requesting XML document' do
+ get 'index', :format=>'xml'
+ response.should be(:unauthorized)
+ end
+
+ it 'should return 401 when requesting JSON document' do
+ get 'index', :format=>'json'
+ response.should be(:unauthorized)
+ end
+ end
+
+
+ describe 'Session' do
+ it 'should redirect to login page if person does not exist' do
+ get 'index', nil, :person_id=>0
+ response.should redirect_to('/session')
+ end
+
+ it 'should return response if user account exists' do
+ get 'index', nil, :person_id=>@person.id
+ response.should be(:ok)
+ end
+
+ it 'should assign authenticated user to controller' do
+ get 'index', nil, :person_id=>@person.id
+ assigns[:authenticated].should == @person
+ end
+ end
+
+
+ describe 'HTTP Basic' do
+ it 'should return response if authenticated' do
+ request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('lucy', 'secret')
+ get 'index'
+ response.should be(:ok)
+ end
+
+ it 'should assign authenticated user in controller' do
+ request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('lucy', 'secret')
+ get 'index'
+ assigns[:authenticated].should == @person
+ end
+
+ it 'should return 401 if not authenticated' do
+ request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('lucy', 'wrong')
+ get 'index'
+ response.should be(:unauthorized)
+ end
+
+ it 'should not fail on password-less account' do
+ Person.create(:email=>'[EMAIL PROTECTED]')
+ request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('mary', 'wrong')
+ get 'index'
+ response.should be(:unauthorized)
+ end
+ end
+
+
+ describe 'Access key' do
+ it 'should work when requesting Atom representation' do
+ get 'feed', :access_key=>@person.access_key, :format=>Mime::ATOM
+ response.should be(:ok)
+ end
+
+ it 'should work when requesting iCal representation' do
+ get 'feed', :access_key=>@person.access_key, :format=>Mime::ICS
+ response.should be(:ok)
+ end
+
+ it 'should not apply to content types other than Atom/iCal' do
+ get 'feed', :access_key=>@person.access_key, :format=>Mime::HTML
+ response.should redirect_to('/session')
+ end
+
+ it 'should return 403 if not matching user\'s access key' do
+ get 'feed', 'access_key'=>'wrong', :format=>Mime::ATOM
+ response.should be(:forbidden)
+ end
+
+ it 'should return 405 if POST request' do
+ lambda { post 'feed', 'access_key'=>@person.access_key,
:format=>Mime::ATOM }.
+ should raise_error(ActionController::MethodNotAllowed, /Only GET/)
+ end
+
+ it 'should assign authenticated user in controller' do
+ get 'feed', :access_key=>@person.access_key, :format=>Mime::ATOM
+ assigns[:authenticated].should == @person
+ end
end
- it 'should return 404 if not matching user\'s access key' do
- lambda { get 'feed', :access_key=>'wrong' }.should
raise_error(ActiveRecord::RecordNotFound)
- end
- it 'should return 404 if no access key provided' do
- lambda { get 'feed' }.should raise_error(ActiveRecord::RecordNotFound)
- end
-
- it 'should return 405 if POST request' do
- lambda { post 'feed', :access_key=>@person.access_key }.should
raise_error(ActionController::MethodNotAllowed, /Only GET/)
- end
-
- it 'should return response if matching user\'s access key' do
- get 'feed', :access_key=>@person.access_key
- response.should be(:ok)
- end
-
- it 'should assign authenticated user in controller' do
- get 'feed', :access_key=>@person.access_key
- assigns[:authenticated].should == @person
- end
-end
-
-
-describe 'HTTP Basic username/password' do
- it_should_behave_like 'AuthenticationTest'
-
- it 'should return 401 if not authenticated' do
- request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('lucy', 'wrong')
- get 'index'
- response.should be(:unauthorized)
- end
-
- it 'should return response if authenticated' do
- request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('lucy', 'secret')
- get 'index'
- response.should be(:ok)
- end
-
- it 'should assign authenticated user in controller' do
- request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('lucy', 'secret')
- get 'index'
- assigns[:authenticated].should == @person
- end
-
- it 'should not fail on password-less account' do
- Person.create(:email=>'[EMAIL PROTECTED]')
- request.env['HTTP_AUTHORIZATION'] =
ActionController::HttpAuthentication::Basic.encode_credentials('mary', 'wrong')
- get 'index'
- response.should be(:unauthorized)
+ after :all do
+ Person.delete_all
end
end