omniauth-cas 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class CAS
4
+ class LogoutRequest
5
+ def initialize(strategy, request)
6
+ @strategy, @request = strategy, request
7
+ end
8
+
9
+ def call(options = {})
10
+ @options = options
11
+
12
+ begin
13
+ result = single_sign_out_callback.call(*logout_request)
14
+ rescue StandardError => err
15
+ return @strategy.fail! :logout_request, err
16
+ else
17
+ result = [200,{},'OK'] if result == true || result.nil?
18
+ ensure
19
+ return unless result
20
+
21
+ # TODO: Why does ActionPack::Response return [status,headers,body]
22
+ # when Rack::Response#new wants [body,status,headers]? Additionally,
23
+ # why does Rack::Response differ in argument order from the usual
24
+ # Rack-like [status,headers,body] array?
25
+ return Rack::Response.new(result[2],result[0],result[1]).finish
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def logout_request
32
+ @logout_request ||= begin
33
+ saml = Nokogiri.parse(@request.params['logoutRequest'])
34
+ name_id = saml.xpath('//saml:NameID').text
35
+ sess_idx = saml.xpath('//samlp:SessionIndex').text
36
+ inject_params(name_id:name_id, session_index:sess_idx)
37
+ @request
38
+ end
39
+ end
40
+
41
+ def inject_params(new_params)
42
+ rack_input = @request.env['rack.input'].read
43
+ params = Rack::Utils.parse_query(rack_input, '&').merge new_params
44
+ @request.env['rack.input'] = StringIO.new(Rack::Utils.build_query(params))
45
+ rescue
46
+ # A no-op intended to ensure that the ensure block is run
47
+ raise
48
+ ensure
49
+ @request.env['rack.input'].rewind
50
+ end
51
+
52
+ def single_sign_out_callback
53
+ @options[:on_single_sign_out]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -6,9 +6,10 @@ module OmniAuth
6
6
  module Strategies
7
7
  class CAS
8
8
  class ServiceTicketValidator
9
-
10
9
  VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
11
10
 
11
+ attr_reader :success_body
12
+
12
13
  # Build a validator from a +configuration+, a
13
14
  # +return_to+ URL, and a +ticket+.
14
15
  #
@@ -20,6 +21,13 @@ module OmniAuth
20
21
  @uri = URI.parse(strategy.service_validate_url(return_to_url, ticket))
21
22
  end
22
23
 
24
+ # Executes a network request to process the CAS Service Response
25
+ def call
26
+ @response_body = get_service_response_body
27
+ @success_body = find_authentication_success(@response_body)
28
+ self
29
+ end
30
+
23
31
  # Request validation of the ticket from the CAS server's
24
32
  # serviceValidate (CAS 2.0) function.
25
33
  #
@@ -29,31 +37,39 @@ module OmniAuth
29
37
  #
30
38
  # @raise any connection errors encountered.
31
39
  def user_info
32
- parse_user_info( find_authentication_success( get_service_response_body ) )
40
+ parse_user_info(@success_body)
33
41
  end
34
42
 
35
43
  private
36
44
 
45
+ # Merges attributes with multiple values into an array if support is
46
+ # enabled (disabled by default)
47
+ def attribute_value(user_info, attribute, value)
48
+ if @options.merge_multivalued_attributes && user_info.key?(attribute)
49
+ Array(user_info[attribute]).push(value)
50
+ else
51
+ value
52
+ end
53
+ end
54
+
37
55
  # turns an `<cas:authenticationSuccess>` node into a Hash;
38
56
  # returns nil if given nil
39
57
  def parse_user_info(node)
40
58
  return nil if node.nil?
41
-
42
59
  {}.tap do |hash|
43
60
  node.children.each do |e|
44
61
  node_name = e.name.sub(/^cas:/, '')
45
- unless e.kind_of?(Nokogiri::XML::Text) ||
46
- node_name == 'proxies'
62
+ unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
47
63
  # There are no child elements
48
64
  if e.element_children.count == 0
49
- hash[node_name] = e.content
65
+ hash[node_name] = attribute_value(hash, node_name, e.content)
50
66
  elsif e.element_children.count
51
67
  # JASIG style extra attributes
52
68
  if node_name == 'attributes'
53
- hash.merge! parse_user_info e
69
+ hash.merge!(parse_user_info(e))
54
70
  else
