Skip to content

Commit db91e40

Browse files
committed
✨ token_name
- Allow caller to specify name of parameter that identifies an access token - 100% line code coverage - 100% branch code coverage
1 parent bb49c71 commit db91e40

File tree

10 files changed

+395
-50
lines changed

10 files changed

+395
-50
lines changed

.envrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export K_SOUP_COV_DO=true # Means you want code coverage
1919
# Available formats are html, xml, rcov, lcov, json, tty
2020
export K_SOUP_COV_COMMAND_NAME="RSpec Coverage"
2121
export K_SOUP_COV_FORMATTERS="html,tty"
22-
export K_SOUP_COV_MIN_BRANCH=99 # Means you want to enforce X% branch coverage
22+
export K_SOUP_COV_MIN_BRANCH=100 # Means you want to enforce X% branch coverage
2323
export K_SOUP_COV_MIN_LINE=100 # Means you want to enforce X% line coverage
2424
export K_SOUP_COV_MIN_HARD=true # Means you want the build to fail if the coverage thresholds are not met
2525
export K_SOUP_COV_MULTI_FORMATTERS=true

.github/workflows/coverage.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: Test Coverage
22

33
env:
4-
K_SOUP_COV_MIN_BRANCH: 98
5-
K_SOUP_COV_MIN_LINE: 98
4+
K_SOUP_COV_MIN_BRANCH: 100
5+
K_SOUP_COV_MIN_LINE: 100
66
K_SOUP_COV_MIN_HARD: true
77
K_SOUP_COV_FORMATTERS: "html,rcov,lcov,json,tty"
88
K_SOUP_COV_DO: true

.gitlab-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ variables:
77
K_SOUP_COV_DEBUG: true
88
K_SOUP_COV_DO: true
99
K_SOUP_COV_HARD: true
10-
K_SOUP_COV_MIN_BRANCH: 98
11-
K_SOUP_COV_MIN_LINE: 98
10+
K_SOUP_COV_MIN_BRANCH: 100
11+
K_SOUP_COV_MIN_LINE: 100
1212
K_SOUP_COV_VERBOSE: true
1313
K_SOUP_COV_FORMATTERS: "html,xml,rcov,lcov,json,tty"
1414
K_SOUP_COV_MULTI_FORMATTERS: true

