aker 3.0.0.pre

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.
Files changed (118) hide show
  1. data/CHANGELOG.md +210 -0
  2. data/README.md +282 -0
  3. data/assets/aker/form/login.css +73 -0
  4. data/assets/aker/form/login.html.erb +44 -0
  5. data/lib/aker/authorities/automatic_access.rb +36 -0
  6. data/lib/aker/authorities/composite.rb +301 -0
  7. data/lib/aker/authorities/static.rb +283 -0
  8. data/lib/aker/authorities/support/find_sole_user.rb +24 -0
  9. data/lib/aker/authorities/support.rb +9 -0
  10. data/lib/aker/authorities.rb +46 -0
  11. data/lib/aker/cas/authority.rb +79 -0
  12. data/lib/aker/cas/configuration_helper.rb +85 -0
  13. data/lib/aker/cas/middleware/logout_responder.rb +49 -0
  14. data/lib/aker/cas/middleware/ticket_remover.rb +35 -0
  15. data/lib/aker/cas/middleware.rb +6 -0
  16. data/lib/aker/cas/proxy_mode.rb +108 -0
  17. data/lib/aker/cas/rack_proxy_callback.rb +188 -0
  18. data/lib/aker/cas/service_mode.rb +88 -0
  19. data/lib/aker/cas/service_url.rb +62 -0
  20. data/lib/aker/cas/user_ext.rb +64 -0
  21. data/lib/aker/cas.rb +31 -0
  22. data/lib/aker/central_parameters.rb +101 -0
  23. data/lib/aker/configuration.rb +534 -0
  24. data/lib/aker/deprecation.rb +105 -0
  25. data/lib/aker/form/custom_views_mode.rb +80 -0
  26. data/lib/aker/form/login_form_asset_provider.rb +56 -0
  27. data/lib/aker/form/middleware/custom_view_login_responder.rb +19 -0
  28. data/lib/aker/form/middleware/login_renderer.rb +72 -0
  29. data/lib/aker/form/middleware/login_responder.rb +71 -0
  30. data/lib/aker/form/middleware/logout_responder.rb +26 -0
  31. data/lib/aker/form/middleware.rb +10 -0
  32. data/lib/aker/form/mode.rb +118 -0
  33. data/lib/aker/form.rb +26 -0
  34. data/lib/aker/group.rb +67 -0
  35. data/lib/aker/group_membership.rb +162 -0
  36. data/lib/aker/ldap/authority.rb +392 -0
  37. data/lib/aker/ldap/user_ext.rb +19 -0
  38. data/lib/aker/ldap.rb +22 -0
  39. data/lib/aker/modes/base.rb +85 -0
  40. data/lib/aker/modes/http_basic.rb +100 -0
  41. data/lib/aker/modes/support/attempted_path.rb +22 -0
  42. data/lib/aker/modes/support/rfc_2617.rb +32 -0
  43. data/lib/aker/modes/support.rb +12 -0
  44. data/lib/aker/modes.rb +48 -0
  45. data/lib/aker/rack/authenticate.rb +37 -0
  46. data/lib/aker/rack/configuration_helper.rb +18 -0
  47. data/lib/aker/rack/default_logout_responder.rb +36 -0
  48. data/lib/aker/rack/environment_helper.rb +34 -0
  49. data/lib/aker/rack/facade.rb +102 -0
  50. data/lib/aker/rack/failure.rb +69 -0
  51. data/lib/aker/rack/logout.rb +63 -0
  52. data/lib/aker/rack/request_ext.rb +19 -0
  53. data/lib/aker/rack/session_timer.rb +95 -0
  54. data/lib/aker/rack/setup.rb +77 -0
  55. data/lib/aker/rack.rb +107 -0
  56. data/lib/aker/test/helpers.rb +22 -0
  57. data/lib/aker/test.rb +8 -0
  58. data/lib/aker/user.rb +231 -0
  59. data/lib/aker/version.rb +3 -0
  60. data/lib/aker.rb +51 -0
  61. data/spec/aker/aker-sample.yml +11 -0
  62. data/spec/aker/authorities/automatic_access_spec.rb +52 -0
  63. data/spec/aker/authorities/composite_spec.rb +488 -0
  64. data/spec/aker/authorities/nu-schema.jar +0 -0
  65. data/spec/aker/authorities/static_spec.rb +455 -0
  66. data/spec/aker/authorities/support/find_sole_user_spec.rb +33 -0
  67. data/spec/aker/authorities_spec.rb +16 -0
  68. data/spec/aker/cas/authority_spec.rb +106 -0
  69. data/spec/aker/cas/configuration_helper_spec.rb +92 -0
  70. data/spec/aker/cas/middleware/logout_responder_spec.rb +47 -0
  71. data/spec/aker/cas/middleware/ticket_remover_spec.rb +49 -0
  72. data/spec/aker/cas/proxy_mode_spec.rb +185 -0
  73. data/spec/aker/cas/rack_proxy_callback_spec.rb +190 -0
  74. data/spec/aker/cas/service_mode_spec.rb +122 -0
  75. data/spec/aker/cas/service_url_spec.rb +114 -0
  76. data/spec/aker/cas/user_ext_spec.rb +27 -0
  77. data/spec/aker/cas_spec.rb +19 -0
  78. data/spec/aker/central_parameters_spec.rb +44 -0
  79. data/spec/aker/configuration_spec.rb +465 -0
  80. data/spec/aker/deprecation_spec.rb +115 -0
  81. data/spec/aker/form/a_form_mode.rb +129 -0
  82. data/spec/aker/form/custom_views_mode_spec.rb +34 -0
  83. data/spec/aker/form/login_form_asset_provider_spec.rb +80 -0
  84. data/spec/aker/form/middleware/a_form_login_responder.rb +89 -0
  85. data/spec/aker/form/middleware/custom_view_login_responder_spec.rb +47 -0
  86. data/spec/aker/form/middleware/login_renderer_spec.rb +56 -0
  87. data/spec/aker/form/middleware/login_responder_spec.rb +34 -0
  88. data/spec/aker/form/middleware/logout_responder_spec.rb +55 -0
  89. data/spec/aker/form/mode_spec.rb +15 -0
  90. data/spec/aker/form_spec.rb +11 -0
  91. data/spec/aker/group_membership_spec.rb +208 -0
  92. data/spec/aker/group_spec.rb +66 -0
  93. data/spec/aker/ldap/authority_spec.rb +414 -0
  94. data/spec/aker/ldap/ldap-users.ldif +197 -0
  95. data/spec/aker/ldap_spec.rb +11 -0
  96. data/spec/aker/modes/a_aker_mode.rb +41 -0
  97. data/spec/aker/modes/http_basic_spec.rb +127 -0
  98. data/spec/aker/modes/support/attempted_path_spec.rb +32 -0
  99. data/spec/aker/modes_spec.rb +11 -0
  100. data/spec/aker/rack/authenticate_spec.rb +78 -0
  101. data/spec/aker/rack/default_logout_responder_spec.rb +67 -0
  102. data/spec/aker/rack/facade_spec.rb +154 -0
  103. data/spec/aker/rack/failure_spec.rb +151 -0
  104. data/spec/aker/rack/logout_spec.rb +63 -0
  105. data/spec/aker/rack/request_ext_spec.rb +29 -0
  106. data/spec/aker/rack/session_timer_spec.rb +134 -0
  107. data/spec/aker/rack/setup_spec.rb +87 -0
  108. data/spec/aker/rack_spec.rb +216 -0
  109. data/spec/aker/test/helpers_spec.rb +44 -0
  110. data/spec/aker/user_spec.rb +362 -0
  111. data/spec/aker_spec.rb +80 -0
  112. data/spec/deprecation_helper.rb +58 -0
  113. data/spec/java_helper.rb +5 -0
  114. data/spec/logger_helper.rb +17 -0
  115. data/spec/matchers.rb +31 -0
  116. data/spec/mock_builder.rb +25 -0
  117. data/spec/spec_helper.rb +52 -0
  118. metadata +265 -0