55
71
  hash[node_name] = [] if hash[node_name].nil?
56
- hash[node_name].push parse_user_info e
72
+ hash[node_name].push(parse_user_info(e))
57
73
  end
58
74
  end
59
75
  end
@@ -93,7 +109,6 @@ module OmniAuth
93
109
  end
94
110
  result
95
111
  end
96
-
97
112
  end
98
113
  end
99
114
  end
@@ -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'
19
- gem.add_dependency 'nokogiri', '~> 1.6'
18
+ gem.add_dependency 'omniauth', '~> 1.2'
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'
26
- gem.add_development_dependency 'rack-test', '~> 0.6'
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'webmock'
24
+ gem.add_development_dependency 'rspec'
25
+ gem.add_development_dependency 'rack-test'
27
26
 
28
27
  gem.add_development_dependency 'awesome_print'
29
-
30
28
  end
@@ -10,5 +10,8 @@
10
10
  <cas:image>/images/user.jpg</cas:image>
11
11
  <cas:phone>555-555-5555</cas:phone>
12
12
  <cas:hire_date>2004-07-13</cas:hire_date>
13
+ <cas:roles>senator</cas:roles>
14
+ <cas:roles>lobbyist</cas:roles>
15
+ <cas:roles>financier</cas:roles>
13
16
  </cas:authenticationSuccess>
14
17
  </cas:serviceResponse>
@@ -11,6 +11,9 @@
11
11
  <cas:image>/images/user.jpg</cas:image>
12
12
  <cas:phone>555-555-5555</cas:phone>
13
13
  <cas:hire_date>2004-07-13</cas:hire_date>
14
+ <cas:roles>senator</cas:roles>
15
+ <cas:roles>lobbyist</cas:roles>
16
+ <cas:roles>financier</cas:roles>
14
17
  </cas:attributes>
15
18
  </cas:authenticationSuccess>
16
19
  </cas:serviceResponse>
@@ -0,0 +1,105 @@
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
+ let(:response_body) { subject[2].respond_to?(:body) ? subject[2].body : subject[2] }
62
+
63
+ context 'that returns TRUE' do
64
+ let(:callback) { Proc.new{true} }
65
+
66
+ it 'responds with OK' do
67
+ expect(subject[0]).to eq 200
68
+ expect(response_body).to eq ['OK']
69
+ end
70
+ end
71
+
72
+ context 'that returns Nil' do
73
+ let(:callback) { Proc.new{} }
74
+
75
+ it 'responds with OK' do
76
+ expect(subject[0]).to eq 200
77
+ expect(response_body).to eq ['OK']
78
+ end
79
+ end
80
+
81
+ context 'that returns a tuple' do
82
+ let(:callback) { Proc.new{ [400,{},'Bad Request'] } }
83
+
84
+ it 'responds with OK' do
85
+ expect(subject[0]).to eq 400
86
+ expect(response_body).to eq ['Bad Request']
87
+ end
88
+ end
89
+
90
+ context 'that raises an error' do
91
+ let(:exception) { RuntimeError.new('error' )}
92
+ let(:callback) { Proc.new{raise exception} }
93
+
94
+ before do
95
+ allow(strategy).to receive(:fail!)
96
+ subject
97
+ end
98
+
99
+ it 'responds with an error' do
100
+ expect(strategy).to have_received(:fail!)
101
+ .with(:logout_request, exception)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,33 +1,74 @@
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,
12
+ merge_multivalued_attributes: false,
13
13
  ca_path: '/etc/ssl/certsZOMG'
14
14
  )
15
15
  end
16
-
17
16
  let(:validator) do
18
- OmniAuth::Strategies::CAS::ServiceTicketValidator.new( strategy_stub, provider_options, '/foo', nil )
17
+ OmniAuth::Strategies::CAS::ServiceTicketValidator.new( strategy, provider_options, '/foo', nil )
19
18
  end
20
19
 
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
20
+ describe '#call' do
21
+ before do
22
+ stub_request(:get, 'https://example.org/serviceValidate?')
23
+ .to_return(status: 200, body: '')
25
24
  end
26
25
 
27
- it 'should use the configured CA path' do
28
- provider_options.should_receive :ca_path
26
+ subject { validator.call }
27
+
28
+ it 'returns itself' do
29
+ expect(subject).to eq validator
30
+ end
29
31
 
