omniauth-oauth2 1.9.0 → 1.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a61f10acb3f0765bd7f4e3eea6b6fa3cf4a0a882c5e2a09e37d17417050e9ff2
4
- data.tar.gz: c558846dc4e8313e580b5ac32705da006227287ef5a0e20df63cf30df4bb8150
3
+ metadata.gz: 56ea3a5f282231d2b4dbfc7393ab753608e47be23a5759dbe2b0bb5865a04b30
4
+ data.tar.gz: e3ed45d971bafe000603a58ef6221be463d08b6649065c6a6bb1de908d87fb64
5
5
  SHA512:
6
- metadata.gz: e6fe7535a2b86388f19ff959bed28ec44582e6994bca1442e775c7cdbcb61158a6133a72d2fa86a60252432f05eb12080aa4fb78f01d5de1ae2137f97f07d25a
7
- data.tar.gz: e2aadabef58d24f214bd83bcdb15426c1a93ac3e8009823285c304467168cfbe9de3ad3a51c6756bd382dadc129d57ab0fef00d0a218d1c5c1d27b19ce958d8d
6
+ metadata.gz: 7b326c3f87b4fdee92bcf1bd41c36378f91a3a9a6678461c16f9dcec95f995eaebd40808e1d9262d485ced7cfab49007476becdc3a94e49cb1b1411f884f6723
7
+ data.tar.gz: b847b133de1f64cf23bae72b92e5453c0fc9846248bdd8eee71b9197632cdbdbaaaab36bc6f7792a257d20132205206619227dfc4accdc0e869bf8a4edb16c07
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2014 Michael Bleigh, Erik Michaels-Ober and Intridea, Inc.
1
+ Copyright (c) 2011-2026 Michael Bleigh, Erik Berlin and Intridea, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # OmniAuth OAuth2
2
2
 
