omniauth-jwt2 0.1.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 +7 -0
- data/.github/FUNDING.yml +11 -0
- data/.github/dependabot.yml +2 -0
- data/.github/workflows/ancient.yml +53 -0
- data/.github/workflows/ci.yml +59 -0
- data/.github/workflows/coverage.yml +91 -0
- data/.github/workflows/legacy.yml +54 -0
- data/.github/workflows/style.yml +43 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_gradual.lock +39 -0
- data/.simplecov +2 -0
- data/.tool-versions +1 -0
- data/Gemfile +17 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +23 -0
- data/README.md +125 -0
- data/Rakefile +23 -0
- data/gemfiles/ancient.gemfile +20 -0
- data/gemfiles/contexts/coverage.gemfile +2 -0
- data/gemfiles/contexts/debug.gemfile +6 -0
- data/gemfiles/contexts/style.gemfile +5 -0
- data/gemfiles/contexts/testing.gemfile +8 -0
- data/gemfiles/coverage.gemfile +20 -0
- data/gemfiles/legacy.gemfile +26 -0
- data/gemfiles/style.gemfile +20 -0
- data/gemfiles/vanilla.gemfile +20 -0
- data/lib/omniauth/jwt/version.rb +7 -0
- data/lib/omniauth/jwt.rb +10 -0
- data/lib/omniauth/strategies/jwt.rb +97 -0
- data/omniauth-jwt2.gemspec +41 -0
- data/spec/lib/omniauth/strategies/jwt_spec.rb +213 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/support/hash.rb +9 -0
- data/spec/support/next_instance_of.rb +43 -0
- metadata +232 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
# Gemfile is only for local development.
|
8
|
+
# On CI we only need the gemspecs' dependencies (including development dependencies).
|
9
|
+
# Exceptions, if any, will be found in gemfiles/*
|
10
|
+
|
11
|
+
# Coverage
|
12
|
+
eval_gemfile "contexts/coverage.gemfile"
|
13
|
+
|
14
|
+
# Testing
|
15
|
+
eval_gemfile "contexts/testing.gemfile"
|
16
|
+
|
17
|
+
# Debugging
|
18
|
+
eval_gemfile "contexts/debug.gemfile"
|
19
|
+
|
20
|
+
gemspec path: "../"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
# Gemfile is only for local development.
|
8
|
+
# On CI we only need the gemspecs' dependencies (including development dependencies).
|
9
|
+
# Exceptions, if any, will be found in gemfiles/*
|
10
|
+
|
11
|
+
# Testing
|
12
|
+
gem "rspec", "~> 3.12" # ruby *
|
13
|
+
gem "rack-test", "~> 2.1" # ruby 2.0
|
14
|
+
gem "rack" # ruby 2.4
|
15
|
+
gem "rack-session", "< 2", github: "pboling/rack-session", branch: "fix-missing-rack-session" # ruby < 2.4
|
16
|
+
gem "json" # ruby 2.3
|
17
|
+
gem "openssl" # ruby 2.3
|
18
|
+
gem "openssl-signature_algorithm" # ruby 2.4
|
19
|
+
gem "ed25519" # ruby 2.4
|
20
|
+
|
21
|
+
# Debugging
|
22
|
+
eval_gemfile "contexts/debug.gemfile"
|
23
|
+
|
24
|
+
gemspec path: "../"
|
25
|
+
|
26
|
+
gem "omniauth", "< 2"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
# Gemfile is only for local development.
|
8
|
+
# On CI we only need the gemspecs' dependencies (including development dependencies).
|
9
|
+
# Exceptions, if any, will be found in gemfiles/*
|
10
|
+
|
11
|
+
# Coverage
|
12
|
+
eval_gemfile "contexts/coverage.gemfile"
|
13
|
+
|
14
|
+
# Style
|
15
|
+
eval_gemfile "contexts/style.gemfile"
|
16
|
+
|
17
|
+
# Debugging
|
18
|
+
eval_gemfile "contexts/debug.gemfile"
|
19
|
+
|
20
|
+
gemspec path: "../"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
# Gemfile is only for local development.
|
8
|
+
# On CI we only need the gemspecs' dependencies (including development dependencies).
|
9
|
+
# Exceptions, if any, will be found in gemfiles/*
|
10
|
+
|
11
|
+
# Coverage
|
12
|
+
eval_gemfile "contexts/coverage.gemfile"
|
13
|
+
|
14
|
+
# Testing
|
15
|
+
eval_gemfile "contexts/testing.gemfile"
|
16
|
+
|
17
|
+
# Debugging
|
18
|
+
eval_gemfile "contexts/debug.gemfile"
|
19
|
+
|
20
|
+
gemspec path: "../"
|
data/lib/omniauth/jwt.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require "omniauth"
|
2
|
+
require "jwt"
|
3
|
+
|
4
|
+
module OmniAuth
|
5
|
+
module Strategies
|
6
|
+
class JWT
|
7
|
+
class ClaimInvalid < StandardError; end
|
8
|
+
|
9
|
+
class BadJwt < StandardError; end
|
10
|
+
|
11
|
+
include OmniAuth::Strategy
|
12
|
+
|
13
|
+
args [:secret]
|
14
|
+
|
15
|
+
option :secret, nil
|
16
|
+
option :decode_options, {}
|
17
|
+
option :jwks_loader
|
18
|
+
option :algorithm, "HS256" # overridden by options.decode_options[:algorithms]
|
19
|
+
option :decode_options, {}
|
20
|
+
option :uid_claim, "email"
|
21
|
+
option :required_claims, %w(name email)
|
22
|
+
option :info_map, {name: "name", email: "email"}
|
23
|
+
option :auth_url, nil
|
24
|
+
option :valid_within, nil
|
25
|
+
|
26
|
+
def request_phase
|
27
|
+
redirect(options.auth_url)
|
28
|
+
end
|
29
|
+
|
30
|
+
def decoded
|
31
|
+
begin
|
32
|
+
secret = if defined?(OpenSSL)
|
33
|
+
case options.algorithm
|
34
|
+
when "RS256", "RS384", "RS512"
|
35
|
+
OpenSSL::PKey::RSA.new(options.secret).public_key
|
36
|
+
when "ES256", "ES384", "ES512"
|
37
|
+
OpenSSL::PKey::EC.new(options.secret)
|
38
|
+
when "HS256", "HS384", "HS512"
|
39
|
+
options.secret
|
40
|
+
else
|
41
|
+
raise NotImplementedError, "Unsupported algorithm: #{options.algorithm}"
|
42
|
+
end
|
43
|
+
else
|
44
|
+
options.secret
|
45
|
+
end
|
46
|
+
|
47
|
+
# JWT.decode can handle either algorithms or algorithm, but not both.
|
48
|
+
default_algos = options.decode_options.key?(:algorithms) ? options.decode_options[:algorithms] : [options.algorithm]
|
49
|
+
@decoded ||= ::JWT.decode(
|
50
|
+
request.params["jwt"],
|
51
|
+
secret,
|
52
|
+
true,
|
53
|
+
options.decode_options.merge(
|
54
|
+
{
|
55
|
+
algorithms: default_algos,
|
56
|
+
jwks: options.jwks_loader,
|
57
|
+
}.delete_if { |_, v| v.nil? },
|
58
|
+
),
|
59
|
+
)[0]
|
60
|
+
rescue Exception => e
|
61
|
+
raise BadJwt.new("#{e.class}: #{e.message}")
|
62
|
+
end
|
63
|
+
(options.required_claims || []).each do |field|
|
64
|
+
raise ClaimInvalid.new("Missing required '#{field}' claim.") if !@decoded.key?(field.to_s)
|
65
|
+
end
|
66
|
+
raise ClaimInvalid.new("Missing required 'iat' claim.") if options.valid_within && !@decoded["iat"]
|
67
|
+
if options.valid_within && (Time.now.to_i - @decoded["iat"]).abs > options.valid_within.to_i
|
68
|
+
raise ClaimInvalid, "'iat' timestamp claim is too skewed from present"
|
69
|
+
end
|
70
|
+
|
71
|
+
@decoded
|
72
|
+
end
|
73
|
+
|
74
|
+
def callback_phase
|
75
|
+
super
|
76
|
+
rescue BadJwt => e
|
77
|
+
fail!("bad_jwt", e)
|
78
|
+
rescue ClaimInvalid => e
|
79
|
+
fail!(:claim_invalid, e)
|
80
|
+
end
|
81
|
+
|
82
|
+
uid { decoded[options.uid_claim] }
|
83
|
+
|
84
|
+
extra do
|
85
|
+
{raw_info: decoded}
|
86
|
+
end
|
87
|
+
|
88
|
+
info do
|
89
|
+
options.info_map.each_with_object({}) do |(k, v), h|
|
90
|
+
h[k.to_s] = decoded[v.to_s]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Jwt < JWT; end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Get the GEMFILE_VERSION without *require* "my_gem/version", for code coverage accuracy
|
2
|
+
# See: https://github.com/simplecov-ruby/simplecov/issues/557#issuecomment-825171399
|
3
|
+
load "lib/omniauth/jwt/version.rb"
|
4
|
+
gem_version = Omniauth::JWT::Version::VERSION
|
5
|
+
Omniauth::JWT::Version.send(:remove_const, :VERSION)
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "omniauth-jwt2"
|
9
|
+
spec.version = gem_version
|
10
|
+
spec.authors = ["Michael Bleigh", "Robin Ward", "Peter Boling"]
|
11
|
+
spec.email = ["mbleigh@mbleigh.com", "robin.ward@gmail.com", "peter.boling@gmail.com"]
|
12
|
+
spec.description = "An OmniAuth strategy to accept JWT-based single sign-on."
|
13
|
+
spec.summary = "An OmniAuth strategy to accept JWT-based single sign-on."
|
14
|
+
spec.homepage = "http://github.com/pboling/omniauth-jwt2"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = ">= 2.2"
|
17
|
+
|
18
|
+
spec.files = %x(git ls-files).split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
# TODO: Since this gem supports Ruby >= 2.2 we need to ensure no gems are
|
23
|
+
# added here that require a newer version. Once this gem progresses to
|
24
|
+
# only support non-EOL Rubies, all dependencies can be listed in this
|
25
|
+
# gemspec, and the gemfiles/* pattern can be dispensed with.
|
26
|
+
spec.add_dependency("jwt", "~> 2.2", ">= 2.2.1") # ruby 2.1
|
27
|
+
spec.add_dependency("omniauth", ">= 1.1") # ruby 2.2
|
28
|
+
|
29
|
+
# Utilities
|
30
|
+
spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.3") # ruby 2.2
|
31
|
+
spec.add_development_dependency("rake", "~> 13.0") # ruby 2.2, v13.1 is >= 2.3
|
32
|
+
|
33
|
+
# Hot reload
|
34
|
+
spec.add_development_dependency("guard", "~> 2.18", ">= 2.18.1") # ruby 1.9.3
|
35
|
+
spec.add_development_dependency("guard-rspec", "~> 4.7", ">= 4.7.3") # ruby *
|
36
|
+
|
37
|
+
# Testing
|
38
|
+
spec.add_development_dependency("rack-test", "~> 2.1") # ruby 2.0
|
39
|
+
spec.add_development_dependency("rspec", "~> 3.12") # ruby *
|
40
|
+
spec.add_development_dependency("rspec-pending_for", "~> 0.1") # ruby *
|
41
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe OmniAuth::Strategies::JWT do
|
4
|
+
let(:response_json) { JSON.parse(last_response.body) }
|
5
|
+
let(:rand_secret) { SecureRandom.hex(10) }
|
6
|
+
let(:args) { [rand_secret, {auth_url: "http://example.com/login"}] }
|
7
|
+
|
8
|
+
let(:app) {
|
9
|
+
the_args = args
|
10
|
+
Rack::Builder.new do |b|
|
11
|
+
b.use Rack::Session::Cookie, secret: SecureRandom.hex(32)
|
12
|
+
b.use OmniAuth::Strategies::JWT, *the_args
|
13
|
+
b.run lambda { |env|
|
14
|
+
[200, {}, [(env["omniauth.auth"] || {}).to_json]]
|
15
|
+
}
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
context "request phase" do
|
20
|
+
it "redirects to the configured login url" do
|
21
|
+
# TODO: Figure out how to write this test without using the deprecated
|
22
|
+
# and unsafe, "get" method for the request phase.
|
23
|
+
get "/auth/jwt"
|
24
|
+
expect(last_response.status).to eq(302)
|
25
|
+
expect(last_response.headers["Location"]).to eq("http://example.com/login")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "callback phase" do
|
30
|
+
it "decodes the response" do
|
31
|
+
encoded = JWT.encode({name: "Bob", email: "steve@example.com"}, rand_secret)
|
32
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
33
|
+
expect(response_json["info"]["email"]).to eq("steve@example.com")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "does not work without required fields" do
|
37
|
+
encoded = JWT.encode({name: "Steve"}, rand_secret)
|
38
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
39
|
+
expect(last_response.status).to eq(302)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "assigns the uid" do
|
43
|
+
encoded = JWT.encode({name: "Steve", email: "dude@awesome.com"}, rand_secret)
|
44
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
45
|
+
expect(response_json["uid"]).to eq("dude@awesome.com")
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with a non-default encoding algorithm" do
|
49
|
+
let(:args) { [rand_secret, {auth_url: "http://example.com/login", decode_options: {algorithms: ["HS512", "HS256"]}}] }
|
50
|
+
|
51
|
+
it "decodes the response with an allowed algorithm" do
|
52
|
+
encoded = JWT.encode({name: "Bob", email: "steve@example.com"}, rand_secret, "HS512")
|
53
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
54
|
+
expect(JSON.parse(last_response.body)["info"]["email"]).to eq("steve@example.com")
|
55
|
+
|
56
|
+
encoded = JWT.encode({name: "Bob", email: "steve@example.com"}, rand_secret, "HS256")
|
57
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
58
|
+
expect(JSON.parse(last_response.body)["info"]["email"]).to eq("steve@example.com")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "fails decoding the response with a different algorithm" do
|
62
|
+
encoded = JWT.encode({name: "Bob", email: "steve@example.com"}, rand_secret, "HS384")
|
63
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
64
|
+
expect(last_response.headers["Location"]).to include("/auth/failure")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with a :valid_within option set" do
|
69
|
+
let(:args) { [rand_secret, {auth_url: "http://example.com/login", valid_within: 300}] }
|
70
|
+
|
71
|
+
it "works if the iat key is within the time window" do
|
72
|
+
encoded = JWT.encode({name: "Ted", email: "ted@example.com", iat: Time.now.to_i}, rand_secret)
|
73
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
74
|
+
expect(last_response.status).to eq(200)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not work if the iat key is outside the time window" do
|
78
|
+
encoded = JWT.encode({name: "Ted", email: "ted@example.com", iat: Time.now.to_i + 500}, rand_secret)
|
79
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
80
|
+
expect(last_response.status).to eq(302)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "does not work if the iat key is missing" do
|
84
|
+
encoded = JWT.encode({name: "Ted", email: "ted@example.com"}, rand_secret)
|
85
|
+
get "/auth/jwt/callback?jwt=" + encoded
|
86
|
+
expect(last_response.status).to eq(302)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#decoded" do
|
92
|
+
subject { described_class.new({}) }
|
93
|
+
|
94
|
+
let(:timestamp) { Time.now.to_i }
|
95
|
+
let(:claims) do
|
96
|
+
{
|
97
|
+
id: 123,
|
98
|
+
name: "user_example",
|
99
|
+
email: "user@example.com",
|
100
|
+
iat: timestamp,
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
let(:algorithm) { "HS256" }
|
105
|
+
let(:secret) { rand_secret }
|
106
|
+
let(:private_key) { secret }
|
107
|
+
let(:payload) { JWT.encode(claims, private_key, algorithm) }
|
108
|
+
|
109
|
+
before do
|
110
|
+
subject.options[:secret] = secret
|
111
|
+
subject.options[:algorithm] = algorithm
|
112
|
+
|
113
|
+
# We use Rack::Request instead of ActionDispatch::Request because
|
114
|
+
# Rack::Test::Methods enables testing of this module.
|
115
|
+
expect_next_instance_of(Rack::Request) do |rack_request|
|
116
|
+
expect(rack_request).to receive(:params).and_return("jwt" => payload)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
ecdsa_named_curves = {
|
121
|
+
"ES256" => "prime256v1",
|
122
|
+
"ES384" => "secp384r1",
|
123
|
+
"ES512" => "secp521r1",
|
124
|
+
}.freeze
|
125
|
+
|
126
|
+
algos = {
|
127
|
+
OpenSSL::PKey::RSA => %w[RS256 RS384 RS512],
|
128
|
+
String => %w[HS256 HS384 HS512],
|
129
|
+
}
|
130
|
+
algos.merge!(OpenSSL::PKey::EC => %w[ES256 ES384 ES512]) unless ["2.2.10", "2.3.8"].include?(RubyVersion.to_s)
|
131
|
+
algos.each do |private_key_class, algorithms|
|
132
|
+
algorithms.each do |algorithm|
|
133
|
+
context "when the #{algorithm} algorithm is used" do
|
134
|
+
let(:algorithm) { algorithm }
|
135
|
+
let(:secret) do
|
136
|
+
# rubocop:disable Style/CaseLikeIf
|
137
|
+
if private_key_class == OpenSSL::PKey::RSA
|
138
|
+
private_key_class.generate(2048)
|
139
|
+
.to_pem
|
140
|
+
elsif private_key_class == OpenSSL::PKey::EC
|
141
|
+
private_key_class.generate(ecdsa_named_curves[algorithm])
|
142
|
+
.to_pem
|
143
|
+
else
|
144
|
+
private_key_class.new(rand_secret)
|
145
|
+
end
|
146
|
+
# rubocop:enable Style/CaseLikeIf
|
147
|
+
end
|
148
|
+
|
149
|
+
let(:private_key) { private_key_class ? private_key_class.new(secret) : secret }
|
150
|
+
|
151
|
+
it "decodes the user information" do
|
152
|
+
result = subject.decoded
|
153
|
+
|
154
|
+
expect(result).to eq(claims.stringify_keys)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "required claims is missing" do
|
161
|
+
let(:claims) do
|
162
|
+
{
|
163
|
+
id: 123,
|
164
|
+
email: "user@example.com",
|
165
|
+
iat: timestamp,
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
it "raises error" do
|
170
|
+
expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context "when valid_within is specified but iat attribute is missing in response" do
|
175
|
+
let(:claims) do
|
176
|
+
{
|
177
|
+
id: 123,
|
178
|
+
name: "user_example",
|
179
|
+
email: "user@example.com",
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
before do
|
184
|
+
# Omniauth config values are always strings!
|
185
|
+
subject.options[:valid_within] = (60 * 60 * 24 * 2).to_s # 2 days
|
186
|
+
end
|
187
|
+
|
188
|
+
it "raises error" do
|
189
|
+
expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "when timestamp claim is too skewed from present" do
|
194
|
+
let(:claims) do
|
195
|
+
{
|
196
|
+
id: 123,
|
197
|
+
name: "user_example",
|
198
|
+
email: "user@example.com",
|
199
|
+
iat: timestamp - (60 * 60 * 10), # minus ten minutes
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
before do
|
204
|
+
# Omniauth config values are always strings!
|
205
|
+
subject.options[:valid_within] = "2" # 2 seconds
|
206
|
+
end
|
207
|
+
|
208
|
+
it "raises error" do
|
209
|
+
expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Std Lib
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
# 3rd party gems
|
5
|
+
require "rspec/pending_for"
|
6
|
+
begin
|
7
|
+
require "rack/session"
|
8
|
+
rescue LoadError
|
9
|
+
nil # File won't exist in old rack for Ruby 2.2 & 2.3
|
10
|
+
end
|
11
|
+
require "rack/test"
|
12
|
+
require "json"
|
13
|
+
require "omniauth"
|
14
|
+
begin
|
15
|
+
require "openssl"
|
16
|
+
require "openssl/signature_algorithm"
|
17
|
+
require "ed25519"
|
18
|
+
rescue LoadError
|
19
|
+
nil # Gem doesn't exist for ancient Rubies 2.2 & 2.3
|
20
|
+
end
|
21
|
+
|
22
|
+
require "byebug" if ENV["DEBUG"] == "true"
|
23
|
+
# This does not require "simplecov",
|
24
|
+
# because that has a side-effect of running `.simplecov`
|
25
|
+
begin
|
26
|
+
require "kettle-soup-cover"
|
27
|
+
rescue LoadError
|
28
|
+
puts "Not analyzing test coverage"
|
29
|
+
end
|
30
|
+
|
31
|
+
require "support/hash"
|
32
|
+
require "support/next_instance_of"
|
33
|
+
|
34
|
+
OmniAuth.config.logger = Logger.new("/dev/null")
|
35
|
+
require "omniauth/version"
|
36
|
+
puts "OMNIAUTH VERSION: #{OmniAuth::VERSION}"
|
37
|
+
if Gem::Version.new(OmniAuth::VERSION) > Gem::Version.new("2.0")
|
38
|
+
OmniAuth.config.silence_get_warning = true
|
39
|
+
OmniAuth.config.allowed_request_methods |= [:get, :post]
|
40
|
+
end
|
41
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
42
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
43
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
44
|
+
# loaded once.
|
45
|
+
#
|
46
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
47
|
+
RSpec.configure do |config|
|
48
|
+
config.run_all_when_everything_filtered = true
|
49
|
+
config.filter_run :focus
|
50
|
+
|
51
|
+
include Rack::Test::Methods
|
52
|
+
include NextInstanceOf
|
53
|
+
|
54
|
+
# Run specs in random order to surface order dependencies. If you find an
|
55
|
+
# order dependency and want to debug it, you can fix the order by providing
|
56
|
+
# the seed, which is printed after each run.
|
57
|
+
# --seed 1234
|
58
|
+
config.order = "random"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Last thing before loading this library, load simplecov:
|
62
|
+
require "simplecov" if defined?(Kettle::Soup::Cover) && Kettle::Soup::Cover::DO_COV
|
63
|
+
|
64
|
+
require "omniauth/jwt"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# From: https://github.com/gitlabhq/gitlabhq/blob/master/gems/gitlab-rspec/lib/gitlab/rspec/next_instance_of.rb#L4
|
2
|
+
module NextInstanceOf
|
3
|
+
def expect_next_instance_of(klass, *new_args, &blk)
|
4
|
+
stub_new(expect(klass), nil, false, *new_args, &blk)
|
5
|
+
end
|
6
|
+
|
7
|
+
def expect_next_instances_of(klass, number, ordered = false, *new_args, &blk)
|
8
|
+
stub_new(expect(klass), number, ordered, *new_args, &blk)
|
9
|
+
end
|
10
|
+
|
11
|
+
def allow_next_instance_of(klass, *new_args, &blk)
|
12
|
+
stub_new(allow(klass), nil, false, *new_args, &blk)
|
13
|
+
end
|
14
|
+
|
15
|
+
def allow_next_instances_of(klass, number, ordered = false, *new_args, &blk)
|
16
|
+
stub_new(allow(klass), number, ordered, *new_args, &blk)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def stub_new(target, number, ordered = false, *new_args, &blk)
|
22
|
+
receive_new = receive(:new)
|
23
|
+
receive_new.ordered if ordered
|
24
|
+
receive_new.with(*new_args) if !(new_args.empty? || new_args.blank?)
|
25
|
+
|
26
|
+
if number.is_a?(Range)
|
27
|
+
receive_new.at_least(number.begin).times if number.begin
|
28
|
+
receive_new.at_most(number.end).times if number.end
|
29
|
+
elsif number
|
30
|
+
receive_new.exactly(number).times
|
31
|
+
end
|
32
|
+
|
33
|
+
target.to receive_new.and_wrap_original do |*original_args, **original_kwargs|
|
34
|
+
method, *original_args = original_args
|
35
|
+
begin
|
36
|
+
method.call(*original_args, **original_kwargs).tap(&blk)
|
37
|
+
rescue ArgumentError
|
38
|
+
# Kludge for old ruby < 2.7
|
39
|
+
method.call(*original_args).tap(&blk)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|