32
+ it 'uses the configured CA path' do
30
33
  subject
34
+ expect(provider_options).to have_received :ca_path
35
+ end
36
+ end
37
+
38
+ describe '#user_info' do
39
+ let(:ok_fixture) do
40
+ File.expand_path(File.join(File.dirname(__FILE__), '../../../fixtures/cas_success.xml'))
41
+ end
42
+ let(:service_response) { File.read(ok_fixture) }
43
+
44
+ before do
45
+ stub_request(:get, 'https://example.org/serviceValidate?')
46
+ .to_return(status: 200, body:service_response)
47
+ validator.call
48
+ end
49
+
50
+ subject { validator.user_info }
51
+
52
+ context 'with default settings' do
53
+ it 'parses user info from the response' do
54
+ expect(subject).to include 'user' => 'psegel'
55
+ expect(subject).to include 'roles' => 'financier'
56
+ end
57
+ end
58
+
59
+ context 'when merging multivalued attributes' do
60
+ let(:provider_options) do
61
+ double('provider_options',
62
+ disable_ssl_verification?: false,
63
+ merge_multivalued_attributes: true,
64
+ ca_path: '/etc/ssl/certsZOMG'
65
+ )
66
+ end
67
+
68
+ it 'parses multivalued user info from the response' do
69
+ expect(subject).to include 'user' => 'psegel'
70
+ expect(subject).to include 'roles' => %w[senator lobbyist financier]
71
+ end
31
72
  end
32
73
  end
33
- end
74
+ end
@@ -3,11 +3,24 @@ 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,
14
+ name: :cas,
15
+ host: 'cas.example.org',
16
+ ssl: false,
17
+ port: 8080,
18
+ uid_field: :employeeid,
19
+ fetch_raw_info: Proc.new { |v, opts, ticket, info, node|
20
+ info.empty? ? {} : {
21
+ "roles" => node.xpath('//cas:roles').map(&:text),
22
+ }
23
+ }
11
24
  run lambda { |env| [404, {'Content-Type' => 'text/plain'}, [env.key?('omniauth.auth').to_s]] }
12
25
  }.to_app
13
26
  end
@@ -22,13 +35,56 @@ describe OmniAuth::Strategies::CAS, type: :strategy do
22
35
 
23
36
  it { should be_redirect }
24
37
 
25
- it 'should redirect to the CAS server' do
26
- subject.headers['Location'].should == 'http://cas.example.org:8080/login?' + redirect_params
38
+ it 'redirects to the CAS server' do
39
+ expect(subject.headers).to include 'Location' => "http://cas.example.org:8080/login?#{redirect_params}"
40
+ end
41
+ end
42
+
43
+ describe '#cas_url' do
44
+ let(:params) { Hash.new }
45
+ let(:provider) { MyCasProvider.new(nil, params) }
46
+
47
+ subject { provider.cas_url }
48
+
49
+ it 'raises an ArgumentError' do
50
+ expect{subject}.to raise_error ArgumentError, %r{:host and :login_url MUST be provided}
51
+ end
52
+
53
+ context 'with an explicit :url option' do
54
+ let(:url) { 'https://example.org:8080/my_cas' }
55
+ let(:params) { super().merge url:url }
56
+
57
+ before { subject }
58
+
59
+ it { should eq url }
60
+
61
+ it 'parses the URL into it the appropriate strategy options' do
62
+ expect(provider.options).to include ssl:true
63
+ expect(provider.options).to include host:'example.org'
64
+ expect(provider.options).to include port:8080
65
+ expect(provider.options).to include path:'/my_cas'
66
+ end
67
+ end
68
+
69
+ context 'with explicit URL component' do
70
+ let(:params) { super().merge host:'example.org', port:1234, ssl:true, path:'/a/path' }
71
+
72
+ before { subject }
73
+
74
+ it { should eq 'https://example.org:1234/a/path' }
75
+
76
+ it 'parses the URL into it the appropriate strategy options' do
77
+ expect(provider.options).to include ssl:true
78
+ expect(provider.options).to include host:'example.org'
79
+ expect(provider.options).to include port:1234
80
+ expect(provider.options).to include path:'/a/path'
81
+ end
27
82
  end
28
83
  end
29
84
 
30
85
  describe 'defaults' do
