eriko-omniauth-cas 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 38e39ed4f218491f1a36012d8ff4251a51412aa0
4
+ data.tar.gz: 373a1424d1cc93b5ba445f492c0137b17042a909
5
+ SHA512:
6
+ metadata.gz: a011844945919e0c7c026ee4911dd30812f94da7073c1eacb53b8fda94074e29c1a3df01bb510ca058002e9938d6eac8671b95f1c2003c0ab18ccfdfe45c29ff
7
+ data.tar.gz: e5a94f7882809980c417a1925f07116ea9bf62382aa05341a60105a265ecc08dbf483b2283340bba67785f17ae7021c9fdb37b44f3078853a7fb64ba1ddc173c
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
19
+
20
+ # RSpec
21
+ .rspec
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - 2.0.0
5
+ branches:
6
+ only:
7
+ - master
8
+ before_install:
9
+ - gem install bundler
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in omniauth-cas.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # OmniAuth CAS Strategy [![Gem Version][version_badge]][version] [![Build Status][travis_status]][travis]
2
+
3
+ [version_badge]: https://badge.fury.io/rb/omniauth-cas.png
4
+ [version]: http://badge.fury.io/rb/omniauth-cas
5
+ [travis]: http://travis-ci.org/dlindahl/omniauth-cas
6
+ [travis_status]: https://secure.travis-ci.org/dlindahl/omniauth-cas.png
7
+ [releases]: https://github.com/dlindahl/omniauth-cas/releases
8
+
9
+ This is a OmniAuth 1.0 compatible port of the previously available
10
+ [OmniAuth CAS strategy][old_omniauth_cas] that was bundled with OmniAuth 0.3.
11
+
12
+ * [View the documentation][document_up]
13
+ * [Changelog][releases]
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'omniauth-cas'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install omniauth-cas
28
+
29
+ ## Usage
30
+
31
+ Use like any other OmniAuth strategy:
32
+
33
+ ```ruby
34
+ Rails.application.config.middleware.use OmniAuth::Builder do
35
+ provider :cas, host: 'cas.yourdomain.com'
36
+ end
37
+ ```
38
+
39
+ ### Configuration Options
40
+
41
+ #### Required
42
+
43
+ OmniAuth CAS requires at least one of the following two configuration options:
44
+
45
+ * `url` - Defines the URL of your CAS server (i.e. `http://example.org:8080`)
46
+ * `host` - Defines the host of your CAS server (i.e. `example.org`).
47
+
48
+ #### Optional
49
+
50
+ Other configuration options:
51
+
52
+ * `port` - The port to use for your configured CAS `host`. Optional if using `url`.
53
+ * `ssl` - TRUE to connect to your CAS server over SSL. Optional if using `url`.
54
+ * `service_validate_url` - The URL to use to validate a user. Defaults to `'/serviceValidate'`.
55
+ * `logout_url` - The URL to use to logout a user. Defaults to `'/logout'`.
56
+ * `login_url` - Defines the URL used to prompt users for their login information. Defaults to `/login` If no `host` is configured, the host application's domain will be used.
57
+ * `uid_field` - The user data attribute to use as your user's unique identifier. Defaults to `'user'` (which usually contains the user's login name).
58
+ * `ca_path` - Optional when `ssl` is `true`. Sets path of a CA certification directory. See [Net::HTTP][net_http] for more details.
59
+ * `disable_ssl_verification` - Optional when `ssl` is true. Disables verification.
60
+ * `on_single_sign_out` - Callback used when a [CAS 3.1 Single Sign Out][sso]
61
+ request is received.
62
+
63
+ Configurable options for values returned by CAS:
64
+
65
+ * `uid_key` - The user ID data attribute to use as your user's unique identifier. Defaults to `'user'` (which usually contains the user's login name).
66
+ * `name_key` - The data attribute containing user first and last name. Defaults to `'name'`.
67
+ * `email_key` - The data attribute containing user email address. Defaults to `'email'`.
68
+ * `first_name_key` - The data attribute containing user first name. Defaults to `'first_name'`.
69
+ * `last_name_key` - The data attribute containing user last name. Defaults to `'last_name'`.
70
+ * `location_key` - The data attribute containing user location/address. Defaults to `'location'`.
71
+ * `image_key` - The data attribute containing user image/picture. Defaults to `'image'`.
72
+ * `phone_key` - The data attribute containing user contact phone number. Defaults to `'phone'`.
73
+
74
+ ## Migrating from OmniAuth 0.3
75
+
76
+ Given the following OmniAuth 0.3 configuration:
77
+
78
+ ```ruby
79
+ provider :CAS, cas_server: 'https://cas.example.com/cas/'
80
+ ```
81
+
82
+ Your new settings should look similar to this:
83
+
84
+ ```ruby
85
+ provider :cas,
86
+ host: 'cas.example.com',
87
+ login_url: '/cas/login',
88
+ service_validate_url: '/cas/serviceValidate'
89
+ ```
90
+
91
+ If you encounter problems wih SSL certificates you may want to set the `ca_path` parameter or activate `disable_ssl_verification` (not recommended).
92
+
93
+ ## Contributing
94
+
95
+ 1. Fork it
96
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
97
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
98
+ 4. Push to the branch (`git push origin my-new-feature`)
99
+ 5. Create new Pull Request
100
+
101
+ ## Thanks
102
+
103
+ Special thanks go out to the following people
104
+
105
+ * Phillip Aldridge (@iterateNZ) and JB Barth (@jbbarth) for helping out with Issue #3
106
+ * Elber Ribeiro (@dynaum) for Ubuntu SSL configuration support
107
+ * @rbq for README updates and OmniAuth 0.3 migration guide
108
+
109
+ [old_omniauth_cas]: https://github.com/intridea/omniauth/blob/0-3-stable/oa-enterprise/lib/omniauth/strategies/cas.rb
110
+ [document_up]: http://dlindahl.github.com/omniauth-cas/
111
+ [net_http]: http://ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html
112
+ [sso]: https://wiki.jasig.org/display/CASUM/Single+Sign+Out
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rspec/core/rake_task'
5
+ desc 'Default: run specs.'
6
+ task default: :spec
7
+
8
+ desc 'Run specs'
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.rspec_opts = '--require spec_helper --color --order rand'
11
+ end
12
+
13
+ task :test do
14
+ fail %q{This application uses RSpec. Try running "rake spec"}
15
+ end
@@ -0,0 +1,5 @@
1
+ module Omniauth
2
+ module Cas
3
+ VERSION = '1.0.4'
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require 'omniauth/cas/version'
2
+ require 'omniauth/strategies/cas'
@@ -0,0 +1,65 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class CAS
4
+ class LogoutRequest
5
+ def initialize(strategy, request)
6
+ @strategy = strategy
7
+ @request = request
8
+ end
9
+
10
+ def call(options = {})
11
+ @options = options
12
+
13
+ begin
14
+ result = single_sign_out_callback.call *logout_request
15
+ rescue StandardError => err
16
+ return @strategy.fail! :logout_request, err
17
+ else
18
+ result = [200,{},'OK'] if result == true || result.nil?
19
+ ensure
20
+ return unless result
21
+
22
+ # TODO: Why does ActionPack::Response return [status,headers,body]
23
+ # when Rack::Response#new wants [body,status,headers]? Additionally,
24
+ # why does Rack::Response differ in argument order from the usual
25
+ # Rack-like [status,headers,body] array?
26
+ return Rack::Response.new(result[2],result[0],result[1]).finish
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def logout_request
33
+ @logout_request ||= begin
34
+ saml = Nokogiri.parse @request.params['logoutRequest']
35
+ name_id = saml.xpath('//saml:NameID').text
36
+ sess_idx = saml.xpath('//samlp:SessionIndex').text
37
+
38
+ inject_params name_id:name_id, session_index:sess_idx
39
+
40
+ @request
41
+ end
42
+ end
43
+
44
+ def inject_params(new_params)
45
+ rack_input = @request.env['rack.input'].read
46
+ params = Rack::Utils.parse_query(rack_input, '&').merge new_params
47
+ @request.env['rack.input'] = StringIO.new(Rack::Utils.build_query(params))
48
+ rescue
49
+ # A no-op intended to ensure that the ensure block is run
50
+ raise
51
+ ensure
52
+ @request.env['rack.input'].rewind
53
+ end
54
+
55
+ def single_sign_out_callback
56
+ @options.fetch :on_single_sign_out, _fallback_callback_
57
+ end
58
+
59
+ def _fallback_callback_
60
+ Proc.new {}
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,100 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'nokogiri'
4
+
5
+ module OmniAuth
6
+ module Strategies
7
+ class CAS
8
+ class ServiceTicketValidator
9
+
10
+ VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
11
+
12
+ # Build a validator from a +configuration+, a
13
+ # +return_to+ URL, and a +ticket+.
14
+ #
15
+ # @param [Hash] options the OmniAuth Strategy options
16
+ # @param [String] return_to_url the URL of this CAS client service
17
+ # @param [String] ticket the service ticket to validate
18
+ def initialize(strategy, options, return_to_url, ticket)
19
+ @options = options
20
+ @uri = URI.parse(strategy.service_validate_url(return_to_url, ticket))
21
+ end
22
+
23
+ # Request validation of the ticket from the CAS server's
24
+ # serviceValidate (CAS 2.0) function.
25
+ #
26
+ # Swallows all XML parsing errors (and returns +nil+ in those cases).
27
+ #
28
+ # @return [Hash, nil] a user information hash if the response is valid; +nil+ otherwise.
29
+ #
30
+ # @raise any connection errors encountered.
31
+ def user_info
32
+ parse_user_info( find_authentication_success( get_service_response_body ) )
33
+ end
34
+
35
+ private
36
+
37
+ # turns an `<cas:authenticationSuccess>` node into a Hash;
38
+ # returns nil if given nil
39
+ def parse_user_info(node)
40
+ return nil if node.nil?
41
+
42
+ {}.tap do |hash|
43
+ node.children.each do |e|
44
+ node_name = e.name.sub(/^cas:/, '')
45
+ unless e.kind_of?(Nokogiri::XML::Text) ||
46
+ node_name == 'proxies'
47
+ # There are no child elements
48
+ if e.element_children.count == 0
49
+ hash[node_name] = e.content
50
+ elsif e.element_children.count
51
+ # JASIG style extra attributes
52
+ if node_name == 'attributes'
53
+ hash.merge! parse_user_info e
54
+ else
55
+ hash[node_name] = [] if hash[node_name].nil?
56
+ hash[node_name].push parse_user_info e
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # finds an `<cas:authenticationSuccess>` node in
65
+ # a `<cas:serviceResponse>` body if present; returns nil
66
+ # if the passed body is nil or if there is no such node.
67
+ def find_authentication_success(body)
68
+ return nil if body.nil? || body == ''
69
+ begin
70
+ doc = Nokogiri::XML(body)
71
+ begin
72
+ doc.xpath('/cas:serviceResponse/cas:authenticationSuccess')
73
+ rescue Nokogiri::XML::XPath::SyntaxError
74
+ doc.xpath('/serviceResponse/authenticationSuccess')
75
+ end
76
+ rescue Nokogiri::XML::XPath::SyntaxError
77
+ nil
78
+ end
79
+ end
80
+
81
+ # retrieves the `<cas:serviceResponse>` XML from the CAS server
82
+ def get_service_response_body
83
+ result = ''
84
+ http = Net::HTTP.new(@uri.host, @uri.port)
85
+ http.use_ssl = @uri.port == 443 || @uri.instance_of?(URI::HTTPS)
86
+ if http.use_ssl?
87
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options.disable_ssl_verification?
88
+ http.ca_path = @options.ca_path
89
+ end
90
+ http.start do |c|
91
+ response = c.get "#{@uri.path}?#{@uri.query}", VALIDATION_REQUEST_HEADERS.dup
92
+ result = response.body
93
+ end
94
+ result
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,206 @@
1
+ require 'omniauth/strategy'
2
+ require 'addressable/uri'
3
+
4
+ module OmniAuth
5
+ module Strategies
6
+ class CAS
7
+ include OmniAuth::Strategy
8
+
9
+ # Custom Exceptions
10
+ class MissingCASTicket < StandardError; end
11
+ class InvalidCASTicket < StandardError; end
12
+
13
+ autoload :ServiceTicketValidator, 'omniauth/strategies/cas/service_ticket_validator'
14
+ autoload :LogoutRequest, 'omniauth/strategies/cas/logout_request'
15
+
16
+ attr_accessor :raw_info
17
+ alias_method :user_info, :raw_info
18
+
19
+ option :name, :cas # Required property by OmniAuth::Strategy
20
+
21
+ option :host, nil
22
+ option :port, nil
23
+ option :path, nil
24
+ option :ssl, true
25
+ option :service_validate_url, '/serviceValidate'
26
+ option :login_url, '/login'
27
+ option :logout_url, '/logout'
28
+ # Make all the keys configurable with some defaults set here
29
+ option :uid_field, 'user'
30
+ option :name_key, 'name'
31
+ option :email_key, 'email'
32
+ option :first_name_key, 'first_name'
33
+ option :last_name_key, 'last_name'
34
+ option :location_key, 'location'
35
+ option :image_key, 'image'
36
+ option :phone_key, 'phone'
37
+
38
+ # As required by https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
39
+ AuthHashSchemaKeys = %w{name email first_name last_name location image phone}
40
+ info do
41
+ prune!({
42
+ :name => raw_info[ @options[:name_key].to_s ],
43
+ :email => raw_info[ @options[:email_key].to_s ],
44
+ :first_name => raw_info[ @options[:first_name_key].to_s ],
45
+ :last_name => raw_info[ @options[:last_name_key].to_s ],
46
+ :location => raw_info[ @options[:location_key].to_s ],
47
+ :image => raw_info[ @options[:image_key].to_s ],
48
+ :phone => raw_info[ @options[:phone_key].to_s ]
49
+ })
50
+ end
51
+
52
+ extra do
53
+ prune! raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
54
+ end
55
+
56
+ uid do
57
+ raw_info[ @options[:uid_field].to_s ]
58
+ end
59
+
60
+ credentials do
61
+ prune!({
62
+ :ticket => @ticket
63
+ })
64
+ end
65
+
66
+
67
+ def callback_phase
68
+ if on_sso_path?
69
+ single_sign_out_phase
70
+ else
71
+ @ticket = request.params['ticket']
72
+ return fail!(:no_ticket, MissingCASTicket.new('No CAS Ticket')) unless @ticket
73
+
74
+ self.raw_info = ServiceTicketValidator.new(self, @options, callback_url, @ticket).user_info
75
+
76
+ return fail!(:invalid_ticket, InvalidCASTicket.new('Invalid CAS Ticket')) if raw_info.empty?
77
+
78
+ super
79
+ end
80
+ end
81
+
82
+ def request_phase
83
+ service_url = append_params( callback_url, return_url )
84
+
85
+ [
86
+ 302,
87
+ {
88
+ 'Location' => login_url( service_url ),
89
+ 'Content-Type' => 'text/plain'
90
+ },
91
+ ["You are being redirected to CAS for sign-in."]
92
+ ]
93
+ end
94
+
95
+ def on_sso_path?
96
+ request.post? && request.params.has_key?( 'logoutRequest' )
97
+ end
98
+
99
+ def single_sign_out_phase
100
+ logout_request_service.new(self, request).call @options
101
+ end
102
+
103
+ # Build a CAS host with protocol and port
104
+ #
105
+ #
106
+ def cas_url
107
+ extract_url if @options['url']
108
+
109
+ validate_cas_setup
110
+
111
+ @cas_url ||= begin
112
+ uri = Addressable::URI.new
113
+ uri.host = @options.host
114
+ uri.scheme = @options.ssl ? 'https' : 'http'
115
+ uri.port = @options.port
116
+ uri.path = @options.path
117
+
118
+ uri.to_s
119
+ end
120
+ end
121
+
122
+ def extract_url
123
+ url = Addressable::URI.parse( @options.delete('url') )
124
+
125
+ @options.merge!(
126
+ 'host' => url.host,
127
+ 'port' => url.port,
128
+ 'path' => url.path,
129
+ 'ssl' => url.scheme == 'https'
130
+ )
131
+ end
132
+
133
+ def validate_cas_setup
134
+ if @options.host.nil? || @options.login_url.nil?
135
+ raise ArgumentError.new(":host and :login_url MUST be provided")
136
+ end
137
+ end
138
+
139
+ # Build a service-validation URL from +service+ and +ticket+.
140
+ # If +service+ has a ticket param, first remove it. URL-encode
141
+ # +service+ and add it and the +ticket+ as paraemters to the
142
+ # CAS serviceValidate URL.
143
+ #
144
+ # @param [String] service the service (a.k.a. return-to) URL
145
+ # @param [String] ticket the ticket to validate
146
+ #
147
+ # @return [String] a URL like `http://cas.mycompany.com/serviceValidate?service=...&ticket=...`
148
+ def service_validate_url(service_url, ticket)
149
+ service_url = Addressable::URI.parse( service_url )
150
+ service_url.query_values = service_url.query_values.tap { |qs| qs.delete('ticket') }
151
+
152
+ cas_url + append_params(@options.service_validate_url, { :service => service_url.to_s, :ticket => ticket })
153
+ end
154
+
155
+ # Build a CAS login URL from +service+.
156
+ #
157
+ # @param [String] service the service (a.k.a. return-to) URL
158
+ #
159
+ # @return [String] a URL like `http://cas.mycompany.com/login?service=...`
160
+ def login_url(service)
161
+ cas_url + append_params( @options.login_url, { :service => service })
162
+ end
163
+
164
+ # Adds URL-escaped +parameters+ to +base+.
165
+ #
166
+ # @param [String] base the base URL
167
+ # @param [String] params the parameters to append to the URL
168
+ #
169
+ # @return [String] the new joined URL.
170
+ def append_params(base, params)
171
+ params = params.each { |k,v| v = Rack::Utils.escape(v) }
172
+
173
+ Addressable::URI.parse(base).tap do |base_uri|
174
+ base_uri.query_values = (base_uri.query_values || {}).merge( params )
175
+ end.to_s
176
+ end
177
+
178
+ private
179
+
180
+ # Deletes Hash pairs with `nil` values.
181
+ # From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
182
+ def prune!(hash)
183
+ hash.delete_if do |_, value|
184
+ prune!(value) if value.is_a?(Hash)
185
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
186
+ end
187
+ end
188
+
189
+ def return_url
190
+ # If the request already has a `url` parameter, then it will already be appended to the callback URL.
191
+ if request.params and request.params['url']
192
+ {}
193
+ else
194
+ { :url => request.referer }
195
+ end
196
+ end
197
+
198
+ def logout_request_service
199
+ LogoutRequest
200
+ end
201
+
202
+ end
203
+ end
204
+ end
205
+
206
+ OmniAuth.config.add_camelization 'cas', 'CAS'
@@ -0,0 +1 @@
1
+ require "omniauth/cas"
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/omniauth/cas/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Derek Lindahl","Erik Ordway"]
6
+ gem.email = ["dlindahl@customink.com","eriko@jumpsuit.org"]
7
+ gem.summary = %q{CAS Strategy for OmniAuth}
8
+ gem.description = gem.summary
9
+ gem.homepage = "https://github.com/eriko/omniauth-cas"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "eriko-omniauth-cas"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Omniauth::Cas::VERSION
17
+
18
+ gem.add_dependency 'omniauth', '~> 1.1.0'
19
+ gem.add_dependency 'nokogiri', '~> 1.5'
20
+ gem.add_dependency 'addressable', '~> 2.3'
21
+
22
+ gem.add_development_dependency 'rake', '~> 0.9'
23
+ gem.add_development_dependency 'webmock', '~> 1.16.0'
24
+ gem.add_development_dependency 'simplecov', '~> 0.8.1'
25
+ gem.add_development_dependency 'rspec', '~> 2.99.0.beta1'
26
+ gem.add_development_dependency 'rack-test', '~> 0.6'
27
+
28
+ gem.add_development_dependency 'awesome_print'
29
+ end
@@ -0,0 +1,4 @@
1
+ <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2
+ <cas:authenticationFailure>
3
+ </cas:authenticationFailure>
4
+ </cas:serviceResponse>
@@ -0,0 +1,14 @@
1
+ <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
2
+ <cas:authenticationSuccess>
3
+ <cas:user>psegel</cas:user>
4
+ <cas:employeeid>54</cas:employeeid>
5
+ <cas:first_name>P. Segel</cas:first_name>
6
+ <cas:first_name>Peter</cas:first_name>
7
+ <cas:last_name>Segel</cas:last_name>
8
+ <cas:email>psegel@intridea.com</cas:email>
9
+ <cas:location>Washington, D.C.</cas:location>
10
+ <cas:image>/images/user.jpg</cas:image>
11
+ <cas:phone>555-555-5555</cas:phone>
12
+ <cas:hire_date>2004-07-13</cas:hire_date>
13
+ </cas:authenticationSuccess>
14
+ </cas:serviceResponse>
@@ -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,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS::LogoutRequest do
4
+ let(:strategy) { double('strategy') }
5
+
6
+ let(:env) do
7
+ { 'rack.input' => StringIO.new('','r') }
8
+ end
9
+
10
+ let(:request) { double('request', params:params, env:env) }
11
+
12
+ let(:params) { { 'url' => url, 'logoutRequest' => logoutRequest } }
13
+
14
+ let(:url) { 'http://notes.dev/signed_in' }
15
+
16
+ let(:logoutRequest) do
17
+ %Q[
18
+ <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}">
19
+ <saml:NameID>@NOT_USED@</saml:NameID>
20
+ <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex>
21
+ </samlp:LogoutRequest>
22
+ ]
23
+ end
24
+
25
+ subject { described_class.new(strategy, request).call(options) }
26
+
27
+ describe 'with no configured SSO callback' do
28
+ let(:options) { {} }
29
+
30
+ it 'responds with OK' do
31
+ expect(subject[0]).to eq 200
32
+ expect(subject[2].body).to eq ['OK']
33
+ end
34
+ end
35
+
36
+ describe 'SAML attributes' do
37
+ let(:callback) { Proc.new{} }
38
+
39
+ let(:options) do
40
+ {
41
+ on_single_sign_out: callback
42
+ }
43
+ end
44
+
45
+ before do
46
+ @rack_input = nil
47
+
48
+ callback.stub(:call) do |req|
49
+ @rack_input = req.env['rack.input'].read
50
+ true
51
+ end
52
+
53
+ subject
54
+ end
55
+
56
+ it 'are parsed and injected into the Rack Request parameters' do
57
+ expect(@rack_input).to eq 'name_id=%40NOT_USED%40&session_index=ST-123456-123abc456def'
58
+ end
59
+
60
+ context 'that raise when parsed' do
61
+ let(:env) { { 'rack.input' => nil } }
62
+
63
+ before do
64
+ strategy.stub :fail!
65
+ subject
66
+ end
67
+
68
+ it 'responds with an error' do
69
+ expect(strategy).to have_received(:fail!)
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'with a configured callback' do
75
+ let(:options) do
76
+ {
77
+ on_single_sign_out: callback
78
+ }
79
+ end
80
+
81
+ context 'that returns TRUE' do
82
+ let(:callback) { Proc.new{true} }
83
+
84
+ it 'responds with OK' do
85
+ expect(subject[0]).to eq 200
86
+ expect(subject[2].body).to eq ['OK']
87
+ end
88
+ end
89
+
90
+ context 'that returns Nil' do
91
+ let(:callback) { Proc.new{} }
92
+
93
+ it 'responds with OK' do
94
+ expect(subject[0]).to eq 200
95
+ expect(subject[2].body).to eq ['OK']
96
+ end
97
+ end
98
+
99
+ context 'that returns a tuple' do
100
+ let(:callback) { Proc.new{ [400,{},'Bad Request'] } }
101
+
102
+ it 'responds with OK' do
103
+ expect(subject[0]).to eq 400
104
+ expect(subject[2].body).to eq ['Bad Request']
105
+ end
106
+ end
107
+
108
+ context 'that raises an error' do
109
+ let(:exception) { RuntimeError.new('error' )}
110
+ let(:callback) { Proc.new{raise exception} }
111
+
112
+ before do
113
+ strategy.stub :fail!
114
+ subject
115
+ end
116
+
117
+ it 'responds with an error' do
118
+ expect(strategy).to have_received(:fail!)
119
+ .with(:logout_request, exception)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS::ServiceTicketValidator do
4
+ let(:strategy) do
5
+ double('strategy',
6
+ service_validate_url: 'https://example.org/serviceValidate'
7
+ )
8
+ end
9
+
10
+ let(:provider_options) do
11
+ double('provider_options',
12
+ disable_ssl_verification?: false,
13
+ ca_path: '/etc/ssl/certsZOMG'
14
+ )
15
+ end
16
+
17
+ let(:validator) do
18
+ OmniAuth::Strategies::CAS::ServiceTicketValidator.new( strategy, provider_options, '/foo', nil )
19
+ end
20
+
21
+ describe '#user_info' do
22
+ before do
23
+ stub_request(:get, 'https://example.org/serviceValidate?')
24
+ .to_return(status: 200, body: '')
25
+ validator.user_info
26
+ end
27
+
28
+ it 'uses the configured CA path' do
29
+ expect(provider_options).to have_received :ca_path
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS, type: :strategy do
4
+ include Rack::Test::Methods
5
+
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
11
+ Rack::Builder.new {
12
+ use OmniAuth::Test::PhonySession
13
+ use MyCasProvider, name: :cas, host: 'cas.example.org', ssl: false, port: 8080, uid_field: :employeeid
14
+ run lambda { |env| [404, {'Content-Type' => 'text/plain'}, [env.key?('omniauth.auth').to_s]] }
15
+ }.to_app
16
+ end
17
+
18
+ # TODO: Verify that these are even useful tests
19
+ shared_examples_for 'a CAS redirect response' do
20
+ let(:redirect_params) { 'service=' + Rack::Utils.escape("http://example.org/auth/cas/callback?url=#{Rack::Utils.escape(return_url)}") }
21
+
22
+ before { get url, nil, request_env }
23
+
24
+ subject { last_response }
25
+
26
+ it { should be_redirect }
27
+
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
+
36
+ let(:provider) { MyCasProvider.new(nil, params) }
37
+
38
+ subject { provider.cas_url }
39
+
40
+ it 'raises an ArgumentError' do
41
+ expect{subject}.to raise_error ArgumentError, %r{:host and :login_url MUST be provided}
42
+ end
43
+
44
+ context 'with an explicit :url option' do
45
+ let(:url) { 'https://example.org:8080/my_cas' }
46
+
47
+ let(:params) { super().merge url:url }
48
+
49
+ before { subject }
50
+
51
+ it { should eq url }
52
+
53
+ it 'parses the URL into it the appropriate strategy options' do
54
+ expect(provider.options).to include ssl:true
55
+ expect(provider.options).to include host:'example.org'
56
+ expect(provider.options).to include port:8080
57
+ expect(provider.options).to include path:'/my_cas'
58
+ end
59
+ end
60
+
61
+ context 'with explicit URL component' do
62
+ let(:params) { super().merge host:'example.org', port:1234, ssl:true, path:'/a/path' }
63
+
64
+ before { subject }
65
+
66
+ it { should eq 'https://example.org:1234/a/path' }
67
+
68
+ it 'parses the URL into it the appropriate strategy options' do
69
+ expect(provider.options).to include ssl:true
70
+ expect(provider.options).to include host:'example.org'
71
+ expect(provider.options).to include port:1234
72
+ expect(provider.options).to include path:'/a/path'
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'defaults' do
78
+ subject { MyCasProvider.default_options.to_hash }
79
+
80
+ it { should include('ssl' => true) }
81
+ end
82
+
83
+ describe 'GET /auth/cas' do
84
+ let(:return_url) { 'http://myapp.com/admin/foo' }
85
+
86
+ context 'with a referer' do
87
+ let(:url) { '/auth/cas' }
88
+
89
+ let(:request_env) { { 'HTTP_REFERER' => return_url } }
90
+
91
+ it_behaves_like 'a CAS redirect response'
92
+ end
93
+
94
+ context 'with an explicit return URL' do
95
+ let(:url) { "/auth/cas?url=#{return_url}" }
96
+
97
+ let(:request_env) { {} }
98
+
99
+ it_behaves_like 'a CAS redirect response'
100
+ end
101
+ end
102
+
103
+ describe 'GET /auth/cas/callback' do
104
+ context 'without a ticket' do
105
+ before { get '/auth/cas/callback' }
106
+
107
+ subject { last_response }
108
+
109
+ it { should be_redirect }
110
+
111
+ it 'redirects with a failure message' do
112
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=no_ticket&strategy=cas'
113
+ end
114
+ end
115
+
116
+ context 'with an invalid ticket' do
117
+ before do
118
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=9391d/).
119
+ to_return( body: File.read('spec/fixtures/cas_failure.xml') )
120
+ get '/auth/cas/callback?ticket=9391d'
121
+ end
122
+
123
+ subject { last_response }
124
+
125
+ it { should be_redirect }
126
+
127
+ it 'redirects with a failure message' do
128
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=invalid_ticket&strategy=cas'
129
+ end
130
+ end
131
+
132
+ describe 'with a valid ticket' do
133
+ shared_examples :successful_validation do
134
+ before do
135
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=593af/)
136
+ .with { |request| @request_uri = request.uri.to_s }
137
+ .to_return( body: File.read("spec/fixtures/#{xml_file_name}") )
138
+
139
+ get "/auth/cas/callback?ticket=593af&url=#{return_url}"
140
+ end
141
+
142
+ it 'strips the ticket parameter from the callback URL' do
143
+ expect(@request_uri.scan('ticket=').size).to eq 1
144
+ end
145
+
146
+ it 'properly encodes the service URL' do
147
+ WebMock.should have_requested(:get, 'http://cas.example.org:8080/serviceValidate')
148
+ .with(query: {
149
+ ticket: '593af',
150
+ service: 'http://example.org/auth/cas/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter')
151
+ })
152
+ end
153
+
154
+ context "request.env['omniauth.auth']" do
155
+ subject { last_request.env['omniauth.auth'] }
156
+
157
+ it { should be_kind_of Hash }
158
+
159
+ it 'identifes the provider' do
160
+ expect(subject.provider).to eq :cas
161
+ end
162
+
163
+ it 'returns the UID of the user' do
164
+ expect(subject.uid).to eq '54'
165
+ end
166
+
167
+ context 'the info hash' do
168
+ subject { last_request.env['omniauth.auth']['info'] }
169
+
170
+ it 'includes user info attributes' do
171
+ expect(subject.name).to eq 'Peter Segel'
172
+ expect(subject.first_name).to eq 'Peter'
173
+ expect(subject.last_name).to eq 'Segel'
174
+ expect(subject.email).to eq 'psegel@intridea.com'
175
+ expect(subject.location).to eq 'Washington, D.C.'
176
+ expect(subject.image).to eq '/images/user.jpg'
177
+ expect(subject.phone).to eq '555-555-5555'
178
+ end
179
+ end
180
+
181
+ context 'the extra hash' do
182
+ subject { last_request.env['omniauth.auth']['extra'] }
183
+
184
+ it 'includes additional user attributes' do
185
+ expect(subject.user).to eq 'psegel'
186
+ expect(subject.employeeid).to eq '54'
187
+ expect(subject.hire_date).to eq '2004-07-13'
188
+ end
189
+ end
190
+
191
+ context 'the credentials hash' do
192
+ subject { last_request.env['omniauth.auth']['credentials'] }
193
+
194
+ it 'has a ticket value' do
195
+ expect(subject.ticket).to eq '593af'
196
+ end
197
+ end
198
+ end
199
+
200
+ it 'calls through to the master app' do
201
+ expect(last_response.body).to eq 'true'
202
+ end
203
+ end
204
+
205
+ let(:return_url) { 'http://127.0.0.10/?some=parameter' }
206
+
207
+ context 'with JASIG flavored XML' do
208
+ let(:xml_file_name) { 'cas_success_jasig.xml' }
209
+
210
+ it_behaves_like :successful_validation
211
+ end
212
+
213
+ context 'with classic XML' do
214
+ let(:xml_file_name) { 'cas_success.xml' }
215
+
216
+ it_behaves_like :successful_validation
217
+ end
218
+ end
219
+ end
220
+
221
+ describe 'POST /auth/cas/callback' do
222
+ describe 'with a Single Sign-Out logoutRequest' do
223
+ let(:logoutRequest) do
224
+ %Q[
225
+ <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}">
226
+ <saml:NameID>@NOT_USED@</saml:NameID>
227
+ <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex>
228
+ </samlp:LogoutRequest>
229
+ ]
230
+ end
231
+
232
+ let(:logout_request) { double('logout_request', call:[200,{},'OK']) }
233
+
234
+ subject do
235
+ post 'auth/cas/callback', logoutRequest:logoutRequest
236
+ end
237
+
238
+ before do
239
+ MyCasProvider.any_instance.stub(:logout_request_service)
240
+ .and_return double('LogoutRequest', new:logout_request)
241
+
242
+ subject
243
+ end
244
+
245
+ it 'initializes a LogoutRequest' do
246
+ expect(logout_request).to have_received :call
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,19 @@
1
+ require 'bundler/setup'
2
+ require 'awesome_print'
3
+
4
+ RSpec.configure do |c|
5
+ c.filter_run focus: true
6
+ c.run_all_when_everything_filtered = true
7
+ c.treat_symbols_as_metadata_keys_with_true_values = true
8
+ end
9
+
10
+ require 'simplecov'
11
+ SimpleCov.start do
12
+ add_filter '.bundle'
13
+ end
14
+
15
+ require 'rack/test'
16
+ require 'webmock/rspec'
17
+ require 'omniauth-cas'
18
+
19
+ OmniAuth.config.logger = Logger.new( '/dev/null' )
metadata ADDED
@@ -0,0 +1,199 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eriko-omniauth-cas
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Derek Lindahl
8
+ - Erik Ordway
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: omniauth
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: 1.1.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: 1.1.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: nokogiri
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '1.5'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '1.5'
42
+ - !ruby/object:Gem::Dependency
43
+ name: addressable
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '2.3'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '2.3'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '0.9'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.9'
70
+ - !ruby/object:Gem::Dependency
71
+ name: webmock
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.16.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: 1.16.0
84
+ - !ruby/object:Gem::Dependency
85
+ name: simplecov
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: 0.8.1
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ~>
96
+ - !ruby/object:Gem::Version
97
+ version: 0.8.1
98
+ - !ruby/object:Gem::Dependency
99
+ name: rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ~>
103
+ - !ruby/object:Gem::Version
104
+ version: 2.99.0.beta1
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ version: 2.99.0.beta1
112
+ - !ruby/object:Gem::Dependency
113
+ name: rack-test
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ~>
117
+ - !ruby/object:Gem::Version
118
+ version: '0.6'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '0.6'
126
+ - !ruby/object:Gem::Dependency
127
+ name: awesome_print
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ description: CAS Strategy for OmniAuth
141
+ email:
142
+ - dlindahl@customink.com
143
+ - eriko@jumpsuit.org
144
+ executables: []
145
+ extensions: []
146
+ extra_rdoc_files: []
147
+ files:
148
+ - .gitignore
149
+ - .ruby-version
150
+ - .travis.yml
151
+ - Gemfile
152
+ - LICENSE
153
+ - README.md
154
+ - Rakefile
155
+ - lib/omniauth-cas.rb
156
+ - lib/omniauth/cas.rb
157
+ - lib/omniauth/cas/version.rb
158
+ - lib/omniauth/strategies/cas.rb
159
+ - lib/omniauth/strategies/cas/logout_request.rb
160
+ - lib/omniauth/strategies/cas/service_ticket_validator.rb
161
+ - omniauth-cas.gemspec
162
+ - spec/fixtures/cas_failure.xml
163
+ - spec/fixtures/cas_success.xml
164
+ - spec/fixtures/cas_success_jasig.xml
165
+ - spec/omniauth/strategies/cas/logout_request_spec.rb
166
+ - spec/omniauth/strategies/cas/service_ticket_validator_spec.rb
167
+ - spec/omniauth/strategies/cas_spec.rb
168
+ - spec/spec_helper.rb
169
+ homepage: https://github.com/eriko/omniauth-cas
170
+ licenses: []
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - '>='
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.0.14
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: CAS Strategy for OmniAuth
192
+ test_files:
193
+ - spec/fixtures/cas_failure.xml
194
+ - spec/fixtures/cas_success.xml
195
+ - spec/fixtures/cas_success_jasig.xml
196
+ - spec/omniauth/strategies/cas/logout_request_spec.rb
197
+ - spec/omniauth/strategies/cas/service_ticket_validator_spec.rb
198
+ - spec/omniauth/strategies/cas_spec.rb
199
+ - spec/spec_helper.rb