Module: OmniAuth::Strategy

Included in:
OmniAuth::Strategies::Developer
Defined in:
lib/omniauth/strategy.rb

Overview

The Strategy is the base unit of OmniAuth's ability to wrangle multiple providers. Each strategy provided by OmniAuth includes this mixin to gain the default functionality necessary to be compatible with the OmniAuth library.

Defined Under Namespace

Modules: ClassMethods Classes: Options

Constant Summary collapse

CURRENT_PATH_REGEX =
%r{/$}.freeze
EMPTY_STRING =
''.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



116
117
118
# File 'lib/omniauth/strategy.rb', line 116

def app
  @app
end

#envObject (readonly)

Returns the value of attribute env.



116
117
118
# File 'lib/omniauth/strategy.rb', line 116

def env
  @env
end

#optionsObject (readonly)

Returns the value of attribute options.



116
117
118
# File 'lib/omniauth/strategy.rb', line 116

def options
  @options
end

#responseObject (readonly)

Returns the value of attribute response.



116
117
118
# File 'lib/omniauth/strategy.rb', line 116

def response
  @response
end

Class Method Details

.included(base) ⇒ Object

rubocop:disable ModuleLength



10
11
12
13
14
15
16
17
18
19
# File 'lib/omniauth/strategy.rb', line 10

def self.included(base)
  OmniAuth.strategies << base

  base.extend ClassMethods
  base.class_eval do
    option :setup, false
    option :skip_info, false
    option :origin_param, 'origin'
  end
end

Instance Method Details

#auth_hashObject



398
399
400
401
402
403
404
405
406
# File 'lib/omniauth/strategy.rb', line 398

def auth_hash
  credentials_data = credentials
  extra_data = extra
  AuthHash.new(:provider => name, :uid => uid).tap do |auth|
    auth.info = info unless skip_info?
    auth.credentials = credentials_data if credentials_data
    auth.extra = extra_data if extra_data
  end
end

#call(env) ⇒ Object

Duplicates this instance and runs #call! on it.

Parameters:

  • The (Hash)

    Rack environment.



168
169
170
# File 'lib/omniauth/strategy.rb', line 168

def call(env)
  dup.call!(env)
end

#call!(env) ⇒ Object

The logic for dispatching any additional actions that need to be taken. For instance, calling the request phase if the request path is recognized.

Parameters:

  • env (Hash)

    The Rack environment.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/omniauth/strategy.rb', line 177

def call!(env) # rubocop:disable CyclomaticComplexity, PerceivedComplexity
  unless env['rack.session']
    error = OmniAuth::NoSessionError.new('You must provide a session to use OmniAuth.')
    raise(error)
  end

  @env = env

  warn_if_using_get_on_request_path

  @env['omniauth.strategy'] = self if on_auth_path?

  return mock_call!(env) if OmniAuth.config.test_mode

  begin
    return options_call if on_auth_path? && options_request?
    return request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym)
    return callback_call if on_callback_path?
    return other_phase if respond_to?(:other_phase)
  rescue StandardError => e
    raise e if env.delete('omniauth.error.app')

    return fail!(e.message, e)
  end

  @app.call(env)
end

#call_app!(env = @env) ⇒ Object



477
478
479
480
481
482
# File 'lib/omniauth/strategy.rb', line 477

def call_app!(env = @env)
  @app.call(env)
rescue StandardError => e
  env['omniauth.error.app'] = true
  raise e
end

#callback_callObject

Performs the steps necessary to run the callback phase of a strategy.



268
269
270
271
272
273
274
275
276
# File 'lib/omniauth/strategy.rb', line 268

def callback_call
  setup_phase
  log :debug, 'Callback phase initiated.'
  @env['omniauth.origin'] = session.delete('omniauth.origin')
  @env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
  @env['omniauth.params'] = session.delete('omniauth.params') || {}
  OmniAuth.config.before_callback_phase.call(@env) if OmniAuth.config.before_callback_phase
  callback_phase
end

#callback_pathObject



453
454
455
456
457
458
459
460
461
# File 'lib/omniauth/strategy.rb', line 453

def callback_path
  @callback_path ||= begin
    path = options[:callback_path] if options[:callback_path].is_a?(String)
    path ||= current_path if options[:callback_path].respond_to?(:call) && options[:callback_path].call(env)
    path ||= custom_path(:request_path)
    path ||= "#{script_name}#{path_prefix}/#{name}/callback"
    path
  end
end

#callback_phaseObject



424
425
426
427
# File 'lib/omniauth/strategy.rb', line 424

def callback_phase
  env['omniauth.auth'] = auth_hash
  call_app!