31
86
  subject { MyCasProvider.default_options.to_hash }
87
+
32
88
  it { should include('ssl' => true) }
33
89
  end
34
90
 
@@ -52,114 +108,154 @@ describe OmniAuth::Strategies::CAS, type: :strategy do
52
108
  end
53
109
  end
54
110
 
55
- describe 'GET /auth/cas/callback without a ticket' do
56
- before { get '/auth/cas/callback' }
57
-
58
- subject { last_response }
59
-
60
- it { should be_redirect }
111
+ describe 'GET /auth/cas/callback' do
112
+ context 'without a ticket' do
113
+ before { get '/auth/cas/callback' }
61
114
 
62
- it 'should have a failure message' do
63
- subject.headers['Location'].should == '/auth/failure?message=no_ticket&strategy=cas'
64
- end
65
- end
115
+ subject { last_response }
66
116
 
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 }
117
+ it { should be_redirect }
75
118
 
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'
119
+ it 'redirects with a failure message' do
120
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=no_ticket&strategy=cas'
121
+ end
80
122
  end
81
- end
82
123
 
83
- describe 'GET /auth/cas/callback with a valid ticket' do
84
- shared_examples :successful_validation do
124
+ context 'with an invalid ticket' do
85
125
  before do
86
- stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=593af/)
87
- .with { |request| @request_uri = request.uri.to_s }
88
- .to_return( body: File.read("spec/fixtures/#{xml_file_name}") )
89
-
90
- get "/auth/cas/callback?ticket=593af&url=#{return_url}"
126
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=9391d/).
127
+ to_return( body: File.read('spec/fixtures/cas_failure.xml') )
128
+ get '/auth/cas/callback?ticket=9391d'
91
129
  end
92
130
 
93
- it 'should strip the ticket parameter from the callback URL' do
94
- @request_uri.scan('ticket=').length.should == 1
95
- end
131
+ subject { last_response }
96
132
 
97
- it 'should properly encode the service URL' do
98
- WebMock.should have_requested(:get, 'http://cas.example.org:8080/serviceValidate')
99
- .with(query: {
100
- ticket: '593af',
101
- service: 'http://example.org/auth/cas/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter')
102
- })
103
- end
133
+ it { should be_redirect }
104
134
 
105
- context "request.env['omniauth.auth']" do
106
- subject { last_request.env['omniauth.auth'] }
135
+ it 'redirects with a failure message' do
136
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=invalid_ticket&strategy=cas'
137
+ end
138
+ end
107
139
 
108
- it { should be_kind_of Hash }
140
+ describe 'with a valid ticket' do
141
+ shared_examples :successful_validation do
142
+ before do
143
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=593af/)
144
+ .with { |request| @request_uri = request.uri.to_s }
145
+ .to_return( body: File.read("spec/fixtures/#{xml_file_name}") )
109
146
 
110
- its(:provider) { should == :cas }
147
+ get "/auth/cas/callback?ticket=593af&url=#{return_url}"
148
+ end
111
149
 
112
- its(:uid) { should == '54'}
150
+ it 'strips the ticket parameter from the callback URL' do
151
+ expect(@request_uri.scan('ticket=').size).to eq 1
152
+ end
113
153
 
114
- context 'the info hash' do
115
- subject { last_request.env['omniauth.auth']['info'] }
154
+ it 'properly encodes the service URL' do
155
+ expect(WebMock).to have_requested(:get, 'http://cas.example.org:8080/serviceValidate')
156
+ .with(query: {
157
+ ticket: '593af',
158
+ service: 'http://example.org/auth/cas/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter')
159
+ })
160
+ end
116
161
 
