omniauth-cas 1.1.0.beta.1 → 1.1.0.pre.rc.1

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/omniauth-cas.gemspec CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/omniauth/cas/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Derek Lindahl"]
6
6
  gem.email = ["dlindahl@customink.com"]
7
- # gem.description = %q{TODO: Write a gem description}
8
7
  gem.summary = %q{CAS Strategy for OmniAuth}
8
+ gem.description = gem.summary
9
9
  gem.homepage = "https://github.com/dlindahl/omniauth-cas"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -15,16 +15,14 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Omniauth::Cas::VERSION
17
17
 
18
- gem.add_dependency 'omniauth', '~> 1.1.0'
18
+ gem.add_dependency 'omniauth', '~> 1.2.0'
19
19
  gem.add_dependency 'nokogiri', '~> 1.5'
20
20
  gem.add_dependency 'addressable', '~> 2.3'
21
21
 
22
- gem.add_development_dependency 'rake', '~> 0.9'
23
- gem.add_development_dependency 'webmock', '~> 1.8.11'
24
- gem.add_development_dependency 'simplecov', '~> 0.7.1'
25
- gem.add_development_dependency 'rspec', '~> 2.11'
22
+ gem.add_development_dependency 'rake', '~> 10.0'
23
+ gem.add_development_dependency 'webmock', '~> 1.19.0'
24
+ gem.add_development_dependency 'rspec', '~> 3.1.0'
26
25
  gem.add_development_dependency 'rack-test', '~> 0.6'
27
26
 
28
27
  gem.add_development_dependency 'awesome_print'
29
-
30
28
  end
