omniauth-cas 1.1.0.beta.1 → 1.1.0.pre.rc.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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- Mjc3ZjA4OWRiNjhlNDgxMzcxNTEyYWIxODE1Y2U5ZWY2Y2U0YmU4MA==
5
- data.tar.gz: !binary |-
6
- YmE0ZjRhNzRkYmM5NTYwNGE1M2QwNjgxNGFjODAyZjk3MDk0MTE4ZA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- NWYwMGVjNWEzMzkzODlhOWEwNWY0MWExYTBiNWM2YjdlNGNlMDAyZjJlMjQx
10
- YTdiZWRkMzJhODgwOTY2YjMzZTg2ZWZiOWVjYTNiN2NlMmUzZTNhYzFjZGYx
11
- MGJiODI3OTdkMDBkMGM3YjA3MmU1YWFkNmQ0YTg0Y2E4NjAzYjI=
12
- data.tar.gz: !binary |-
13
- YTkyYjcwZTQxMzE3NzNmYmFjOWE0ZjkyMDY4MzMxMjlhNDU1MTg2ZjQwYmFm
14
- YzA2MzJkZGNlNjYzYmE2YjE3NjZiMTZiZGJkYzZkMzdlMzY3NjE1OWU5MjI1
15
- MGYxZWQ1MDQ2NzhmNjUzNjVmNzJkMTc0OGJjMGIyOTYyN2MwOGM=
2
+ SHA1:
3
+ metadata.gz: 9060c98d3a2f5102d1060c66308ecc86ec7fa0eb
4
+ data.tar.gz: 8c46c748a3580c4d5ef43307e283f003110dd6ef
5
+ SHA512:
6
+ metadata.gz: 46e3e70d1513ee2f883f4996a575626b32c3c781e084f88709813b4490542e6fc10a85eaf01b9d68af8a889f48f692e7d934cac504d18462c21ce84685ffadb4
7
+ data.tar.gz: 3987aebc35372d17e80487bcc688805f37c4ced4434d43db504d38ab17de6dc97bc7bf1a1d4d60aadc33887ef1daffc018d97b92c8b004f14858f4720d28da14
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/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.3
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  rvm:
2
2
  - 1.9.2
3
3
  - 1.9.3
4
+ - 2.0.0
4
5
  branches:
5
6
  only:
6
7
  - master
data/README.md CHANGED
@@ -4,11 +4,13 @@
4
4
  [version]: http://badge.fury.io/rb/omniauth-cas
5
5
  [travis]: http://travis-ci.org/dlindahl/omniauth-cas
6
6
  [travis_status]: https://secure.travis-ci.org/dlindahl/omniauth-cas.png
7
+ [releases]: https://github.com/dlindahl/omniauth-cas/releases
7
8
 
8
9
  This is a OmniAuth 1.0 compatible port of the previously available
9
10
  [OmniAuth CAS strategy][old_omniauth_cas] that was bundled with OmniAuth 0.3.
10
11
 
11
- [View the documentation][document_up]
12
+ * [View the documentation][document_up]
13
+ * [Changelog][releases]
12
14
 
13
15
  ## Installation
14
16
 
@@ -41,21 +43,44 @@ end
41
43
  OmniAuth CAS requires at least one of the following two configuration options:
42
44
 
43
45
  * `url` - Defines the URL of your CAS server (i.e. `http://example.org:8080`)
44
- * `host` - Defines the host of your CAS server. Optional if using `url`
45
- * `login_url` - Defines the URL used to prompt users for their login information. Defaults to `/login`
46
- If no `host` is configured, the host application's domain will be used.
46
+ * `host` - Defines the host of your CAS server (i.e. `example.org`).
47
47
 
48
48
  #### Optional
49
49
 
50
50
  Other configuration options:
51
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
- * `uid_key` - The user data attribute to use as your user's unique identifier. Defaults to `'user'` (which usually contains the user's login name)
57
- * `ca_path` - Optional when `ssl` is `true`. Sets path of a CA certification directory. See [Net::HTTP][net_http] for more details
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/cas/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.
58
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 :cas,
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'`.
59
84
 