@@ -0,0 +1,47 @@
1
+ require File.expand_path("../../../../spec_helper", __FILE__)
2
+ require "rack/test"
3
+
4
+ module Aker::Cas::Middleware
5
+ describe LogoutResponder do
6
+ include Rack::Test::Methods
7
+
8
+ let(:app) do
9
+ Rack::Builder.new do
10
+ use Aker::Cas::Middleware::LogoutResponder
11
+ run lambda { |env| [404, { "Content-Type" => "text/plain" }, []] }
12
+ end
13
+ end
14
+
15
+ let(:configuration) do
16
+ Aker::Configuration.new do
17
+ cas_parameters :logout_url => 'https://cas.example.edu/logout'
18
+ rack_parameters :logout_path => '/some/logout'
19
+ end
20
+ end
21
+
22
+ let(:env) do
23
+ { 'aker.configuration' => configuration }
24
+ end
25
+
26
+ describe '#call' do
27
+ it 'redirects to the CAS logout URL on GET {configured logout path}' do
28
+ get "/some/logout", {}, env
29
+
30
+ last_response.should be_redirect
31
+ last_response.location.should == 'https://cas.example.edu/logout'
32
+ end
33
+
34
+ it "does not respond to other paths" do
35
+ get "/", {}, env
36
+
37
+ last_response.status.should == 404
38
+ end
39
+
40
+ it "does not respond to other methods" do
41
+ post "/some/logout", {}, env
42
+
43
+ last_response.status.should == 404
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path("../../../../spec_helper", __FILE__)
2
+ require "rack/test"
3
+
4
+ module Aker::Cas::Middleware
5
+ describe TicketRemover do
6
+ include Rack::Test::Methods
7
+
8
+ let(:app) do
9
+ Rack::Builder.new do
10
+ use Aker::Cas::Middleware::TicketRemover
11
+ run lambda { |env| [404, { "Content-Type" => "text/plain" }, ['Requested content']] }
12
+ end
13
+ end
14
+
15
+ let(:env) do
16
+ { }
17
+ end
18
+
19
+ describe '#call' do
20
+ it 'does nothing if not authenticated' do
21
+ get '/foo?ticket=ST-45&q=bar', {}, env
22
+
23
+ last_response.body.should == 'Requested content'
24
+ end
25
+
26
+ it 'does nothing if no ticket is present' do
27
+ get '/foo?q=bar', {}, env
28
+
29
+ last_response.body.should == 'Requested content'
30
+ end
31
+
32
+ context 'ticket is present and the user is authenticated' do
33
+ before do
34
+ env['aker.check'] = Aker::Rack::Facade.new(Aker.configuration, Aker::User.new('jo'))
35
+
36
+ get '/foo?ticket=ST-45&q=bar', {}, env
37
+ end
38
+
39
+ it 'sends a permanent redirect' do
40
+ last_response.status.should == 301
41
+ end
42
+
43
+ it 'redirects to the same URI without the ticket' do
44
+ last_response.headers['Location'].should == 'http://example.org/foo?q=bar'
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,185 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+ require File.expand_path("../../modes/a_aker_mode", __FILE__)
3
+ require 'rack'
4
+
5
+ module Aker::Cas
6
+ describe ProxyMode do
7
+ before do
8
+ @env = Rack::MockRequest.env_for('/')
9
+ @scope = mock
10
+ @mode = ProxyMode.new(@env, @scope)
11
+ @env['aker.configuration'] = Aker::Configuration.new
12
+ end
13
+
14
+ it_should_behave_like "a aker mode"
15
+
16
+ describe "#key" do
17
+ it "is :cas_proxy" do
18
+ ProxyMode.key.should == :cas_proxy
19
+ end
20
+ end
21
+
22
+ describe "#kind" do
23
+ it "is :cas_proxy" do
24
+ @mode.kind.should == :cas_proxy
25
+ end
26
+ end
27
+
28
+ describe "#credentials" do
29
+ it "finds two credentials" do
30
+ @env["HTTP_AUTHORIZATION"] = "CasProxy PT-1foo"
31
+
32
+ @mode.credentials.size.should == 2
33
+ end
34
+
35
+ describe "[0]" do
36
+ it "finds a proxy ticket that starts with PT" do
37
+ @env["HTTP_AUTHORIZATION"] = "CasProxy PT-1foo"
38
+
39
+ @mode.credentials.first.should == "PT-1foo"
40
+ end
41
+
42
+ it "finds a proxy ticket that starts with ST" do
43
+ @env["HTTP_AUTHORIZATION"] = "CasProxy ST-9936-bam"
44
+
45
+ @mode.credentials.first.should == "ST-9936-bam"
46
+ end
47
+
48
+ it "ignores an out-of-spec proxy ticket" do
49
+ @env["HTTP_AUTHORIZATION"] = "CasProxy MyAttackVector"
50
+
51
+ @mode.credentials.should == []
52
+ end
53
+
54
+ it "ignores tickets that contain characters not in [^0-9A-Za-z-]" do
55
+ @env["HTTP_AUTHORIZATION"] = "CasProxy ST-@@&%#"
56
+
57
+ @mode.credentials.should == []
58
+ end
59
+
60
+ it "returns an empty array if no proxy ticket is present" do
61
+ @mode.credentials.should == []
62
+ end
63
+ end
64
+
65
+ describe "[1]" do
66
+ it "is the service_url" do
67
+ @env["HTTP_AUTHORIZATION"] = "CasProxy ST-1701"
68
+
69
+ @env["SERVER_PORT"] = 80
70
+ @env["SERVER_HOST"] = "example.org"
71
+ @env["rack.url_scheme"] = "http"
72
+
73
+ @mode.credentials[1].should == "http://example.org"
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#valid?" do
79
+ it "returns false if there is no authorization header" do
80
+ @mode.should_not be_valid
81
+ end
82
+
83
+ it "returns false if the authorization header is for a different scheme" do
84
+ @env["HTTP_AUTHORIZATION"] = "Basic " + Base64.encode64("bas:baz")
85
+
86
+ @mode.should_not be_valid
87
+ end
88
+
89
+ it "returns true if the authorization header is for CasProxy" do
90
+ @env["HTTP_AUTHORIZATION"] = "CasProxy PT-5-256a"
91
+
92
+ @mode.should be_valid
93
+ end
94
+ end
95
+
96
+ describe "#scheme" do
97
+ it "returns CasProxy" do
98
+ @mode.scheme.should == "CasProxy"
99
+ end
100
+ end
101
+
102
+ describe "#challenge" do
103
+ it "includes the scheme and the realm" do
104
+ @mode.challenge.should == "CasProxy realm=\"Aker\""
105
+ end
106
+ end
107
+
108
+ describe "#service_url" do
109
+ before do
110
+ @env["rack.url_scheme"] = "http"
111
+ @env["SERVER_NAME"] = "local.example.net"
112
+ @env["SERVER_PORT"] = "80"
113
+ @env["SCRIPT_NAME"] = "/api"
114
+ @env["PATH_INFO"] = "/people/josephine"
115
+ @env["QUERY_STRING"] = "all=true"
116
+ end
117
+
118
+ it "includes the host and script name but not the path info or query string" do
119
+ @mode.service_url.should == "http://local.example.net/api"
120
+ end
121
+
122
+ it "prefers the HTTP Host header over the server name and port if present" do
123
+ @env["HTTP_HOST"] = "virtual.example.com:3400"
124
+ @env["SERVER_PORT"] = "8080"
125
+ @mode.service_url.should == "http://virtual.example.com:3400/api"
126
+ end
127
+
128
+ it "does not end with a slash if the script name is blank" do
129
+ @env["SCRIPT_NAME"] = ""
130
+ @mode.service_url.should == "http://local.example.net"
131
+ end
132
+
133
+ describe "for http" do
134
+ it "does not include the port if it is 80" do
135
+ @mode.service_url.should == "http://local.example.net/api"
136
+ end
137
+
138
+ it "includes the port if it isn't 80" do
139
+ @env["SERVER_PORT"] = "3000"
140
+ @mode.service_url.should == "http://local.example.net:3000/api"
141
+ end
142
+ end
143
+
144
+ describe "for https" do
145
+ before do
146
+ @env['rack.url_scheme'] = "https"
147
+ end
148
+
149
+ it "does not include the port if it is 443" do
150
+ @env["SERVER_PORT"] = "443"
151
+ @mode.service_url.should == "https://local.example.net/api"
152
+ end
153
+
154
+ it "includes the port if it isn't 443" do
155
+ @env["SERVER_PORT"] = "3003"
156
+ @mode.service_url.should == "https://local.example.net:3003/api"
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "#authenticate!" do
162
+ before do
163
+ @authority = mock
164
+ @mode.stub!(:authority => @authority)
165
+ @env["HTTP_AUTHORIZATION"] = "CasProxy PT-1foo"
166
+ end
167
+
168
+ it "signals success if the proxy ticket is good" do
169
+ user = stub
170
+ @authority.should_receive(:valid_credentials?).
171
+ with(:cas_proxy, "PT-1foo", "http://example.org").and_return(user)
172
+ @mode.should_receive(:success!).with(user)
173
+
174
+ @mode.authenticate!
175
+ end
176
+
177
+ it "does not signal success if the proxy ticket is bad" do
178
+ @authority.stub!(:valid_credentials? => nil)
179
+ @mode.should_not_receive(:success!)
180
+
181
+ @mode.authenticate!
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,190 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+ require 'rack'
3
+ require 'fileutils'
4
+
5
+ module Aker::Cas
6
+ shared_examples_for "rack proxy callback handler" do
7
+ describe "/receive_pgt" do
8
+ describe "with both a pgtId and pgtIou" do
9
+ before do
10
+ @response = Rack::MockRequest.new(@app).
11
+ get("/receive_pgt?pgtId=PGT-baz&pgtIou=PGTIOU-foo")
12
+ end
13
+
14
+ it "stores the pair" do
15
+ pstore = PStore.new(@store_filename)
16
+ pstore.transaction do
17
+ pstore["PGTIOU-foo"].should == "PGT-baz"
18
+ end
19
+ end
20
+
21
+ it "returns success" do
22
+ @response.status.should == 200
23
+ end
24
+
25
+ it "gives a worthwhile message" do
26
+ @response.headers["Content-Type"].should == "text/plain"
27
+ @response.body.should =~ /PGT and PGTIOU received./
28
+ end
29
+ end
30
+
31
+ describe "without a pgtId" do
32
+ before do
33
+ @response = Rack::MockRequest.new(@app).
34
+ get("/receive_pgt?pgtIou=PGTIOU-foo")
35
+ end
36
+
37
+ it "is a bad request" do
38
+ @response.status.should == 400
39
+ end
40
+
41
+ it "gives a worthwile error message" do
42
+ @response.headers["Content-Type"].should == "text/plain"
43
+ @response.body.should ==
44
+ "pgtId is a required query parameter." <<
45
+ "\nSee section 2.5.4 of the CAS protocol specification."
46
+ end
47
+ end
48
+
49
+ describe "without a pgtIou" do
50
+ before do
51
+ @response = Rack::MockRequest.new(@app).
52
+ get("/receive_pgt?pgtId=PGT-foo")
53
+ end
54
+
55
+ it "is a bad request" do
56
+ @response.status.should == 400
57
+ end
58
+
59
+ it "gives a worthwile error message" do
60
+ @response.headers["Content-Type"].should == "text/plain"
61
+ @response.body.should ==
62
+ "pgtIou is a required query parameter." <<
63
+ "\nSee section 2.5.4 of the CAS protocol specification."
64
+ end
65
+ end
66
+
67
+ describe "without either" do
68
+ before do
69
+ @response = Rack::MockRequest.new(@app).
70
+ get("/receive_pgt")
71
+ end
72
+
73
+ it "is OK, because the JA-SIG CAS server expects it to be" do
74
+ @response.status.should == 200
75
+ end
76
+
77
+ it "gives a worthwile error message" do
78
+ @response.headers["Content-Type"].should == "text/plain"
79
+ @response.body.should ==
80
+ "Both pgtId and pgtIou are required query parameters." <<
81
+ "\nSee section 2.5.4 of the CAS protocol specification."
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "/retrieve_pgt" do
87
+ describe "with a known pgtIou" do
88
+ before do
89
+ pstore = PStore.new(@store_filename)
90
+ pstore.transaction do
91
+ pstore["PGTIOU-678"] = "PGT-876"
92
+ end
93
+
94
+ @response = Rack::MockRequest.new(@app).
95
+ get("/retrieve_pgt?pgtIou=PGTIOU-678")
96
+ end
97
+
98
+ it "is successful" do
99
+ @response.status.should == 200
100
+ end
101
+
102
+ it "returns the PGT" do
103
+ @response.headers["Content-Type"].should == "text/plain"
104
+ @response.body.should == "PGT-876"
105
+ end
106
+
107
+ it "deletes the PGT from the store once it has been retrieved" do
108
+ pstore = PStore.new(@store_filename)
109
+ pstore.transaction do
110
+ pstore["PGTIOU-678"].should be_nil
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "with an unknown pgtIou" do
116
+ before do
117
+ @response = Rack::MockRequest.new(@app).
118
+ get("/retrieve_pgt?pgtIou=PGTIOU-1234")
119
+ end
120
+
121
+ it "returns 404" do
122
+ @response.status.should == 404
123
+ end
124
+
125
+ it "provides a helpful message" do
126
+ @response.headers["Content-Type"].should == "text/plain"
127
+ @response.body.should ==
128
+ "pgtIou=PGTIOU-1234 does not exist. Perhaps it has already been retrieved."
129
+ end
130
+ end
131
+
132
+ describe "without a pgtIou" do
133
+ before do
134
+ @response = Rack::MockRequest.new(@app).
135
+ get("/retrieve_pgt")
136
+ end
137
+
138
+ it "is a bad request" do
139
+ @response.status.should == 400
140
+ end
141
+
142
+ it "provides a worthwhile error message" do
143
+ @response.headers["Content-Type"].should == "text/plain"
144
+ @response.body.should ==
145
+ "pgtIou is a required query parameter."
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe RackProxyCallback do
152
+ before do
153
+ @store_filename = "#{tmpdir}/#{File.basename(__FILE__)}.pstore"
154
+ end
155
+
156
+ after do
157
+ FileUtils.rm @store_filename if File.exist?(@store_filename)
158
+ end
159
+
160
+ describe "as middleware" do
161
+ before do
162
+ endpoint = lambda { |env| [200, {}, ["App invoked"]] }
163
+ @app = RackProxyCallback.new(endpoint, :store => @store_filename)
164
+ end
165
+
166
+ it_should_behave_like "rack proxy callback handler"
167
+
168
+ it "invokes the app for other paths" do
169
+ Rack::MockRequest.new(@app).get("/foo").should =~ /App invoked/
170
+ end
171
+
172
+ it "requires a filename for the store" do
173
+ lambda { RackProxyCallback.new(Object.new) }.
174
+ should raise_error(/Please specify a filename for the PGT store/)
175
+ end
176
+ end
177
+
178
+ describe "as an application" do
179
+ before do
180
+ @app = RackProxyCallback.application :store => @store_filename
181
+ end
182
+
183
+ it_should_behave_like "rack proxy callback handler"
184
+
185
+ it "404s for non-pgt requests" do
186
+ Rack::MockRequest.new(@app).get("/foo").status.should == 404
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,122 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+ require File.expand_path("../../modes/a_aker_mode", __FILE__)
3
+ require 'rack'
4
+ require 'uri'
5
+
6
+ module Aker::Cas
7
+ describe ServiceMode do
8
+ before do
9
+ @env = ::Rack::MockRequest.env_for('/')
10
+ @scope = mock
11
+ @mode = ServiceMode.new(@env, @scope)
12
+ end
13
+
14
+ it_should_behave_like "a aker mode"
15
+
16
+ describe "#key" do
17
+ it "is :cas" do
18
+ ServiceMode.key.should == :cas
19
+ end
20
+ end
21
+
22
+ describe "#valid?" do
23
+ it "returns false if the ST parameter is not in the query string" do
24
+ @mode.should_not be_valid
25
+ end
26
+
27
+ it "returns true if the ticket parameter is in the query string" do
28
+ @env['QUERY_STRING'] = 'ticket=ST-1foo'
29
+
30
+ @mode.should be_valid
31
+ end
32
+ end
33
+
34
+ describe "#kind" do
35
+ it "is :cas" do
36
+ @mode.kind.should == :cas
37
+ end
38
+ end
39
+
40
+ describe "#credentials" do
41
+ before do
42
+ @env["PATH_INFO"] = "/qu/ux"
43
+ @env["QUERY_STRING"] = "foo=baz"
44
+ end
45
+
46
+ describe "when there's a service ticket" do
47
+ before do
48
+ @env["QUERY_STRING"] << "&ticket=ST-1foo"
49
+ end
50
+
51
+ it "returns two elements" do
52
+ @mode.credentials.size.should == 2
53
+ end
54
+
55
+ it "returns the service ticket first" do
56
+ @mode.credentials[0].should == "ST-1foo"
57
+ end
58
+
59
+ it "returns the service URL second" do
60
+ @mode.credentials[1].should == "http://example.org/qu/ux?foo=baz"
61
+ end
62
+ end
63
+
64
+ it "returns nil if no service ticket was supplied" do
65
+ @mode.credentials.should be_nil
66
+ end
67
+ end
68
+
69
+ describe "#authenticate!" do
70
+ before do
71
+ @authority = mock
72
+ @env['aker.authority'] = @authority
73
+ @env['QUERY_STRING'] = 'ticket=ST-1foo'
74
+ end
75
+
76
+ it "signals success if the service ticket is good" do
77
+ user = stub
78
+ @authority.should_receive(:valid_credentials?).
79
+ with(:cas, 'ST-1foo', "http://example.org/").and_return(user)
80
+ @mode.should_receive(:success!).with(user)
81
+
82
+ @mode.authenticate!
83
+ end
84
+
85
+ it "does not signal success if the service ticket is bad" do
86
+ @authority.stub!(:valid_credentials? => nil)
87
+ @mode.should_not_receive(:success!)
88
+
89
+ @mode.authenticate!
90
+ end
91
+ end
92
+
93
+ describe "#on_ui_failure" do
94
+ before do
95
+ @env['aker.configuration'] = Aker::Configuration.new do
96
+ cas_parameters :login_url => 'https://cas.example.edu/login'
97
+ end
98
+ end
99
+
100
+ it "redirects to the CAS server's login page" do
101
+ response = @mode.on_ui_failure
102
+ location = URI.parse(response.location)
103
+ response.should be_redirect
104
+
105
+ location.scheme.should == "https"
106
+ location.host.should == "cas.example.edu"
107
+ location.path.should == "/login"
108
+ end
109
+
110
+ it "includes the service URL" do
111
+ @env["PATH_INFO"] = "/foo?a=b&c=d"
112
+
113
+ actual_uri.query.should == "service=http%3A%2F%2Fexample.org%2Ffoo%3Fa%3Db%26c%3Dd"
114
+ end
115
+
116
+ def actual_uri
117
+ response = @mode.on_ui_failure
118
+ URI.parse(response.location)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,114 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ require 'rack/request'
4
+ require 'rack/mock'
5
+
6
+ module Aker::Cas
7
+ describe ServiceUrl do
8
+ let(:env) { ::Rack::MockRequest.env_for('/') }
9
+
10
+ shared_examples_for '#service_url' do
11
+ it 'is the URL the user was trying to reach' do
12
+ env['PATH_INFO'] = '/qu/ux'
13
+ env['QUERY_STRING'] = 'a=b'
14
+ actual_url.should == 'http://example.org/qu/ux?a=b'
15
+ end
16
+
17
+ describe 'with warden\'s "attempted path" in the environment' do
18
+ before do
19
+ set_attempted_path
20
+ end
21
+
22
+ it 'uses it if present' do
23
+ env['PATH_INFO'] = '/unauthenticated'
24
+
25
+ actual_url.should == 'http://example.org/foo/quux'
26
+ end
27
+
28
+ it 'includes the port if not the default for http' do
29
+ env['rack.url_scheme'] = 'http'
30
+ env['SERVER_PORT'] = 81
31
+
32
+ actual_url.should == 'http://example.org:81/foo/quux'
33
+ end
34
+
35
+ it 'includes the port if not the default for https' do
36
+ env['rack.url_scheme'] = 'https'
37
+ env['SERVER_PORT'] = 80
38
+
39
+ actual_url.should == 'https://example.org:80/foo/quux'
40
+ end
41
+ end
42
+
43
+ it "filters out a service ticket that's the sole parameter" do
44
+ env['QUERY_STRING'] = 'ticket=ST-foo'
45
+ actual_url.should == 'http://example.org/'
46
+ end
47
+
48
+ it "filters out a service ticket that's the first parameter of several" do
49
+ env['QUERY_STRING'] = 'ticket=ST-bar&foo=baz'
50
+ actual_url.should == 'http://example.org/?foo=baz'
51
+ end
52
+
53
+ it "filters out a service ticket that's the last parameter of several" do
54
+ env['QUERY_STRING'] = 'foo=baz&ticket=ST-bar'
55
+ actual_url.should == 'http://example.org/?foo=baz'
56
+ end
57
+
58
+ it "filters out a service ticket that's in the middle of several" do
59
+ env['QUERY_STRING'] = 'foo=baz&ticket=ST-bar&zazz=quux'
60
+ actual_url.should == 'http://example.org/?foo=baz&zazz=quux'
61
+ end
62
+ end
63
+
64
+ context 'when mixed in' do
65
+ it_behaves_like '#service_url'
66
+
67
+ class WithRequest
68
+ include ServiceUrl
69
+
70
+ attr_reader :request, :env
71
+
72
+ def initialize(env)
73
+ @env = env
74
+ @request = Rack::Request.new(env)
75
+ end
76
+ end
77
+
78
+ class WithAttemptedPath < WithRequest
79
+ include Aker::Modes::Support::AttemptedPath
80
+ end
81
+
82
+ let(:with_request_only) { WithRequest.new(env).service_url }
83
+ let(:with_attempted_path) { WithAttemptedPath.new(env).service_url }
84
+ let(:actual_url) { with_attempted_path }
85
+
86
+ def set_attempted_path
87
+ env['warden.options'] = { :attempted_path => '/foo/quux' }
88
+ end
89
+
90
+ describe 'when the host class does not have an #attempted_path method' do
91
+ it 'does nothing with the attempted path' do
92
+ env['PATH_INFO'] = '/foo/bar'
93
+ set_attempted_path
94
+ with_request_only.should == 'http://example.org/foo/bar'
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'when called directly' do
100
+ it_behaves_like '#service_url'
101
+
102
+ let(:request) { ::Rack::Request.new(env) }
103
+ let(:actual_url) { ServiceUrl.service_url(request, attempted_path) }
104
+
105
+ def attempted_path
106
+ @attempted_path
107
+ end
108
+
109
+ def set_attempted_path
110
+ @attempted_path = '/foo/quux'
111
+ end
112
+ end
113
+ end
114
+ end