@@ -0,0 +1,16 @@
1
+ <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2
+ <cas:authenticationSuccess>
3
+ <cas:user>psegel</cas:user>
4
+ <cas:attributes>
5
+ <cas:employeeid>54</cas:employeeid>
6
+ <cas:first_name>P. Segel</cas:first_name>
7
+ <cas:first_name>Peter</cas:first_name>
8
+ <cas:last_name>Segel</cas:last_name>
9
+ <cas:email>psegel@intridea.com</cas:email>
10
+ <cas:location>Washington, D.C.</cas:location>
11
+ <cas:image>/images/user.jpg</cas:image>
12
+ <cas:phone>555-555-5555</cas:phone>
13
+ <cas:hire_date>2004-07-13</cas:hire_date>
14
+ </cas:attributes>
15
+ </cas:authenticationSuccess>
16
+ </cas:serviceResponse>
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS::LogoutRequest do
4
+ let(:strategy) { double('strategy') }
5
+ let(:env) do
6
+ { 'rack.input' => StringIO.new('','r') }
7
+ end
8
+ let(:request) { double('request', params:params, env:env) }
9
+ let(:params) { { 'url' => url, 'logoutRequest' => logoutRequest } }
10
+ let(:url) { 'http://notes.dev/signed_in' }
11
+ let(:logoutRequest) do
12
+ %Q[
13
+ <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion\" ID="123abc-1234-ab12-cd34-1234abcd" Version="2.0" IssueInstant="#{Time.now.to_s}">
14
+ <saml:NameID>@NOT_USED@</saml:NameID>
15
+ <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex>
16
+ </samlp:LogoutRequest>
17
+ ]
18
+ end
19
+
20
+ subject { described_class.new(strategy, request).call(options) }
21
+
22
+ describe 'SAML attributes' do
23
+ let(:callback) { Proc.new{} }
24
+ let(:options) do
25
+ { on_single_sign_out: callback }
26
+ end
27
+
28
+ before do
29
+ @rack_input = nil
30
+ allow(callback).to receive(:call) do |req|
31
+ @rack_input = req.env['rack.input'].read
32
+ true
33
+ end
34
+ subject
35
+ end
36
+
37
+ it 'are parsed and injected into the Rack Request parameters' do
38
+ expect(@rack_input).to eq 'name_id=%40NOT_USED%40&session_index=ST-123456-123abc456def'
39
+ end
40
+
41
+ context 'that raise when parsed' do
42
+ let(:env) { { 'rack.input' => nil } }
43
+
44
+ before do
45
+ allow(strategy).to receive(:fail!)
46
+ subject
47
+ expect(strategy).to have_received(:fail!)
48
+ end
49
+
50
+ it 'responds with an error' do
51
+ expect(strategy).to have_received(:fail!)
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'with a configured callback' do
57
+ let(:options) do
58
+ { on_single_sign_out: callback }
59
+ end
60
+
61
+ context 'that returns TRUE' do
62
+ let(:callback) { Proc.new{true} }
63
+
64
+ it 'responds with OK' do
65
+ expect(subject[0]).to eq 200
66
+ expect(subject[2].body).to eq ['OK']
67
+ end
68
+ end
69
+
70
+ context 'that returns Nil' do
71
+ let(:callback) { Proc.new{} }
72
+
73
+ it 'responds with OK' do
74
+ expect(subject[0]).to eq 200
75
+ expect(subject[2].body).to eq ['OK']
76
+ end
77
+ end
78
+
79
+ context 'that returns a tuple' do
80
+ let(:callback) { Proc.new{ [400,{},'Bad Request'] } }
81
+
82
+ it 'responds with OK' do
83
+ expect(subject[0]).to eq 400
84
+ expect(subject[2].body).to eq ['Bad Request']
85
+ end
86
+ end
87
+
88
+ context 'that raises an error' do
89
+ let(:exception) { RuntimeError.new('error' )}
90
+ let(:callback) { Proc.new{raise exception} }
91
+
92
+ before do
93
+ allow(strategy).to receive(:fail!)
94
+ subject
95
+ end
96
+
97
+ it 'responds with an error' do
98
+ expect(strategy).to have_received(:fail!)
99
+ .with(:logout_request, exception)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,33 +1,55 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe OmniAuth::Strategies::CAS::ServiceTicketValidator do
4
- let(:strategy_stub) do
5
- stub('strategy stub',
4
+ let(:strategy) do
5
+ double('strategy',
6
6
  service_validate_url: 'https://example.org/serviceValidate'
7
7
  )
8
8
  end
9
-
10
9
  let(:provider_options) do
11
- stub('provider options',
10
+ double('provider_options',
12
11
  disable_ssl_verification?: false,
13
12
  ca_path: '/etc/ssl/certsZOMG'
14
13
  )
15
14
  end
16
-
17
15
  let(:validator) do
18
- OmniAuth::Strategies::CAS::ServiceTicketValidator.new( strategy_stub, provider_options, '/foo', nil )
16
+ OmniAuth::Strategies::CAS::ServiceTicketValidator.new( strategy, provider_options, '/foo', nil )
19
17
  end
20
18
 
21
- describe '#user_info' do
22
- subject do
23
- stub_request(:get, 'https://example.org/serviceValidate?').to_return(status: 200, body: '')
24
- validator.user_info
19
+ describe '#call' do
20
+ before do
21
+ stub_request(:get, 'https://example.org/serviceValidate?')
22
+ .to_return(status: 200, body: '')
25
23
  end
26
24
 
27
- it 'should use the configured CA path' do
28
- provider_options.should_receive :ca_path
25
+ subject { validator.call }
29
26
 
27
+ it 'returns itself' do
28
+ expect(subject).to eq validator
29
+ end
30
+
31
+ it 'uses the configured CA path' do
30
32
  subject
33
+ expect(provider_options).to have_received :ca_path
34
+ end
35
+ end
36
+
37
+ describe '#user_info' do
38
+ let(:ok_fixture) do
39
+ File.expand_path(File.join(File.dirname(__FILE__), '../../../fixtures/cas_success.xml'))
40
+ end
41
+ let(:service_response) { File.read(ok_fixture) }
42
+
43
+ before do
44
+ stub_request(:get, 'https://example.org/serviceValidate?')
45
+ .to_return(status: 200, body:service_response)
46
+ validator.call
47
+ end
48
+
49
+ subject { validator.user_info }
50
+
51
+ it 'parses user info from the response' do
52
+ expect(subject).to include 'user' => 'psegel'
31
53
  end
32
54
  end
33
- end
55
+ end
@@ -3,11 +3,14 @@ require 'spec_helper'
3
3
  describe OmniAuth::Strategies::CAS, type: :strategy do
4
4
  include Rack::Test::Methods
5
5
 
6
- class MyCasProvider < OmniAuth::Strategies::CAS; end # TODO: Not really needed. just an alias but it requires the :name option which might confuse users...
7
- def app
6
+ let(:my_cas_provider) { Class.new(OmniAuth::Strategies::CAS) }
7
+ before do
8
+ stub_const 'MyCasProvider', my_cas_provider
9
+ end
10
+ let(:app) do
8
11
  Rack::Builder.new {
9
12
  use OmniAuth::Test::PhonySession
10
- use MyCasProvider, name: :cas, host: 'cas.example.org', ssl: false, port: 8080, uid_key: :employeeid
13
+ use MyCasProvider, name: :cas, host: 'cas.example.org', ssl: false, port: 8080, uid_field: :employeeid
11
14
  run lambda { |env| [404, {'Content-Type' => 'text/plain'}, [env.key?('omniauth.auth').to_s]] }
12
15
  }.to_app
13
16
  end
@@ -22,13 +25,56 @@ describe OmniAuth::Strategies::CAS, type: :strategy do
22
25
 
23
26
  it { should be_redirect }
24
27
 
25
- it 'should redirect to the CAS server' do
26
- subject.headers['Location'].should == 'http://cas.example.org:8080/login?' + redirect_params
28
+ it 'redirects to the CAS server' do
29
+ expect(subject.headers).to include 'Location' => "http://cas.example.org:8080/login?#{redirect_params}"
30
+ end
31
+ end
32
+
33
+ describe '#cas_url' do
34
+ let(:params) { Hash.new }
35
+ let(:provider) { MyCasProvider.new(nil, params) }
36
+
37
+ subject { provider.cas_url }
38
+
39
+ it 'raises an ArgumentError' do
40
+ expect{subject}.to raise_error ArgumentError, %r{:host and :login_url MUST be provided}
41
+ end
42
+
43
+ context 'with an explicit :url option' do
44
+ let(:url) { 'https://example.org:8080/my_cas' }
45
+ let(:params) { super().merge url:url }
46
+
47
+ before { subject }
48
+
49
+ it { should eq url }
50
+
51
+ it 'parses the URL into it the appropriate strategy options' do
52
+ expect(provider.options).to include ssl:true
53
+ expect(provider.options).to include host:'example.org'
54
+ expect(provider.options).to include port:8080
55
+ expect(provider.options).to include path:'/my_cas'
56
+ end
57
+ end
58
+
59
+ context 'with explicit URL component' do
60
+ let(:params) { super().merge host:'example.org', port:1234, ssl:true, path:'/a/path' }
61
+
62
+ before { subject }
63
+
64
+ it { should eq 'https://example.org:1234/a/path' }
65
+
66
+ it 'parses the URL into it the appropriate strategy options' do
67
+ expect(provider.options).to include ssl:true
68
+ expect(provider.options).to include host:'example.org'
69
+ expect(provider.options).to include port:1234
70
+ expect(provider.options).to include path:'/a/path'
71
+ end
27
72
  end
28
73
  end
29
74
 
30
75
  describe 'defaults' do
31
76
  subject { MyCasProvider.default_options.to_hash }
77
+
32
78
  it { should include('ssl' => true) }
33
79
  end
34
80
 
@@ -52,102 +98,153 @@ describe OmniAuth::Strategies::CAS, type: :strategy do
52
98
  end
53
99
  end
54
100
 
55
- describe 'GET /auth/cas/callback without a ticket' do
56
- before { get '/auth/cas/callback' }
57
-
58
- subject { last_response }
101
+ describe 'GET /auth/cas/callback' do
102
+ context 'without a ticket' do
103
+ before { get '/auth/cas/callback' }
59
104
 
60
- it { should be_redirect }
105
+ subject { last_response }
61
106
 
62
- it 'should have a failure message' do
63
- subject.headers['Location'].should == '/auth/failure?message=no_ticket&strategy=cas'
64
- end
65
- end
107
+ it { should be_redirect }
66
108
 
67
- describe 'GET /auth/cas/callback with an invalid ticket' do
68
- before do
69
- stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=9391d/).
70
- to_return( body: File.read('spec/fixtures/cas_failure.xml') )
71
- get '/auth/cas/callback?ticket=9391d'
72
- end
73
-
74
- subject { last_response }
75
-
76
- it { should be_redirect }
77
-
78
- it 'should have a failure message' do
79
- subject.headers['Location'].should == '/auth/failure?message=invalid_ticket&strategy=cas'
109
+ it 'redirects with a failure message' do
110
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=no_ticket&strategy=cas'
111
+ end
80
112
  end
81
- end
82
-
83
- describe 'GET /auth/cas/callback with a valid ticket' do
84
- let(:return_url) { 'http://127.0.0.10/?some=parameter' }
85
113
 
86
- before do
87
- stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=593af/)
88
- .with { |request| @request_uri = request.uri.to_s }
89
- .to_return( body: File.read('spec/fixtures/cas_success.xml') )
114
+ context 'with an invalid ticket' do
115
+ before do
116
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=9391d/).
117
+ to_return( body: File.read('spec/fixtures/cas_failure.xml') )
118
+ get '/auth/cas/callback?ticket=9391d'
119
+ end
90
120
 