60
85
  ## Migrating from OmniAuth 0.3
61
86
 
@@ -95,3 +120,4 @@ Special thanks go out to the following people
95
120
  [old_omniauth_cas]: https://github.com/intridea/omniauth/blob/0-3-stable/oa-enterprise/lib/omniauth/strategies/cas.rb
96
121
  [document_up]: http://dlindahl.github.com/omniauth-cas/
97
122
  [net_http]: http://ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html
123
+ [sso]: https://wiki.jasig.org/display/CASUM/Single+Sign+Out
data/lib/omniauth-cas.rb CHANGED
@@ -1 +1 @@
1
- require "omniauth/cas"
1
+ require 'omniauth/cas'
@@ -1,5 +1,5 @@
1
1
  module Omniauth
2
2
  module Cas
3
- VERSION = '1.1.0.beta.1'
3
+ VERSION = '1.1.0-rc.1'
4
4
  end
5
5
  end
@@ -1,4 +1,4 @@
1
- require 'omniauth/strategy'
1
+ require 'omniauth'
2
2
  require 'addressable/uri'
3
3
 
4
4
  module OmniAuth
@@ -10,8 +10,8 @@ module OmniAuth
10
10
  class MissingCASTicket < StandardError; end
11
11
  class InvalidCASTicket < StandardError; end
12
12
 
13
- autoload :Configuration, 'omniauth/strategies/cas/configuration'
14
13
  autoload :ServiceTicketValidator, 'omniauth/strategies/cas/service_ticket_validator'
14
+ autoload :LogoutRequest, 'omniauth/strategies/cas/logout_request'
15
15
 
16
16
  attr_accessor :raw_info
17
17
  alias_method :user_info, :raw_info
@@ -25,81 +25,121 @@ module OmniAuth
25
25
  option :service_validate_url, '/serviceValidate'
26
26
  option :login_url, '/login'
27
27
  option :logout_url, '/logout'
28
- option :uid_key, 'user'
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'
29
48
 
30
49
  # As required by https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
31
- AuthHashSchemaKeys = %w{name email first_name last_name location image phone}
50
+ AuthHashSchemaKeys = %w{name email nickname first_name last_name location image phone}
32
51
  info do
33
52
  prune!({
34
- :name => raw_info['name'],
35
- :email => raw_info['email'],
36
- :first_name => raw_info['first_name'],
37
- :last_name => raw_info['last_name'],
38
- :location => raw_info['location'],
39
- :image => raw_info['image'],
40
- :phone => raw_info['phone']
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]
41
61
  })
42
62
  end
43
63
 
44
64
  extra do
45
- prune! raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
65
+ prune!(
66
+ raw_info.delete_if{ |k,v| AuthHashSchemaKeys.include?(k) }
67
+ )
46
68
  end
47
69
 
48
70
  uid do
49
- raw_info[ @options[:uid_key].to_s ]
71
+ raw_info[options[:uid_field].to_s]
50
72
  end
51
73
 
52
74
  credentials do
53
- prune!({
54
- :ticket => @ticket
55
- })
56
- end
57
-
58
- def initialize( app, *args, &block )
59
- super
60
- @configuration = Configuration.new( @options )
75
+ prune!({ ticket: @ticket })
61
76
  end
62
77
 
63
78
  def callback_phase
64
- @ticket = request.params['ticket']
65
-
66
- return fail!(:no_ticket, MissingCASTicket.new('No CAS Ticket')) unless @ticket
67
-
68
- self.raw_info = ServiceTicketValidator.new(self, @options, callback_url, @ticket).user_info
69
-
70
- return fail!(:invalid_ticket, InvalidCASTicket.new('Invalid CAS Ticket')) if raw_info.empty?
71
-
72
- super
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
73
88
  end
74
89
 
75
90
  def request_phase
76
- service_url = append_params( callback_url, return_url )
91
+ service_url = append_params(callback_url, return_url)
77
92
 
