omniauth 2.1.3 → 2.2.0
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 +4 -4
- data/CHANGELOG.md +726 -0
- data/LICENSE.md +1 -1
- data/README.md +141 -29
- data/SECURITY.md +4 -3
- data/lib/omniauth/auth_hash.rb +32 -6
- data/lib/omniauth/authenticity_token_protection.rb +6 -4
- data/lib/omniauth/builder.rb +13 -6
- data/lib/omniauth/failure_endpoint.rb +4 -4
- data/lib/omniauth/form.rb +5 -7
- data/lib/omniauth/key_store.rb +2 -10
- data/lib/omniauth/strategies/developer.rb +3 -5
- data/lib/omniauth/strategy/class_methods.rb +118 -0
- data/lib/omniauth/strategy/fetchers.rb +57 -0
- data/lib/omniauth/strategy/hosts.rb +48 -0
- data/lib/omniauth/strategy/paths.rb +140 -0
- data/lib/omniauth/strategy/test_mode.rb +53 -0
- data/lib/omniauth/strategy.rb +145 -354
- data/lib/omniauth/test/phony_session.rb +1 -1
- data/lib/omniauth/test/strategy_test_case.rb +8 -9
- data/lib/omniauth/version.rb +1 -1
- data/lib/omniauth.rb +38 -26
- data/sig/manifest.yaml +5 -0
- data/sig/omniauth.rbs +638 -0
- metadata +33 -51
- data/.github/FUNDING.yml +0 -2
- data/.github/ISSUE_TEMPLATE.md +0 -20
- data/.github/dependabot.yml +0 -18
- data/.github/workflows/jruby.yml +0 -28
- data/.github/workflows/main.yml +0 -64
- data/.github/workflows/truffle_ruby.yml +0 -24
- data/.gitignore +0 -14
- data/.rspec +0 -2
- data/.rubocop.yml +0 -69
- data/.yardopts +0 -4
- data/Gemfile +0 -23
- data/Rakefile +0 -53
- data/omniauth.gemspec +0 -25
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module OmniAuth
|
|
2
|
+
module Strategy
|
|
3
|
+
# Builds the auth hash from the uid, info, credentials, and extra
|
|
4
|
+
# blocks a strategy declares with the class-level fetcher DSL.
|
|
5
|
+
module Fetchers
|
|
6
|
+
def uid
|
|
7
|
+
self.class.uid_stack(self).last
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def info
|
|
11
|
+
merge_stack(self.class.info_stack(self))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def credentials
|
|
15
|
+
merge_stack(self.class.credentials_stack(self))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extra
|
|
19
|
+
merge_stack(self.class.extra_stack(self))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def auth_hash
|
|
23
|
+
credentials_data = credentials
|
|
24
|
+
extra_data = extra
|
|
25
|
+
AuthHash.new(:provider => name, :uid => uid).tap do |auth|
|
|
26
|
+
auth.info = info unless skip_info?
|
|
27
|
+
auth.credentials = credentials_data if credentials_data
|
|
28
|
+
auth.extra = extra_data if extra_data
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Determines whether or not user info should be retrieved. This
|
|
33
|
+
# allows some strategies to save a call to an external API service
|
|
34
|
+
# for existing users. You can use it either by setting the `:skip_info`
|
|
35
|
+
# to true or by setting `:skip_info` to a Proc that takes a uid and
|
|
36
|
+
# evaluates to true when you would like to skip info.
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
#
|
|
40
|
+
# use MyStrategy, :skip_info => lambda{|uid| User.find_by_uid(uid)}
|
|
41
|
+
def skip_info?
|
|
42
|
+
return false unless options.skip_info?
|
|
43
|
+
return true unless options.skip_info.respond_to?(:call)
|
|
44
|
+
|
|
45
|
+
options.skip_info.call(uid)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected
|
|
49
|
+
|
|
50
|
+
def merge_stack(stack)
|
|
51
|
+
merged = {} #: Hash[untyped, untyped]
|
|
52
|
+
stack.each { |hash| merged.merge!(hash) }
|
|
53
|
+
merged
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module OmniAuth
|
|
2
|
+
module Strategy
|
|
3
|
+
# Guards against forged hosts. Unless a full_host is set, callback URLs
|
|
4
|
+
# use the request's host, which comes from the Host or X-Forwarded-Host
|
|
5
|
+
# header, so a forged one would send the provider a redirect_uri on
|
|
6
|
+
# another host.
|
|
7
|
+
module Hosts
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
# Whether callback URLs may use this request's host. They always may
|
|
11
|
+
# when a full_host is set, since then the request's host isn't used.
|
|
12
|
+
# Otherwise, if OmniAuth.config.allowed_hosts is set, the host must
|
|
13
|
+
# match one of its entries: a string matches the whole host, ignoring
|
|
14
|
+
# case, and a regexp must match the whole host.
|
|
15
|
+
def allowed_host?
|
|
16
|
+
return true if full_host_set?
|
|
17
|
+
|
|
18
|
+
allowed_hosts = OmniAuth.config.allowed_hosts
|
|
19
|
+
return true unless allowed_hosts
|
|
20
|
+
|
|
21
|
+
host = request.host
|
|
22
|
+
allowed_hosts.any? do |allowed|
|
|
23
|
+
allowed.instance_of?(Regexp) ? /\A(?:#{allowed})\z/ === host : allowed.casecmp?(host)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def full_host_set?
|
|
28
|
+
!(options[:full_host] || OmniAuth.config.full_host).nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def warn_if_using_request_host
|
|
32
|
+
return if full_host_set? || OmniAuth.config.allowed_hosts
|
|
33
|
+
|
|
34
|
+
log :warn, <<-WARN
|
|
35
|
+
OmniAuth is building callback URLs from the request's host, which comes
|
|
36
|
+
from the Host or X-Forwarded-Host header and can be forged unless your
|
|
37
|
+
app or a proxy in front of it checks it. To fix this, set either:
|
|
38
|
+
OmniAuth.config.full_host = 'https://example.com'
|
|
39
|
+
OmniAuth.config.allowed_hosts = ['example.com']
|
|
40
|
+
WARN
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def invalid_host!
|
|
44
|
+
fail!(:invalid_host, Error.new('The request host is not in OmniAuth.config.allowed_hosts'))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module OmniAuth
|
|
2
|
+
module Strategy
|
|
3
|
+
# Works out the request, callback, and setup paths for a strategy and
|
|
4
|
+
# whether the current request is on one of them, plus the full URLs
|
|
5
|
+
# built from them.
|
|
6
|
+
module Paths
|
|
7
|
+
# Returns true if the environment recognizes either the
|
|
8
|
+
# request or callback path.
|
|
9
|
+
def on_auth_path?
|
|
10
|
+
on_request_path? || on_callback_path?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on_request_path?
|
|
14
|
+
if options[:request_path].respond_to?(:call)
|
|
15
|
+
options.fetch(:request_path).call(env)
|
|
16
|
+
else
|
|
17
|
+
on_path?(request_path)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_callback_path?
|
|
22
|
+
on_path?(callback_path) || current_path.sub(FORMAT_EXTENSION_REGEX, EMPTY_STRING).casecmp(callback_path).zero?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_path?(path)
|
|
26
|
+
current_path.casecmp(path).zero?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def path_prefix
|
|
30
|
+
options[:path_prefix] || OmniAuth.config.path_prefix
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def custom_path(kind)
|
|
34
|
+
if options[kind].respond_to?(:call)
|
|
35
|
+
result = options.fetch(kind).call(env)
|
|
36
|
+
return nil unless result.is_a?(String)
|
|
37
|
+
|
|
38
|
+
result
|
|
39
|
+
else
|
|
40
|
+
options[kind]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def request_path
|
|
45
|
+
@request_path ||=
|
|
46
|
+
if options[:request_path].is_a?(String)
|
|
47
|
+
options.fetch(:request_path)
|
|
48
|
+
else
|
|
49
|
+
"#{script_name}#{path_prefix}/#{name}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def callback_path
|
|
54
|
+
@callback_path ||= callback_path_option || custom_path(:request_path) || "#{script_name}#{path_prefix}/#{name}/callback"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def setup_path
|
|
58
|
+
options[:setup_path] || "#{path_prefix}/#{name}/setup"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
CURRENT_PATH_REGEX = %r{/$}.freeze
|
|
62
|
+
FORMAT_EXTENSION_REGEX = /\.\w+\z/.freeze
|
|
63
|
+
EMPTY_STRING = ''.freeze
|
|
64
|
+
# Repeated slashes are collapsed, as Rails routing does, so a proxy
|
|
65
|
+
# that sends //auth/:provider still reaches the strategy.
|
|
66
|
+
def current_path
|
|
67
|
+
@current_path ||= request.path.downcase.squeeze('/').sub(CURRENT_PATH_REGEX, EMPTY_STRING)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def query_string
|
|
71
|
+
request.query_string.empty? ? '' : "?#{request.query_string}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The scheme and host for URLs such as callback_url: the strategy's
|
|
75
|
+
# :full_host option, else OmniAuth.config.full_host, else the
|
|
76
|
+
# request's. Either setting can be a string or a proc taking the env.
|
|
77
|
+
def full_host
|
|
78
|
+
host = options[:full_host] || OmniAuth.config.full_host
|
|
79
|
+
case host
|
|
80
|
+
when String then host
|
|
81
|
+
when Proc then host.call(env)
|
|
82
|
+
else full_host_from_request
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def callback_url
|
|
87
|
+
full_host + callback_path + query_string
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def script_name
|
|
91
|
+
current_env = @env
|
|
92
|
+
return '' if current_env.nil?
|
|
93
|
+
|
|
94
|
+
current_env['SCRIPT_NAME'] || ''
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
protected
|
|
98
|
+
|
|
99
|
+
# The :callback_path option: a string, or a lambda that returns true
|
|
100
|
+
# when the current path is the callback path.
|
|
101
|
+
def callback_path_option
|
|
102
|
+
option = options[:callback_path]
|
|
103
|
+
return option if option.is_a?(String)
|
|
104
|
+
|
|
105
|
+
current_path if option.respond_to?(:call) && option.call(env)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The scheme, host, and port of the current request, or an empty
|
|
109
|
+
# string if the request URL isn't absolute.
|
|
110
|
+
def full_host_from_request
|
|
111
|
+
# in Rack 1.3.x, request.url explodes if scheme is nil
|
|
112
|
+
return '' unless request.scheme && URI.parse(request.url).absolute?
|
|
113
|
+
|
|
114
|
+
uri = URI.parse(request.url.sub(/\?.*/, ''))
|
|
115
|
+
uri.path = ''
|
|
116
|
+
# sometimes the url is actually showing http inside rails because the
|
|
117
|
+
# other layers (like nginx) have handled the ssl termination.
|
|
118
|
+
uri.scheme = 'https' if ssl?
|
|
119
|
+
String(uri)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Rack env values that mean the request came in over HTTPS.
|
|
123
|
+
SSL_ENV_VALUES = {
|
|
124
|
+
'HTTPS' => 'on',
|
|
125
|
+
'HTTP_X_FORWARDED_SSL' => 'on',
|
|
126
|
+
'HTTP_X_FORWARDED_SCHEME' => 'https',
|
|
127
|
+
'rack.url_scheme' => 'https'
|
|
128
|
+
}.freeze
|
|
129
|
+
|
|
130
|
+
# Reads request.env rather than env, so full_host and callback_url also
|
|
131
|
+
# work outside a Rack call, as when a strategy's tests stub #request.
|
|
132
|
+
def ssl?
|
|
133
|
+
request_env = request.env
|
|
134
|
+
# A proxy chain can list several protocols; the first is the client's.
|
|
135
|
+
forwarded_proto = request_env['HTTP_X_FORWARDED_PROTO'].to_s.split(',').first
|
|
136
|
+
forwarded_proto.eql?('https') || SSL_ENV_VALUES.any? { |key, value| request_env[key].eql?(value) }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module OmniAuth
|
|
2
|
+
module Strategy
|
|
3
|
+
# Runs the request and callback phases against OmniAuth.config.mock_auth
|
|
4
|
+
# instead of the provider when OmniAuth.config.test_mode is enabled.
|
|
5
|
+
module TestMode
|
|
6
|
+
# This is called in lieu of the normal request process
|
|
7
|
+
# in the event that OmniAuth has been configured to be
|
|
8
|
+
# in test mode.
|
|
9
|
+
def mock_call!(*)
|
|
10
|
+
if request_call?
|
|
11
|
+
mock_request_call
|
|
12
|
+
elsif on_callback_path?
|
|
13
|
+
mock_callback_call
|
|
14
|
+
else
|
|
15
|
+
call_app!
|
|
16
|
+
end
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
handle_phase_error(e)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def mock_request_call
|
|
22
|
+
setup_phase
|
|
23
|
+
|
|
24
|
+
run_hook(:request_validation_phase)
|
|
25
|
+
|
|
26
|
+
session['omniauth.params'] = omniauth_params
|
|
27
|
+
|
|
28
|
+
run_hook(:before_request_phase)
|
|
29
|
+
|
|
30
|
+
store_origin if options.origin_param
|
|
31
|
+
|
|
32
|
+
result = redirect(callback_url)
|
|
33
|
+
|
|
34
|
+
run_hook(:after_request_phase)
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def mock_callback_call
|
|
39
|
+
setup_phase
|
|
40
|
+
|
|
41
|
+
origin = session.delete('omniauth.origin')
|
|
42
|
+
env['omniauth.origin'] ||= origin
|
|
43
|
+
env['omniauth.origin'] = nil if env.fetch('omniauth.origin').eql?('')
|
|
44
|
+
env['omniauth.params'] = session.delete('omniauth.params') || {}
|
|
45
|
+
|
|
46
|
+
mocked_auth = OmniAuth.mock_auth_for(name)
|
|
47
|
+
env['omniauth.auth'] = mocked_auth unless mocked_auth.instance_of?(Symbol)
|
|
48
|
+
run_hook(:before_callback_phase)
|
|
49
|
+
mocked_auth.instance_of?(Symbol) ? fail!(mocked_auth) : call_app!
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|