oauth2 2.0.9 → 2.0.11
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +329 -168
- data/CONTRIBUTING.md +126 -31
- data/{LICENSE → LICENSE.txt} +1 -1
- data/README.md +643 -266
- data/SECURITY.md +2 -2
- data/lib/oauth2/access_token.rb +210 -32
- data/lib/oauth2/authenticator.rb +9 -6
- data/lib/oauth2/client.rb +307 -96
- data/lib/oauth2/error.rb +14 -14
- data/lib/oauth2/filtered_attributes.rb +31 -0
- data/lib/oauth2/response.rb +78 -42
- data/lib/oauth2/strategy/assertion.rb +4 -4
- data/lib/oauth2/strategy/auth_code.rb +3 -3
- data/lib/oauth2/strategy/client_credentials.rb +2 -2
- data/lib/oauth2/strategy/implicit.rb +3 -3
- data/lib/oauth2/strategy/password.rb +6 -4
- data/lib/oauth2/version.rb +1 -1
- data/lib/oauth2.rb +23 -18
- data.tar.gz.sig +0 -0
- metadata +200 -68
- metadata.gz.sig +0 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module OAuth2
|
2
|
+
module FilteredAttributes
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def filtered_attributes(*attributes)
|
9
|
+
@filtered_attribute_names = attributes.map(&:to_sym)
|
10
|
+
end
|
11
|
+
|
12
|
+
def filtered_attribute_names
|
13
|
+
@filtered_attribute_names || []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
filtered_attribute_names = self.class.filtered_attribute_names
|
19
|
+
return super if filtered_attribute_names.empty?
|
20
|
+
|
21
|
+
inspected_vars = instance_variables.map do |var|
|
22
|
+
if filtered_attribute_names.any? { |filtered_var| var.to_s.include?(filtered_var.to_s) }
|
23
|
+
"#{var}=[FILTERED]"
|
24
|
+
else
|
25
|
+
"#{var}=#{instance_variable_get(var).inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
"#<#{self.class}:#{object_id} #{inspected_vars.join(", ")}>"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/oauth2/response.rb
CHANGED
@@ -1,37 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "multi_xml"
|
5
|
+
require "rack"
|
6
6
|
|
7
7
|
module OAuth2
|
8
|
-
#
|
8
|
+
# The Response class handles HTTP responses in the OAuth2 gem, providing methods
|
9
|
+
# to access and parse response data in various formats.
|
10
|
+
#
|
11
|
+
# @since 1.0.0
|
9
12
|
class Response
|
13
|
+
# Default configuration options for Response instances
|
14
|
+
#
|
15
|
+
# @return [Hash] The default options hash
|
10
16
|
DEFAULT_OPTIONS = {
|
11
17
|
parse: :automatic,
|
12
18
|
snaky: true,
|
19
|
+
snaky_hash_klass: SnakyHash::StringKeyed,
|
13
20
|
}.freeze
|
21
|
+
|
22
|
+
# @return [Faraday::Response] The raw Faraday response object
|
14
23
|
attr_reader :response
|
24
|
+
|
25
|
+
# @return [Hash] The options hash for this instance
|
15
26
|
attr_accessor :options
|
16
27
|
|
17
|
-
#
|
18
|
-
#
|
28
|
+
# @private
|
29
|
+
# Storage for response body parser procedures
|
30
|
+
#
|
31
|
+
# @return [Hash<Symbol, Proc>] Hash of parser procs keyed by format symbol
|
19
32
|
@@parsers = {
|
20
33
|
query: ->(body) { Rack::Utils.parse_query(body) },
|
21
34
|
text: ->(body) { body },
|
22
35
|
}
|
23
36
|
|
24
|
-
#
|
37
|
+
# @private
|
38
|
+
# Maps content types to parser symbols
|
39
|
+
#
|
40
|
+
# @return [Hash<String, Symbol>] Hash of content types mapped to parser symbols
|
25
41
|
@@content_types = {
|
26
|
-
|
27
|
-
|
42
|
+
"application/x-www-form-urlencoded" => :query,
|
43
|
+
"text/plain" => :text,
|
28
44
|
}
|
29
45
|
|
30
46
|
# Adds a new content type parser.
|
31
47
|
#
|
32
|
-
# @param [Symbol] key A descriptive symbol key such as :json or :query
|
33
|
-
# @param [Array] mime_types One or more mime types to which this parser applies
|
34
|
-
# @yield [String]
|
48
|
+
# @param [Symbol] key A descriptive symbol key such as :json or :query
|
49
|
+
# @param [Array<String>, String] mime_types One or more mime types to which this parser applies
|
50
|
+
# @yield [String] Block that will be called to parse the response body
|
51
|
+
# @yieldparam [String] body The response body to parse
|
52
|
+
# @return [void]
|
35
53
|
def self.register_parser(key, mime_types, &block)
|
36
54
|
key = key.to_sym
|
37
55
|
@@parsers[key] = block
|
@@ -43,38 +61,48 @@ module OAuth2
|
|
43
61
|
# Initializes a Response instance
|
44
62
|
#
|
45
63
|
# @param [Faraday::Response] response The Faraday response instance
|
46
|
-
# @param [Symbol] parse (:automatic)
|
47
|
-
#
|
48
|
-
# @param [
|
49
|
-
#
|
50
|
-
# @
|
51
|
-
|
64
|
+
# @param [Symbol] parse (:automatic) How to parse the response body
|
65
|
+
# @param [Boolean] snaky (true) Whether to convert parsed response to snake_case using SnakyHash
|
66
|
+
# @param [Class, nil] snaky_hash_klass (nil) Custom class for snake_case hash conversion
|
67
|
+
# @param [Hash] options Additional options for the response
|
68
|
+
# @option options [Symbol] :parse (:automatic) Parse strategy (:query, :json, or :automatic)
|
69
|
+
# @option options [Boolean] :snaky (true) Enable/disable snake_case conversion
|
70
|
+
# @option options [Class] :snaky_hash_klass (SnakyHash::StringKeyed) Class to use for hash conversion
|
71
|
+
# @return [OAuth2::Response] The new Response instance
|
72
|
+
def initialize(response, parse: :automatic, snaky: true, snaky_hash_klass: nil, **options)
|
52
73
|
@response = response
|
53
74
|
@options = {
|
54
75
|
parse: parse,
|
55
76
|
snaky: snaky,
|
77
|
+
snaky_hash_klass: snaky_hash_klass,
|
56
78
|
}.merge(options)
|
57
79
|
end
|
58
80
|
|
59
81
|
# The HTTP response headers
|
82
|
+
#
|
83
|
+
# @return [Hash] The response headers
|
60
84
|
def headers
|
61
85
|
response.headers
|
62
86
|
end
|
63
87
|
|
64
88
|
# The HTTP response status code
|
89
|
+
#
|
90
|
+
# @return [Integer] The response status code
|
65
91
|
def status
|
66
92
|
response.status
|
67
93
|
end
|
68
94
|
|
69
95
|
# The HTTP response body
|
96
|
+
#
|
97
|
+
# @return [String] The response body or empty string if nil
|
70
98
|
def body
|
71
|
-
response.body ||
|
99
|
+
response.body || ""
|
72
100
|
end
|
73
101
|
|
74
|
-
# The
|
102
|
+
# The parsed response body
|
75
103
|
#
|
76
|
-
# @return [Object]
|
77
|
-
# @return [nil] If
|
104
|
+
# @return [Object, SnakyHash::StringKeyed] The parsed response body
|
105
|
+
# @return [nil] If no parser is available
|
78
106
|
def parsed
|
79
107
|
return @parsed if defined?(@parsed)
|
80
108
|
|
@@ -90,34 +118,37 @@ module OAuth2
|
|
90
118
|
end
|
91
119
|
end
|
92
120
|
|
93
|
-
|
121
|
+
if options[:snaky] && @parsed.is_a?(Hash)
|
122
|
+
hash_klass = options[:snaky_hash_klass] || DEFAULT_OPTIONS[:snaky_hash_klass]
|
123
|
+
@parsed = hash_klass[@parsed]
|
124
|
+
end
|
94
125
|
|
95
126
|
@parsed
|
96
127
|
end
|
97
128
|
|
98
|
-
#
|
129
|
+
# Determines the content type of the response
|
130
|
+
#
|
131
|
+
# @return [String, nil] The content type or nil if headers are not present
|
99
132
|
def content_type
|
100
|
-
return
|
133
|
+
return unless response.headers
|
101
134
|
|
102
|
-
((response.headers.values_at(
|
135
|
+
((response.headers.values_at("content-type", "Content-Type").compact.first || "").split(";").first || "").strip.downcase
|
103
136
|
end
|
104
137
|
|
105
|
-
# Determines the parser
|
106
|
-
# that will be passed the {#body} (and optional {#response}) to supply
|
107
|
-
# {#parsed}.
|
138
|
+
# Determines the parser to be used for the response body
|
108
139
|
#
|
109
|
-
# The parser can be supplied as the +:parse+ option in the form of a Proc
|
110
|
-
#
|
111
|
-
#
|
140
|
+
# @note The parser can be supplied as the +:parse+ option in the form of a Proc
|
141
|
+
# (or other Object responding to #call) or a Symbol. In the latter case,
|
142
|
+
# the actual parser will be looked up in {@@parsers} by the supplied Symbol.
|
112
143
|
#
|
113
|
-
# If no +:parse+ option is supplied, the lookup Symbol will be determined
|
114
|
-
#
|
144
|
+
# @note If no +:parse+ option is supplied, the lookup Symbol will be determined
|
145
|
+
# by looking up {#content_type} in {@@content_types}.
|
115
146
|
#
|
116
|
-
# If {#parser} is a Proc, it will be called with no arguments, just
|
117
|
-
#
|
147
|
+
# @note If {#parser} is a Proc, it will be called with no arguments, just
|
148
|
+
# {#body}, or {#body} and {#response}, depending on the Proc's arity.
|
118
149
|
#
|
119
|
-
# @return [Proc, #call]
|
120
|
-
# @return [nil] If no parser
|
150
|
+
# @return [Proc, #call] The parser proc or callable object
|
151
|
+
# @return [nil] If no suitable parser is found
|
121
152
|
def parser
|
122
153
|
return @parser if defined?(@parser)
|
123
154
|
|
@@ -133,16 +164,21 @@ module OAuth2
|
|
133
164
|
end
|
134
165
|
end
|
135
166
|
|
136
|
-
|
167
|
+
# Register XML parser
|
168
|
+
# @api private
|
169
|
+
OAuth2::Response.register_parser(:xml, ["text/xml", "application/rss+xml", "application/rdf+xml", "application/atom+xml", "application/xml"]) do |body|
|
137
170
|
next body unless body.respond_to?(:to_str)
|
138
171
|
|
139
172
|
MultiXml.parse(body)
|
140
173
|
end
|
141
174
|
|
142
|
-
|
175
|
+
# Register JSON parser
|
176
|
+
# @api private
|
177
|
+
OAuth2::Response.register_parser(:json, ["application/json", "text/javascript", "application/hal+json", "application/vnd.collection+json", "application/vnd.api+json", "application/problem+json"]) do |body|
|
143
178
|
next body unless body.respond_to?(:to_str)
|
144
179
|
|
145
|
-
body = body.dup.force_encoding(
|
180
|
+
body = body.dup.force_encoding(Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)
|
181
|
+
next body if body.respond_to?(:empty?) && body.empty?
|
146
182
|
|
147
|
-
|
183
|
+
JSON.parse(body)
|
148
184
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "jwt"
|
4
4
|
|
5
5
|
module OAuth2
|
6
6
|
module Strategy
|
@@ -34,7 +34,7 @@ module OAuth2
|
|
34
34
|
#
|
35
35
|
# @raise [NotImplementedError]
|
36
36
|
def authorize_url
|
37
|
-
raise(NotImplementedError,
|
37
|
+
raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
|
38
38
|
end
|
39
39
|
|
40
40
|
# Retrieve an access token given the specified client.
|
@@ -87,13 +87,13 @@ module OAuth2
|
|
87
87
|
|
88
88
|
def build_request(assertion, request_opts = {})
|
89
89
|
{
|
90
|
-
grant_type:
|
90
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
91
91
|
assertion: assertion,
|
92
92
|
}.merge(request_opts)
|
93
93
|
end
|
94
94
|
|
95
95
|
def build_assertion(claims, encoding_opts)
|
96
|
-
raise ArgumentError.new(message:
|
96
|
+
raise ArgumentError.new(message: "Please provide an encoding_opts hash with :algorithm and :key") if !encoding_opts.is_a?(Hash) || (%i[algorithm key] - encoding_opts.keys).any?
|
97
97
|
|
98
98
|
JWT.encode(claims, encoding_opts[:key], encoding_opts[:algorithm])
|
99
99
|
end
|
@@ -10,7 +10,7 @@ module OAuth2
|
|
10
10
|
#
|
11
11
|
# @param [Hash] params additional query parameters
|
12
12
|
def authorize_params(params = {})
|
13
|
-
params.merge(
|
13
|
+
params.merge("response_type" => "code", "client_id" => @client.id)
|
14
14
|
end
|
15
15
|
|
16
16
|
# The authorization URL endpoint of the provider
|
@@ -28,7 +28,7 @@ module OAuth2
|
|
28
28
|
# @param [Hash] opts access_token_opts, @see Client#get_token
|
29
29
|
# @note that you must also provide a :redirect_uri with most OAuth 2.0 providers
|
30
30
|
def get_token(code, params = {}, opts = {})
|
31
|
-
params = {
|
31
|
+
params = {"grant_type" => "authorization_code", "code" => code}.merge(@client.redirection_params).merge(params)
|
32
32
|
params_dup = params.dup
|
33
33
|
params.each_key do |key|
|
34
34
|
params_dup[key.to_s] = params_dup.delete(key) if key.is_a?(Symbol)
|
@@ -40,7 +40,7 @@ module OAuth2
|
|
40
40
|
private
|
41
41
|
|
42
42
|
def assert_valid_params(params)
|
43
|
-
raise(ArgumentError,
|
43
|
+
raise(ArgumentError, "client_secret is not allowed in authorize URL query params") if params.key?(:client_secret) || params.key?("client_secret")
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -10,7 +10,7 @@ module OAuth2
|
|
10
10
|
#
|
11
11
|
# @raise [NotImplementedError]
|
12
12
|
def authorize_url
|
13
|
-
raise(NotImplementedError,
|
13
|
+
raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
|
14
14
|
end
|
15
15
|
|
16
16
|
# Retrieve an access token given the specified client.
|
@@ -18,7 +18,7 @@ module OAuth2
|
|
18
18
|
# @param [Hash] params additional params
|
19
19
|
# @param [Hash] opts options
|
20
20
|
def get_token(params = {}, opts = {})
|
21
|
-
params = params.merge(
|
21
|
+
params = params.merge("grant_type" => "client_credentials")
|
22
22
|
@client.get_token(params, opts)
|
23
23
|
end
|
24
24
|
end
|
@@ -10,7 +10,7 @@ module OAuth2
|
|
10
10
|
#
|
11
11
|
# @param [Hash] params additional query parameters
|
12
12
|
def authorize_params(params = {})
|
13
|
-
params.merge(
|
13
|
+
params.merge("response_type" => "token", "client_id" => @client.id)
|
14
14
|
end
|
15
15
|
|
16
16
|
# The authorization URL endpoint of the provider
|
@@ -25,13 +25,13 @@ module OAuth2
|
|
25
25
|
#
|
26
26
|
# @raise [NotImplementedError]
|
27
27
|
def get_token(*)
|
28
|
-
raise(NotImplementedError,
|
28
|
+
raise(NotImplementedError, "The token is accessed differently in this strategy")
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def assert_valid_params(params)
|
34
|
-
raise(ArgumentError,
|
34
|
+
raise(ArgumentError, "client_secret is not allowed in authorize URL query params") if params.key?(:client_secret) || params.key?("client_secret")
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -10,7 +10,7 @@ module OAuth2
|
|
10
10
|
#
|
11
11
|
# @raise [NotImplementedError]
|
12
12
|
def authorize_url
|
13
|
-
raise(NotImplementedError,
|
13
|
+
raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
|
14
14
|
end
|
15
15
|
|
16
16
|
# Retrieve an access token given the specified End User username and password.
|
@@ -19,9 +19,11 @@ module OAuth2
|
|
19
19
|
# @param [String] password the End User password
|
20
20
|
# @param [Hash] params additional params
|
21
21
|
def get_token(username, password, params = {}, opts = {})
|
22
|
-
params = {
|
23
|
-
|
24
|
-
|
22
|
+
params = {
|
23
|
+
"grant_type" => "password",
|
24
|
+
"username" => username,
|
25
|
+
"password" => password,
|
26
|
+
}.merge(params)
|
25
27
|
@client.get_token(params, opts)
|
26
28
|
end
|
27
29
|
end
|
data/lib/oauth2/version.rb
CHANGED
data/lib/oauth2.rb
CHANGED
@@ -1,33 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# includes modules from stdlib
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "cgi"
|
5
|
+
require "time"
|
6
6
|
|
7
7
|
# third party gems
|
8
|
-
require
|
9
|
-
require
|
8
|
+
require "snaky_hash"
|
9
|
+
require "version_gem"
|
10
10
|
|
11
11
|
# includes gem files
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
require_relative "oauth2/version"
|
13
|
+
require_relative "oauth2/filtered_attributes"
|
14
|
+
require_relative "oauth2/error"
|
15
|
+
require_relative "oauth2/authenticator"
|
16
|
+
require_relative "oauth2/client"
|
17
|
+
require_relative "oauth2/strategy/base"
|
18
|
+
require_relative "oauth2/strategy/auth_code"
|
19
|
+
require_relative "oauth2/strategy/implicit"
|
20
|
+
require_relative "oauth2/strategy/password"
|
21
|
+
require_relative "oauth2/strategy/client_credentials"
|
22
|
+
require_relative "oauth2/strategy/assertion"
|
23
|
+
require_relative "oauth2/access_token"
|
24
|
+
require_relative "oauth2/response"
|
24
25
|
|
25
26
|
# The namespace of this library
|
26
27
|
module OAuth2
|
27
|
-
|
28
|
+
OAUTH_DEBUG = ENV.fetch("OAUTH_DEBUG", "false").casecmp("true").zero?
|
29
|
+
DEFAULT_CONFIG = SnakyHash::SymbolKeyed.new(
|
30
|
+
silence_extra_tokens_warning: true,
|
31
|
+
silence_no_tokens_warning: true,
|
32
|
+
)
|
28
33
|
@config = DEFAULT_CONFIG.dup
|
29
34
|
class << self
|
30
|
-
|
35
|
+
attr_reader :config
|
31
36
|
end
|
32
37
|
def configure
|
33
38
|
yield @config
|
data.tar.gz.sig
ADDED
Binary file
|