omniauth-cas3 1.1.1

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: fb755a8d3fdc733fb3a0eb368844ea4386e86666
4
+ data.tar.gz: 42b6db5b805dd203b90b764b8a169ec656c79aa2
5
+ SHA512:
6
+ metadata.gz: 3efeb0d5520267a5cd42577584b7df5332ff7466806eea54d790f3489bb9de7c1f0897df07640af90843c5429c55cbc6bfa2b2654832c5d4c0cd62fd3daca880
7
+ data.tar.gz: 4a6b2ca7ca3b8b3b56ab681a853992baa49a7307bccfac3e39790581d2daf61833bbdafa5d8a8debd47729b59ca0b40fd8e492cf3a396b50d0d101b88354a4c6
data/.editorconfig ADDED
@@ -0,0 +1,16 @@
1
+ # EditorConfig helps developers define and maintain consistent
2
+ # coding styles between different editors and IDEs
3
+ # editorconfig.org
4
+
5
+ root = true
6
+
7
+ [*]
8
+ # Change these settings to your own preference
9
+ indent_style = space
10
+ indent_size = 2
11
+
12
+ # We recommend you to keep these unchanged
13
+ end_of_line = lf
14
+ charset = utf-8
15
+ trim_trailing_whitespace = true
16
+ insert_final_newline = true
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *~
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ vendor
20
+
21
+ # RSpec
22
+ .rspec
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1
5
+ - 2.2
6
+ branches:
7
+ only:
8
+ - master
9
+ - /.*-test$/
10
+ before_install:
11
+ - gem install bundler
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in omniauth-cas3.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2011 Derek Lindahl and CustomInk, LLC
2
+ Copyright (c) 2015 tduehr
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # OmniAuth CAS Strategy [![Gem Version][version_badge]][version] [![Build Status][travis_status]][travis]
2
+
3
+ [version_badge]: https://badge.fury.io/rb/omniauth-cas3.png
4
+ [version]: http://badge.fury.io/rb/omniauth-cas3
5
+ [travis]: http://travis-ci.org/tduehr/omniauth-cas3
6
+ [travis_status]: https://secure.travis-ci.org/dlindahl/omniauth-cas3.png
7
+ [releases]: https://github.com/tduehr/omniauth-cas3/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. This strategy haas also been updated for CAS protocol version 3.0 and patched to deal with namespace issues.
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-cas3'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install omniauth-cas3
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 :cas3, 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
+ * `callback_url` - The URL custom URL path which CAS uses to call back to the service. Defaults to `/users/auth/cas3/callback`.
56
+ * `logout_url` - The URL to use to logout a user. Defaults to `'/logout'`.
57
+ * `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.
58
+ * `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).
59
+ * `ca_path` - Optional when `ssl` is `true`. Sets path of a CA certification directory. See [Net::HTTP][net_http] for more details.
60
+ * `disable_ssl_verification` - Optional when `ssl` is true. Disables verification.
61
+ * `on_single_sign_out` - Optional. Callback used when a [CAS 3.1 Single Sign Out][sso]
62
+ request is received.
63
+ * `fetch_raw_info` - Optional. Callback used to return additional "raw" user
64
+ info from other sources.
65
+
66
+ ```ruby
67
+ provider :cas3,
68
+ fetch_raw_info: lambda { |strategy, options, ticket, user_info|
69
+ ExternalService.get(user_info[:user]).attributes
70
+ }
71
+ ```
72
+
73
+ Configurable options for values returned by CAS:
74
+
75
+ * `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).
76
+ * `name_key` - The data attribute containing user first and last name. Defaults to `'name'`.
77
+ * `email_key` - The data attribute containing user email address. Defaults to `'email'`.
78
+ * `nickname_key` - The data attribute containing user's nickname. Defaults to `'user'`.
79
+ * `first_name_key` - The data attribute containing user first name. Defaults to `'first_name'`.
80
+ * `last_name_key` - The data attribute containing user last name. Defaults to `'last_name'`.
81
+ * `location_key` - The data attribute containing user location/address. Defaults to `'location'`.
82
+ * `image_key` - The data attribute containing user image/picture. Defaults to `'image'`.
83
+ * `phone_key` - The data attribute containing user contact phone number. Defaults to `'phone'`.
84
+
85
+ ## Migrating from OmniAuth 0.3
86
+
87
+ Given the following OmniAuth 0.3 configuration:
88
+
89
+ ```ruby
90
+ provider :CAS, cas_server: 'https://cas.example.com/cas/'
91
+ ```
92
+
93
+ Your new settings should look similar to this:
94
+
95
+ ```ruby
96
+ provider :cas3,
97
+ host: 'cas.example.com',
98
+ login_url: '/cas/login',
99
+ service_validate_url: '/cas/p3/serviceValidate'
100
+ ```
101
+
102
+ If you encounter problems wih SSL certificates you may want to set the `ca_path` parameter or activate `disable_ssl_verification` (not recommended).
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create new Pull Request
111
+
112
+ ## Thanks
113
+
114
+ Special thanks go out to the following people
115
+
116
+ * @dlindahl For the original work in porting this from OmniAuth 0.3
117
+ * Phillip Aldridge (@iterateNZ) and JB Barth (@jbbarth) for helping out with Issue #3
118
+ * Elber Ribeiro (@dynaum) for Ubuntu SSL configuration support
119
+ * @rbq for README updates and OmniAuth 0.3 migration guide
120
+
121
+ [old_omniauth_cas]: https://github.com/intridea/omniauth/blob/0-3-stable/oa-enterprise/lib/omniauth/strategies/cas.rb
122
+ [document_up]: http://tduehr.github.com/omniauth-cas3/
123
+ [net_http]: http://ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html
124
+ [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 @@
1
+ require 'omniauth/cas3'
@@ -0,0 +1,2 @@
1
+ require 'omniauth/cas3/version'
2
+ require 'omniauth/strategies/cas3'
@@ -0,0 +1,5 @@
1
+ module Omniauth
2
+ module Cas3
3
+ VERSION = '1.1.1'
4
+ end
5
+ end
@@ -0,0 +1,222 @@
1
+ require 'omniauth'
2
+ require 'addressable/uri'
3
+
4
+ module OmniAuth
5
+ module Strategies
6
+ class CAS3
7
+ include OmniAuth::Strategy
8
+
9
+ # Custom Exceptions
10
+ class MissingCASTicket < StandardError; end
11
+ class InvalidCASTicket < StandardError; end
12
+
13
+ autoload :ServiceTicketValidator, 'omniauth/strategies/cas3/service_ticket_validator'
14
+ autoload :LogoutRequest, 'omniauth/strategies/cas3/logout_request'
15
+
16
+ attr_accessor :raw_info
17
+ alias_method :user_info, :raw_info
18
+
19
+ option :name, :cas3 # 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, '/p3/serviceValidate'
26
+ option :login_url, '/login'
27
+ option :logout_url, '/logout'
28
+ option :on_single_sign_out, Proc.new {}
29
+ # A Proc or lambda that returns a Hash of additional user info to be
30
+ # merged with the info returned by the CAS server.
31
+ #
32
+ # @param [Object] An instance of OmniAuth::Strategies::CAS for the current request
33
+ # @param [String] The user's Service Ticket value
34
+ # @param [Hash] The user info for the Service Ticket returned by the CAS server
35
+ #
36
+ # @return [Hash] Extra user info
37
+ option :fetch_raw_info, Proc.new { Hash.new }
38
+ # Make all the keys configurable with some defaults set here
39
+ option :uid_field, 'user'
40
+ option :name_key, 'name'
41
+ option :email_key, 'email'
42
+ option :nickname_key, 'user'
43
+ option :first_name_key, 'first_name'
44
+ option :last_name_key, 'last_name'
45
+ option :location_key, 'location'
46
+ option :image_key, 'image'
47
+ option :phone_key, 'phone'
48
+
49
+ # As required by https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
50
+ AuthHashSchemaKeys = %w{name email nickname first_name last_name location image phone}
51
+ info do
52
+ prune!({
53
+ name: raw_info[options[:name_key].to_s],
54
+ email: raw_info[options[:email_key].to_s],
55
+ nickname: raw_info[options[:nickname_key].to_s],
56
+ first_name: raw_info[options[:first_name_key].to_s],
57
+ last_name: raw_info[options[:last_name_key].to_s],
58
+ location: raw_info[options[:location_key].to_s],
59
+ image: raw_info[options[:image_key].to_s],
60
+ phone: raw_info[options[:phone_key].to_s]
61
+ })
62
+ end
63
+
64
+ extra do
65
+ prune!(
66
+ raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
67
+ )
68
+ end
69
+
70
+ uid do
71
+ raw_info[options[:uid_field].to_s]
72
+ end
73
+
74
+ credentials do
75
+ prune!({ ticket: @ticket })
76
+ end
77
+
78
+ def callback_phase
79
+ if on_sso_path?
80
+ single_sign_out_phase
81
+ else
82
+ @ticket = request.params['ticket']
83
+ return fail!(:no_ticket, MissingCASTicket.new('No CAS Ticket')) unless @ticket
84
+ fetch_raw_info(@ticket)
85
+ return fail!(:invalid_ticket, InvalidCASTicket.new('Invalid CAS Ticket')) if raw_info.empty?
86
+ super
87
+ end
88
+ end
89
+
90
+ def request_phase
91
+ service_url = append_params(callback_url, return_url)
92
+
93
+ [
94
+ 302,
95
+ {
96
+ 'Location' => login_url(service_url),
97
+ 'Content-Type' => 'text/plain'
98
+ },
99
+ ["You are being redirected to CAS for sign-in."]
100
+ ]
101
+ end
102
+
103
+ def on_sso_path?
104
+ request.post? && request.params.has_key?('logoutRequest')
105
+ end
106
+
107
+ def single_sign_out_phase
108
+ logout_request_service.new(self, request).call(options)
109
+ end
110
+
111
+ # Build a CAS host with protocol and port
112
+ #
113
+ #
114
+ def cas_url
115
+ extract_url if options['url']
116
+ validate_cas_setup
117
+ @cas_url ||= begin
118
+ uri = Addressable::URI.new
119
+ uri.host = options.host
120
+ uri.scheme = options.ssl ? 'https' : 'http'
121
+ uri.port = options.port
122
+ uri.path = options.path
123
+ uri.to_s
124
+ end
125
+ end
126
+
127
+ def extract_url
128
+ url = Addressable::URI.parse(options.delete('url'))
129
+ options.merge!(
130
+ 'host' => url.host,
131
+ 'port' => url.port,
132
+ 'path' => url.path,
133
+ 'ssl' => url.scheme == 'https'
134
+ )
135
+ end
136
+
137
+ def validate_cas_setup
138
+ if options.host.nil? || options.login_url.nil?
139
+ raise ArgumentError.new(":host and :login_url MUST be provided")
140
+ end
141
+ end
142
+
143
+ # Build a service-validation URL from +service+ and +ticket+.
144
+ # If +service+ has a ticket param, first remove it. URL-encode
145
+ # +service+ and add it and the +ticket+ as paraemters to the
146
+ # CAS serviceValidate URL.
147
+ #
148
+ # @param [String] service the service (a.k.a. return-to) URL
149
+ # @param [String] ticket the ticket to validate
150
+ #
151
+ # @return [String] a URL like `http://cas.mycompany.com/serviceValidate?service=...&ticket=...`
152
+ def service_validate_url(service_url, ticket)
153
+ service_url = Addressable::URI.parse(service_url)
154
+ service_url.query_values = service_url.query_values.tap { |qs| qs.delete('ticket') }
155
+ cas_url + append_params(options.service_validate_url, {
156
+ service: service_url.to_s,
157
+ ticket: ticket
158
+ })
159
+ end
160
+
161
+ # Build a CAS login URL from +service+.
162
+ #
163
+ # @param [String] service the service (a.k.a. return-to) URL
164
+ #
165
+ # @return [String] a URL like `http://cas.mycompany.com/login?service=...`
166
+ def login_url(service)
167
+ cas_url + append_params(options.login_url, { service: service })
168
+ end
169
+
170
+ # Adds URL-escaped +parameters+ to +base+.
171
+ #
172
+ # @param [String] base the base URL
173
+ # @param [String] params the parameters to append to the URL
174
+ #
175
+ # @return [String] the new joined URL.
176
+ def append_params(base, params)
177
+ params = params.each { |k,v| v = Rack::Utils.escape(v) }
178
+ Addressable::URI.parse(base).tap do |base_uri|
179
+ base_uri.query_values = (base_uri.query_values || {}).merge(params)
180
+ end.to_s
181
+ end
182
+
183
+ # Validate the Service Ticket
184
+ # @return [Object] the validated Service Ticket
185
+ def validate_service_ticket(ticket)
186
+ ServiceTicketValidator.new(self, options, callback_url, ticket).call
187
+ end
188
+
189
+ private
190
+
191
+ def fetch_raw_info(ticket)
192
+ ticket_user_info = validate_service_ticket(ticket).user_info
193
+ custom_user_info = options.fetch_raw_info.call(self, options, ticket, ticket_user_info)
194
+ self.raw_info = ticket_user_info.merge(custom_user_info)
195
+ end
196
+
197
+ # Deletes Hash pairs with `nil` values.
198
+ # From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
199
+ def prune!(hash)
200
+ hash.delete_if do |_, value|
201
+ prune!(value) if value.is_a?(Hash)
202
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
203
+ end
204
+ end
205
+
206
+ def return_url
207
+ # If the request already has a `url` parameter, then it will already be appended to the callback URL.
208
+ if request.params && request.params['url']
209
+ {}
210
+ else
211
+ { url: request.referer }
212
+ end
213
+ end
214
+
215
+ def logout_request_service
216
+ LogoutRequest
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ OmniAuth.config.add_camelization 'cas3', 'CAS3'
@@ -0,0 +1,75 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class CAS3
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 = parse_and_ensure_namespaces(@request.params['logoutRequest'])
34
+ ns = saml.collect_namespaces
35
+ name_id = saml.xpath('//saml:NameID', ns).text
36
+ sess_idx = saml.xpath('//samlp:SessionIndex', ns).text
37
+ inject_params(name_id:name_id, session_index:sess_idx)
38
+ @request
39
+ end
40
+ end
41
+
42
+ def parse_and_ensure_namespaces(logout_request_xml)
43
+ doc = Nokogiri.parse(logout_request_xml)
44
+ ns = doc.collect_namespaces
45
+ if ns.include?('xmlns:samlp') && ns.include?('xmlns:saml')
46
+ doc
47
+ else
48
+ add_namespaces(doc)
49
+ end
50
+ end
51
+
52
+ def add_namespaces(logout_request_doc)
53
+ root = logout_request_doc.root
54
+ root.add_namespace('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol')
55
+ root.add_namespace('saml', 'urn:oasis:names:tc:SAML:2.0:assertion\\')
56
+
57
+ # In order to add namespaces properly we need to re-parse the document
58
+ Nokogiri.parse(logout_request_doc.to_s)
59
+ end
60
+
61
+ def inject_params(new_params)
62
+ rack_input = @request.env['rack.input'].read
63
+ params = Rack::Utils.parse_query(rack_input).merge new_params
64
+ @request.env['rack.input'] = StringIO.new(Rack::Utils.build_query(params))
65
+ ensure
66
+ @request.env['rack.input'].rewind
67
+ end
68
+
69
+ def single_sign_out_callback
70
+ @options[:on_single_sign_out]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,103 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'nokogiri'
4
+
5
+ module OmniAuth
6
+ module Strategies
7
+ class CAS3
8
+ class ServiceTicketValidator
9
+ VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
10
+
11
+ # Build a validator from a +configuration+, a
12
+ # +return_to+ URL, and a +ticket+.
13
+ #
14
+ # @param [Hash] options the OmniAuth Strategy options
15
+ # @param [String] return_to_url the URL of this CAS client service
16
+ # @param [String] ticket the service ticket to validate
17
+ def initialize(strategy, options, return_to_url, ticket)
18
+ @options = options
19
+ @uri = URI.parse(strategy.service_validate_url(return_to_url, ticket))
20
+ end
21
+
22
+ # Executes a network request to process the CAS Service Response
23
+ def call
24
+ @response_body = get_service_response_body
25
+ @success_body = find_authentication_success(@response_body)
26
+ self
27
+ end
28
+
29
+ # Request validation of the ticket from the CAS server's
30
+ # serviceValidate (CAS 2.0) function.
31
+ #
32
+ # Swallows all XML parsing errors (and returns +nil+ in those cases).
33
+ #
34
+ # @return [Hash, nil] a user information hash if the response is valid; +nil+ otherwise.
35
+ #
36
+ # @raise any connection errors encountered.
37
+ def user_info
38
+ parse_user_info(@success_body)
39
+ end
40
+
41
+ private
42
+
43
+ # turns an `<cas:authenticationSuccess>` node into a Hash;
44
+ # returns nil if given nil
45
+ def parse_user_info(node)
46
+ return nil if node.nil?
47
+ {}.tap do |hash|
48
+ node.children.each do |e|
49
+ node_name = e.name.sub(/^cas:/, '')
50
+ unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
51
+ # There are no child elements
52
+ if e.element_children.count == 0
53
+ hash[node_name] = e.content
54
+ elsif e.element_children.count
55
+ # JASIG style extra attributes
56
+ if node_name == 'attributes'
57
+ hash.merge!(parse_user_info(e))
58
+ else
59
+ hash[node_name] = [] if hash[node_name].nil?
60
+ hash[node_name].push(parse_user_info(e))
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ # finds an `<cas:authenticationSuccess>` node in
69
+ # a `<cas:serviceResponse>` body if present; returns nil
70
+ # if the passed body is nil or if there is no such node.
71
+ def find_authentication_success(body)
72
+ return nil if body.nil? || body == ''
73
+ begin
74
+ doc = Nokogiri::XML(body)
75
+ begin
76
+ doc.xpath('/cas:serviceResponse/cas:authenticationSuccess')
77
+ rescue Nokogiri::XML::XPath::SyntaxError
78
+ doc.xpath('/serviceResponse/authenticationSuccess')
79
+ end
80
+ rescue Nokogiri::XML::XPath::SyntaxError
81
+ nil
82
+ end
83
+ end
84
+
85
+ # retrieves the `<cas:serviceResponse>` XML from the CAS server
86
+ def get_service_response_body
87
+ result = ''
88
+ http = Net::HTTP.new(@uri.host, @uri.port)
89
+ http.use_ssl = @uri.port == 443 || @uri.instance_of?(URI::HTTPS)
90
+ if http.use_ssl?
91
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options.disable_ssl_verification?
92
+ http.ca_path = @options.ca_path
93
+ end
94
+ http.start do |c|
95
+ response = c.get "#{@uri.path}?#{@uri.query}", VALIDATION_REQUEST_HEADERS.dup
96
+ result = response.body
97
+ end
98
+ result
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/omniauth/cas3/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Derek Lindahl, tduehr"]
6
+ gem.email = ["td@matasano.com"]
7
+ gem.summary = %q{CAS 3.0 Strategy for OmniAuth}
8
+ gem.description = gem.summary
9
+ gem.homepage = "https://github.com/tduehr/omniauth-cas3"
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 = "omniauth-cas3"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Omniauth::Cas3::VERSION
17
+
18
+ gem.add_dependency 'omniauth', '~> 1.2'
19
+ gem.add_dependency 'nokogiri', '~> 1.6.6'
20
+ gem.add_dependency 'addressable', '~> 2.3'
21
+
22
+ gem.add_development_dependency 'rake', '~> 10.0'
23
+ gem.add_development_dependency 'webmock', '~> 1.19.0'
24
+ gem.add_development_dependency 'rspec', '~> 3.1.0'
25
+ gem.add_development_dependency 'rack-test', '~> 0.6'
26
+
27
+ gem.add_development_dependency 'awesome_print'
28
+ 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,127 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS3::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
+ end
35
+
36
+ it 'are parsed and injected into the Rack Request parameters' do
37
+ subject
38
+ expect(@rack_input).to eq 'name_id=%40NOT_USED%40&session_index=ST-123456-123abc456def'
39
+ end
40
+
41
+ it 'are parsed and injected even if saml defined inside NameID' do
42
+ request.params['logoutRequest'] =
43
+ %Q[
44
+ <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="foobarbaz" Version="2.0" IssueInstant="2014-10-19T17:13:50Z">
45
+ <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID>
46
+ <samlp:SessionIndex>ST-foo-bar</samlp:SessionIndex>
47
+ </samlp:LogoutRequest>
48
+ ]
49
+ subject
50
+ expect(@rack_input).to eq 'name_id=%40NOT_USED%40&session_index=ST-foo-bar'
51
+ end
52
+
53
+ it 'are parsed and injected even if saml and samlp namespaces not defined' do
54
+ request.params['logoutRequest'] =
55
+ %Q[
56
+ <samlp:LogoutRequest ID="123abc-1234-ab12-cd34-1234abcd" Version="2.0" IssueInstant="#{Time.now.to_s}">
57
+ <saml:NameID>@NOT_USED@</saml:NameID>
58
+ <samlp:SessionIndex>ST-789000-456def789ghi</samlp:SessionIndex>
59
+ </samlp:LogoutRequest>
60
+ ]
61
+ subject
62
+ expect(@rack_input).to eq 'name_id=%40NOT_USED%40&session_index=ST-789000-456def789ghi'
63
+ end
64
+
65
+ context 'that raise when parsed' do
66
+ let(:env) { { 'rack.input' => nil } }
67
+
68
+ before do
69
+ allow(strategy).to receive(:fail!)
70
+ subject
71
+ expect(strategy).to have_received(:fail!)
72
+ end
73
+
74
+ it 'responds with an error' do
75
+ expect(strategy).to have_received(:fail!)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe 'with a configured callback' do
81
+ let(:options) do
82
+ { on_single_sign_out: callback }
83
+ end
84
+
85
+ context 'that returns TRUE' do
86
+ let(:callback) { Proc.new{true} }
87
+
88
+ it 'responds with OK' do
89
+ expect(subject[0]).to eq 200
90
+ expect(subject[2].body).to eq ['OK']
91
+ end
92
+ end
93
+
94
+ context 'that returns Nil' do
95
+ let(:callback) { Proc.new{} }
96
+
97
+ it 'responds with OK' do
98
+ expect(subject[0]).to eq 200
99
+ expect(subject[2].body).to eq ['OK']
100
+ end
101
+ end
102
+
103
+ context 'that returns a tuple' do
104
+ let(:callback) { Proc.new{ [400,{},'Bad Request'] } }
105
+
106
+ it 'responds with OK' do
107
+ expect(subject[0]).to eq 400
108
+ expect(subject[2].body).to eq ['Bad Request']
109
+ end
110
+ end
111
+
112
+ context 'that raises an error' do
113
+ let(:exception) { RuntimeError.new('error' )}
114
+ let(:callback) { Proc.new{raise exception} }
115
+
116
+ before do
117
+ allow(strategy).to receive(:fail!)
118
+ subject
119
+ end
120
+
121
+ it 'responds with an error' do
122
+ expect(strategy).to have_received(:fail!)
123
+ .with(:logout_request, exception)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS3::ServiceTicketValidator do
4
+ let(:strategy) do
5
+ double('strategy',
6
+ service_validate_url: 'https://example.org/serviceValidate'
7
+ )
8
+ end
9
+ let(:provider_options) do
10
+ double('provider_options',
11
+ disable_ssl_verification?: false,
12
+ ca_path: '/etc/ssl/certsZOMG'
13
+ )
14
+ end
15
+ let(:validator) do
16
+ OmniAuth::Strategies::CAS3::ServiceTicketValidator.new( strategy, provider_options, '/foo', nil )
17
+ end
18
+
19
+ describe '#call' do
20
+ before do
21
+ stub_request(:get, 'https://example.org/serviceValidate?')
22
+ .to_return(status: 200, body: '')
23
+ end
24
+
25
+ subject { validator.call }
26
+
27
+ it 'returns itself' do
28
+ expect(subject).to eq validator
29
+ end
30
+
31
+ it 'uses the configured CA path' do
32
+ subject
33
+ expect(provider_options).to have_received :ca_path
34
+ end
35
+ end
36
+
37
+ describe '#user_info' do
38
+ let(:ok_fixture) do
39
+ File.expand_path(File.join(File.dirname(__FILE__), '../../../fixtures/cas_success.xml'))
40
+ end
41
+ let(:service_response) { File.read(ok_fixture) }
42
+
43
+ before do
44
+ stub_request(:get, 'https://example.org/serviceValidate?')
45
+ .to_return(status: 200, body:service_response)
46
+ validator.call
47
+ end
48
+
49
+ subject { validator.user_info }
50
+
51
+ it 'parses user info from the response' do
52
+ expect(subject).to include 'user' => 'psegel'
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+
3
+ describe OmniAuth::Strategies::CAS3, type: :strategy do
4
+ include Rack::Test::Methods
5
+
6
+ let(:my_cas_provider) { Class.new(OmniAuth::Strategies::CAS3) }
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: :cas3, 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/cas3/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
+ let(:provider) { MyCasProvider.new(nil, params) }
36
+
37
+ subject { provider.cas_url }
38
+
39
+ it 'raises an ArgumentError' do
40
+ expect{subject}.to raise_error ArgumentError, %r{:host and :login_url MUST be provided}
41
+ end
42
+
43
+ context 'with an explicit :url option' do
44
+ let(:url) { 'https://example.org:8080/my_cas' }
45
+ let(:params) { super().merge url:url }
46
+
47
+ before { subject }
48
+
49
+ it { should eq url }
50
+
51
+ it 'parses the URL into it the appropriate strategy options' do
52
+ expect(provider.options).to include ssl:true
53
+ expect(provider.options).to include host:'example.org'
54
+ expect(provider.options).to include port:8080
55
+ expect(provider.options).to include path:'/my_cas'
56
+ end
57
+ end
58
+
59
+ context 'with explicit URL component' do
60
+ let(:params) { super().merge host:'example.org', port:1234, ssl:true, path:'/a/path' }
61
+
62
+ before { subject }
63
+
64
+ it { should eq 'https://example.org:1234/a/path' }
65
+
66
+ it 'parses the URL into it the appropriate strategy options' do
67
+ expect(provider.options).to include ssl:true
68
+ expect(provider.options).to include host:'example.org'
69
+ expect(provider.options).to include port:1234
70
+ expect(provider.options).to include path:'/a/path'
71
+ end
72
+ end
73
+ end
74
+
75
+ describe 'defaults' do
76
+ subject { MyCasProvider.default_options.to_hash }
77
+
78
+ it { should include('ssl' => true) }
79
+ end
80
+
81
+ describe 'GET /auth/cas3' do
82
+ let(:return_url) { 'http://myapp.com/admin/foo' }
83
+
84
+ context 'with a referer' do
85
+ let(:url) { '/auth/cas3' }
86
+
87
+ let(:request_env) { { 'HTTP_REFERER' => return_url } }
88
+
89
+ it_behaves_like 'a CAS redirect response'
90
+ end
91
+
92
+ context 'with an explicit return URL' do
93
+ let(:url) { "/auth/cas3?url=#{return_url}" }
94
+
95
+ let(:request_env) { {} }
96
+
97
+ it_behaves_like 'a CAS redirect response'
98
+ end
99
+ end
100
+
101
+ describe 'GET /auth/cas3/callback' do
102
+ context 'without a ticket' do
103
+ before { get '/auth/cas3/callback' }
104
+
105
+ subject { last_response }
106
+
107
+ it { should be_redirect }
108
+
109
+ it 'redirects with a failure message' do
110
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=no_ticket&strategy=cas3'
111
+ end
112
+ end
113
+
114
+ context 'with an invalid ticket' do
115
+ before do
116
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/p3\/serviceValidate\?([^&]+&)?ticket=9391d/).
117
+ to_return( body: File.read('spec/fixtures/cas_failure.xml') )
118
+ get '/auth/cas3/callback?ticket=9391d'
119
+ end
120
+
121
+ subject { last_response }
122
+
123
+ it { should be_redirect }
124
+
125
+ it 'redirects with a failure message' do
126
+ expect(subject.headers).to include 'Location' => '/auth/failure?message=invalid_ticket&strategy=cas3'
127
+ end
128
+ end
129
+
130
+ describe 'with a valid ticket' do
131
+ shared_examples :successful_validation do
132
+ before do
133
+ stub_request(:get, /^http:\/\/cas.example.org:8080?\/p3\/serviceValidate\?([^&]+&)?ticket=593af/)
134
+ .with { |request| @request_uri = request.uri.to_s }
135
+ .to_return( body: File.read("spec/fixtures/#{xml_file_name}") )
136
+
137
+ get "/auth/cas3/callback?ticket=593af&url=#{return_url}"
138
+ end
139
+
140
+ it 'strips the ticket parameter from the callback URL' do
141
+ expect(@request_uri.scan('ticket=').size).to eq 1
142
+ end
143
+
144
+ it 'properly encodes the service URL' do
145
+ expect(WebMock).to have_requested(:get, 'http://cas.example.org:8080/p3/serviceValidate')
146
+ .with(query: {
147
+ ticket: '593af',
148
+ service: 'http://example.org/auth/cas3/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter')
149
+ })
150
+ end
151
+
152
+ context "request.env['omniauth.auth']" do
153
+ subject { last_request.env['omniauth.auth'] }
154
+
155
+ it { should be_kind_of Hash }
156
+
157
+ it 'identifes the provider' do
158
+ expect(subject.provider).to eq :cas3
159
+ end
160
+
161
+ it 'returns the UID of the user' do
162
+ expect(subject.uid).to eq '54'
163
+ end
164
+
165
+ context 'the info hash' do
166
+ subject { last_request.env['omniauth.auth']['info'] }
167
+
168
+ it 'includes user info attributes' do
169
+ expect(subject.name).to eq 'Peter Segel'
170
+ expect(subject.first_name).to eq 'Peter'
171
+ expect(subject.last_name).to eq 'Segel'
172
+ expect(subject.nickname).to eq 'psegel'
173
+ expect(subject.email).to eq 'psegel@intridea.com'
174
+ expect(subject.location).to eq 'Washington, D.C.'
175
+ expect(subject.image).to eq '/images/user.jpg'
176
+ expect(subject.phone).to eq '555-555-5555'
177
+ end
178
+ end
179
+
180
+ context 'the extra hash' do
181
+ subject { last_request.env['omniauth.auth']['extra'] }
182
+
183
+ it 'includes additional user attributes' do
184
+ expect(subject.user).to eq 'psegel'
185
+ expect(subject.employeeid).to eq '54'
186
+ expect(subject.hire_date).to eq '2004-07-13'
187
+ end
188
+ end
189
+
190
+ context 'the credentials hash' do
191
+ subject { last_request.env['omniauth.auth']['credentials'] }
192
+
193
+ it 'has a ticket value' do
194
+ expect(subject.ticket).to eq '593af'
195
+ end
196
+ end
197
+ end
198
+
199
+ it 'calls through to the master app' do
200
+ expect(last_response.body).to eq 'true'
201
+ end
202
+ end
203
+
204
+ let(:return_url) { 'http://127.0.0.10/?some=parameter' }
205
+
206
+ context 'with JASIG flavored XML' do
207
+ let(:xml_file_name) { 'cas_success_jasig.xml' }
208
+
209
+ it_behaves_like :successful_validation
210
+ end
211
+
212
+ context 'with classic XML' do
213
+ let(:xml_file_name) { 'cas_success.xml' }
214
+
215
+ it_behaves_like :successful_validation
216
+ end
217
+ end
218
+ end
219
+
220
+ describe 'POST /auth/cas3/callback' do
221
+ describe 'with a Single Sign-Out logoutRequest' do
222
+ let(:logoutRequest) do
223
+ %Q[
224
+ <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion\" ID="123abc-1234-ab12-cd34-1234abcd" Version="2.0" IssueInstant="#{Time.now.to_s}">
225
+ <saml:NameID>@NOT_USED@</saml:NameID>
226
+ <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex>
227
+ </samlp:LogoutRequest>
228
+ ]
229
+ end
230
+
231
+ let(:logout_request) { double('logout_request', call:[200,{},'OK']) }
232
+
233
+ subject do
234
+ post 'auth/cas3/callback', logoutRequest:logoutRequest
235
+ end
236
+
237
+ before do
238
+ allow_any_instance_of(MyCasProvider)
239
+ .to receive(: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,13 @@
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
+ end
8
+
9
+ require 'rack/test'
10
+ require 'webmock/rspec'
11
+ require 'omniauth-cas3'
12
+
13
+ OmniAuth.config.logger = Logger.new( '/dev/null' )
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-cas3
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Derek Lindahl, tduehr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: omniauth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.6
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.6.6
41
+ - !ruby/object:Gem::Dependency
42
+ name: addressable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.19.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.19.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.1.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.1.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack-test
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.6'
111
+ - !ruby/object:Gem::Dependency
112
+ name: awesome_print
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: CAS 3.0 Strategy for OmniAuth
126
+ email:
127
+ - td@matasano.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".editorconfig"
133
+ - ".gitignore"
134
+ - ".travis.yml"
135
+ - Gemfile
136
+ - LICENSE
137
+ - README.md
138
+ - Rakefile
139
+ - lib/omniauth-cas3.rb
140
+ - lib/omniauth/cas3.rb
141
+ - lib/omniauth/cas3/version.rb
142
+ - lib/omniauth/strategies/cas3.rb
143
+ - lib/omniauth/strategies/cas3/logout_request.rb
144
+ - lib/omniauth/strategies/cas3/service_ticket_validator.rb
145
+ - omniauth-cas3.gemspec
146
+ - spec/fixtures/cas_failure.xml
147
+ - spec/fixtures/cas_success.xml
148
+ - spec/fixtures/cas_success_jasig.xml
149
+ - spec/omniauth/strategies/cas3/logout_request_spec.rb
150
+ - spec/omniauth/strategies/cas3/service_ticket_validator_spec.rb
151
+ - spec/omniauth/strategies/cas3_spec.rb
152
+ - spec/spec_helper.rb
153
+ homepage: https://github.com/tduehr/omniauth-cas3
154
+ licenses: []
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubyforge_project:
172
+ rubygems_version: 2.4.8
173
+ signing_key:
174
+ specification_version: 4
175
+ summary: CAS 3.0 Strategy for OmniAuth
176
+ test_files:
177
+ - spec/fixtures/cas_failure.xml
178
+ - spec/fixtures/cas_success.xml
179
+ - spec/fixtures/cas_success_jasig.xml
180
+ - spec/omniauth/strategies/cas3/logout_request_spec.rb
181
+ - spec/omniauth/strategies/cas3/service_ticket_validator_spec.rb
182
+ - spec/omniauth/strategies/cas3_spec.rb
183
+ - spec/spec_helper.rb
184
+ has_rdoc: