oauth2 1.4.9 → 2.0.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 +76 -23
- data/CONTRIBUTING.md +18 -0
- data/README.md +140 -69
- data/SECURITY.md +14 -0
- data/lib/oauth2/access_token.rb +28 -19
- data/lib/oauth2/authenticator.rb +9 -4
- data/lib/oauth2/client.rb +76 -60
- data/lib/oauth2/error.rb +27 -18
- data/lib/oauth2/response.rb +61 -19
- data/lib/oauth2/snaky_hash.rb +8 -0
- data/lib/oauth2/strategy/assertion.rb +63 -38
- data/lib/oauth2/strategy/auth_code.rb +12 -1
- data/lib/oauth2/strategy/implicit.rb +7 -0
- data/lib/oauth2/version.rb +1 -59
- data/lib/oauth2.rb +19 -1
- metadata +95 -76
- data/lib/oauth2/mac_token.rb +0 -130
- data/spec/fixtures/README.md +0 -11
- data/spec/fixtures/RS256/jwtRS256.key +0 -51
- data/spec/fixtures/RS256/jwtRS256.key.pub +0 -14
- data/spec/helper.rb +0 -33
- data/spec/oauth2/access_token_spec.rb +0 -218
- data/spec/oauth2/authenticator_spec.rb +0 -86
- data/spec/oauth2/client_spec.rb +0 -556
- data/spec/oauth2/mac_token_spec.rb +0 -122
- data/spec/oauth2/response_spec.rb +0 -96
- data/spec/oauth2/strategy/assertion_spec.rb +0 -113
- data/spec/oauth2/strategy/auth_code_spec.rb +0 -108
- data/spec/oauth2/strategy/base_spec.rb +0 -7
- data/spec/oauth2/strategy/client_credentials_spec.rb +0 -71
- data/spec/oauth2/strategy/implicit_spec.rb +0 -28
- data/spec/oauth2/strategy/password_spec.rb +0 -58
- data/spec/oauth2/version_spec.rb +0 -23
data/lib/oauth2/mac_token.rb
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'base64'
|
4
|
-
require 'digest'
|
5
|
-
require 'openssl'
|
6
|
-
require 'securerandom'
|
7
|
-
|
8
|
-
module OAuth2
|
9
|
-
class MACToken < AccessToken
|
10
|
-
# Generates a MACToken from an AccessToken and secret
|
11
|
-
#
|
12
|
-
# @param [AccessToken] token the OAuth2::Token instance
|
13
|
-
# @option [String] secret the secret key value
|
14
|
-
# @param [Hash] opts the options to create the Access Token with
|
15
|
-
# @see MACToken#initialize
|
16
|
-
def self.from_access_token(token, secret, options = {})
|
17
|
-
new(token.client, token.token, secret, token.params.merge(:refresh_token => token.refresh_token, :expires_in => token.expires_in, :expires_at => token.expires_at).merge(options))
|
18
|
-
end
|
19
|
-
|
20
|
-
attr_reader :secret, :algorithm
|
21
|
-
|
22
|
-
# Initalize a MACToken
|
23
|
-
#
|
24
|
-
# @param [Client] client the OAuth2::Client instance
|
25
|
-
# @param [String] token the Access Token value
|
26
|
-
# @option [String] secret the secret key value
|
27
|
-
# @param [Hash] opts the options to create the Access Token with
|
28
|
-
# @option opts [String] :refresh_token (nil) the refresh_token value
|
29
|
-
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
|
30
|
-
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
|
31
|
-
# @option opts [FixNum, String] :algorithm (hmac-sha-256) the algorithm to use for the HMAC digest (one of 'hmac-sha-256', 'hmac-sha-1')
|
32
|
-
def initialize(client, token, secret, opts = {})
|
33
|
-
@secret = secret
|
34
|
-
self.algorithm = opts.delete(:algorithm) || 'hmac-sha-256'
|
35
|
-
|
36
|
-
super(client, token, opts)
|
37
|
-
end
|
38
|
-
|
39
|
-
# Make a request with the MAC Token
|
40
|
-
#
|
41
|
-
# @param [Symbol] verb the HTTP request method
|
42
|
-
# @param [String] path the HTTP URL path of the request
|
43
|
-
# @param [Hash] opts the options to make the request with
|
44
|
-
# @see Client#request
|
45
|
-
def request(verb, path, opts = {}, &block)
|
46
|
-
url = client.connection.build_url(path, opts[:params]).to_s
|
47
|
-
|
48
|
-
opts[:headers] ||= {}
|
49
|
-
opts[:headers]['Authorization'] = header(verb, url)
|
50
|
-
|
51
|
-
@client.request(verb, path, opts, &block)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Get the headers hash (always an empty hash)
|
55
|
-
def headers
|
56
|
-
{}
|
57
|
-
end
|
58
|
-
|
59
|
-
# Generate the MAC header
|
60
|
-
#
|
61
|
-
# @param [Symbol] verb the HTTP request method
|
62
|
-
# @param [String] url the HTTP URL path of the request
|
63
|
-
def header(verb, url)
|
64
|
-
timestamp = Time.now.utc.to_i
|
65
|
-
nonce = Digest::MD5.hexdigest([timestamp, SecureRandom.hex].join(':'))
|
66
|
-
|
67
|
-
uri = URI.parse(url)
|
68
|
-
|
69
|
-
raise(ArgumentError, "could not parse \"#{url}\" into URI") unless uri.is_a?(URI::HTTP)
|
70
|
-
|
71
|
-
mac = signature(timestamp, nonce, verb, uri)
|
72
|
-
|
73
|
-
"MAC id=\"#{token}\", ts=\"#{timestamp}\", nonce=\"#{nonce}\", mac=\"#{mac}\""
|
74
|
-
end
|
75
|
-
|
76
|
-
# Generate the Base64-encoded HMAC digest signature
|
77
|
-
#
|
78
|
-
# @param [Fixnum] timestamp the timestamp of the request in seconds since epoch
|
79
|
-
# @param [String] nonce the MAC header nonce
|
80
|
-
# @param [Symbol] verb the HTTP request method
|
81
|
-
# @param [String] url the HTTP URL path of the request
|
82
|
-
def signature(timestamp, nonce, verb, uri)
|
83
|
-
signature = [
|
84
|
-
timestamp,
|
85
|
-
nonce,
|
86
|
-
verb.to_s.upcase,
|
87
|
-
uri.request_uri,
|
88
|
-
uri.host,
|
89
|
-
uri.port,
|
90
|
-
'', nil
|
91
|
-
].join("\n")
|
92
|
-
|
93
|
-
strict_encode64(OpenSSL::HMAC.digest(@algorithm, secret, signature))
|
94
|
-
end
|
95
|
-
|
96
|
-
# Set the HMAC algorithm
|
97
|
-
#
|
98
|
-
# @param [String] alg the algorithm to use (one of 'hmac-sha-1', 'hmac-sha-256')
|
99
|
-
def algorithm=(alg)
|
100
|
-
@algorithm = case alg.to_s
|
101
|
-
when 'hmac-sha-1'
|
102
|
-
begin
|
103
|
-
OpenSSL::Digest('SHA1').new
|
104
|
-
rescue StandardError
|
105
|
-
OpenSSL::Digest.new('SHA1')
|
106
|
-
end
|
107
|
-
when 'hmac-sha-256'
|
108
|
-
begin
|
109
|
-
OpenSSL::Digest('SHA256').new
|
110
|
-
rescue StandardError
|
111
|
-
OpenSSL::Digest.new('SHA256')
|
112
|
-
end
|
113
|
-
else
|
114
|
-
raise(ArgumentError, 'Unsupported algorithm')
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
# No-op since we need the verb and path
|
121
|
-
# and the MAC always goes in a header
|
122
|
-
def token=(_noop)
|
123
|
-
end
|
124
|
-
|
125
|
-
# Base64.strict_encode64 is not available on Ruby 1.8.7
|
126
|
-
def strict_encode64(str)
|
127
|
-
Base64.encode64(str).delete("\n")
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
data/spec/fixtures/README.md
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# RS256
|
2
|
-
|
3
|
-
## How keys were made
|
4
|
-
|
5
|
-
```shell
|
6
|
-
# No passphrase
|
7
|
-
# Generates the public and private keys:
|
8
|
-
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
|
9
|
-
# Converts the key to PEM format
|
10
|
-
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
|
11
|
-
```
|
@@ -1,51 +0,0 @@
|
|
1
|
-
-----BEGIN RSA PRIVATE KEY-----
|
2
|
-
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
-
+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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-
IWHkw2YOCLtEbFYrl3AI7Na5lHvrGEsREzQSN4Yh83Has0guAy1iyeNb+FFgq/XO
|
16
|
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-
F1xN+gxnWGPUKDVRPYBpVzDR036w65ODgg2FROK2vIxlStiAC/rc0JLsvaWfb1Rn
|
29
|
-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
-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
-
L7KJpylgh9JhMvbMz3VVjTgYRt+kjX+3uFMZNx1YfiBP+S6xx5sjK9CKDz3H99kC
|
32
|
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-
Tv+DsE3ZJPYlyW+hGuO0uazWrilUpNAwuJmhHFdq2+azPkqYNVGVvhB37oWsHGd0
|
43
|
-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
-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
-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
-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
-
3C1Qz/3iv/S3/ktgmqGhQiqybkkHZcbqTXB2wxrx+aaLS7PEfYiuYCrPbX93160k
|
48
|
-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
-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
-
NizQPR6/Ar1ewLPMh1exDoAfLnCIMk8nbSraW+cebLAZctPugUpfpu3j2LM98aE=
|
51
|
-
-----END RSA PRIVATE KEY-----
|
@@ -1,14 +0,0 @@
|
|
1
|
-
-----BEGIN PUBLIC KEY-----
|
2
|
-
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5hdXV/4YSymY1T9VNvK2
|
3
|
-
bWRfulwIty1RnAPNINQmfh3aRRkV+PNrbC2Crji9G0AHmQwgW1bZ3kgkkpIm6RVn
|
4
|
-
44fHvBvuXkZ9ABgXw0d2cLIHmwOFxSKmWAm/EW//GszUTLLLsMZUe2udtFJW0jxX
|
5
|
-
B2GRY0WVYuo6Oo58RCeP719lw3Ags0YF9/IobxKkGd4BautUPw6ZszAa3o+j0zR7
|
6
|
-
4x7ouPxybZAOuPsMxqanyeYJeH4osJjLMYV9qem9uG2sj7GENJ8UszcpmGbqxBhe
|
7
|
-
xPEB7mgDeONIF0XJF23zdOf8ANE5mAU2h2v7M6moAfkdUzJ+j48+VT2omHAzAL5y
|
8
|
-
Ncmrl2xiWdyoxOw1Y1UmfEmJYV5VgGYyZ12JZRKY+szPT+vR+MDuYxbquF40O7kv
|
9
|
-
kFNBfL1yCpzfSQCLnEs4rX8qRzZXciLeyq4Ht5FLuRFgxjA//XI8LAmp0u7gk+Q7
|
10
|
-
FUH1UgW3kmJDTG0XaxQxYTBSIO7mcmyjDyBgKVuQmt5E1ycFeteOVdPD/CG/fPYh
|
11
|
-
thvc4UytEFwsMdNy3iD6/wuUH68tAKam28UZaOb0qK+00cQQD8fulY9rKtSL10Lv
|
12
|
-
JFWUOa/SJyLvk9vUmfvFn182il1nX6GpyxyMmE/FCnH4CT/DjrSZf08mOO8eL5of
|
13
|
-
YHMK/oiXr1eODqx+pOwClNsCAwEAAQ==
|
14
|
-
-----END PUBLIC KEY-----
|
data/spec/helper.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
DEBUG = ENV['DEBUG'] == 'true'
|
4
|
-
RUN_COVERAGE = ENV['CI_CODECOV'] || ENV['CI'].nil?
|
5
|
-
|
6
|
-
ruby_version = Gem::Version.new(RUBY_VERSION)
|
7
|
-
minimum_version = ->(version) { ruby_version >= Gem::Version.new(version) && RUBY_ENGINE == 'ruby' }
|
8
|
-
coverage = minimum_version.call('2.7') && RUN_COVERAGE
|
9
|
-
debug = minimum_version.call('2.5') && DEBUG
|
10
|
-
|
11
|
-
require 'simplecov' if coverage
|
12
|
-
require 'byebug' if debug
|
13
|
-
|
14
|
-
require 'oauth2'
|
15
|
-
require 'addressable/uri'
|
16
|
-
require 'rspec'
|
17
|
-
require 'rspec/stubbed_env'
|
18
|
-
require 'rspec/pending_for'
|
19
|
-
require 'silent_stream'
|
20
|
-
|
21
|
-
RSpec.configure do |config|
|
22
|
-
config.expect_with :rspec do |c|
|
23
|
-
c.syntax = :expect
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
Faraday.default_adapter = :test
|
28
|
-
|
29
|
-
RSpec.configure do |conf|
|
30
|
-
conf.include SilentStream
|
31
|
-
end
|
32
|
-
|
33
|
-
VERBS = [:get, :post, :put, :delete].freeze
|
@@ -1,218 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe OAuth2::AccessToken do
|
4
|
-
subject { described_class.new(client, token) }
|
5
|
-
|
6
|
-
let(:token) { 'monkey' }
|
7
|
-
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => 'refresh_bar') }
|
8
|
-
let(:client) do
|
9
|
-
OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
|
10
|
-
builder.request :url_encoded
|
11
|
-
builder.adapter :test do |stub|
|
12
|
-
VERBS.each do |verb|
|
13
|
-
stub.send(verb, '/token/header') { |env| [200, {}, env[:request_headers]['Authorization']] }
|
14
|
-
stub.send(verb, "/token/query?access_token=#{token}") { |env| [200, {}, Addressable::URI.parse(env[:url]).query_values['access_token']] }
|
15
|
-
stub.send(verb, '/token/query_string') { |env| [200, {}, CGI.unescape(Addressable::URI.parse(env[:url]).query)] }
|
16
|
-
stub.send(verb, '/token/body') { |env| [200, {}, env[:body]] }
|
17
|
-
end
|
18
|
-
stub.post('/oauth/token') { |env| [200, {'Content-Type' => 'application/json'}, refresh_body] }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe '#initialize' do
|
24
|
-
it 'assigns client and token' do
|
25
|
-
expect(subject.client).to eq(client)
|
26
|
-
expect(subject.token).to eq(token)
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'assigns extra params' do
|
30
|
-
target = described_class.new(client, token, 'foo' => 'bar')
|
31
|
-
expect(target.params).to include('foo')
|
32
|
-
expect(target.params['foo']).to eq('bar')
|
33
|
-
end
|
34
|
-
|
35
|
-
def assert_initialized_token(target)
|
36
|
-
expect(target.token).to eq(token)
|
37
|
-
expect(target).to be_expires
|
38
|
-
expect(target.params.keys).to include('foo')
|
39
|
-
expect(target.params['foo']).to eq('bar')
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'initializes with a Hash' do
|
43
|
-
hash = {:access_token => token, :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
|
44
|
-
target = described_class.from_hash(client, hash)
|
45
|
-
assert_initialized_token(target)
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'from_hash does not modify opts hash' do
|
49
|
-
hash = {:access_token => token, :expires_at => Time.now.to_i}
|
50
|
-
hash_before = hash.dup
|
51
|
-
described_class.from_hash(client, hash)
|
52
|
-
expect(hash).to eq(hash_before)
|
53
|
-
end
|
54
|
-
|
55
|
-
it 'initializes with a form-urlencoded key/value string' do
|
56
|
-
kvform = "access_token=#{token}&expires_at=#{Time.now.to_i + 200}&foo=bar"
|
57
|
-
target = described_class.from_kvform(client, kvform)
|
58
|
-
assert_initialized_token(target)
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'sets options' do
|
62
|
-
target = described_class.new(client, token, :param_name => 'foo', :header_format => 'Bearer %', :mode => :body)
|
63
|
-
expect(target.options[:param_name]).to eq('foo')
|
64
|
-
expect(target.options[:header_format]).to eq('Bearer %')
|
65
|
-
expect(target.options[:mode]).to eq(:body)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'does not modify opts hash' do
|
69
|
-
opts = {:param_name => 'foo', :header_format => 'Bearer %', :mode => :body}
|
70
|
-
opts_before = opts.dup
|
71
|
-
described_class.new(client, token, opts)
|
72
|
-
expect(opts).to eq(opts_before)
|
73
|
-
end
|
74
|
-
|
75
|
-
describe 'expires_at' do
|
76
|
-
let(:expires_at) { 1_361_396_829 }
|
77
|
-
let(:hash) do
|
78
|
-
{
|
79
|
-
:access_token => token,
|
80
|
-
:expires_at => expires_at.to_s,
|
81
|
-
'foo' => 'bar',
|
82
|
-
}
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'initializes with an integer timestamp expires_at' do
|
86
|
-
target = described_class.from_hash(client, hash.merge(:expires_at => expires_at))
|
87
|
-
assert_initialized_token(target)
|
88
|
-
expect(target.expires_at).to eql(expires_at)
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'initializes with a string timestamp expires_at' do
|
92
|
-
target = described_class.from_hash(client, hash)
|
93
|
-
assert_initialized_token(target)
|
94
|
-
expect(target.expires_at).to eql(expires_at)
|
95
|
-
end
|
96
|
-
|
97
|
-
it 'initializes with a string time expires_at' do
|
98
|
-
target = described_class.from_hash(client, hash.merge(:expires_at => Time.at(expires_at).iso8601))
|
99
|
-
assert_initialized_token(target)
|
100
|
-
expect(target.expires_at).to eql(expires_at)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
describe '#request' do
|
106
|
-
context 'with :mode => :header' do
|
107
|
-
before do
|
108
|
-
subject.options[:mode] = :header
|
109
|
-
end
|
110
|
-
|
111
|
-
VERBS.each do |verb|
|
112
|
-
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
|
113
|
-
expect(subject.post('/token/header').body).to include(token)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
context 'with :mode => :query' do
|
119
|
-
before do
|
120
|
-
subject.options[:mode] = :query
|
121
|
-
end
|
122
|
-
|
123
|
-
VERBS.each do |verb|
|
124
|
-
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
|
125
|
-
expect(subject.post('/token/query').body).to eq(token)
|
126
|
-
end
|
127
|
-
|
128
|
-
it "sends a #{verb.to_s.upcase} request and options[:param_name] include [number]." do
|
129
|
-
subject.options[:param_name] = 'auth[1]'
|
130
|
-
expect(subject.__send__(verb, '/token/query_string').body).to include("auth[1]=#{token}")
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
context 'with :mode => :body' do
|
136
|
-
before do
|
137
|
-
subject.options[:mode] = :body
|
138
|
-
end
|
139
|
-
|
140
|
-
VERBS.each do |verb|
|
141
|
-
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
|
142
|
-
expect(subject.post('/token/body').body.split('=').last).to eq(token)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
context 'params include [number]' do
|
148
|
-
VERBS.each do |verb|
|
149
|
-
it "sends #{verb.to_s.upcase} correct query" do
|
150
|
-
expect(subject.__send__(verb, '/token/query_string', :params => {'foo[bar][1]' => 'val'}).body).to include('foo[bar][1]=val')
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
describe '#expires?' do
|
157
|
-
it 'is false if there is no expires_at' do
|
158
|
-
expect(described_class.new(client, token)).not_to be_expires
|
159
|
-
end
|
160
|
-
|
161
|
-
it 'is true if there is an expires_in' do
|
162
|
-
expect(described_class.new(client, token, :refresh_token => 'abaca', :expires_in => 600)).to be_expires
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'is true if there is an expires_at' do
|
166
|
-
expect(described_class.new(client, token, :refresh_token => 'abaca', :expires_in => Time.now.getutc.to_i + 600)).to be_expires
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
describe '#expired?' do
|
171
|
-
it 'is false if there is no expires_in or expires_at' do
|
172
|
-
expect(described_class.new(client, token)).not_to be_expired
|
173
|
-
end
|
174
|
-
|
175
|
-
it 'is false if expires_in is in the future' do
|
176
|
-
expect(described_class.new(client, token, :refresh_token => 'abaca', :expires_in => 10_800)).not_to be_expired
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'is true if expires_at is in the past' do
|
180
|
-
access = described_class.new(client, token, :refresh_token => 'abaca', :expires_in => 600)
|
181
|
-
@now = Time.now + 10_800
|
182
|
-
allow(Time).to receive(:now).and_return(@now)
|
183
|
-
expect(access).to be_expired
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
describe '#refresh!' do
|
188
|
-
let(:access) do
|
189
|
-
described_class.new(client, token, :refresh_token => 'abaca',
|
190
|
-
:expires_in => 600,
|
191
|
-
:param_name => 'o_param')
|
192
|
-
end
|
193
|
-
|
194
|
-
it 'returns a refresh token with appropriate values carried over' do
|
195
|
-
refreshed = access.refresh!
|
196
|
-
expect(access.client).to eq(refreshed.client)
|
197
|
-
expect(access.options[:param_name]).to eq(refreshed.options[:param_name])
|
198
|
-
end
|
199
|
-
|
200
|
-
context 'with a nil refresh_token in the response' do
|
201
|
-
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => nil) }
|
202
|
-
|
203
|
-
it 'copies the refresh_token from the original token' do
|
204
|
-
refreshed = access.refresh!
|
205
|
-
|
206
|
-
expect(refreshed.refresh_token).to eq(access.refresh_token)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
describe '#to_hash' do
|
212
|
-
it 'return a hash equals to the hash used to initialize access token' do
|
213
|
-
hash = {:access_token => token, :refresh_token => 'foobar', :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
|
214
|
-
access_token = described_class.from_hash(client, hash.clone)
|
215
|
-
expect(access_token.to_hash).to eq(hash)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe OAuth2::Authenticator do
|
4
|
-
subject do
|
5
|
-
described_class.new(client_id, client_secret, mode)
|
6
|
-
end
|
7
|
-
|
8
|
-
let(:client_id) { 'foo' }
|
9
|
-
let(:client_secret) { 'bar' }
|
10
|
-
let(:mode) { :undefined }
|
11
|
-
|
12
|
-
it 'raises NotImplementedError for unknown authentication mode' do
|
13
|
-
expect { subject.apply({}) }.to raise_error(NotImplementedError)
|
14
|
-
end
|
15
|
-
|
16
|
-
describe '#apply' do
|
17
|
-
context 'with parameter-based authentication' do
|
18
|
-
let(:mode) { :request_body }
|
19
|
-
|
20
|
-
it 'adds client_id and client_secret to params' do
|
21
|
-
output = subject.apply({})
|
22
|
-
expect(output).to eq('client_id' => 'foo', 'client_secret' => 'bar')
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'does not overwrite existing credentials' do
|
26
|
-
input = {'client_secret' => 's3cr3t'}
|
27
|
-
output = subject.apply(input)
|
28
|
-
expect(output).to eq('client_id' => 'foo', 'client_secret' => 's3cr3t')
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'preserves other parameters' do
|
32
|
-
input = {'state' => '42', :headers => {'A' => 'b'}}
|
33
|
-
output = subject.apply(input)
|
34
|
-
expect(output).to eq(
|
35
|
-
'client_id' => 'foo',
|
36
|
-
'client_secret' => 'bar',
|
37
|
-
'state' => '42',
|
38
|
-
:headers => {'A' => 'b'}
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
context 'using tls client authentication' do
|
43
|
-
let(:mode) { :tls_client_auth }
|
44
|
-
|
45
|
-
it 'does not add client_secret' do
|
46
|
-
output = subject.apply({})
|
47
|
-
expect(output).to eq('client_id' => 'foo')
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
context 'using private key jwt authentication' do
|
52
|
-
let(:mode) { :private_key_jwt }
|
53
|
-
|
54
|
-
it 'does not add client_secret or client_id' do
|
55
|
-
output = subject.apply({})
|
56
|
-
expect(output).to eq({})
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context 'with Basic authentication' do
|
62
|
-
let(:mode) { :basic_auth }
|
63
|
-
let(:header) { 'Basic ' + Base64.encode64("#{client_id}:#{client_secret}").delete("\n") }
|
64
|
-
|
65
|
-
it 'encodes credentials in headers' do
|
66
|
-
output = subject.apply({})
|
67
|
-
expect(output).to eq(:headers => {'Authorization' => header})
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'does not overwrite existing credentials' do
|
71
|
-
input = {:headers => {'Authorization' => 'Bearer abc123'}}
|
72
|
-
output = subject.apply(input)
|
73
|
-
expect(output).to eq(:headers => {'Authorization' => 'Bearer abc123'})
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'does not overwrite existing params or headers' do
|
77
|
-
input = {'state' => '42', :headers => {'A' => 'b'}}
|
78
|
-
output = subject.apply(input)
|
79
|
-
expect(output).to eq(
|
80
|
-
'state' => '42',
|
81
|
-
:headers => {'A' => 'b', 'Authorization' => header}
|
82
|
-
)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|