omniauth-cas 1.1.0.beta.1 → 1.1.0.pre.rc.1

Sign up to get free protection for your applications and to get access to all the features.
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