end

#callback_urlObject



504
505
506
# File 'lib/omniauth/strategy.rb', line 504

def callback_url
  full_host + callback_path + query_string
end

#credentialsObject



390
391
392
# File 'lib/omniauth/strategy.rb', line 390

def credentials
  merge_stack(self.class.credentials_stack(self))
end

#current_pathObject



469
470
471
# File 'lib/omniauth/strategy.rb', line 469

def current_path
  @current_path ||= request.path.downcase.sub(CURRENT_PATH_REGEX, EMPTY_STRING)
end

#custom_path(kind) ⇒ Object



433
434
435
436
437
438
439
440
441
442
# File 'lib/omniauth/strategy.rb', line 433

def custom_path(kind)
  if options[kind].respond_to?(:call)
    result = options[kind].call(env)
    return nil unless result.is_a?(String)

    result
  else
    options[kind]
  end
end

#extraObject



394
395
396
# File 'lib/omniauth/strategy.rb', line 394

def extra
  merge_stack(self.class.extra_stack(self))
end

#fail!(message_key, exception = nil) ⇒ Object



542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/omniauth/strategy.rb', line 542

def fail!(message_key, exception = nil)
  env['omniauth.error'] = exception
  env['omniauth.error.type'] = message_key.to_sym
  env['omniauth.error.strategy'] = self

  if exception
    log :error, "Authentication failure! #{message_key}: #{exception.class}, #{exception.message}"
  else
    log :error, "Authentication failure! #{message_key} encountered."
  end

  OmniAuth.config.on_failure.call(env)
end

#full_hostObject



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/omniauth/strategy.rb', line 484

def full_host
  case OmniAuth.config.full_host
  when String
    OmniAuth.config.full_host
  when Proc
    OmniAuth.config.full_host.call(env)
  else
    # in Rack 1.3.x, request.url explodes if scheme is nil
    if request.scheme && URI.parse(request.url).absolute?
      uri = URI.parse(request.url.gsub(/\?.*$/, ''))
      uri.path = ''
      # sometimes the url is actually showing http inside rails because the
      # other layers (like nginx) have handled the ssl termination.
      uri.scheme = 'https' if ssl? # rubocop:disable BlockNesting
      uri.to_s
    else ''
    end
  end
end

#infoObject



386
387
388
# File 'lib/omniauth/strategy.rb', line 386

def info
  merge_stack(self.class.info_stack(self))
end

#new(app, options = {}) ⇒ Object #new(app, *args, options = {}) ⇒ Object

Initializes the strategy by passing in the Rack endpoint, the unique URL segment name for this strategy, and any additional arguments. An options hash is automatically created from the last argument if it is a hash.

Overloads:

  • #new(app, options = {}) ⇒ Object

    If nothing but a hash is supplied, initialized with the supplied options overriding the strategy's default options via a deep merge.

  • #new(app, *args, options = {}) ⇒ Object

    If the strategy has supplied custom arguments that it accepts, they may will be passed through and set to the appropriate values.

Parameters:

  • app (Rack application)

    The application on which this middleware is applied.

Yields:

  • (Options)

    Yields options to block for further configuration.

Raises:

  • (ArgumentError)


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/omniauth/strategy.rb', line 133

def initialize(app, *args, &block) # rubocop:disable UnusedMethodArgument
  @app = app
  @env = nil
  @options = self.class.default_options.dup

  options.deep_merge!(args.pop) if args.last.is_a?(Hash)
  options[:name] ||= self.class.to_s.split('::').last.downcase

  self.class.args.each do |arg|
    break if args.empty?

    options[arg] = args.shift
  end

  # Make sure that all of the args have been dealt with, otherwise error out.
  raise(ArgumentError.new("Received wrong number of arguments. #{args.inspect}")) unless args.empty?

  yield options if block_given?
end

#inspectObject



153
154
155
# File 'lib/omniauth/strategy.rb', line 153

def inspect
  "#<#{self.class}>"
end

#log(level, message) ⇒ Object

Direct access to the OmniAuth logger, automatically prefixed with this strategy's name.

Examples:

log :warn, "This is a warning."


162
163
164
# File 'lib/omniauth/strategy.rb', line 162

def log(level, message)
  OmniAuth.logger.send(level, "(#{name}) #{message}")
end

#mock_call!Object

This is called in lieu of the normal request process in the event that OmniAuth has been configured to be in test mode.



307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/omniauth/strategy.rb', line 307

def mock_call!(*)
  begin
    return mock_request_call if on_request_path? && OmniAuth.config.allowed_request_methods.include?(request.request_method.downcase.to_sym)
    return mock_callback_call if on_callback_path?
  rescue StandardError => e
    raise e if env.delete('omniauth.error.app')

    return fail!(e.message, e)
  end

  call_app!
