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


Reply via email to