omniauth-cas 1.0.4 → 2.0.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,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