rack-cas_client 0.3.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.
@@ -0,0 +1,26 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+ require 'autotest/isolate'
5
+
6
+ # Autotest.add_hook :initialize do |at|
7
+ # at.extra_files << "../some/external/dependency.rb"
8
+ #
9
+ # at.libs << ":../some/external"
10
+ #
11
+ # at.add_exception 'vendor'
12
+ #
13
+ # at.add_mapping(/dependency.rb/) do |f, _|
14
+ # at.files_matching(/test_.*rb$/)
15
+ # end
16
+ #
17
+ # %w(TestA TestB).each do |klass|
18
+ # at.extra_class_map[klass] = "test/test_misc.rb"
19
+ # end
20
+ # end
21
+
22
+ # Autotest.add_hook :run_command do |at|
23
+ # system "rake build"
24
+ # end
25
+
26
+ # vim: syntax=ruby
File without changes
@@ -0,0 +1,2 @@
1
+ *.sw[op]
2
+ tmp/*
@@ -0,0 +1,15 @@
1
+ === 0.3.0 / 2012-07-24
2
+
3
+ * 3 unknowns:
4
+
5
+ * Adds hoe plugins. Updates readme.
6
+ * Uses attr_reader.
7
+ * remove's cruft code.
8
+
9
+
10
+ === 1.0.0 / 2012-03-15
11
+
12
+ * 1 major enhancement
13
+
14
+ * Birthday!
15
+
@@ -0,0 +1,10 @@
1
+ .autotest
2
+ .gitignore
3
+ History.txt
4
+ Manifest.txt
5
+ README.rdoc
6
+ Rakefile
7
+ bin/rack_cas_client
8
+ lib/rack/cas_client.rb
9
+ test/helper.rb
10
+ test/test_rack_cas_client.rb
@@ -0,0 +1,93 @@
1
+ = rack-cas_client
2
+
3
+ * https://github.com/bhenderson/rack-cas_client
4
+
5
+ == DESCRIPTION:
6
+
7
+ A Rack Middleware component to authenticate against a CAS server.
8
+
9
+ I (shamefully) forgot to attribute this to
10
+ https://github.com/garnieretienne/rubycas-client-sinatra. I had reached out to
11
+ garnieretienne and he was no longer interested in his project. There is also
12
+ rack-cas-client gem but that was too rails focused for me.
13
+
14
+ == FEATURES/PROBLEMS:
15
+
16
+ * Tries to be a simple authentication mechanism and leaves it up to the app chain to determine endpoints that should be auth'd.
17
+ * Does not depend on Sinatra or Rails.
18
+ * Does use 'rack.session' so anything that uses that will work.
19
+ * Passes options hash to CASClient::Client (see docs for exceptions)
20
+
21
+ == SYNOPSIS:
22
+
23
+ # config.ru
24
+
25
+ require 'rack/cas_client'
26
+
27
+ use Rack::Session::Cookie
28
+
29
+ map '/login' do
30
+ run Rack::CASClient.new nil, :cas_base_url => 'https://example/cas'
31
+ end
32
+
33
+ class MyApp < Struct.new(:app)
34
+ def call(env)
35
+ request = Rack::Request.new env
36
+ response = Rack::Response.new
37
+
38
+ user = request.session[:cas_user]
39
+ unless user and not user.empty?
40
+ query = URI.encode_www_form url: request.url
41
+ response.redirect "/login?#{query}"
42
+ return response.finish
43
+ end
44
+
45
+ response.write "hi #{user.inspect}"
46
+ response.finish
47
+ end
48
+ end
49
+
50
+ run MyApp.new
51
+
52
+ == REQUIREMENTS:
53
+
54
+ * rack
55
+ * rubycas-client
56
+
57
+ == INSTALL:
58
+
59
+ * gem install rack-cas_client
60
+
61
+ == DEVELOPERS:
62
+
63
+ After checking out the source, run:
64
+
65
+ $ rake newb
66
+
67
+ This task will install any missing dependencies, run the tests/specs,
68
+ and generate the RDoc.
69
+
70
+ == LICENSE:
71
+
72
+ (The MIT License)
73
+
74
+ Copyright (c) 2012 bhenderson
75
+
76
+ Permission is hereby granted, free of charge, to any person obtaining
77
+ a copy of this software and associated documentation files (the
78
+ 'Software'), to deal in the Software without restriction, including
79
+ without limitation the rights to use, copy, modify, merge, publish,
80
+ distribute, sublicense, and/or sell copies of the Software, and to
81
+ permit persons to whom the Software is furnished to do so, subject to
82
+ the following conditions:
83
+
84
+ The above copyright notice and this permission notice shall be
85
+ included in all copies or substantial portions of the Software.
86
+
87
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
88
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
89
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
90
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
91
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
92
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
93
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :isolate
7
+ Hoe.plugin :minitest
8
+
9
+ Hoe.plugin :version, :git
10
+
11
+ Hoe.spec 'rack-cas_client' do
12
+ developer 'Brian Henderson', 'henderson.bj@gmail.com'
13
+
14
+ self.readme_file = "README.rdoc"
15
+
16
+ self.testlib = :minitest
17
+
18
+ extra_deps << ['rack']
19
+ extra_deps << ['rubycas-client']
20
+ extra_dev_deps << ['rack-test']
21
+ end
22
+
23
+ # vim: syntax=ruby
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ abort "you need to write me"
@@ -0,0 +1,156 @@
1
+ require 'rack/request'
2
+ require 'rubycas-client'
3
+
4
+ module Rack
5
+ ##
6
+ # Middleware component to authenticate with a CAS server.
7
+ class CASClient
8
+ VERSION = '0.3.0'
9
+
10
+ # Public: CASCLient depends on these things but does not explicitely require them.
11
+ # Call this method (before any forking) if you care. (You may not if you
12
+ # implement your own #blank? for example.)
13
+ def self.require_casclient_deps
14
+ # YUCK! These are required for casclient
15
+ require 'active_support/core_ext/object/blank'
16
+ # I believe one or the other of these is required (not both) but since
17
+ # they're stdlib I'm leaving them in.
18
+ require 'json'
19
+ require 'yaml'
20
+ end
21
+
22
+ attr_reader :cas_client, :request
23
+
24
+ # options - Hash
25
+ # :logger - Logger, must respond to :<<
26
+ # Everything else is used by CASClient::Client#configure. Notable params are:
27
+ # :cas_base_url - String, your CAS Server base url. raises if not there.
28
+ # :force_ssl_verification - Bool, force ssl verification? Defaults to true.
29
+ def initialize app = nil, opts = {}
30
+ @app = app
31
+
32
+ # quiet cas_client
33
+ @logger = opts.delete :logger
34
+ raise ArgumentError, "invalid logger #{@logger}" if
35
+ @logger and not @logger.respond_to? :<<
36
+
37
+ opts[:force_ssl_verification] = opts.fetch(:force_ssl_verification, true)
38
+ @cas_client = ::CASClient::Client.new opts
39
+ end
40
+
41
+ # Private: Accessor method for app.
42
+ #
43
+ # If app is nil, redirects back to the referrer, otherwise, just displays a
44
+ # simple page saying that login was successful.
45
+ def app
46
+ @app || lambda{|env|
47
+ req = Rack::Request.new env
48
+ if url = req.params['url']
49
+ self.redirect url
50
+ else
51
+ [200, {'Content-Type'=>'text/plain'},['logged in!']]
52
+ end
53
+ }
54
+ end
55
+
56
+ # Public: Rack interface.
57
+ #
58
+ # Calls +app+ if already authenticated.
59
+ #
60
+ # If not authenticated, redirects user to login server.
61
+ # Login server should redirect back with a ticket param.
62
+ # If ticket is valid, redirect back to original request.
63
+ #
64
+ # It's up to the session manager to manage timeouts etc.
65
+ def call env
66
+ dup.call! env
67
+ end
68
+
69
+ # Private: Rack call method.
70
+ def call! env
71
+ @request = Rack::Request.new env
72
+
73
+ return app.call(env) if authenticated?
74
+
75
+ service_url = request_url_without_ticket
76
+ cas_login_url = cas_client.add_service_to_login_url(service_url)
77
+
78
+ if st = service_ticket(service_url)
79
+
80
+ cas_client.validate_service_ticket(st) unless st.has_been_validated?
81
+
82
+ if st.is_valid?
83
+ log 'ticket is valid'
84
+ # use string because extra_attributes will always have strings as keys
85
+ user['username'] = st.user
86
+ user.merge! st.extra_attributes || {}
87
+ log "user logged in as #{user.inspect}"
88
+ redirect service_url
89
+ else
90
+ log 'ticket is not valid'
91
+ user.clear # why?
92
+ redirect cas_login_url
93
+ end
94
+ else
95
+ log 'No ticket, redirecting to login server'
96
+ redirect cas_login_url
97
+ end
98
+ end
99
+
100
+ # Private: Returns a ticket configured with +service_url+
101
+ #
102
+ # service_url - String, the request url that needs to be validated.
103
+ def service_ticket service_url
104
+ ticket = request.params['ticket']
105
+ return unless ticket
106
+ ticket_class = ticket =~ /^PT-/ ?
107
+ ::CASClient::ProxyTicket :
108
+ ::CASClient::ServiceTicket
109
+
110
+ st = ticket_class.new(ticket, service_url, false)
111
+ log "User has a #{ticket_class}! #{st.inspect}"
112
+ st
113
+ end
114
+
115
+ def authenticated?
116
+ !user.empty? && log("#{user.inspect} is already authenticated")
117
+ end
118
+
119
+ def log msg
120
+ return true unless @logger
121
+
122
+ @logger << msg
123
+ @logger << "\n" unless msg["\n"]
124
+ true
125
+ end
126
+
127
+ def redirect loc
128
+ [302,
129
+ { 'Content-Type' => 'text/plain',
130
+ 'Content-Length' => '0',
131
+ 'Location' => loc},
132
+ ['']]
133
+ end
134
+
135
+ # return the request.url minus params['ticket']
136
+ def request_url_without_ticket
137
+ # I feel like this should be in Rack::Request
138
+ # request['ticket'] = nil
139
+ # request.url
140
+ url = request.base_url + request.path
141
+ query = request.params.dup
142
+ query.delete 'ticket'
143
+ query.empty? ? url : "#{url}?#{Rack::Utils.build_nested_query query}"
144
+ end
145
+
146
+ def session
147
+ request.session
148
+ end
149
+
150
+ def user
151
+ # session[:cas_user]
152
+ session[cas_client.username_session_key] ||= {}
153
+ end
154
+
155
+ end
156
+ end
@@ -0,0 +1,46 @@
1
+ require 'minitest/autorun'
2
+ require 'rack/cas_client'
3
+ require 'rack/test'
4
+ require 'json'
5
+
6
+ class TestRack < MiniTest::Unit::TestCase
7
+ end
8
+ class TestRack::TestCASClient < MiniTest::Unit::TestCase
9
+ include Rack::Test::Methods
10
+
11
+ def setup
12
+ app = lambda{|env|
13
+ request = Rack::Request.new(env)
14
+ [200,
15
+ {'Content-Type' => 'text/plain'},
16
+ [request.session[:cas_user].to_json]]
17
+ }
18
+ @session = {}
19
+ @env = {'rack.session' => @session}
20
+ @client = Rack::CASClient.new app, cas_base_url: 'http://example/cas'
21
+ @mock_request = MiniTest::Mock.new
22
+ @client.instance_variable_set :@request, @mock_request
23
+ end
24
+
25
+ def teardown
26
+ @mock_request.verify
27
+ end
28
+
29
+ def app
30
+ @app ||= Rack::Lint.new @client
31
+ end
32
+
33
+ def assert_request_url_without_ticket expected, url
34
+ expected[/^/] = 'http://example'
35
+ url[/^/] = 'http://example'
36
+
37
+ browser = Rack::Test::Session.new(lambda{|e| [200, {},['']]})
38
+ browser.get url
39
+ @client.instance_variable_set :@request, browser.last_request
40
+
41
+ actual = @client.request_url_without_ticket
42
+
43
+ assert_equal expected, actual
44
+ assert_equal url, @client.request.url, "expected url to be unmodified"
45
+ end
46
+ end
@@ -0,0 +1,100 @@
1
+ require 'helper'
2
+
3
+ class TestRack::TestCASClient
4
+
5
+ def test_all_ready_authd
6
+ @session[:cas_user] = {'username' => 'me'}
7
+ get '/', nil, @env
8
+ assert_equal @session[:cas_user].to_json, last_response.body
9
+ end
10
+
11
+ def test_all_ready_authd_with_no_app
12
+ @app = Rack::CASClient.new nil, cas_base_url: 'http://example/cas'
13
+
14
+ @session[:cas_user] = {'username' => 'me'}
15
+ get '/', nil, @env
16
+ assert last_response.ok?
17
+ assert_equal 'logged in!', last_response.body
18
+ end
19
+
20
+ def test_all_ready_authd_with_no_app_redirects_if_given_url
21
+ @app = Rack::CASClient.new nil, cas_base_url: 'http://example/cas'
22
+
23
+ @session[:cas_user] = {'username' => 'me'}
24
+ get '/?url=http%3A%2F%2Fexample%2Ffoo', nil, @env
25
+ assert last_response.redirect?
26
+ assert_equal 'http://example/foo', last_response['Location']
27
+ end
28
+
29
+ def test_redirects_to_cas_server
30
+ get '/'
31
+ assert last_response.redirect?
32
+
33
+ expected = 'http://example/cas/login?service=http%3A%2F%2Fexample.org%2F'
34
+ assert_equal expected, last_response['Location']
35
+
36
+ get '/foo?q=bar'
37
+ assert last_response.redirect?
38
+
39
+ expected = 'http://example/cas/login?service=http%3A%2F%2Fexample.org%2Ffoo%3Fq%3Dbar'
40
+ assert_equal expected, last_response['Location']
41
+ end
42
+
43
+ def test_request_url
44
+ assert_request_url_without_ticket '/', '/?ticket=123'
45
+ assert_request_url_without_ticket '/?foo=bar', '/?foo=bar&ticket=123'
46
+ assert_request_url_without_ticket '/?bar=foo', '/?ticket=123&bar=foo'
47
+ assert_request_url_without_ticket '/?foo=bar&bar=foo', '/?foo=bar&ticket=123&bar=foo'
48
+ assert_request_url_without_ticket '/?foo=bar&bar=foo', '/?foo=bar&bar=foo'
49
+ assert_request_url_without_ticket '/foo', '/foo?ticket=123'
50
+ assert_request_url_without_ticket '/foo/', '/foo/?ticket=123'
51
+ end
52
+
53
+ def test_service_ticket
54
+ @mock_request.expect :params, {}
55
+ t = @client.service_ticket ''
56
+ refute t
57
+ end
58
+
59
+ def test_service_ticket_returns_proxy_ticket
60
+ @mock_request.expect :params, 'ticket' => 'PT-123'
61
+ t = @client.service_ticket ''
62
+ assert_kind_of CASClient::ProxyTicket, t
63
+ end
64
+
65
+ def test_service_ticket_returns_service_ticket
66
+ @mock_request.expect :params, 'ticket' => 'ST-123'
67
+ t = @client.service_ticket ''
68
+ assert_kind_of CASClient::ServiceTicket, t
69
+ end
70
+
71
+ def test_ticket_validation
72
+ expected = 'http://example.org/foo'
73
+
74
+ cli = @client.cas_client
75
+ def cli.validate_service_ticket(t)
76
+ t.success = true
77
+ t.user = 'me'
78
+ end
79
+
80
+ get '/foo?ticket=ST-123', nil, @env
81
+ assert last_response.redirect?
82
+ assert_equal expected, last_response['Location']
83
+
84
+ expected = {'username' => 'me'}
85
+ assert_equal expected, @session[:cas_user]
86
+ end
87
+
88
+ def test_ticket_invalid
89
+ expected = 'http://example/cas/login?service=http%3A%2F%2Fexample.org%2Ffoo'
90
+
91
+ cli = @client.cas_client
92
+ def cli.validate_service_ticket(t)
93
+ t.success = false
94
+ end
95
+
96
+ get '/foo?ticket=ST-123'
97
+ assert last_response.redirect?
98
+ assert_equal expected, last_response['Location']
99
+ end
100
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-cas_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Henderson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rubycas-client
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rdoc
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.10'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.10'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rack-test
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: hoe
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '3.0'
110
+ description: ! 'A Rack Middleware component to authenticate against a CAS server.
111
+
112
+
113
+ I (shamefully) forgot to attribute this to
114
+
115
+ https://github.com/garnieretienne/rubycas-client-sinatra. I had reached out to
116
+
117
+ garnieretienne and he was no longer interested in his project. There is also
118
+
119
+ rack-cas-client gem but that was too rails focused for me.'
120
+ email:
121
+ - henderson.bj@gmail.com
122
+ executables:
123
+ - rack_cas_client
124
+ extensions: []
125
+ extra_rdoc_files:
126
+ - History.txt
127
+ - Manifest.txt
128
+ - README.rdoc
129
+ files:
130
+ - .autotest
131
+ - .gitignore
132
+ - History.txt
133
+ - Manifest.txt
134
+ - README.rdoc
135
+ - Rakefile
136
+ - bin/rack_cas_client
137
+ - lib/rack/cas_client.rb
138
+ - test/helper.rb
139
+ - test/test_rack_cas_client.rb
140
+ - .gemtest
141
+ homepage: https://github.com/bhenderson/rack-cas_client
142
+ licenses: []
143
+ post_install_message:
144
+ rdoc_options:
145
+ - --main
146
+ - README.rdoc
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ! '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ none: false
157
+ requirements:
158
+ - - ! '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project: rack-cas_client
163
+ rubygems_version: 1.8.24
164
+ signing_key:
165
+ specification_version: 3
166
+ summary: A Rack Middleware component to authenticate against a CAS server
167
+ test_files:
168
+ - test/test_rack_cas_client.rb