rack-casual 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Gudleik Rasch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ Rack::Casual
2
+ ============
3
+
4
+ A very simple Rack authentication plugin using CAS or a token.
5
+ It kicks in whenever a 401 response is returned from the server.
6
+
7
+ The plugin has only been tested using ActiveRecord and Rails 3.
8
+
9
+ Installation
10
+ ============
11
+
12
+ Add this to your Gemfile:
13
+
14
+ gem 'rack-casual'
15
+
16
+ Run bundle install, and add a configuration file:
17
+
18
+ $ rails generate rack_casual
19
+
20
+ This creates a config/initializers/rack-casual.rb file.
21
+ Make sure base_url points to your CAS server.
22
+ If your user model is called something other than "User", you can change this here.
23
+
24
+ Next you must configure your application to use the plugin.
25
+ For Rails3, you can add this to your config/application.rb
26
+ config.middleware.use "Rack::Casual::Authentication"
27
+
28
+ Finally, to authenticate your users, add a before_filter to your controller:
29
+
30
+ class ApplicationController < ActionController::Base
31
+ before_filter :authenticate!
32
+ end
33
+
34
+
35
+ Usage
36
+ =====
37
+
38
+ Rack::Casual adds some helper methods to ActionController::Base
39
+
40
+ * logged_in?
41
+ Returns true if session contains user-id
42
+
43
+ * current_user
44
+ Returns the currently logged in user.
45
+
46
+ * authenticate!
47
+ This is the method you want to use in a before_filter
48
+
49
+
50
+ Authentication token
51
+ ====================
52
+
53
+ CAS is nice and all that, but it's not so nice for webservices.
54
+ Therefore Rack::Casual can authenticate requests using a token.
55
+ Make sure your User model has a auth_token attribute. You can call it whatever you want, but it defaults to auth_token.
56
+
57
+ From your client you can now authenticate using this token:
58
+
59
+ http://your-app.com/my-protected-webservice?auth_token=secret
60
+
61
+ If there are no users with that token, the client just receives the 401 error.
62
+ It does not fallback to CAS or create a user automatically (doh).
63
+
64
+
65
+ Finding users
66
+ =============
67
+
68
+ If you want to control how Rack::Casual finds the user, you can set a scope to be used.
69
+ # config/initializers/rack-casual.rb:
70
+ config.authentication_scope = :active
71
+
72
+ # app/models/user.rb
73
+ class User < ActiveRecord::Base
74
+ def self.active
75
+ where(:active => true)
76
+ end
77
+ end
78
+
79
+ Then Rack::Casual will only search among users where active is true.
80
+ A side effect of this is that Rack::Casual will try to create a user that already exists.
81
+ However, this should not be a problem as long as your User model validates the uniqueness of the username.
82
+
83
+ The default scope to use is
84
+
85
+ Extra attributes
86
+ ================
87
+
88
+ When creating users automatically, Rack::Casual can also add extra attributes if your CAS server provides this.
89
+ For this to work your User model must have a cas_extra_attributes= instance method.
90
+ Here's an example:
91
+
92
+ class User < ActiveRecord::Base
93
+ def cas_extra_attributes=(extra_attributes)
94
+ extra_attributes.each do |name, value|
95
+ case name.to_sym
96
+ when :name then self.name = value
97
+ when :email then self.email = value
98
+ when :phone then self.phone = value
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+
105
+ Tracking
106
+ ========
107
+
108
+ If you have enabled tracking, Rack::Casual can update the logged in user with information about last login time and IP.
109
+ These variables will be updated if they are present in your User model:
110
+ * last_login_at (datetime)
111
+ * last_login_ip (string)
112
+ * login_count (integer)
113
+
114
+ TODO
115
+ ====
116
+
117
+ 1. Testing. How embarrasing. A gem without tests is like a forrest without trees.
118
+ 2. Replace ruby-cas with something "lighter", like casual, but casual doesn't seem to support extra attributes...
119
+ Note to self: http://rubycas-client.rubyforge.org/classes/CASClient/ValidationResponse.src/M000044.html
120
+
121
+ Copyright (c) 2010 Gudleik Rasch <gudleik@gmail.com>, released under the MIT license
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Installs an initializer in config/initializers/ for Rack::Casual
3
+
4
+ Example:
5
+ rails generate rack_casual
6
+
7
+ This will create:
8
+ config/initializers/rack-casual.rb
@@ -0,0 +1,8 @@
1
+ class RackCasualGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def copy_initializer
5
+ copy_file "initializer.rb", "config/initializers/rack-casual.rb"
6
+ end
7
+
8
+ end
@@ -0,0 +1,58 @@
1
+ # Configuration for Rack::Casual
2
+ # Commented values are default.
3
+
4
+ Rack::Casual.setup do |config|
5
+
6
+ # Base URL to your CAS server -- required
7
+ config.base_url = 'http://localhost:8088'
8
+
9
+ # If you want users to authenticate using an authentication token,
10
+ # set the auth_token_key to the name of the attribute in your user model.
11
+ # Users can now authenticate using http://your-app.com/?auth_token=a-very-secret-key
12
+ # Setting this value to nil disables token authentication.
13
+ # config.auth_token_key = 'auth_token'
14
+
15
+ # Name of the session key used to store the user-id
16
+ # Default is "user", so you get session[:user]
17
+ # config.session_key_user = "user"
18
+
19
+ # Rack::Casual can create the user automatically on successful login
20
+ # Set this to false to disable this feature.
21
+ # If you want to include extra attributes provided by your CAS server,
22
+ # you must add a cas_extra_attributes=(attributes) method in your User model.
23
+ # See the README for an example.
24
+ # config.create_user = true
25
+
26
+ # If you have enabled create_user, here you can specify the name of your
27
+ # user class. Defaults to 'User'.
28
+ # config.user_class = "User"
29
+
30
+ # This is the username attribute used by your User model.
31
+ # config.username = "username"
32
+
33
+ # Finding the user
34
+ # You can set a custom scope that Rack::Casual should use when finding the user.
35
+ # If you have a active scope, you can set this to :active.
36
+ # config.authentication_scope = nil
37
+
38
+ # Tracking
39
+ # If you have last_login_at and/or last_login_ip attributes on your User model,
40
+ # Rack::Casual can update these when user logs in.
41
+ # config.enable_tracking = true
42
+
43
+ # Name of the ticket parameter used by CAS.
44
+ # config.ticket_param = 'ticket'
45
+
46
+ # URL to the service validation on your CAS server.
47
+ # nil = use defaults
48
+ # config.validate_url = nil
49
+
50
+ # CAS login url.
51
+ # nil = use defaults
52
+ # config.login_url = nil
53
+
54
+ # CAS logout url.
55
+ # nil = use defaults
56
+ # config.logout_url = nil
57
+
58
+ end
@@ -0,0 +1,79 @@
1
+ module Rack
2
+
3
+ module Casual
4
+
5
+ class Authentication
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ @request = Rack::Request.new(env)
13
+ @response = @app.call(env)
14
+ @env = env
15
+
16
+ process_request_from_cas
17
+ handle_401
18
+ end
19
+
20
+ private
21
+
22
+ def process_request_from_cas
23
+ if ticket = read_ticket
24
+ if user = UserFactory.authenticate_with_cas_ticket(ticket, @request)
25
+ # TODO: remove the params['ticket'] so the app doesn't see this...
26
+ @request.session[Rack::Casual.session_key_user] = user.id
27
+ else
28
+ # [ 403, { "Content-Type" => "text/plain" }, "Sorry, I was unable to authenticate you" ]
29
+ end
30
+ end
31
+ end
32
+
33
+ def handle_401
34
+ return @response unless @response[0] == 401
35
+
36
+ if Rack::Casual.auth_token_key && @request.params[Rack::Casual.auth_token_key]
37
+ authenticate_with_token
38
+ else
39
+ redirect_to_cas
40
+ end
41
+ end
42
+
43
+ def authenticate_with_token
44
+ user = UserFactory.authenticate_with_token(@request)
45
+ @request.session[Rack::Casual.session_key_user] = user.id if user
46
+ @app.call(@env)
47
+ end
48
+
49
+ def redirect_to_cas
50
+ url = Rack::Casual.cas_client.add_service_to_login_url(service_url)
51
+ [ 302,
52
+ {
53
+ "Location" => url,
54
+ "Content-Type" => "text/plain"
55
+ },
56
+ "Redirecting to CAS for authentication"
57
+ ]
58
+ end
59
+
60
+ def service_url
61
+ @request.url.sub(/[\?&]#{Rack::Casual.ticket_param}=[^\?&]+/, '')
62
+ end
63
+
64
+ # Read ticket from params and create a CASClient ticket
65
+ def read_ticket
66
+ ticket = @request.params[Rack::Casual.ticket_param]
67
+ return nil unless ticket
68
+
69
+ if ticket =~ /^PT-/
70
+ ::CASClient::ProxyTicket.new(ticket, service_url, @request.params[:renew])
71
+ else
72
+ ::CASClient::ServiceTicket.new(ticket, service_url, @request.params[:renew])
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,22 @@
1
+ module Rack
2
+
3
+ module Casual
4
+
5
+ module Controller
6
+
7
+ def authenticate!
8
+ authenticate_or_request_with_http_token unless logged_in?
9
+ end
10
+
11
+ def logged_in?
12
+ !session[::Rack::Casual.session_key_user].nil?
13
+ end
14
+
15
+ def current_user
16
+ @current_user ||= ::Rack::Casual::UserFactory.authentication_scope.find(session[:user])
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,79 @@
1
+ # encoding: utf-8
2
+ module Rack
3
+ module Casual
4
+
5
+ class UserFactory
6
+
7
+ def self.authenticate_with_cas_ticket(ticket, request)
8
+ user = nil
9
+ Rack::Casual.cas_client.validate_service_ticket(ticket) unless ticket.has_been_validated?
10
+
11
+ if ticket.is_valid?
12
+
13
+ # find user
14
+ user = find(ticket.response.user)
15
+
16
+ if user.nil? && Rack::Casual.create_user
17
+ user = make(ticket.response.user)
18
+ end
19
+
20
+ return nil unless user
21
+
22
+ if user.respond_to?(:cas_extra_attributes=)
23
+ user.cas_extra_attributes = ticket.response.extra_attributes
24
+ end
25
+
26
+ update_tracking(user, request)
27
+ end
28
+
29
+ user
30
+ end
31
+
32
+ protected
33
+
34
+ def self.authenticate_with_token(request)
35
+ user = authentication_scope.where(Rack::Casual.auth_token_key => request.params[Rack::Casual.auth_token_key]).first
36
+ update_tracking(user, request) if user
37
+ user
38
+ end
39
+
40
+ # Update tracking info (last logged in at / ip) if tracking_enabled is set.
41
+ # Saves the user regardless of whether tracking was updated or not.
42
+ def self.update_tracking(user, request)
43
+ if Rack::Casual.tracking_enabled
44
+ user.last_login_at = Time.now if user.respond_to?(:last_login_at)
45
+ user.last_login_ip = request.ip if user.respond_to?(:last_login_ip)
46
+ user.login_count += 1 if user.respond_to?(:login_count)
47
+ end
48
+ user.save
49
+ end
50
+
51
+ # Find user by username
52
+ def self.find(username)
53
+ authentication_scope.where(Rack::Casual.username => username).first
54
+ end
55
+
56
+ # Initializes a new user
57
+ def self.make(username)
58
+ resource.new(Rack::Casual.username => username)
59
+ end
60
+
61
+ # Returns the user class
62
+ def self.resource
63
+ Rack::Casual.user_class.constantize
64
+ end
65
+
66
+ # Returns the scope used to find users
67
+ def self.authentication_scope
68
+ if Rack::Casual.authentication_scope
69
+ puts "Authentication scope is kinda broken and should be avoided"
70
+ resource.send(Rack::Casual.authentication_scope)
71
+ else
72
+ resource.scoped
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,92 @@
1
+ # RackCasual
2
+ # encoding: utf-8
3
+
4
+ module Rack
5
+
6
+ module Casual
7
+
8
+ autoload :Authentication, 'rack/casual/authentication'
9
+ autoload :UserFactory, 'rack/casual/user_factory'
10
+ autoload :Controller, 'rack/casual/controller'
11
+
12
+ # Base URI to your CAS server
13
+ mattr_accessor :base_url
14
+ @@base_url = 'http://192.168.0.15:8088'
15
+
16
+ # URI to CAS login
17
+ # Default is base_url/login
18
+ mattr_accessor :login_url
19
+ @@login_url = nil
20
+
21
+ # URI to CAS logout
22
+ # Default is base_url/logout
23
+ mattr_accessor :logout_url
24
+ @@logout_url = nil
25
+
26
+ # URI to service validation
27
+ # Default is base_url/serviceValidate
28
+ mattr_accessor :validate_url
29
+ @@validate_url = nil
30
+
31
+ # Name of authentication token to use in params
32
+ # Set to nil to disable token authentication
33
+ mattr_accessor :auth_token_key
34
+ @@auth_token_key = "auth_token"
35
+
36
+ # Name of the ticket parameter used by CAS
37
+ mattr_accessor :ticket_param
38
+ @@ticket_param = "ticket"
39
+
40
+ # Name of key to store user id
41
+ # Default is 'user' => session[:user]
42
+ mattr_accessor :session_key_user
43
+ @@session_key_user = "user"
44
+
45
+ # Use this scope when finding users
46
+ mattr_accessor :authentication_scope
47
+ @@authentication_scope = nil
48
+
49
+ # Set to true to auto-create users
50
+ mattr_accessor :create_user
51
+ @@create_user = true
52
+
53
+ # Name of the User class
54
+ mattr_accessor :user_class
55
+ @@user_class = "User"
56
+
57
+ # Username attribute on user
58
+ mattr_accessor :username
59
+ @@username = "username"
60
+
61
+ # Update user with last_login_at and last_login_ip info
62
+ mattr_accessor :tracking_enabled
63
+ @@tracking_enabled = true
64
+
65
+ # Default way to setup Devise. Run rails generate devise_install to create
66
+ # a fresh initializer with all configuration values.
67
+ def self.setup
68
+ yield self
69
+ end
70
+
71
+ def self.cas_client
72
+ @@cas_client ||= ::CASClient::Client.new(
73
+ :cas_base_url => @@base_url,
74
+ :login_url => @@login_url,
75
+ :logout_url => @@logout_url,
76
+ :validate_url => @@validate_url
77
+ )
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+
84
+ if defined?(ActionController::Base)
85
+ class ActionController::Base
86
+ include ::Rack::Casual::Controller
87
+
88
+ # before_filter :authenticate!
89
+
90
+ helper_method :logged_in?, :current_user
91
+ end
92
+ end
@@ -0,0 +1,2 @@
1
+ require 'rubycas-client'
2
+ require 'rack/casual'
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-casual
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Gudleik Rasch
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-02 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rubycas-client
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 2
31
+ - 1
32
+ version: 2.2.1
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 3
45
+ - 0
46
+ version: "3.0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: Rack module for authentication using CAS and/or tokens
50
+ email:
51
+ - gudleik@gmail.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - lib/generators/rack_casual_generator.rb
60
+ - lib/generators/templates/initializer.rb
61
+ - lib/generators/USAGE
62
+ - lib/rack/casual/authentication.rb
63
+ - lib/rack/casual/controller.rb
64
+ - lib/rack/casual/user_factory.rb
65
+ - lib/rack/casual.rb
66
+ - lib/rack-casual.rb
67
+ - LICENSE
68
+ - README.md
69
+ has_rdoc: true
70
+ homepage: http://github.com/gudleik/rack-casual
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options: []
75
+
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 1
93
+ - 3
94
+ - 7
95
+ version: 1.3.7
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.7
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: CAS and token authentication using Rack
103
+ test_files: []
104
+