91
- get "/auth/cas/callback?ticket=593af&url=#{return_url}"
92
- end
121
+ subject { last_response }
93
122
 
94
- it 'should strip the ticket parameter from the callback URL' do
95
- @request_uri.scan('ticket=').length.should == 1
96
- end
123
+ it { should be_redirect }
97
124
 
98
- it 'should properly encode the service URL' do
99
- WebMock.should have_requested(:get, 'http://cas.example.org:8080/serviceValidate')
100
- .with(query: {
101
- ticket: '593af',
102
- service: 'http://example.org/auth/cas/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter')
103
- })
125
+ it 'redirects with a failure message' do
126
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=invalid_ticket&strategy=cas'
127
+ end
104
128
  end
105
129
 
106
- context "request.env['omniauth.auth']" do
107
- subject { last_request.env['omniauth.auth'] }
108
-
109
- it { should be_kind_of Hash }
130
+ describe 'with a valid ticket' do
131
+ shared_examples :successful_validation do
132
+ before do
133
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=593af/)
134
+ .with { |request| @request_uri = request.uri.to_s }
135
+ .to_return( body: File.read("spec/fixtures/#{xml_file_name}") )
136
+
137
+ get "/auth/cas/callback?ticket=593af&url=#{return_url}"
138
+ end
139
+
140
+ it 'strips the ticket parameter from the callback URL' do
141
+ expect(@request_uri.scan('ticket=').size).to eq 1
142
+ end
143
+
144
+ it 'properly encodes the service URL' do
145
+ expect(WebMock).to have_requested(:get, 'http://cas.example.org:8080/serviceValidate')
146
+ .with(query: {
147
+ ticket: '593af',
148
+ service: 'http://example.org/auth/cas/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter')
149
+ })
150
+ end
151
+
152
+ context "request.env['omniauth.auth']" do
153
+ subject { last_request.env['omniauth.auth'] }
154
+
155
+ it { should be_kind_of Hash }
156
+
157
+ it 'identifes the provider' do
158
+ expect(subject.provider).to eq :cas
159
+ end
160
+
161
+ it 'returns the UID of the user' do
162
+ expect(subject.uid).to eq '54'
163
+ end
164
+
165
+ context 'the info hash' do
166
+ subject { last_request.env['omniauth.auth']['info'] }
167
+
168
+ it 'includes user info attributes' do
169
+ expect(subject.name).to eq 'Peter Segel'
170
+ expect(subject.first_name).to eq 'Peter'
171
+ expect(subject.last_name).to eq 'Segel'
172
+ expect(subject.nickname).to eq 'psegel'
173
+ expect(subject.email).to eq 'psegel@intridea.com'
174
+ expect(subject.location).to eq 'Washington, D.C.'
175
+ expect(subject.image).to eq '/images/user.jpg'
176
+ expect(subject.phone).to eq '555-555-5555'
177
+ end
178
+ end
179
+
180
+ context 'the extra hash' do
181
+ subject { last_request.env['omniauth.auth']['extra'] }
182
+
183
+ it 'includes additional user attributes' do
184
+ expect(subject.user).to eq 'psegel'
185
+ expect(subject.employeeid).to eq '54'
186
+ expect(subject.hire_date).to eq '2004-07-13'
187
+ end
188
+ end
189
+
190
+ context 'the credentials hash' do
191
+ subject { last_request.env['omniauth.auth']['credentials'] }
192
+
193
+ it 'has a ticket value' do
194
+ expect(subject.ticket).to eq '593af'
195
+ end
196
+ end
197
+ end
198
+
199
+ it 'calls through to the master app' do
200
+ expect(last_response.body).to eq 'true'
201
+ end
202
+ end
110
203
 