end

#mock_callback_callObject



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/omniauth/strategy.rb', line 342

def mock_callback_call
  setup_phase

  origin = session.delete('omniauth.origin')
  @env['omniauth.origin'] ||= origin
  @env['omniauth.origin'] = nil if env['omniauth.origin'] == ''
  @env['omniauth.params'] = session.delete('omniauth.params') || {}

  mocked_auth = OmniAuth.mock_auth_for(name.to_s)
  if mocked_auth.is_a?(Symbol)
    fail!(mocked_auth)
  else
    @env['omniauth.auth'] = mocked_auth
    OmniAuth.config.before_callback_phase.call(@env) if OmniAuth.config.before_callback_phase
    call_app!
  end
end

#mock_request_callObject



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/omniauth/strategy.rb', line 320

def mock_request_call
  setup_phase

  session['omniauth.params'] = request.GET

  OmniAuth.config.request_validation_phase.call(env) if OmniAuth.config.request_validation_phase
  OmniAuth.config.before_request_phase.call(env) if OmniAuth.config.before_request_phase

  if options.origin_param
    if request.params[options.origin_param]
      session['omniauth.origin'] = request.params[options.origin_param]
    elsif env['HTTP_REFERER'] && !env['HTTP_REFERER'].match(/#{request_path}$/)
      session['omniauth.origin'] = env['HTTP_REFERER']
    end
  end

  result = redirect(callback_url)

  OmniAuth.config.after_request_phase.call(env) if OmniAuth.config.after_request_phase
  result
end

#nameObject



521
522
523
# File 'lib/omniauth/strategy.rb', line 521

def name
  options[:name]
end

#on_auth_path?Boolean

Returns true if the environment recognizes either the request or callback path.

Returns:

  • (Boolean)


280
281
282
# File 'lib/omniauth/strategy.rb', line 280

def on_auth_path?
  on_request_path? || on_callback_path?
end

#on_callback_path?Boolean

Returns:

  • (Boolean)


292
293
294
# File 'lib/omniauth/strategy.rb', line 292

def on_callback_path?
  on_path?(callback_path)
end

#on_path?(path) ⇒ Boolean

Returns:

  • (Boolean)


296
297
298
# File 'lib/omniauth/strategy.rb', line 296

def on_path?(path)
  current_path.casecmp(path).zero?
end

#on_request_path?Boolean

Returns:

  • (Boolean)


284
285
286
287
288
289
290
# File 'lib/omniauth/strategy.rb', line 284

def on_request_path?
  if options[:request_path].respond_to?(:call)
    options[:request_path].call(env)
  else
    on_path?(request_path)
  end
end

#options_callObject

Responds to an OPTIONS request.



226
227
228
229
230
# File 'lib/omniauth/strategy.rb', line 226

def options_call
  OmniAuth.config.before_options_phase.call(env) if OmniAuth.config.before_options_phase
  verbs = OmniAuth.config.allowed_request_methods.collect(&:to_s).collect(&:upcase).join(', ')
  [200, {'Allow' => verbs}, []]
end

#options_request?Boolean

Returns:

  • (Boolean)


300
301
302
# File 'lib/omniauth/strategy.rb', line 300

def options_request?
  request.request_method == 'OPTIONS'
end

#path_prefixObject



429
430
431
# File 'lib/omniauth/strategy.rb', line 429

def path_prefix
  options[:path_prefix] || OmniAuth.config.path_prefix
end

#query_stringObject



473
474
475
# File 'lib/omniauth/strategy.rb', line 473

def query_string
  request.query_string.empty? ? '' : "?#{request.query_string}"
end

#redirect(uri) ⇒ Object



525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/omniauth/strategy.rb', line 525

def redirect(uri)
  r = Rack::Response.new

  if options[:iframe]
    r.write("<script type='text/javascript' charset='utf-8'>top.location.href = '#{uri}';</script>")
  else
    r.write("Redirecting to #{uri}...")
    r.redirect(uri)
  end

  r.finish
end

#requestObject



517
518
519
# File 'lib/omniauth/strategy.rb', line 517

def request
  @request ||= Rack::Request.new(@env)
end

#request_callObject

Performs the steps necessary to run the request phase of a strategy.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/omniauth/strategy.rb', line 233

def request_call # rubocop:disable CyclomaticComplexity, MethodLength, PerceivedComplexity
  setup_phase
  log :debug, 'Request phase initiated.'

  # store query params from the request url, extracted in the callback_phase
  session['omniauth.params'] = request.GET

  OmniAuth.config.request_validation_phase.call(env) if OmniAuth.config.request_validation_phase
  OmniAuth.config.before_request_phase.call(env) if OmniAuth.config.before_request_phase

  result = if options.form.respond_to?(:call)
    log :debug, 'Rendering form from supplied Rack endpoint.'
    options.form.call(env)
  elsif options.form
    log :debug, 'Rendering form from underlying application.'
    call_app!
  elsif !options.origin_param
    request_phase
  else
    if request.params[options.origin_param]
      env['rack.session']['omniauth.origin'] = request.params[options.origin_param]
    elsif env['HTTP_REFERER'] && !env['HTTP_REFERER'].match(/#{request_path}$/)
      env['rack.session']['omniauth.origin'] = env['HTTP_REFERER']
    end

    request_phase
  end
  
  OmniAuth.config.after_request_phase.call(env) if OmniAuth.config.after_request_phase
  result
rescue OmniAuth::AuthenticityError => e
  fail!(:authenticity_error, e)
end

#request_pathObject



444
445
446
447
448
449
450
451
# File 'lib/omniauth/strategy.rb', line 444

def request_path
  @request_path ||=
    if options[:request_path].is_a?(String)
      options[:request_path]
    else
      "#{script_name}#{path_prefix}/#{name}"
    end
end

#request_phaseObject

This method is abstract.

This method is called when the user is on the request path. You should

perform any information gathering you need to be able to authenticate the user in this phase.

Raises:

  • (NotImplementedError)


378
379
380
# File 'lib/omniauth/strategy.rb', line 378

def request_phase
  raise(NotImplementedError)
end

#script_nameObject



508
509
510
511
# File 'lib/omniauth/strategy.rb', line 508

def script_name
  return '' if @env.nil?
  @env['SCRIPT_NAME'] || ''
end

#sessionObject



513
514
515
# File 'lib/omniauth/strategy.rb', line 513

def session
  @env['rack.session']
end

#setup_pathObject



463
464
465
# File 'lib/omniauth/strategy.rb', line 463

def setup_path
  options[:setup_path] || "#{path_prefix}/#{name}/setup"
end

#setup_phaseObject

The setup phase looks for the :setup option to exist and, if it is, will call either the Rack endpoint supplied to the :setup option or it will call out to the setup path of the underlying application. This will default to /auth/:provider/setup.



364
365
366
367
368
369
370
371
372
373
# File 'lib/omniauth/strategy.rb', line 364

def setup_phase
  if options[:setup].respond_to?(:call)
    log :debug, 'Setup endpoint detected, running now.'
    options[:setup].call(env)
  elsif options[:setup]
    log :debug, 'Calling through to underlying application for setup.'
    setup_env = env.merge('PATH_INFO' => setup_path, 'REQUEST_METHOD' => 'GET')
    call_app!(setup_env)
  end
end

#skip_info?Boolean

Determines whether or not user info should be retrieved. This allows some strategies to save a call to an external API service for existing users. You can use it either by setting the :skip_info to true or by setting :skip_info to a Proc that takes a uid and evaluates to true when you would like to skip info.

Examples:


use MyStrategy, :skip_info => lambda{|uid| User.find_by_uid(uid)}

Returns:

  • (Boolean)


417
418
419
420
421
422
# File 'lib/omniauth/strategy.rb', line 417

def skip_info?
  return false unless options.skip_info?
  return true unless options.skip_info.respond_to?(:call)

  options.skip_info.call(uid)
end

#uidObject



382
383
384
# File 'lib/omniauth/strategy.rb', line 382

def uid
  self.class.uid_stack(self).last
end

#user_infoObject



538
539
540
# File 'lib/omniauth/strategy.rb', line 538

def 
  {}
end

#warn_if_using_get_on_request_pathObject



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/omniauth/strategy.rb', line 205

def warn_if_using_get_on_request_path
  return unless on_request_path?
  return unless OmniAuth.config.allowed_request_methods.include?(:get)
  return if OmniAuth.config.silence_get_warning

  log :warn, "  You are using GET as an allowed request method for OmniAuth. This may leave\n  you open to CSRF attacks. As of v2.0.0, OmniAuth by default allows only POST\n  to its own routes. You should review the following resources to guide your\n  mitigation:\n  https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284\n  https://github.com/omniauth/omniauth/issues/960\n  https://nvd.nist.gov/vuln/detail/CVE-2015-9284\n  https://github.com/omniauth/omniauth/pull/809\n\n  You can ignore this warning by setting:\n  OmniAuth.config.silence_get_warning = true\n  WARN\nend\n"