factorylabs-casrack_the_authenticator 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ doc/rdoc
2
+ .yardoc
3
+ pkg
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ Casrack the Authenticator is a
2
+ Rack[http://github.com/chneukirchen/rack] middleware
3
+ that provides CAS[http://www.jasig.org/cas] support.
4
+
5
+ As of the current version, Casrack the Authenticator only supports the most basic
6
+ of CAS scenarios: it requires CAS authentication if it receives a 401 Unauthorized
7
+ response from lower-down in the Rack stack, and it stores the authentication token
8
+ in the session (so logout happens when users close their browers). Casrack the Authenticator
9
+ is a very open-minded beast, though, so please contribute (well-tested) additions to do
10
+ proxy-authentication and single-sign-out, or for anything else you desire.
11
+
12
+ === How-To
13
+
14
+ ==== 1: install
15
+
16
+ [sudo] gem install casrack_the_authenticator
17
+
18
+ ==== 2: set up the middleware:
19
+
20
+ # in your rackup:
21
+ use CasrackTheAuthenticator::Simple, :cas_server => "http://cas.mycompany.com/cas"
22
+ # or "config.middleware.use" if you're on Rails
23
+
24
+ See CasrackTheAuthenticator::Configuration for specifics on that Hash argument.
25
+
26
+ ==== 3: optionally install CasrackTheAuthenticator::RequireCAS if you want _every_ request to require CAS authentication:
27
+
28
+ # in your rackup:
29
+ use CasrackTheAuthenticator::Simple, :cas_server => ...
30
+ use CasrackTheAuthenticator::RequireCAS
31
+ # or "config.middleware.use" if you're on Rails
32
+
33
+ ==== 4: pull the authenticated CAS username out of the Rack session:
34
+
35
+ # in a Rack app:
36
+ def call(env)
37
+ user = cas_user(env)
38
+ ...
39
+ end
40
+
41
+ def cas_user(env)
42
+ username = Rack::Request.new(env).session[CasrackTheAuthenticator::USERNAME_PARAM]
43
+ User.find_by_username(username)
44
+ end
45
+
46
+ # or, in a Rails controller:
47
+
48
+ def cas_user
49
+ username = Rack::Request.new(request.env).session[CasrackTheAuthenticator::USERNAME_PARAM]
50
+ User.find_by_username(username)
51
+ end
52
+
53
+ === Disconnected (Fake) Mode
54
+
55
+ I've often found myself working on a CAS-ified project while away from the office
56
+ and unable to access the CAS server. To support this type of disconnected development,
57
+ just substitute in the CasrackTheAuthenticator::Fake middleware. It acts like
58
+ CasrackTheAuthenticator::Simple, but it uses HTTP Basic authentication against a
59
+ preset list of usernames.
60
+
61
+ A common pattern for Rails apps is to create a disconnected environment:
62
+
63
+ ==== 1: set up <tt>[rails_root]/config/database.yml</tt>
64
+
65
+ development: &DEV
66
+ adapter: sqlite3
67
+ database: db/development.sqlite3
68
+ pool: 5
69
+ timeout: 5000
70
+
71
+ # development mode when disconnected from MITRE
72
+ disconnected:
73
+ <<: *DEV
74
+
75
+ ==== 2: set up <tt>[rails_root]/config/disconnected.rb</tt>:
76
+
77
+ load './development.rb'
78
+ config.middleware.swap 'CasrackTheAuthenticator::Simple', CasrackTheAuthenticator::Fake, 'jimbob', 'sueann'
79
+
80
+ ==== 3: run in disconnected mode:
81
+
82
+ script/server -e disconnected
83
+
84
+ ==== 4: login as 'jimbob' or 'sueann'
85
+
86
+ Passwords are ignored.
87
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+
3
+ CASRACK_PROJECT_ROOT = File.expand_path(File.dirname(__FILE__))
4
+ Dir['developer_tasks/*.rake'].each { |f| load(f) }
5
+
6
+ desc "Default: run all tests, including features"
7
+ task :default => ['test']
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.6.0
@@ -0,0 +1,83 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{casrack_the_authenticator}
8
+ s.version = "1.6.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["James Rosen"]
12
+ s.date = %q{2009-09-23}
13
+ s.description = %q{CAS Authentication via Rack Middleware}
14
+ s.email = %q{james.a.rosen@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "casrack_the_authenticator.gemspec",
24
+ "developer_tasks/doc.rake",
25
+ "developer_tasks/gem.rake",
26
+ "developer_tasks/test.rake",
27
+ "features/fake.feature",
28
+ "features/require_cas.feature",
29
+ "features/simple.feature",
30
+ "features/step_definitions/fake_cas_steps.rb",
31
+ "features/step_definitions/rack_steps.rb",
32
+ "features/support/assertions.rb",
33
+ "features/support/rack_support.rb",
34
+ "lib/casrack_the_authenticator.rb",
35
+ "lib/casrack_the_authenticator/configuration.rb",
36
+ "lib/casrack_the_authenticator/fake.rb",
37
+ "lib/casrack_the_authenticator/require_cas.rb",
38
+ "lib/casrack_the_authenticator/service_ticket_validator.rb",
39
+ "lib/casrack_the_authenticator/simple.rb",
40
+ "test/configuration_test.rb",
41
+ "test/fake_test.rb",
42
+ "test/service_ticket_validator_test.rb",
43
+ "test/simple_test.rb",
44
+ "test/test_helper.rb"
45
+ ]
46
+ s.homepage = %q{http://github.com/gcnovus/casrack_the_authenticator}
47
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Casrack the Authenticator: RDoc", "--charset", "utf-8"]
48
+ s.require_paths = ["lib"]
49
+ s.rubygems_version = %q{1.3.3}
50
+ s.summary = %q{CAS Authentication via Rack Middleware}
51
+ s.test_files = [
52
+ "test/configuration_test.rb",
53
+ "test/fake_test.rb",
54
+ "test/service_ticket_validator_test.rb",
55
+ "test/simple_test.rb",
56
+ "test/test_helper.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<nokogiri>, ["~> 1.3.2"])
65
+ s.add_development_dependency(%q<thoughtbot-shoulda>, ["~> 2.10.2"])
66
+ s.add_development_dependency(%q<jferris-mocha>, ["~> 0.9.7"])
67
+ s.add_development_dependency(%q<redgreen>, ["~> 1.2.2"])
68
+ s.add_development_dependency(%q<rack>, ["~> 1.0.0"])
69
+ else
70
+ s.add_dependency(%q<nokogiri>, ["~> 1.3.2"])
71
+ s.add_dependency(%q<thoughtbot-shoulda>, ["~> 2.10.2"])
72
+ s.add_dependency(%q<jferris-mocha>, ["~> 0.9.7"])
73
+ s.add_dependency(%q<redgreen>, ["~> 1.2.2"])
74
+ s.add_dependency(%q<rack>, ["~> 1.0.0"])
75
+ end
76
+ else
77
+ s.add_dependency(%q<nokogiri>, ["~> 1.3.2"])
78
+ s.add_dependency(%q<thoughtbot-shoulda>, ["~> 2.10.2"])
79
+ s.add_dependency(%q<jferris-mocha>, ["~> 0.9.7"])
80
+ s.add_dependency(%q<redgreen>, ["~> 1.2.2"])
81
+ s.add_dependency(%q<rack>, ["~> 1.0.0"])
82
+ end
83
+ end
@@ -0,0 +1,22 @@
1
+ require 'yard'
2
+ require 'yard/rake/yardoc_task'
3
+
4
+ desc "Generate RDoc"
5
+ task :doc => ['doc:generate']
6
+
7
+ namespace :doc do
8
+
9
+ doc_dir = File.join(CASRACK_PROJECT_ROOT, 'doc', 'rdoc')
10
+
11
+ YARD::Rake::YardocTask.new(:generate) do |yt|
12
+ yt.files = Dir.glob(File.join(CASRACK_PROJECT_ROOT, 'lib', '**', '*.rb')) +
13
+ [ File.join(CASRACK_PROJECT_ROOT, 'README.rdoc') ]
14
+ yt.options = ['--output-dir', doc_dir, '--readme', 'README.rdoc']
15
+ end
16
+
17
+ desc "Remove generated documenation"
18
+ task :clean do
19
+ rm_r doc_dir if File.exists?(doc_dir)
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "factorylabs-casrack_the_authenticator"
5
+ gemspec.summary = "CAS Authentication via Rack Middleware"
6
+ gemspec.description = gemspec.summary
7
+ gemspec.email = "james.a.rosen@gmail.com"
8
+ gemspec.homepage = "http://github.com/gcnovus/casrack_the_authenticator"
9
+ gemspec.authors = ["James Rosen"]
10
+ gemspec.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Casrack the Authenticator: RDoc", "--charset", "utf-8"]
11
+ gemspec.platform = Gem::Platform::RUBY
12
+ gemspec.add_dependency 'nokogiri', '~> 1.4.1'
13
+ gemspec.add_development_dependency 'thoughtbot-shoulda', '~> 2.10.2'
14
+ gemspec.add_development_dependency 'jferris-mocha', '~> 0.9.7'
15
+ gemspec.add_development_dependency 'redgreen', '~> 1.2.2'
16
+ gemspec.add_development_dependency 'rack', '~> 1.0.0'
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'cucumber'
2
+ require 'cucumber/rake/task'
3
+ require 'rake/testtask'
4
+
5
+ Cucumber::Rake::Task.new(:features) do |t|
6
+ t.cucumber_opts = "features --format pretty"
7
+ end
8
+
9
+ LIB_DIRECTORIES = FileList.new do |fl|
10
+ fl.include "#{CASRACK_PROJECT_ROOT}/lib"
11
+ fl.include "#{CASRACK_PROJECT_ROOT}/test/lib"
12
+ end
13
+
14
+ TEST_FILES = FileList.new do |fl|
15
+ fl.include "#{CASRACK_PROJECT_ROOT}/test/**/*_test.rb"
16
+ fl.exclude "#{CASRACK_PROJECT_ROOT}/test/test_helper.rb"
17
+ fl.exclude "#{CASRACK_PROJECT_ROOT}/test/lib/**/*.rb"
18
+ end
19
+
20
+ Rake::TestTask.new(:test) do |t|
21
+ t.libs = LIB_DIRECTORIES
22
+ t.test_files = TEST_FILES
23
+ t.verbose = true
24
+ end
@@ -0,0 +1,34 @@
1
+ Feature: Fake CAS Authentication
2
+ In order to support developers who work away from the office
3
+ Casrack the Authenticator provides a Fake middleware.
4
+
5
+ Background:
6
+ Given a Rack application exists
7
+ And the fake Casrack middleware is installed with user "missy"
8
+
9
+ Scenario: not-signed-in user makes a request to a public area
10
+ Given the underlying Rack application returns [200, {}, "Public Information"]
11
+ When I make a request
12
+ Then the response should be successful
13
+ And the response body should include "Public Information"
14
+ And the CAS user should be nil
15
+
16
+ Scenario: not-signed-in user makes a request to a private area
17
+ Given the underlying Rack application returns [401, {}, 'Restricted. Go away.']
18
+ When I make a request
19
+ Then I should be presented with a HTTP Basic authentication request
20
+ And the CAS user should be nil
21
+
22
+ Scenario: user presenting invalid credentials makes a request to a private area
23
+ Given the underlying Rack application returns [401, {}, 'Restricted. Go away.']
24
+ When I make a request as "thomas"
25
+ Then I should be presented with a HTTP Basic authentication request
26
+ And the CAS user should be nil
27
+
28
+ Scenario: user presenting credentials makes a request to a private area
29
+ Given the underlying Rack application returns [200, {}, 'Restricted. Shh.']
30
+ When I make a request as "missy"
31
+ Then the response should be successful
32
+ And the response body should include "Restricted. Shh."
33
+ And the CAS user should be "missy"
34
+
@@ -0,0 +1,19 @@
1
+ Feature: Required CAS Authentication
2
+ In order make it dead-simple for users to implement a CAS-login requirement
3
+ Casrack the Authenticator provides a RequireCAS middleware.
4
+
5
+ Background:
6
+ Given a Rack application exists
7
+ And the RequireCAS middleware is installed
8
+ And the simple version of Casrack the Authenticator is installed
9
+ And the underlying Rack application returns [200, {}, "Public Information"]
10
+
11
+ Scenario: not-signed-in user makes a request
12
+ When I make a request
13
+ Then I should be redirected to CAS
14
+ And the "Content-Type" header should be "text/plain"
15
+
16
+ Scenario: signed-in-user makes a request
17
+ When I return to "http://myapp.org/bar" with a valid CAS ticket for "tperon"
18
+ Then the response should be successful
19
+ And the response body should include "Public Information"
@@ -0,0 +1,32 @@
1
+ Feature: Simple CAS Authentication
2
+ In order to maintain privacy and accountability while keeping IT costs low
3
+ "Upper Management" wants to use CAS authentication
4
+
5
+ Background:
6
+ Given a Rack application exists
7
+ And the simple version of Casrack the Authenticator is installed
8
+
9
+ Scenario: not-signed-in user accesses public material
10
+ Given the underlying Rack application returns [200, {}, "Public Information"]
11
+ When I make a request
12
+ Then the response should be successful
13
+ And the response body should include "Public Information"
14
+
15
+ Scenario: not-signed-in user accesses restricted material
16
+ Given the underlying Rack application returns [401, {}, "Restricted!"]
17
+ When I make a request to "http://myapp.com/foo?bar=baz"
18
+ Then I should be redirected to CAS
19
+ And the "Content-Type" header should be "text/plain"
20
+ And CAS should return me to "http://myapp.com/foo?bar=baz"
21
+
22
+ Scenario: returning from a successful CAS sign-in
23
+ Given the underlying Rack application returns [200, {}, "Information for jswanson"]
24
+ When I return to "http://myapp.org/bar" with a valid CAS ticket for "jswanson"
25
+ Then the CAS user should be "jswanson"
26
+ And the response should be successful
27
+ And the response body should include "Information for jswanson"
28
+
29
+ Scenario: returning from an unsuccessful CAS sign-in
30
+ Given the underlying Rack application returns [401, {}, "Restricted!"]
31
+ When I return to "http://myapp.org/bar" with an invalid CAS ticket
32
+ Then the CAS user should be nil
@@ -0,0 +1,14 @@
1
+ Given /^the fake Casrack middleware is installed with user "([^\"]*)"$/ do |user|
2
+ self.app = CasrackTheAuthenticator::Fake.new(app, user)
3
+ end
4
+
5
+ Then /^I should be presented with a HTTP Basic authentication request$/ do
6
+ assert_equal 401, response.status
7
+ assert_equal 'text/plain', response.headers['Content-Type']
8
+ assert_equal '0', response.headers['Content-Length']
9
+ assert_equal 'Basic realm="CasrackTheAuthenticator::Fake"', response.headers['WWW-Authenticate']
10
+ end
11
+
12
+ When /^I make a request as "([^\"]*)"$/ do |username|
13
+ get '/', { 'HTTP_AUTHORIZATION' => 'Basic ' + ["#{username}:sekret"].pack("m*") }
14
+ end
@@ -0,0 +1,69 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test', 'test_helper.rb')
2
+ require 'rack/mock'
3
+
4
+ Given /^a Rack application exists$/ do
5
+ self.underlying_app = lambda { |env| nil }
6
+ self.app = underlying_app
7
+ end
8
+
9
+ Given /^the simple version of Casrack the Authenticator is installed$/ do
10
+ self.app = CasrackTheAuthenticator::Simple.new(app, :cas_server => 'http://cas.test/cas')
11
+ end
12
+
13
+ Given /^the RequireCAS middleware is installed$/ do
14
+ self.app = CasrackTheAuthenticator::RequireCAS.new(app)
15
+ end
16
+
17
+ Given /^the underlying Rack application returns (.+)$/ do |response|
18
+ underlying_app.stubs(:call).returns(eval(response))
19
+ end
20
+
21
+ When /^I make a request$/ do
22
+ get '/'
23
+ end
24
+
25
+ When /^I make a request to "([^\"]*)"$/ do |url|
26
+ get url
27
+ end
28
+
29
+ When /^I return to "([^\"]*)" with a valid CAS ticket for "([^\"]*)"$/ do |url, user|
30
+ http_request_returns_valid_cas_user user
31
+ url << (url.include?('?') ? '&' : '?') << 'ticket=ST-123455'
32
+ When "I make a request to \"#{url}\""
33
+ end
34
+
35
+ When /^I return to "([^\"]*)" with an invalid CAS ticket$/ do |url|
36
+ http_request_returns_error
37
+ url << (url.include?('?') ? '&' : '?') << 'ticket=ST-not-a-valid-ticket'
38
+ When "I make a request to \"#{url}\""
39
+ end
40
+
41
+ Then /^the CAS user should be nil$/ do
42
+ assert_equal nil, session[:cas_user]
43
+ end
44
+
45
+ Then /^the CAS user should be "([^\"]*)"$/ do |username|
46
+ assert_equal username, session[:cas_user]
47
+ end
48
+
49
+ Then /^the response should be successful$/ do
50
+ assert((200..299).include?(response.status), "Expected success, but was #{response.status}")
51
+ end
52
+
53
+ Then /^the response body should include "([^\"]*)"$/ do |text|
54
+ assert response.body.include?(text)
55
+ end
56
+
57
+ Then /^I should be redirected to CAS$/ do
58
+ assert((300..399).include?(response.status), "Expected redirect, but was #{response.status}")
59
+ assert !redirected_to.nil?
60
+ assert redirected_to.to_s =~ /cas/i
61
+ end
62
+
63
+ Then /^CAS should return me to "([^\"]*)"$/ do |return_to|
64
+ assert_equal return_to, service_url
65
+ end
66
+
67
+ Then /^the "([^\"]*)" header should be "([^\"]*)"$/ do |header, value|
68
+ assert_equal value, response.headers[header]
69
+ end
@@ -0,0 +1,3 @@
1
+ require 'test/unit/assertions'
2
+
3
+ World(Test::Unit::Assertions)
@@ -0,0 +1,89 @@
1
+ require 'rack'
2
+ require 'rack/mock'
3
+ Rack::MockRequest.class_eval do
4
+
5
+ class <<self
6
+ def env_for_with_hook(*args)
7
+ return ::RackSupport.current_env unless ::RackSupport.current_env.nil?
8
+ env = env_for_without_hook(*args)
9
+ ::RackSupport.current_env = env
10
+ env
11
+ end
12
+ alias_method :env_for_without_hook, :env_for
13
+ alias_method :env_for, :env_for_with_hook
14
+ end
15
+
16
+ end
17
+
18
+ module RackSupport
19
+
20
+ VALID_CAS_USER_XML = <<-EOX
21
+ <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
22
+ <cas:authenticationSuccess>
23
+ <cas:user>%s</cas:user>
24
+ </cas:authenticationSuccess>
25
+ </cas:serviceResponse>
26
+ EOX
27
+
28
+ @@current_env = nil
29
+
30
+ def self.current_env
31
+ @@current_env
32
+ end
33
+
34
+ def self.current_env=(env)
35
+ @@current_env = env
36
+ end
37
+
38
+ attr_accessor :underlying_app, :app, :response, :session
39
+
40
+ def cleanup_rack_variables
41
+ ::RackSupport.current_env = nil
42
+ self.underlying_app = self.app = self.session = self.response = nil
43
+ end
44
+
45
+ def get(url, headers = {})
46
+ env = RackSupport.current_env = Rack::MockRequest.env_for(url, headers)
47
+ if session
48
+ env['rack.session'] = session
49
+ else
50
+ self.session = Rack::Request.new(env).session
51
+ end
52
+ self.response = Rack::MockRequest.new(app).get url, headers
53
+ end
54
+
55
+ def redirected_to
56
+ return nil if response.headers['Location'].nil?
57
+ URI::parse(response.headers['Location'])
58
+ end
59
+
60
+ def service_url
61
+ return nil if redirected_to.nil?
62
+ Rack::Utils.parse_nested_query(redirected_to.query)['service']
63
+ end
64
+
65
+ def http_request_returns_valid_cas_user(username)
66
+ http_request_returns VALID_CAS_USER_XML % username
67
+ end
68
+
69
+ def http_request_returns_error
70
+ http_request_returns "this is not a valid CAS service-ticket-validation response!"
71
+ end
72
+
73
+ def http_request_returns(content)
74
+ server = Object.new
75
+ connection = Object.new
76
+ response = Object.new
77
+ Net::HTTP.stubs(:new).returns(server)
78
+ server.stubs(:start).yields(connection)
79
+ connection.stubs(:get).returns(response)
80
+ response.stubs(:body).returns(content)
81
+ end
82
+
83
+ end
84
+
85
+ After do
86
+ cleanup_rack_variables
87
+ end
88
+
89
+ World(RackSupport)