.rubocop_gradual.lock

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
[9, 9, 25, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2012823020],
1919
[13, 9, 25, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2012823020]
2020
],
21-
"lib/oauth2/response.rb:877496664": [
21+
"lib/oauth2/response.rb:355921218": [
2222
[35, 5, 204, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 996912427]
2323
],
2424
"oauth2.gemspec:290828046": [
@@ -31,11 +31,11 @@
3131
[130, 3, 52, "Gemspec/DependencyVersion: Dependency version specification is required.", 3163430777],
3232
[131, 3, 48, "Gemspec/DependencyVersion: Dependency version specification is required.", 425065368]
3333
],
34-
"spec/oauth2/access_token_spec.rb:1576666213": [
34+
"spec/oauth2/access_token_spec.rb:759866110": [
3535
[3, 1, 34, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/access_token*_spec.rb`.", 1972107547],
36-
[590, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088],
37-
[660, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639],
38-
[664, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967]
36+
[594, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088],
37+
[664, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639],
38+
[668, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967]
3939
],
4040
"spec/oauth2/authenticator_spec.rb:853320290": [
4141
[3, 1, 36, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/authenticator*_spec.rb`.", 819808017],
@@ -44,26 +44,26 @@
4444
[69, 15, 38, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1480816240],
4545
[79, 13, 23, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2314399065]
4646
],
47-
"spec/oauth2/client_spec.rb:3773709445": [
47+
"spec/oauth2/client_spec.rb:824695973": [
4848
[6, 1, 29, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/client*_spec.rb`.", 439549885],
4949
[174, 7, 492, "RSpec/NoExpectationExample: No expectation found in this example.", 1272021224],
5050
[193, 7, 592, "RSpec/NoExpectationExample: No expectation found in this example.", 3428877205],
5151
[206, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2320605227],
5252
[221, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1276531672],
5353
[236, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1383956904],
5454
[251, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3376202107],
55-
[590, 5, 360, "RSpec/NoExpectationExample: No expectation found in this example.", 536201463],
56-
[599, 5, 461, "RSpec/NoExpectationExample: No expectation found in this example.", 3392600621],
57-
[610, 5, 340, "RSpec/NoExpectationExample: No expectation found in this example.", 244592251],
58-
[655, 63, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785],
59-
[700, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
60-
[704, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
61-
[712, 7, 89, "RSpec/NoExpectationExample: No expectation found in this example.", 4609419],
62-
[800, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
63-
[804, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
64-
[884, 17, 12, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 664794325],
65-
[909, 5, 459, "RSpec/NoExpectationExample: No expectation found in this example.", 2216851076],
66-
[919, 7, 450, "RSpec/NoExpectationExample: No expectation found in this example.", 2619808549]
55+
[869, 5, 360, "RSpec/NoExpectationExample: No expectation found in this example.", 536201463],
56+
[878, 5, 461, "RSpec/NoExpectationExample: No expectation found in this example.", 3392600621],
57+
[889, 5, 340, "RSpec/NoExpectationExample: No expectation found in this example.", 244592251],
58+
[934, 63, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785],
59+
[979, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
60+
[983, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
61+
[991, 7, 89, "RSpec/NoExpectationExample: No expectation found in this example.", 4609419],
62+
[1079, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
63+
[1083, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
64+
[1163, 17, 12, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 664794325],
65+
[1188, 5, 459, "RSpec/NoExpectationExample: No expectation found in this example.", 2216851076],
66+
[1198, 7, 450, "RSpec/NoExpectationExample: No expectation found in this example.", 2619808549]
6767
],
6868
"spec/oauth2/error_spec.rb:1209122273": [
6969
[23, 1, 28, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/error*_spec.rb`.", 3385870076],

Rakefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ begin
4242
require "rubocop/lts"
4343

4444
Rubocop::Lts.install_tasks
45-
defaults << "rubocop_gradual"
45+
# Make autocorrect the default rubocop task
46+
defaults << "rubocop_gradual:autocorrect"
4647
rescue LoadError
4748
desc("(stub) rubocop_gradual is unavailable")
4849
task(:rubocop_gradual) do

lib/oauth2/access_token.rb

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,30 @@ class << self
3232
# @note If multiple token keys are present, a warning will be issued unless
3333
# OAuth2.config.silence_extra_tokens_warning is true
3434
# @note For "soon-to-expire"/"clock-skew" functionality see the `:expires_latency` option.
35+
# @mote If snaky key conversion is being used, token_name needs to match the converted key.
3536
#
3637
# @example
3738
# hash = { 'access_token' => 'token_value', 'refresh_token' => 'refresh_value' }
3839
# access_token = OAuth2::AccessToken.from_hash(client, hash)
3940
def from_hash(client, hash)
4041
fresh = hash.dup
41-
supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
42-
key = supported_keys[0]
43-
extra_tokens_warning(supported_keys, key)
44-
token = fresh.delete(key)
42+
# If token_name is present, then use that key name
43+
if fresh.key?(:token_name)
44+
key = fresh[:token_name]
45+
if key.nil? || !fresh.key?(key)
46+
warn(%[
47+
OAuth2::AccessToken#from_hash key mismatch.
48+
Custom token_name (#{key}) does match any keys (#{fresh.keys})
49+
You may need to set `snaky: false`. See inline documentation for more info.
50+
])
51+
end
52+
else
53+
# Otherwise, if one of the supported default keys is present, use whichever has precedence
54+
supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
55+
key = supported_keys[0]
56+
extra_tokens_warning(supported_keys, key)
57+
end
58+
token = fresh.delete(key) || ""
4559
new(client, token, fresh)
4660
end
4761

@@ -89,10 +103,11 @@ def extra_tokens_warning(supported_keys, key)
89103
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
90104
# @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
91105
# Access Token value in :body or :query transmission mode
106+
# @option opts [String] :token_name (nil) the name of the response parameter that identifies the access token
107+
# When nil one of TOKEN_KEY_LOOKUP will be used
92108
def initialize(client, token, opts = {})
93109
@client = client
94110
@token = token.to_s
95-
96111
opts = opts.dup
97112
%i[refresh_token expires_in expires_at expires_latency].each do |arg|
98113
instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
@@ -118,6 +133,8 @@ def initialize(client, token, opts = {})
118133
header_format: opts.delete(:header_format) || "Bearer %s",
119134
param_name: opts.delete(:param_name) || "access_token",
120135
}
136+
@options[:token_name] = opts.delete(:token_name) if opts.key?(:token_name)
137+
121138
@params = opts
122139
end
123140

@@ -166,9 +183,21 @@ def refresh(params = {}, access_token_opts = {})
166183

167184
# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
168185
#
186+
# @note Don't return expires_latency because it has already been deducted from expires_at
187+
#
169188
# @return [Hash] a hash of AccessToken property values
170189
def to_hash
171-
params.merge(access_token: token, refresh_token: refresh_token, expires_at: expires_at)
190+
hsh = {
191+
**params,
192+
access_token: token,
193+
refresh_token: refresh_token,
194+
expires_at: expires_at,
195+
mode: options[:mode],
196+
header_format: options[:header_format],
197+
param_name: options[:param_name],
198+
}
199+
hsh[:token_name] = options[:token_name] if options.key?(:token_name)
200+
hsh
172201
end
173202

174203
# Make a request with the Access Token

lib/oauth2/client.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
require "faraday"
44
require "logger"
55

6+
# :nocov: since coverage tracking only runs on the builds with Faraday v2
7+
# We do run builds on Faraday v0 (and v1!), so this code is actually covered!
8+
# This is the only nocov in the whole project!
69
if Faraday::Utils.respond_to?(:default_space_encoding)
710
# This setting doesn't exist in faraday 0.x
811
Faraday::Utils.default_space_encoding = "%20"
912
end
13+
# :nocov:
1014

1115
module OAuth2
1216
ConnectionError = Class.new(Faraday::ConnectionFailed)

lib/oauth2/response.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ def parsed
9090
end
9191
end
9292

93-
@parsed = SnakyHash::StringKeyed.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash)
93+
if options[:snaky] && @parsed.is_a?(Hash)
94+
parsed = SnakyHash::StringKeyed.new(@parsed)
95+
@parsed = parsed.to_h
96+
end
9497

9598
@parsed
9699
end

spec/oauth2/access_token_spec.rb

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# frozen_string_literal: true
22

33
RSpec.describe OAuth2::AccessToken do
4-
subject { described_class.new(client, token) }
4+
subject { described_class.new(client, token, token_options) }
55

66
let(:base_options) { {site: "https://api.example.com"} }
7+
let(:token_options) { {} }
78
let(:options) { {} }
89
let(:token) { "monkey" }
910
let(:refresh_body) { JSON.dump(access_token: "refreshed_foo", expires_in: 600, refresh_token: "refresh_bar") }
@@ -32,6 +33,9 @@
3233
:refresh_token => "foobar",
3334
:expires_at => Time.now.to_i + 200,
3435
"foo" => "bar",
36+
:header_format => "Bearer %",
37+
:mode => :header,
38+
:param_name => "access_token",
3539
}
3640
end
3741

@@ -744,10 +748,35 @@ def self.contains_token?(hash)
744748

745749
describe "#to_hash" do
746750
it "return a hash equal to the hash used to initialize access token" do
747-
hash = {:access_token => token, :refresh_token => "foobar", :expires_at => Time.now.to_i + 200, "foo" => "bar"}
751+
hash = {
752+
:access_token => token,
753+
:refresh_token => "foobar",
754+
:expires_at => Time.now.to_i + 200,
755+
:header_format => "Bearer %",
756+
:mode => :header,
757+
:param_name => "access_token",
758+
"foo" => "bar",
759+
}
748760
access_token = described_class.from_hash(client, hash.clone)
749761
expect(access_token.to_hash).to eq(hash)
750762
end
763+
764+
context "with token_name" do
765+
it "return a hash equal to the hash used to initialize access token" do
766+
hash = {
767+
:access_token => "",
768+
:refresh_token => "foobar",
769+
:expires_at => Time.now.to_i + 200,
770+
:header_format => "Bearer %",
771+
:mode => :header,
772+
:param_name => "access_token",
773+
:token_name => "banana_face",
774+
"foo" => "bar",
775+
}
776+
access_token = described_class.from_hash(client, hash.clone)
777+
expect(access_token.to_hash).to eq(hash)
778+
end
779+
end
751780
end
752781

753782
describe "#inspect" do

0 commit comments

Comments
 (0)