78
93
  [
79
94
  302,
80
95
  {
81
- 'Location' => login_url( service_url ),
96
+ 'Location' => login_url(service_url),
82
97
  'Content-Type' => 'text/plain'
83
98
  },
84
99
  ["You are being redirected to CAS for sign-in."]
85
100
  ]
86
101
  end
87
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
+
88
111
  # Build a CAS host with protocol and port
89
112
  #
90
113
  #
91
114
  def cas_url
115
+ extract_url if options['url']
116
+ validate_cas_setup
92
117
  @cas_url ||= begin
93
118
  uri = Addressable::URI.new
94
- uri.host = @options.host
95
- uri.scheme = @options.ssl ? 'https' : 'http'
96
- uri.port = @options.port
97
- uri.path = @options.path
98
-
119
+ uri.host = options.host
120
+ uri.scheme = options.ssl ? 'https' : 'http'
121
+ uri.port = options.port
122
+ uri.path = options.path
99
123
  uri.to_s
100
124
  end
101
125
  end
102
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
+
103
143
  # Build a service-validation URL from +service+ and +ticket+.
104
144
  # If +service+ has a ticket param, first remove it. URL-encode
105
145
  # +service+ and add it and the +ticket+ as paraemters to the
@@ -110,10 +150,12 @@ module OmniAuth
110
150
  #
111
151
  # @return [String] a URL like `http://cas.mycompany.com/serviceValidate?service=...&ticket=...`
112
152
  def service_validate_url(service_url, ticket)
113
- service_url = Addressable::URI.parse( service_url )
153
+ service_url = Addressable::URI.parse(service_url)
114
154
  service_url.query_values = service_url.query_values.tap { |qs| qs.delete('ticket') }
115
-
116
- cas_url + append_params(@options.service_validate_url, { :service => service_url.to_s, :ticket => ticket })
155
+ cas_url + append_params(options.service_validate_url, {
156
+ service: service_url.to_s,
157
+ ticket: ticket
158
+ })
117
159
  end
118
160
 
119
161
  # Build a CAS login URL from +service+.
@@ -122,7 +164,7 @@ module OmniAuth
122
164
  #
123
165
  # @return [String] a URL like `http://cas.mycompany.com/login?service=...`
124
166
  def login_url(service)
125
- cas_url + append_params( @options.login_url, { :service => service })
167
+ cas_url + append_params(options.login_url, { service: service })
126
168
  end
127
169
 
128
170
  # Adds URL-escaped +parameters+ to +base+.
@@ -133,20 +175,25 @@ module OmniAuth
133
175
  # @return [String] the new joined URL.
134
176
  def append_params(base, params)
135
177
  params = params.each { |k,v| v = Rack::Utils.escape(v) }
136
-
137
178
  Addressable::URI.parse(base).tap do |base_uri|
138
- base_uri.query_values = (base_uri.query_values || {}).merge( params )
179
+ base_uri.query_values = (base_uri.query_values || {}).merge(params)
139
180
  end.to_s
140
181
  end
141
182
 
142
- # Adds support for custom callback_paths
143
- # See: https://github.com/intridea/omniauth/issues/630
144
- def callback_path
145
- options[:callback_path].is_a?(String) ? options[:callback_path] : (custom_path(:callback_path) || custom_path(:request_path) || "#{path_prefix}/#{name}/callback")
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
146
187
  end
147
188
 
148
189
  private
149
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
+
150
197
  # Deletes Hash pairs with `nil` values.
151
198
  # From https://github.com/mkdynamic/omniauth-facebook/blob/972ed5e3456bcaed7df1f55efd7c05c216c8f48e/lib/omniauth/strategies/facebook.rb#L122-127
152
199
  def prune!(hash)
@@ -158,13 +205,16 @@ module OmniAuth
158
205
 
159
206
  def return_url
160
207
  # If the request already has a `url` parameter, then it will already be appended to the callback URL.
161
- if request.params and request.params['url']
208
+ if request.params && request.params['url']
162
209
  {}
163
210
  else
164
- { :url => request.referer }
211
+ { url: request.referer }
165
212
  end
166
213
  end
167
214
 
215
+ def logout_request_service
216
+ LogoutRequest
217
+ end
168
218
  end
