rack-cas_client 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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