eriko-omniauth-cas 1.0.4

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.
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