117
- it { should have(6).items }
162
+ context "request.env['omniauth.auth']" do
163
+ subject { last_request.env['omniauth.auth'] }
164
+
165
+ it { should be_kind_of Hash }
166
+
167
+ it 'identifes the provider' do
168
+ expect(subject.provider).to eq :cas
169
+ end
170
+
171
+ it 'returns the UID of the user' do
172
+ expect(subject.uid).to eq '54'
173
+ end
174
+
175
+ context 'the info hash' do
176
+ subject { last_request.env['omniauth.auth']['info'] }
177
+
178
+ it 'includes user info attributes' do
179
+ expect(subject.name).to eq 'Peter Segel'
180
+ expect(subject.first_name).to eq 'Peter'
181
+ expect(subject.last_name).to eq 'Segel'
182
+ expect(subject.nickname).to eq 'psegel'
183
+ expect(subject.email).to eq 'psegel@intridea.com'
184
+ expect(subject.location).to eq 'Washington, D.C.'
185
+ expect(subject.image).to eq '/images/user.jpg'
186
+ expect(subject.phone).to eq '555-555-5555'
187
+ end
188
+ end
189
+
190
+ context 'the extra hash' do
191
+ subject { last_request.env['omniauth.auth']['extra'] }
192
+
193
+ it 'includes additional user attributes' do
194
+ expect(subject.user).to eq 'psegel'
195
+ expect(subject.employeeid).to eq '54'
196
+ expect(subject.hire_date).to eq '2004-07-13'
197
+ expect(subject.roles).to eq %w(senator lobbyist financier)
198
+ end
199
+ end
200
+
201
+ context 'the credentials hash' do
202
+ subject { last_request.env['omniauth.auth']['credentials'] }
203
+
204
+ it 'has a ticket value' do
205
+ expect(subject.ticket).to eq '593af'
206
+ end
207
+ end
208
+ end
118
209
 
119
- its(:name) { should == 'Peter Segel' }
120
- its(:first_name) { should == 'Peter' }
121
- its(:last_name) { should == 'Segel' }
122
- its(:email) { should == 'psegel@intridea.com' }
123
- its(:location) { should == 'Washington, D.C.' }
124
- its(:image) { should == '/images/user.jpg' }
125
- its(:phone) { should == '555-555-5555' }
210
+ it 'calls through to the master app' do
211
+ expect(last_response.body).to eq 'true'
126
212
  end
213
+ end
127
214
 
128
- context 'the extra hash' do
129
- subject { last_request.env['omniauth.auth']['extra'] }
215
+ let(:return_url) { 'http://127.0.0.10/?some=parameter' }
130
216
 
131
- it { should have(3).items }
217
+ context 'with JASIG flavored XML' do
218
+ let(:xml_file_name) { 'cas_success_jasig.xml' }
132
219
 
133
- its(:user) { should == 'psegel' }
134
- its(:employeeid) { should == '54' }
135
- its(:hire_date) { should == '2004-07-13' }
136
- end
220
+ it_behaves_like :successful_validation
221
+ end
137
222
 
138
- context 'the credentials hash' do
139
- subject { last_request.env['omniauth.auth']['credentials'] }
223
+ context 'with classic XML' do
224
+ let(:xml_file_name) { 'cas_success.xml' }
140
225
 
141
- it { should have(1).items }
226
+ it_behaves_like :successful_validation
227
+ end
228
+ end
229
+ end
142
230
 
143
- its(:ticket) { should == '593af' }
144
- end
231
+ describe 'POST /auth/cas/callback' do
232
+ describe 'with a Single Sign-Out logoutRequest' do
233
+ let(:logoutRequest) do
234
+ %Q[
235
+ <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}">
236
+ <saml:NameID>@NOT_USED@</saml:NameID>
237
+ <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex>
238
+ </samlp:LogoutRequest>
239
+ ]
145
240
  end
146
241
 
147
- it 'should call through to the master app' do
148
- last_response.body.should == 'true'
242
+ let(:logout_request) { double('logout_request', call:[200,{},'OK']) }
243
+
244
+ subject do
245
+ post 'auth/cas/callback', logoutRequest:logoutRequest
149
246
  end
150
- end
151
247
 
152
- let(:return_url) { 'http://127.0.0.10/?some=parameter' }
248
+ before do
249
+ allow_any_instance_of(MyCasProvider)
250
+ .to receive(:logout_request_service)
251
+ .and_return double('LogoutRequest', new:logout_request)
153
252
 
154
- context 'with JASIG flavored XML' do
155
- let(:xml_file_name) { 'cas_success_jasig.xml' }
156
- it_behaves_like :successful_validation
157
- end
253
+ subject
254
+ end
158
255
 
159
- context 'with classic XML' do
160
- let(:xml_file_name) { 'cas_success.xml' }
161
- it_behaves_like :successful_validation
256
+ it 'initializes a LogoutRequest' do
257
+ expect(logout_request).to have_received :call
258
+ end
162
259
  end
163
260
  end
164
-
165
261
  end