169
219
  end
170
220
  end
@@ -0,0 +1,58 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class CAS
4
+ class LogoutRequest
5
+ def initialize(strategy, request)
6
+ @strategy, @request = strategy, request
7
+ end
8
+
9
+ def call(options = {})
10
+ @options = options
11
+
12
+ begin
13
+ result = single_sign_out_callback.call(*logout_request)
14
+ rescue StandardError => err
15
+ return @strategy.fail! :logout_request, err
16
+ else
17
+ result = [200,{},'OK'] if result == true || result.nil?
18
+ ensure
19
+ return unless result
20
+
21
+ # TODO: Why does ActionPack::Response return [status,headers,body]
22
+ # when Rack::Response#new wants [body,status,headers]? Additionally,
23
+ # why does Rack::Response differ in argument order from the usual
24
+ # Rack-like [status,headers,body] array?
25
+ return Rack::Response.new(result[2],result[0],result[1]).finish
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def logout_request
32
+ @logout_request ||= begin
33
+ saml = Nokogiri.parse(@request.params['logoutRequest'])
34
+ name_id = saml.xpath('//saml:NameID').text
35
+ sess_idx = saml.xpath('//samlp:SessionIndex').text
36
+ inject_params(name_id:name_id, session_index:sess_idx)
37
+ @request
38
+ end
39
+ end
40
+
41
+ def inject_params(new_params)
42
+ rack_input = @request.env['rack.input'].read
43
+ params = Rack::Utils.parse_query(rack_input, '&').merge new_params
44
+ @request.env['rack.input'] = StringIO.new(Rack::Utils.build_query(params))
45
+ rescue
46
+ # A no-op intended to ensure that the ensure block is run
47
+ raise
48
+ ensure
49
+ @request.env['rack.input'].rewind
50
+ end
51
+
52
+ def single_sign_out_callback
53
+ @options[:on_single_sign_out]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -6,7 +6,6 @@ module OmniAuth
6
6
  module Strategies
7
7
  class CAS
8
8
  class ServiceTicketValidator
9
-
10
9
  VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' }
11
10
 
12
11
  # Build a validator from a +configuration+, a
@@ -20,6 +19,13 @@ module OmniAuth
20
19
  @uri = URI.parse(strategy.service_validate_url(return_to_url, ticket))
21
20
  end
22
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
+
23
29
  # Request validation of the ticket from the CAS server's
24
30
  # serviceValidate (CAS 2.0) function.
25
31
  #
@@ -29,7 +35,7 @@ module OmniAuth
29
35
  #
30
36
  # @raise any connection errors encountered.
31
37
  def user_info
32
- parse_user_info( find_authentication_success( get_service_response_body ) )
38
+ parse_user_info(@success_body)
33
39
  end
34
40
 
35
41
  private
@@ -38,18 +44,21 @@ module OmniAuth
38
44
  # returns nil if given nil
39
45
  def parse_user_info(node)
40
46
  return nil if node.nil?
41
-
42
47
  {}.tap do |hash|
43
48
  node.children.each do |e|
44
- unless e.kind_of?(Nokogiri::XML::Text) ||
45
- e.name == 'cas:proxies' ||
46
- e.name == 'proxies'
49
+ node_name = e.name.sub(/^cas:/, '')
50
+ unless e.kind_of?(Nokogiri::XML::Text) || node_name == 'proxies'
47
51
  # There are no child elements
48
52
  if e.element_children.count == 0
49
- hash[e.name.sub(/^cas:/, '')] = e.content
53
+ hash[node_name] = e.content
50
54
  elsif e.element_children.count
51
- hash[e.name.sub(/^cas:/, '')] = [] if hash[e.name.sub(/^cas:/, '')].nil?
52
- hash[e.name.sub(/^cas:/, '')].push parse_user_info e
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
53
62
  end
54
63
  end
55
64
  end
@@ -88,7 +97,6 @@ module OmniAuth
88
97
  end
89
98
  result
90
99
  end
91
-
92
100
  end
93
101
  end
94
102
  end