omniauth-cas3 1.1.1

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