111
- its(:provider) { should == :cas }
204
+ let(:return_url) { 'http://127.0.0.10/?some=parameter' }
112
205
 
113
- its(:uid) { should == '54'}
206
+ context 'with JASIG flavored XML' do
207
+ let(:xml_file_name) { 'cas_success_jasig.xml' }
114
208
 
115
- context 'the info hash' do
116
- subject { last_request.env['omniauth.auth']['info'] }
209
+ it_behaves_like :successful_validation
210
+ end
117
211
 
118
- it { should have(6).items }
212
+ context 'with classic XML' do
213
+ let(:xml_file_name) { 'cas_success.xml' }
119
214
 
120
- its(:name) { should == 'Peter Segel' }
121
- its(:first_name) { should == 'Peter' }
122
- its(:last_name) { should == 'Segel' }
123
- its(:email) { should == 'psegel@intridea.com' }
124
- its(:location) { should == 'Washington, D.C.' }
125
- its(:image) { should == '/images/user.jpg' }
126
- its(:phone) { should == '555-555-5555' }
215
+ it_behaves_like :successful_validation
127
216
  end
217
+ end
218
+ end
128
219
 
129
- context 'the extra hash' do
130
- subject { last_request.env['omniauth.auth']['extra'] }
220
+ describe 'POST /auth/cas/callback' do
221
+ describe 'with a Single Sign-Out logoutRequest' do
222
+ let(:logoutRequest) do
223
+ %Q[
224
+ <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion\" ID="123abc-1234-ab12-cd34-1234abcd" Version="2.0" IssueInstant="#{Time.now.to_s}">
225
+ <saml:NameID>@NOT_USED@</saml:NameID>
226
+ <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex>
227
+ </samlp:LogoutRequest>
228
+ ]
229
+ end
131
230
 
132
- it { should have(3).items }
231
+ let(:logout_request) { double('logout_request', call:[200,{},'OK']) }
133
232
 
134
- its(:user) { should == 'psegel' }
135
- its(:employeeid) { should == '54' }
136
- its(:hire_date) { should == '2004-07-13' }
233
+ subject do
234
+ post 'auth/cas/callback', logoutRequest:logoutRequest
137
235
  end
138
236
 
139
- context 'the credentials hash' do
140
- subject { last_request.env['omniauth.auth']['credentials'] }
141
-
142
- it { should have(1).items }
237
+ before do
238
+ allow_any_instance_of(MyCasProvider)
239
+ .to receive(:logout_request_service)
240
+ .and_return double('LogoutRequest', new:logout_request)
143
241
 
144
- its(:ticket) { should == '593af' }
242
+ subject
145
243
  end
146
- end
147
244
 
148
- it 'should call through to the master app' do
149
- last_response.body.should == 'true'
245
+ it 'initializes a LogoutRequest' do
246
+ expect(logout_request).to have_received :call
247
+ end
150
248
  end
151
249
  end
152
-
153
250
  end