3
3
  [![Gem Version](http://img.shields.io/gem/v/omniauth-oauth2.svg)][gem]
4
- [![Code Climate](http://img.shields.io/codeclimate/maintainability/intridea/omniauth-oauth2.svg)][codeclimate]
5
- [![Coverage Status](http://img.shields.io/coveralls/intridea/omniauth-oauth2.svg)][coveralls]
6
- [![Security](https://hakiri.io/github/omniauth/omniauth-oauth2/master.svg)](https://hakiri.io/github/omniauth/omniauth-oauth2/master)
4
+ [![Ruby](https://github.com/omniauth/omniauth-oauth2/actions/workflows/main.yml/badge.svg)][githubactions]
5
+ [![Maintainability](https://qlty.sh/gh/omniauth/projects/omniauth-oauth2/maintainability.svg)][qlty]
6
+ [![Coverage Status](http://img.shields.io/coveralls/omniauth/omniauth-oauth2.svg)][coveralls]
7
7
 
8
8
  [gem]: https://rubygems.org/gems/omniauth-oauth2
9
- [codeclimate]: https://codeclimate.com/github/intridea/omniauth-oauth2
10
- [coveralls]: https://coveralls.io/r/intridea/omniauth-oauth2
9
+ [githubactions]: https://github.com/omniauth/omniauth-oauth2/actions/workflows/main.yml
10
+ [qlty]: https://qlty.sh/gh/omniauth/projects/omniauth-oauth2
11
+ [coveralls]: https://coveralls.io/r/omniauth/omniauth-oauth2
11
12
 
12
13
  This gem contains a generic OAuth2 strategy for OmniAuth. It is meant to serve
13
14
  as a building block strategy for other strategies and not to be used
@@ -65,12 +66,7 @@ end
65
66
 
66
67
  That's pretty much it!
67
68
 
68
- ## OmniAuth-OAuth2 for Enterprise
69
-
70
- Available as part of the Tidelift Subscription.
71
-
72
- The maintainers of OmniAuth-OAuth2 and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. [Learn more.](https://tidelift.com/subscription/pkg/rubygems-omniauth-oauth2?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise)
73
-
74
69
  ## Supported Ruby Versions
75
70
 
76
- OmniAuth is tested under 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, truffleruby, and JRuby.
71
+ OmniAuth OAuth2 is tested on Ruby 2.5 through 4.0, JRuby, and TruffleRuby,
72
+ on Linux and macOS.
data/SECURITY.md CHANGED
@@ -2,16 +2,10 @@
2
2
 
3
3
  ## Supported Versions
4
4
 
5
- Use this section to tell people about which versions of your project are
6
- currently being supported with security updates.
7
-
8
- | Version | Supported |
9
- | ------- | ------------------ |
10
- | 1.7.x | :white_check_mark: |
11
- | <= 1.6.x | :x: |
5
+ Only the most recent release of omniauth-oauth2 receives security updates.
12
6
 
13
7
  ## Security contact information
14
8
 
15
- To report a security vulnerability, please use the
16
- [Tidelift security contact](https://tidelift.com/security).
17
- Tidelift will coordinate the fix and disclosure.
9
+ To report a security vulnerability, please use GitHub's
10
+ [private vulnerability reporting](https://github.com/omniauth/omniauth-oauth2/security/advisories/new)
11
+ rather than opening a public issue.
@@ -15,9 +15,14 @@ module OmniAuth
15
15
  include OmniAuth::Strategy
16
16
 
17
17
  def self.inherited(subclass)
18
- OmniAuth::Strategy.included(subclass)
18
+ Strategy.included(subclass)
19
19
  end
20
20
 
21
+ # How many sign-ins can be in progress at once, for example in several
22
+ # tabs. Starting another drops the oldest. Each one adds about 300
23
+ # bytes to the session, which may be a 4 KB cookie.
24
+ MAX_STORED_SIGN_INS = 5
25
+
21
26
  args %i[client_id client_secret]
22
27
 
23
28
  option :client_id, nil
@@ -30,7 +35,6 @@ module OmniAuth
30
35
  option :auth_token_params, {}
31
36
  option :provider_ignores_state, false
32
37
  option :pkce, false
33
- option :pkce_verifier, nil
34
38
  option :pkce_options, {
35
39
  :code_challenge => proc { |verifier|
36
40
  Base64.urlsafe_encode64(
@@ -48,48 +52,67 @@ module OmniAuth
48
52
  end
49
53
 
50
54
  credentials do
51
- hash = {"token" => access_token.token}
52
- hash["refresh_token"] = access_token.refresh_token if access_token.expires? && access_token.refresh_token
55
+ hash = {"token" => access_token.token} #: Hash[String, String | Integer | bool | nil]
56
+ hash["refresh_token"] = access_token.refresh_token if access_token.refresh_token
53
57
  hash["expires_at"] = access_token.expires_at if access_token.expires?
54
58
  hash["expires"] = access_token.expires?
55
59
  hash
56
60
  end
57
61
 
58
62
  def request_phase
63
+ # The provider can't do anything with an authorize request that has no
64
+ # client ID, so fail here, where the cause is clear.
65
+ return fail!(:missing_client_id, Error.new("client_id is missing")) if client.id.to_s.empty?
66
+
59
67
  redirect client.auth_code.authorize_url({:redirect_uri => callback_url}.merge(authorize_params))
60
68
  end
61
69
 
62
- def authorize_params # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
70
+ def authorize_params
63
71
  options.authorize_params[:state] = SecureRandom.hex(24)
64
72
 
65
73
  if OmniAuth.config.test_mode
66
- @env ||= {}
67
- @env["rack.session"] ||= {}
74
+ test_env = (@env ||= {}) #: Rack::env
75
+ test_env["rack.session"] ||= {}
68
76
  end
69
77
 
70
78
  params = options.authorize_params
71
79
  .merge(options_for("authorize"))
72
80
  .merge(pkce_authorize_params)
73
81
 
74
- session["omniauth.pkce.verifier"] = options.pkce_verifier if options.pkce
75
- session["omniauth.state"] = params[:state]
82
+ state = params.fetch(:state)
83
+ # Reused in the token request, which must send the same redirect_uri.
84
+ # oauth2 gives a client_options redirect_uri precedence in the
85
+ # authorize URL, so it does here too.
86
+ redirect_uri = client.options[:redirect_uri] || params[:redirect_uri] || callback_url
87
+ session["omniauth.state"] = state
88
+ session["omniauth.redirect_uri"] = redirect_uri
89
+ store_sign_in(state.to_s, redirect_uri)
90
+ # Lets OmniAuth give the callback this sign-in's origin and params.
91
+ remember_sign_in(state.to_s)
76
92
 
77
93
  params
78
94
  end
79
95
 
96
+ # The state the provider sends back, which OmniAuth uses to find the
97
+ # origin and params that remember_sign_in stored.
98
+ def callback_sign_in_key
99
+ request.params["state"]
100
+ end
101
+
80
102
  def token_params
81
103
  options.token_params.merge(options_for("token")).merge(pkce_token_params)
82
104
  end
83
105
 
84
- def callback_phase # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
106
+ def callback_phase
107
+ # Take what the request phase stored, even if the callback fails.
108
+ stored_sign_in
85
109
  error = request.params["error_reason"] || request.params["error"]
86
- if !options.provider_ignores_state && (request.params["state"].to_s.empty? || !secure_compare(request.params["state"], session.delete("omniauth.state")))
110
+ if csrf_detected?
87
111
  fail!(:csrf_detected, CallbackError.new(:csrf_detected, "CSRF detected"))
88
112
  elsif error
89
113
  fail!(error, CallbackError.new(request.params["error"], request.params["error_description"] || request.params["error_reason"], request.params["error_uri"]))
90
114
  else
91
- self.access_token = build_access_token
92
- self.access_token = access_token.refresh! if access_token.expired?
115
+ self.access_token = fetch_access_token
93
116
  super
94
117
  end
95
118
  rescue ::OAuth2::Error, CallbackError => e
@@ -102,57 +125,111 @@ module OmniAuth
102
125
 
103
126
  protected
104
127
 
128
+ # Whether the callback's state is missing or doesn't match the one the
129
+ # request phase stored, unless the provider ignores state.
130
+ def csrf_detected?
131
+ return false if options.provider_ignores_state
132
+
133
+ state = request.params["state"].to_s
134
+ state.empty? || !secure_compare(state, stored_sign_in.fetch("state").to_s)
135
+ end
136
+
137
+ # Stores the state, redirect URI, and PKCE verifier of a sign-in by its
138
+ # state, so several can be in progress at once.
139
+ def store_sign_in(state, redirect_uri)
140
+ return if state.empty?
141
+
142
+ sign_in = {"redirect_uri" => redirect_uri}
143
+ sign_in["pkce_verifier"] = session.fetch("omniauth.pkce.verifier") if options.pkce
144
+ sign_ins = stored_sign_ins
145
+ sign_ins.delete(state)
146
+ sign_ins[state] = sign_in
147
+ session["omniauth.oauth2"] = sign_ins.to_a.last(MAX_STORED_SIGN_INS).to_h
148
+ end
149
+
150
+ # What the request phase stored for this callback's state, removed from
151
+ # the session. Without a stored sign-in for the state, it comes from the
152
+ # omniauth.state and omniauth.redirect_uri keys, which hold the latest
153
+ # sign-in and which provider gems and tests set directly.
154
+ def stored_sign_in
155
+ @stored_sign_in ||= begin
156
+ fallback = {"state" => session.delete("omniauth.state"), "redirect_uri" => session.delete("omniauth.redirect_uri")}
157
+ state = request.params["state"]
158
+ sign_ins = stored_sign_ins
159
+ key = sign_ins.keys.detect { |stored| secure_compare(stored, state) } if state.instance_of?(String)
160
+ sign_in = sign_ins.delete(key)
161
+ if sign_in
162
+ session["omniauth.oauth2"] = sign_ins
163
+ sign_in.merge("state" => key)
164
+ else
165
+ fallback
166
+ end
167
+ end
168
+ end
169
+
170
+ # A copy of the stored sign-ins, which are changed by assigning the
171
+ # session key, not in place.
172
+ def stored_sign_ins
173
+ sign_ins = session["omniauth.oauth2"]
174
+ sign_ins.is_a?(Hash) ? sign_ins.dup : {}
175
+ end
176
+
105
177
  def pkce_authorize_params
106
178
  return {} unless options.pkce
107
179
 
108
- options.pkce_verifier = SecureRandom.hex(64)
180
+ # A fresh verifier for every authorization request, as RFC 7636
181
+ # requires: https://tools.ietf.org/html/rfc7636#section-4.1
182
+ verifier = SecureRandom.hex(64)
183
+ session["omniauth.pkce.verifier"] = verifier
109
184
 
110
185
  # NOTE: see https://tools.ietf.org/html/rfc7636#appendix-A
111
186
  {
112
- :code_challenge => options.pkce_options[:code_challenge]
113
- .call(options.pkce_verifier),
114
- :code_challenge_method => options.pkce_options[:code_challenge_method],
187
+ :code_challenge => options.pkce_options.fetch(:code_challenge).call(verifier),
188
+ :code_challenge_method => options.pkce_options.fetch(:code_challenge_method),
115
189
  }
116
190
  end
117
191
 
118
192
  def pkce_token_params
119
193
  return {} unless options.pkce
120
194
 
121
- {:code_verifier => session.delete("omniauth.pkce.verifier")}
195
+ verifier = session.delete("omniauth.pkce.verifier")
196
+ {:code_verifier => stored_sign_in.fetch("pkce_verifier", verifier)}
197
+ end
198
+
199
+ # The access token from the callback's code, refreshed if it's already
200
+ # expired.
201
+ def fetch_access_token
202
+ token = build_access_token || raise(CallbackError.new(:invalid_credentials, "No access token"))
203
+ token.expired? ? token.refresh! : token
122
204
  end
123
205
 
124
206
  def build_access_token
125
207
  verifier = request.params["code"]
126
- client.auth_code.get_token(verifier, {:redirect_uri => callback_url}.merge(token_params.to_hash(:symbolize_keys => true)), deep_symbolize(options.auth_token_params))
208
+ redirect_uri = stored_sign_in.fetch("redirect_uri") || callback_url
209
+ client.auth_code.get_token(verifier, {:redirect_uri => redirect_uri}.merge(token_params.to_hash(:symbolize_keys => true)), deep_symbolize(options.auth_token_params))
127
210
  end
128
211
 
129
212
  def deep_symbolize(options)
130
- options.each_with_object({}) do |(key, value), hash|
131
- hash[key.to_sym] = value.is_a?(Hash) ? deep_symbolize(value) : value
132
- end
213
+ hash = {} #: Hash[Symbol, untyped]
214
+ options.each { |key, value| hash[key.to_sym] = value.is_a?(Hash) ? deep_symbolize(value) : value }
215
+ hash
133
216
  end
134
217
 
135
218
  def options_for(option)
136
- hash = {}
137
- options.send(:"#{option}_options").select { |key| options[key] }.each do |key|
138
- hash[key.to_sym] = if options[key].respond_to?(:call)
139
- options[key].call(env)
219
+ hash = {} #: Hash[Symbol, untyped]
220
+ options.public_send(:"#{option}_options").select { |key| options[key] }.each do |key|
221
+ hash[key.to_sym] = if options.fetch(key).respond_to?(:call)
222
+ options.fetch(key).call(env)
140
223
  else
141
- options[key]
224
+ options.fetch(key)
142
225
  end
143
226
  end
144
227
  hash
145
228
  end
146
229
 
147
- # constant-time comparison algorithm to prevent timing attacks
148
- def secure_compare(string_a, string_b)
149
- return false unless string_a.bytesize == string_b.bytesize
150
-
151
- l = string_a.unpack "C#{string_a.bytesize}"
152
-
153
- res = 0
154
- string_b.each_byte { |byte| res |= byte ^ l.shift }
155
- res.zero?
230
+ # constant-time comparison algorithm to prevent timing attacks
231
+ def secure_compare(string_a, string_b)
232
+ Rack::Utils.secure_compare(string_a, string_b)
156
233
  end
157
234
 
158
235
  # An error that is indicated in the OAuth 2.0 callback.
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module OAuth2
3
- VERSION = "1.9.0".freeze
3
+ VERSION = "1.10.0".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,127 @@
1
+ module OmniAuth
2
+ module OAuth2
3
+ VERSION: String
4
+ end
5
+
6
+ module Strategies
7
+ # An abstract OAuth 2.0 strategy. Provider strategies subclass it and
8
+ # set client_options (site, authorize_url, and token_url) for their
9
+ # provider.
10
+ class OAuth2
11
+ include Strategy
12
+ extend Strategy::ClassMethods
13
+
14
+ # The options this strategy adds to OmniAuth's. Strategy::Options
15
+ # answers them through Hashie::Mash's method_missing.
16
+ interface _Options
17
+ def client_id: () -> String?
18
+ def client_secret: () -> String?
19
+ def client_options: () -> Strategy::Options
20
+ def authorize_params: () -> Strategy::Options
21
+ # The options copied into the authorize params, when set
22
+ def authorize_options: () -> Array[Symbol | String]
23
+ def token_params: () -> Strategy::Options
24
+ # The options copied into the token params, when set
25
+ def token_options: () -> Array[Symbol | String]
26
+ def auth_token_params: () -> Strategy::Options
27
+ def provider_ignores_state: () -> bool
28
+ def pkce: () -> bool
29
+ # :code_challenge, a callable that takes the verifier, and
30
+ # :code_challenge_method
31
+ def pkce_options: () -> Strategy::Options
32
+ end
33
+
34
+ # Gives each subclass the class-level strategy DSL.
35
+ def self.inherited: (singleton(OAuth2) subclass) -> void
36
+
37
+ # The fetcher blocks run with the strategy instance as self.
38
+ def self.uid: () { () [self: instance] -> untyped } -> Proc
39
+ | () -> Proc?
40
+
41
+ def self.info: () { () [self: instance] -> Hash[untyped, untyped]? } -> Proc
42
+ | () -> Proc?
43
+
44
+ def self.extra: () { () [self: instance] -> Hash[untyped, untyped]? } -> Proc
45
+ | () -> Proc?
46
+
47
+ def self.credentials: () { () [self: instance] -> Hash[untyped, untyped]? } -> Proc
48
+ | () -> Proc?
49
+
50
+ def options: () -> (Strategy::Options & _Options)
51
+
52
+ # Set by the callback phase, which runs before the fetcher blocks that
53
+ # read it.
54
+ attr_accessor access_token: ::OAuth2::AccessToken
55
+
56
+ # How many sign-ins can be in progress at once. Starting another drops
57
+ # the oldest.
58
+ MAX_STORED_SIGN_INS: Integer
59
+
60
+ # What the request phase stored for a sign-in: "state",
61
+ # "redirect_uri", and, with PKCE, "pkce_verifier"
62
+ type sign_in = Hash[String, String?]
63
+
64
+ # The sign-in the callback phase took from the session.
65
+ @stored_sign_in: sign_in?
66
+
67
+ def client: () -> ::OAuth2::Client
68
+
69
+ def request_phase: () -> rack_response
70
+
71
+ def authorize_params: () -> Strategy::Options
72
+
73
+ # The state the provider sends back to the callback.
74
+ def callback_sign_in_key: () -> String?
75
+
76
+ def token_params: () -> Strategy::Options
77
+
78
+ def callback_phase: () -> rack_response
79
+
80
+ # These are protected in Ruby, which RBS has no way to say.
81
+ private
82
+
83
+ # Whether the callback's state is missing or doesn't match the one the
84
+ # request phase stored, unless the provider ignores state.
85
+ def csrf_detected?: () -> bool
86
+
87
+ # Stores the redirect URI and PKCE verifier of a sign-in by its state.
88
+ def store_sign_in: (String state, String redirect_uri) -> void
89
+
90
+ # What the request phase stored for the callback's state, removed from
91
+ # the session, or else the latest sign-in's state and redirect URI.
92
+ def stored_sign_in: () -> sign_in
93
+
94
+ # The sign-ins in progress, by state, oldest first.
95
+ def stored_sign_ins: () -> Hash[String, sign_in]
96
+
97
+ def pkce_authorize_params: () -> Hash[Symbol, untyped]
98
+
99
+ def pkce_token_params: () -> Hash[Symbol, String?]
100
+
101
+ def fetch_access_token: () -> ::OAuth2::AccessToken
102
+
103
+ # Nil when oauth2's raise_errors is off and the token response has no
104
+ # token.
105
+ def build_access_token: () -> ::OAuth2::AccessToken?
106
+
107
+ def deep_symbolize: (Hash[untyped, untyped] options) -> Hash[Symbol, untyped]
108
+
109
+ def options_for: (String option) -> Hash[Symbol, untyped]
110
+
111
+ # Compares two strings in constant time, to prevent timing attacks.
112
+ def secure_compare: (String string_a, String string_b) -> bool
113
+
114
+ # An error that is indicated in the OAuth 2.0 callback, such as a
115
+ # redirect_uri_mismatch.
116
+ class CallbackError < StandardError
117
+ attr_accessor error: String | Symbol
118
+ attr_accessor error_reason: String?
119
+ attr_accessor error_uri: String?
120
+
121
+ def initialize: (String | Symbol error, ?String? error_reason, ?String? error_uri) -> void
122
+
123
+ def message: () -> String
124
+ end
125
+ end
126
+ end
127
+ end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-oauth2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
+ - Erik Berlin
7
8
  - Michael Bleigh
8
- - Erik Michaels-Ober
9
9
  - Tom Milewski
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2025-12-11 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: oauth2
@@ -38,59 +37,42 @@ dependencies:
38
37
  requirements:
39
38
  - - "~>"
40
39
  - !ruby/object:Gem::Version
41
- version: '2.0'
40
+ version: '2.3'
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.1
42
44
  type: :runtime
43
45
  prerelease: false
44
46
  version_requirements: !ruby/object:Gem::Requirement
45
47
  requirements:
46
48
  - - "~>"
47
49
  - !ruby/object:Gem::Version
48
- version: '2.0'
49
- - !ruby/object:Gem::Dependency
50
- name: bundler
51
- requirement: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: '2.0'
56
- type: :development
57
- prerelease: false
58
- version_requirements: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - "~>"
50
+ version: '2.3'
51
+ - - ">="
61
52
  - !ruby/object:Gem::Version
62
- version: '2.0'
53
+ version: 2.3.1
63
54
  description: An abstract OAuth2 strategy for OmniAuth.
64
55
  email:
65
- - michael@intridea.com
66
56
  - sferik@gmail.com
57
+ - michael@intridea.com
67
58
  - tmilewski@gmail.com
68
59
  executables: []
69
60
  extensions: []
70
61
  extra_rdoc_files: []
71
62
  files:
72
- - ".github/FUNDING.yml"
73
- - ".github/workflows/main.yml"
74
- - ".gitignore"
75
- - ".rspec"
76
- - ".rubocop.yml"
77
- - CHANGELOG.md
78
- - Gemfile
79
63
  - LICENSE.md
80
64
  - README.md
81
- - Rakefile
82
65
  - SECURITY.md
83
66
  - lib/omniauth-oauth2.rb
84
67
  - lib/omniauth-oauth2/version.rb
85
68
  - lib/omniauth/strategies/oauth2.rb
86
- - omniauth-oauth2.gemspec
87
- - spec/helper.rb
88
- - spec/omniauth/strategies/oauth2_spec.rb
69
+ - sig/omniauth-oauth2.rbs
89
70
  homepage: https://github.com/omniauth/omniauth-oauth2
90
71
  licenses:
91
72
  - MIT
92
- metadata: {}
93
- post_install_message:
73
+ metadata:
74
+ changelog_uri: https://github.com/omniauth/omniauth-oauth2/blob/master/CHANGELOG.md
75
+ rubygems_mfa_required: 'true'
94
76
  rdoc_options: []
95
77
  require_paths:
96
78
  - lib
@@ -98,17 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
80
  requirements:
99
81
  - - ">="
100
82
  - !ruby/object:Gem::Version
101
- version: '0'
83
+ version: '2.5'
102
84
  required_rubygems_version: !ruby/object:Gem::Requirement
103
85
  requirements:
104
86
  - - ">="
105
87
  - !ruby/object:Gem::Version
106
88
  version: '0'
107
89
  requirements: []
108
- rubygems_version: 3.5.11
109
- signing_key:
90
+ rubygems_version: 4.0.20
110
91
  specification_version: 4
111
92
  summary: An abstract OAuth2 strategy for OmniAuth.
112
- test_files:
113
- - spec/helper.rb
114
- - spec/omniauth/strategies/oauth2_spec.rb
93
+ test_files: []
data/.github/FUNDING.yml DELETED
@@ -1,2 +0,0 @@
1
- github: bobbymcwho
2
- tidelift: rubygems/omniauth-oauth2
@@ -1,47 +0,0 @@
1
- name: Ruby
2
-
3
- on:
4
- push:
5
- branches: [ master ]
6
- pull_request:
7
- branches: [ master ]
8
-
9
- jobs:
10
- test:
11
- runs-on: ${{ matrix.os }}
12
- timeout-minutes: 30
13
- strategy:
14
- fail-fast: false
15
- matrix:
16
- os: [ubuntu-latest, macos-latest]
17
- ruby: [2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head, debug, truffleruby, truffleruby-head, jruby, jruby-head]
18
- steps:
19
- - uses: actions/checkout@v3
20
- - name: Set up Ruby
21
- uses: ruby/setup-ruby@v1
22
- with:
23
- ruby-version: ${{ matrix.ruby }}
24
- bundler-cache: true
25
- - name: Set JRUBY_OPTS environment variable
26
- run: echo "JRUBY_OPTS=--debug" >> "$GITHUB_ENV"
27
- if: ${{ startsWith(matrix.ruby, 'jruby') }}
28
- - name: Run tests
29
- run: bundle exec rake
30
- - uses: actions/upload-artifact@v3
31
- if: ${{ matrix.os == 'ubuntu-latest' && matrix.ruby == '3.0' }}
32
- with:
33
- name: coverage
34
- path: coverage/
35
- retention-days: 1
36
-
37
- coveralls:
38
- needs: test
39
- runs-on: ubuntu-latest
40
- timeout-minutes: 30
41
- steps:
42
- - uses: actions/download-artifact@v3
43
- with:
44
- name: coverage
45
- path: coverage/
46
- - name: Coveralls GitHub Action
47
- uses: coverallsapp/github-action@v2
data/.gitignore DELETED
@@ -1,18 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- *.swp
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --colour
2
- --format=progress
data/.rubocop.yml DELETED
@@ -1,80 +0,0 @@
1
- AllCops:
2
- NewCops: enable
3
-
4
- Gemspec/RequiredRubyVersion:
5
- Enabled: false
6
-
7
- Layout/AccessModifierIndentation:
8
- EnforcedStyle: outdent
9
-
10
- Layout/LineLength:
11
- AllowURI: true
12
- Enabled: false
13
-
14
- Layout/SpaceInsideHashLiteralBraces:
15
- EnforcedStyle: no_space
16
-
17
- Lint/MissingSuper:
18
- Enabled: false
19
-
20
- Metrics/AbcSize:
21
- Max: 18
22
-
23
- Metrics/BlockLength:
24
- Exclude:
25
- - spec/omniauth/strategies/oauth2_spec.rb
26
-
27
- Metrics/BlockNesting:
28
- Max: 2
29
-
30
- Metrics/ClassLength:
31
- Max: 110
32
-
33
- Metrics/MethodLength:
34
- CountComments: false
35
- Max: 10
36
-
37
- Metrics/ParameterLists:
38
- Max: 4
39
- CountKeywordArgs: true
40
-
41
- Naming/FileName:
42
- Exclude:
43
- - lib/omniauth-oauth2.rb
44
-
45
- Style/CollectionMethods:
46
- PreferredMethods:
47
- map: 'collect'
48
- reduce: 'inject'
49
- find: 'detect'
50
- find_all: 'select'
51
-
52
- Style/Documentation:
53
- Enabled: false
54
-
55
- Style/DoubleNegation:
56
- Enabled: false
57
-
58
- Style/ExpandPathArguments:
59
- Enabled: false
60
-
61
- Style/FrozenStringLiteralComment:
62
- Enabled: false
63
-
64
- Style/HashSyntax:
65
- EnforcedStyle: hash_rockets
66
-
67
- Style/StderrPuts:
68
- Enabled: false
69
-
70
- Style/StringLiterals:
71
- EnforcedStyle: double_quotes
72
-
73
- Style/TrailingCommaInArguments:
74
- EnforcedStyleForMultiline: comma
75
-
76
- Style/TrailingCommaInHashLiteral:
77
- EnforcedStyleForMultiline: comma
78
-
79
- Style/TrailingCommaInArrayLiteral:
80
- EnforcedStyleForMultiline: comma
data/CHANGELOG.md DELETED
@@ -1,9 +0,0 @@
1
- ## [v1.9.0](https://github.com/omniauth/omniauth-oauth2/releases/tag/v1.9.0)
2
- - Prevent timing attacks [#174](https://github.com/omniauth/omniauth-oauth2/pull/174)
3
- - Rescue OAuth2 timeouts [#169](https://github.com/omniauth/omniauth-oauth2/pull/169)
4
-
5
- ## [v1.8.0](https://github.com/omniauth/omniauth-oauth2/releases/tag/v1.8.0)
6
- - Relaxes allowed versions of the oauth2 gem. [#146](https://github.com/omniauth/omniauth-oauth2/pull/146)
7
- - Requires omniauth `~> 2.0` [#152](https://github.com/omniauth/omniauth-oauth2/pull/152)
8
-
9
- Please see https://github.com/omniauth/omniauth-oauth2/releases for changelog prior to 1.8.0
data/Gemfile DELETED
@@ -1,20 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "rake", "~> 13.0"
4
-
5
- group :test do
6
- gem "addressable", "~> 2.3.8", :platforms => %i[jruby ruby_18]
7
- gem 'coveralls_reborn', '~> 0.19.0', require: false
8
- gem "json", :platforms => %i[jruby ruby_18 ruby_19]
9
- gem "mime-types", "~> 1.25", :platforms => %i[jruby ruby_18]
10
- gem "rack-test"
11
- gem "rest-client", "~> 1.8.0", :platforms => %i[jruby ruby_18]
12
- gem "rspec", "~> 3.2"
13
- gem "rubocop", ">= 0.51", :platforms => %i[ruby_19 ruby_20 ruby_21 ruby_22 ruby_23 ruby_24]
14
- gem 'simplecov-lcov'
15
- gem 'tins', '~> 1.13', :platforms => %i[jruby_18 jruby_19 ruby_19]
16
- gem "webmock", "~> 3.0"
17
- end
18
-
19
- # Specify your gem's dependencies in omniauth-oauth2.gemspec
20
- gemspec
data/Rakefile DELETED
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env rake
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new
7
-
8
- task :test => :spec
9
-
10
- begin
11
- require "rubocop/rake_task"
12
- RuboCop::RakeTask.new
13
- rescue LoadError
14
- task :rubocop do
15
- $stderr.puts "RuboCop is disabled"
16
- end
17
- end
18
-
19
- task :default => %i[spec rubocop]
@@ -1,24 +0,0 @@
1
- lib = File.expand_path("../lib", __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "omniauth-oauth2/version"
4
-
5
- Gem::Specification.new do |gem|
6
- gem.add_dependency "oauth2", [">= 2.0.2", "< 3"]
7
- gem.add_dependency "omniauth", "~> 2.0"
8
-
9
- gem.add_development_dependency "bundler", "~> 2.0"
10
-
11
- gem.authors = ["Michael Bleigh", "Erik Michaels-Ober", "Tom Milewski"]
12
- gem.email = ["michael@intridea.com", "sferik@gmail.com", "tmilewski@gmail.com"]
13
- gem.description = "An abstract OAuth2 strategy for OmniAuth."
14
- gem.summary = gem.description
15
- gem.homepage = "https://github.com/omniauth/omniauth-oauth2"
16
- gem.licenses = %w[MIT]
17
-
18
- gem.executables = `git ls-files -- bin/*`.split("\n").collect { |f| File.basename(f) }
19
- gem.files = `git ls-files`.split("\n")
20
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
- gem.name = "omniauth-oauth2"
22
- gem.require_paths = %w[lib]
23
- gem.version = OmniAuth::OAuth2::VERSION
24
- end
data/spec/helper.rb DELETED
@@ -1,35 +0,0 @@
1
- $LOAD_PATH.unshift File.expand_path("..", __FILE__)
2
- $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
3
-
4
- if RUBY_VERSION >= "1.9"
5
- require "simplecov"
6
- require "simplecov-lcov"
7
- require "coveralls"
8
-
9
- SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
10
-
11
- SimpleCov.formatters = [
12
- SimpleCov::Formatter::HTMLFormatter,
13
- SimpleCov::Formatter::LcovFormatter,
14
- Coveralls::SimpleCov::Formatter
15
- ]
16
-
17
- SimpleCov.start do
18
- minimum_coverage(78.48)
19
- end
20
- end
21
-
22
- require "rspec"
23
- require "rack/test"
24
- require "webmock/rspec"
25
- require "omniauth"
26
- require "omniauth-oauth2"
27
-
28
- RSpec.configure do |config|
29
- config.expect_with :rspec do |c|
30
- c.syntax = :expect
31
- end
32
- config.extend OmniAuth::Test::StrategyMacros, :type => :strategy
33
- config.include Rack::Test::Methods
34
- config.include WebMock::API
35
- end
@@ -1,198 +0,0 @@
1
- require "helper"
2
-
3
- describe OmniAuth::Strategies::OAuth2 do
4
- def app
5
- lambda do |_env|
6
- [200, {}, ["Hello."]]
7
- end
8
- end
9
- let(:fresh_strategy) { Class.new(OmniAuth::Strategies::OAuth2) }
10
-
11
- before do
12
- OmniAuth.config.test_mode = true
13
- end
14
-
15
- after do
16
- OmniAuth.config.test_mode = false
17
- end
18
-
19
- describe "Subclassing Behavior" do
20
- subject { fresh_strategy }
21
-
22
- it "performs the OmniAuth::Strategy included hook" do
23
- expect(OmniAuth.strategies).to include(OmniAuth::Strategies::OAuth2)
24
- expect(OmniAuth.strategies).to include(subject)
25
- end
26
- end
27
-
28
- describe "#client" do
29
- subject { fresh_strategy }
30
-
31
- it "is initialized with symbolized client_options" do
32
- instance = subject.new(app, :client_options => {"authorize_url" => "https://example.com"})
33
- expect(instance.client.options[:authorize_url]).to eq("https://example.com")
34
- end
35
-
36
- it "sets ssl options as connection options" do
37
- instance = subject.new(app, :client_options => {"ssl" => {"ca_path" => "foo"}})
38
- expect(instance.client.options[:connection_opts][:ssl]).to eq(:ca_path => "foo")
39
- end
40
- end
41
-
42
- describe "#authorize_params" do
43
- subject { fresh_strategy }
44
-
45
- it "includes any authorize params passed in the :authorize_params option" do
46
- instance = subject.new("abc", "def", :authorize_params => {:foo => "bar", :baz => "zip"})
47
- expect(instance.authorize_params["foo"]).to eq("bar")
48
- expect(instance.authorize_params["baz"]).to eq("zip")
49
- end
50
-
51
- it "includes top-level options that are marked as :authorize_options" do
52
- instance = subject.new("abc", "def", :authorize_options => %i[scope foo state], :scope => "bar", :foo => "baz")
53
- expect(instance.authorize_params["scope"]).to eq("bar")
54
- expect(instance.authorize_params["foo"]).to eq("baz")
55
- expect(instance.authorize_params["state"]).not_to be_empty
56
- end
57
-
58
- it "includes random state in the authorize params" do
59
- instance = subject.new("abc", "def")
60
- expect(instance.authorize_params.keys).to eq(["state"])
61
- expect(instance.session["omniauth.state"]).not_to be_empty
62
- end
63
-
64
- it "includes custom state in the authorize params" do
65
- instance = subject.new("abc", "def", :state => proc { "qux" })
66
- expect(instance.authorize_params.keys).to eq(["state"])
67
- expect(instance.session["omniauth.state"]).to eq("qux")
68
- end
69
-
70
- it "includes PKCE parameters if enabled" do
71
- instance = subject.new("abc", "def", :pkce => true)
72
- expect(instance.authorize_params[:code_challenge]).to be_a(String)
73
- expect(instance.authorize_params[:code_challenge_method]).to eq("S256")
74
- expect(instance.session["omniauth.pkce.verifier"]).to be_a(String)
75
- end
76
- end
77
-
78
- describe "#token_params" do
79
- subject { fresh_strategy }
80
-
81
- it "includes any authorize params passed in the :authorize_params option" do
82
- instance = subject.new("abc", "def", :token_params => {:foo => "bar", :baz => "zip"})
83
- expect(instance.token_params).to eq("foo" => "bar", "baz" => "zip")
84
- end
85
-
86
- it "includes top-level options that are marked as :authorize_options" do
87
- instance = subject.new("abc", "def", :token_options => %i[scope foo], :scope => "bar", :foo => "baz")
88
- expect(instance.token_params).to eq("scope" => "bar", "foo" => "baz")
89
- end
90
-
91
- it "includes the PKCE code_verifier if enabled" do
92
- instance = subject.new("abc", "def", :pkce => true)
93
- # setup session
94
- instance.authorize_params
95
- expect(instance.token_params[:code_verifier]).to be_a(String)
96
- end
97
- end
98
-
99
- describe "#callback_phase" do
100
- subject(:instance) { fresh_strategy.new("abc", "def") }
101
-
102
- let(:params) { {"error_reason" => "user_denied", "error" => "access_denied", "state" => state} }
103
- let(:state) { "secret" }
104
-
105
- before do
106
- allow(instance).to receive(:request) do
107
- double("Request", :params => params)
108
- end
109
-
110
- allow(instance).to receive(:session) do
111
- double("Session", :delete => state)
112
- end
113
- end
114
-
115
- it "calls fail with the error received" do
116
- expect(instance).to receive(:fail!).with("user_denied", anything)
117
-
118
- instance.callback_phase
119
- end
120
-
121
- it "calls fail with the error received if state is missing and CSRF verification is disabled" do
122
- params["state"] = nil
123
- instance.options.provider_ignores_state = true
124
-
125
- expect(instance).to receive(:fail!).with("user_denied", anything)
126
-
127
- instance.callback_phase
128
- end
129
-
130
- it "calls fail with a CSRF error if the state is missing" do
131
- params["state"] = nil
132
-
133
- expect(instance).to receive(:fail!).with(:csrf_detected, anything)
134
- instance.callback_phase
135
- end
136
-
137
- it "calls fail with a CSRF error if the state is invalid" do
138
- params["state"] = "invalid"
139
-
140
- expect(instance).to receive(:fail!).with(:csrf_detected, anything)
141
- instance.callback_phase
142
- end
143
-
144
- describe 'exception handlings' do
145
- let(:params) do
146
- {"code" => "code", "state" => state}
147
- end
148
-
149
- before do
150
- allow_any_instance_of(OmniAuth::Strategies::OAuth2).to receive(:build_access_token).and_raise(exception)
151
- end
152
-
153
- {
154
- :invalid_credentials => [OAuth2::Error, OmniAuth::Strategies::OAuth2::CallbackError],
155
- :timeout => [Timeout::Error, Errno::ETIMEDOUT, OAuth2::TimeoutError, OAuth2::ConnectionError],
156
- :failed_to_connect => [SocketError]
157
- }.each do |error_type, exceptions|
158
- exceptions.each do |klass|
159
- context "when #{klass}" do
160
- let(:exception) { klass.new 'error' }
161
-
162
- it do
163
- expect(instance).to receive(:fail!).with(error_type, exception)
164
- instance.callback_phase
165
- end
166
- end
167
- end
168
- end
169
- end
170
- end
171
-
172
- describe "#secure_compare" do
173
- subject { fresh_strategy }
174
-
175
- it "returns true when the two inputs are the same and false otherwise" do
176
- instance = subject.new("abc", "def")
177
- expect(instance.send(:secure_compare, "a", "a")).to be true
178
- expect(instance.send(:secure_compare, "b", "a")).to be false
179
- end
180
- end
181
- end
182
-
183
- describe OmniAuth::Strategies::OAuth2::CallbackError do
184
- let(:error) { Class.new(OmniAuth::Strategies::OAuth2::CallbackError) }
185
- describe "#message" do
186
- subject { error }
187
- it "includes all of the attributes" do
188
- instance = subject.new("error", "description", "uri")
189
- expect(instance.message).to match(/error/)
190
- expect(instance.message).to match(/description/)
191
- expect(instance.message).to match(/uri/)
192
- end
193
- it "includes all of the attributes" do
194
- instance = subject.new(nil, :symbol)
195
- expect(instance.message).to eq("symbol")
196
- end
197
- end
198
- end