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