simple_oauth 0.5.1 → 1.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/.rubocop.yml +1 -1
- data/CHANGELOG.md +32 -0
- data/README.md +116 -2
- data/Rakefile +2 -0
- data/Steepfile +3 -2
- data/lib/simple_oauth/encoding.rb +2 -0
- data/lib/simple_oauth/errors.rb +7 -2
- data/lib/simple_oauth/header/class_methods.rb +23 -25
- data/lib/simple_oauth/header/params.rb +7 -7
- data/lib/simple_oauth/header.rb +7 -6
- data/lib/simple_oauth/oauth2/authorization_response.rb +162 -0
- data/lib/simple_oauth/oauth2/client.rb +307 -0
- data/lib/simple_oauth/oauth2/error.rb +95 -0
- data/lib/simple_oauth/oauth2/pkce.rb +104 -0
- data/lib/simple_oauth/oauth2/request.rb +61 -0
- data/lib/simple_oauth/oauth2/response_body.rb +26 -0
- data/lib/simple_oauth/oauth2/token.rb +209 -0
- data/lib/simple_oauth/oauth2.rb +31 -0
- data/lib/simple_oauth/parser.rb +7 -1
- data/lib/simple_oauth/signature.rb +12 -7
- data/lib/simple_oauth/version.rb +4 -2
- data/lib/simple_oauth.rb +6 -7
- data/sig/simple_oauth/header/class_methods.rbs +3 -5
- data/sig/simple_oauth/oauth2.rbs +144 -0
- data/sig/simple_oauth/signature.rbs +1 -1
- data/sig/simple_oauth.rbs +7 -3
- metadata +13 -32
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
require_relative "response_body"
|
|
5
|
+
|
|
6
|
+
module SimpleOAuth
|
|
7
|
+
module OAuth2
|
|
8
|
+
# An access token from a successful token response, per RFC 6749 Section 5.1
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
# @example Parse a token response
|
|
12
|
+
# token = SimpleOAuth::OAuth2::Token.from_response(status: 200, body: response_body)
|
|
13
|
+
# token.access_token # => "2YotnFZFEjr1zCsicMWpAA"
|
|
14
|
+
class Token
|
|
15
|
+
# The description of a token response that carries no usable access token
|
|
16
|
+
NO_ACCESS_TOKEN = "token response has no access_token"
|
|
17
|
+
# The description of a token response whose lifetime is not a number of seconds
|
|
18
|
+
INVALID_EXPIRES_IN = "token response has an invalid expires_in"
|
|
19
|
+
# The error message for an access token that cannot be used
|
|
20
|
+
INVALID_ACCESS_TOKEN = "The access_token must be a non-empty String"
|
|
21
|
+
# The error message for a token lifetime that is not a number of seconds
|
|
22
|
+
INVALID_LIFETIME = "The expires_in must be a number of seconds"
|
|
23
|
+
|
|
24
|
+
# The access token
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
27
|
+
# @return [String] the access token
|
|
28
|
+
# @example
|
|
29
|
+
# token.access_token # => "2YotnFZFEjr1zCsicMWpAA"
|
|
30
|
+
attr_reader :access_token
|
|
31
|
+
|
|
32
|
+
# The token type, such as bearer
|
|
33
|
+
#
|
|
34
|
+
# @api public
|
|
35
|
+
# @return [String, nil] the token type
|
|
36
|
+
# @example
|
|
37
|
+
# token.token_type # => "bearer"
|
|
38
|
+
attr_reader :token_type
|
|
39
|
+
|
|
40
|
+
# The lifetime of the access token in seconds
|
|
41
|
+
#
|
|
42
|
+
# @api public
|
|
43
|
+
# @return [Integer, nil] the lifetime in seconds
|
|
44
|
+
# @example
|
|
45
|
+
# token.expires_in # => 3600
|
|
46
|
+
attr_reader :expires_in
|
|
47
|
+
|
|
48
|
+
# The refresh token, if one was issued
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
51
|
+
# @return [String, nil] the refresh token
|
|
52
|
+
# @example
|
|
53
|
+
# token.refresh_token # => "tGzv3JOkF0XG5Qx2TlKWIA"
|
|
54
|
+
attr_reader :refresh_token
|
|
55
|
+
|
|
56
|
+
# The granted scope, as a space-delimited string
|
|
57
|
+
#
|
|
58
|
+
# @api public
|
|
59
|
+
# @return [String, nil] the granted scope
|
|
60
|
+
# @example
|
|
61
|
+
# token.scope # => "tweet.read users.read"
|
|
62
|
+
attr_reader :scope
|
|
63
|
+
|
|
64
|
+
# The time when the access token expires
|
|
65
|
+
#
|
|
66
|
+
# @api public
|
|
67
|
+
# @return [Time, nil] the expiration time
|
|
68
|
+
# @example
|
|
69
|
+
# token.expires_at # => 2026-09-11 13:00:00 UTC
|
|
70
|
+
attr_reader :expires_at
|
|
71
|
+
|
|
72
|
+
# Every parameter of the token response, including nonstandard ones
|
|
73
|
+
#
|
|
74
|
+
# @api public
|
|
75
|
+
# @return [Hash{String => Object}] the parameters
|
|
76
|
+
# @example
|
|
77
|
+
# token.params["example_parameter"] # => "example_value"
|
|
78
|
+
attr_reader :params
|
|
79
|
+
|
|
80
|
+
# Check whether a value is usable as an access token (RFC 6749 Section A.12)
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
# @param value [Object] the value from the token response
|
|
84
|
+
# @return [Boolean] true if the value is a non-empty String
|
|
85
|
+
# @example
|
|
86
|
+
# SimpleOAuth::OAuth2::Token.access_token?("2YotnFZFEjr1zCsicMWpAA") # => true
|
|
87
|
+
def self.access_token?(value)
|
|
88
|
+
value.is_a?(String) && !value.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check whether a value is usable as a token lifetime (RFC 6749 Section 5.1)
|
|
92
|
+
#
|
|
93
|
+
# @api private
|
|
94
|
+
# @param value [Object] the value from the token response
|
|
95
|
+
# @return [Boolean] true if the value is absent, or a number of seconds
|
|
96
|
+
# @example
|
|
97
|
+
# SimpleOAuth::OAuth2::Token.expires_in?(3600) # => true
|
|
98
|
+
def self.expires_in?(value)
|
|
99
|
+
value.nil? || !Integer(value, exception: false).nil?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The reason the parameters of a token response cannot be used, if there is one
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
# @param params [Hash] the token response parameters
|
|
106
|
+
# @return [String, nil] the reason, or nil if the response is usable
|
|
107
|
+
# @example
|
|
108
|
+
# SimpleOAuth::OAuth2::Token.rejection_reason({"access_token" => "abc"}) # => nil
|
|
109
|
+
def self.rejection_reason(params)
|
|
110
|
+
return NO_ACCESS_TOKEN unless access_token?(params["access_token"])
|
|
111
|
+
|
|
112
|
+
INVALID_EXPIRES_IN unless expires_in?(params["expires_in"])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Parse a token response, raising the endpoint's error if it failed
|
|
116
|
+
#
|
|
117
|
+
# @api public
|
|
118
|
+
# @param status [Integer, String] the HTTP status of the response
|
|
119
|
+
# @param body [String, nil] the response body
|
|
120
|
+
# @param issued_at [Time] when the token was issued, used to compute its expiration
|
|
121
|
+
# @return [Token] the token
|
|
122
|
+
# @raise [Error] if the response is not successful, or carries no usable token
|
|
123
|
+
# @raise [ArgumentError] if the status is not an HTTP status
|
|
124
|
+
# @example
|
|
125
|
+
# SimpleOAuth::OAuth2::Token.from_response(status: 200, body: '{"access_token":"abc","token_type":"bearer"}')
|
|
126
|
+
def self.from_response(status:, body:, issued_at: Time.now)
|
|
127
|
+
code = Error.http_status(status)
|
|
128
|
+
raise Error.from_response(status:, body:) unless (200..299).cover?(code)
|
|
129
|
+
|
|
130
|
+
params = ResponseBody.parse(body)
|
|
131
|
+
reason = rejection_reason(params)
|
|
132
|
+
raise Error.new(code: nil, description: reason, status: code) if reason
|
|
133
|
+
|
|
134
|
+
new(params, issued_at:)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Initialize a token from the parameters of a token response
|
|
138
|
+
#
|
|
139
|
+
# @api public
|
|
140
|
+
# @param params [Hash] the token response parameters
|
|
141
|
+
# @param issued_at [Time] when the token was issued, used to compute its expiration
|
|
142
|
+
# @raise [KeyError] if the parameters have no access_token
|
|
143
|
+
# @raise [ArgumentError] if the access token cannot be used, or the lifetime is not a number of seconds
|
|
144
|
+
# @example
|
|
145
|
+
# SimpleOAuth::OAuth2::Token.new({"access_token" => "abc", "expires_in" => 3600})
|
|
146
|
+
def initialize(params, issued_at: Time.now)
|
|
147
|
+
@params = params.transform_keys(&:to_s).freeze
|
|
148
|
+
@access_token = validated_access_token
|
|
149
|
+
@token_type = @params["token_type"]
|
|
150
|
+
@expires_in = validated_expires_in
|
|
151
|
+
@refresh_token = @params["refresh_token"]
|
|
152
|
+
@scope = @params["scope"]
|
|
153
|
+
@expires_at = @expires_in&.then { |seconds| issued_at + seconds }
|
|
154
|
+
freeze
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The granted scopes
|
|
158
|
+
#
|
|
159
|
+
# @api public
|
|
160
|
+
# @return [Array<String>] the granted scopes
|
|
161
|
+
# @example
|
|
162
|
+
# token.scopes # => ["tweet.read", "users.read"]
|
|
163
|
+
def scopes
|
|
164
|
+
scope.to_s.split
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Check whether the access token has expired, or will within a leeway
|
|
168
|
+
#
|
|
169
|
+
# @api public
|
|
170
|
+
# @param leeway [Numeric] seconds before expiration to treat the token as expired
|
|
171
|
+
# @param now [Time] the current time
|
|
172
|
+
# @return [Boolean] true if the token has expired; false if it has not or never expires
|
|
173
|
+
# @example Refresh a token that expires within 30 seconds
|
|
174
|
+
# token.expired?(leeway: 30)
|
|
175
|
+
def expired?(leeway: 0, now: Time.now)
|
|
176
|
+
return false if expires_at.nil?
|
|
177
|
+
|
|
178
|
+
now >= expires_at - leeway
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
# The access token from the parameters, which must be usable
|
|
184
|
+
#
|
|
185
|
+
# @api private
|
|
186
|
+
# @return [String] the access token
|
|
187
|
+
# @raise [KeyError] if the parameters have no access_token
|
|
188
|
+
# @raise [ArgumentError] if the access token is not a non-empty String
|
|
189
|
+
def validated_access_token
|
|
190
|
+
token = params.fetch("access_token")
|
|
191
|
+
raise ArgumentError, INVALID_ACCESS_TOKEN unless self.class.access_token?(token)
|
|
192
|
+
|
|
193
|
+
token
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The lifetime from the parameters, which must be a number of seconds
|
|
197
|
+
#
|
|
198
|
+
# @api private
|
|
199
|
+
# @return [Integer, nil] the lifetime in seconds, or nil if the response carries none
|
|
200
|
+
# @raise [ArgumentError] if the lifetime is not a number of seconds
|
|
201
|
+
def validated_expires_in
|
|
202
|
+
seconds = params["expires_in"]
|
|
203
|
+
raise ArgumentError, INVALID_LIFETIME unless self.class.expires_in?(seconds)
|
|
204
|
+
|
|
205
|
+
seconds && Integer(seconds)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "oauth2/authorization_response"
|
|
4
|
+
require_relative "oauth2/client"
|
|
5
|
+
require_relative "oauth2/error"
|
|
6
|
+
require_relative "oauth2/pkce"
|
|
7
|
+
require_relative "oauth2/request"
|
|
8
|
+
require_relative "oauth2/response_body"
|
|
9
|
+
require_relative "oauth2/token"
|
|
10
|
+
|
|
11
|
+
module SimpleOAuth
|
|
12
|
+
# OAuth 2.0 request builders and response parsers
|
|
13
|
+
#
|
|
14
|
+
# Like the OAuth 1.0 header builder, these build requests and parse responses without
|
|
15
|
+
# performing HTTP themselves, so they work with any HTTP client.
|
|
16
|
+
#
|
|
17
|
+
# @api public
|
|
18
|
+
# @example Exchange an authorization code for a token
|
|
19
|
+
# client = SimpleOAuth::OAuth2::Client.new(client_id: "id", token_endpoint: "https://example.com/token")
|
|
20
|
+
# pkce = SimpleOAuth::OAuth2::PKCE.generate
|
|
21
|
+
# request = client.authorization_code_request(code: "code", redirect_uri: "https://app.example/cb",
|
|
22
|
+
# code_verifier: pkce.verifier)
|
|
23
|
+
# response = Net::HTTP.post(URI(request.url), request.body, request.headers)
|
|
24
|
+
# token = SimpleOAuth::OAuth2::Token.from_response(status: response.code, body: response.body)
|
|
25
|
+
#
|
|
26
|
+
# @see https://www.rfc-editor.org/rfc/rfc6749 RFC 6749 - The OAuth 2.0 Authorization Framework
|
|
27
|
+
# @see https://www.rfc-editor.org/rfc/rfc7636 RFC 7636 - Proof Key for Code Exchange (PKCE)
|
|
28
|
+
# @see https://www.rfc-editor.org/rfc/rfc7009 RFC 7009 - OAuth 2.0 Token Revocation
|
|
29
|
+
module OAuth2
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/simple_oauth/parser.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "strscan"
|
|
2
4
|
require_relative "errors"
|
|
3
5
|
|
|
@@ -88,9 +90,13 @@ module SimpleOAuth
|
|
|
88
90
|
# @param value [String] the parameter value
|
|
89
91
|
# @param valid_keys [Array<Symbol>] the valid OAuth parameter keys
|
|
90
92
|
# @return [void]
|
|
93
|
+
# @raise [SimpleOAuth::ParseError] if the header repeats the parameter
|
|
91
94
|
def store_if_valid(key, value, valid_keys)
|
|
92
95
|
parsed_key = valid_keys.find { |k| "oauth_#{k}".eql?(key) }
|
|
93
|
-
|
|
96
|
+
return if parsed_key.nil?
|
|
97
|
+
raise ParseError, "Duplicate protocol parameter: #{key}" if attributes.key?(parsed_key)
|
|
98
|
+
|
|
99
|
+
attributes[parsed_key] = Header.unescape(value)
|
|
94
100
|
end
|
|
95
101
|
|
|
96
102
|
# Verifies that the entire header was parsed
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "openssl"
|
|
3
4
|
|
|
4
5
|
module SimpleOAuth
|
|
@@ -21,7 +22,7 @@ module SimpleOAuth
|
|
|
21
22
|
# SimpleOAuth::Signature.registered?("CUSTOM") # => false
|
|
22
23
|
module Signature
|
|
23
24
|
# The hash algorithm of the signature methods RFC 5849 defines
|
|
24
|
-
DEFAULT_DIGEST = "SHA1"
|
|
25
|
+
DEFAULT_DIGEST = "SHA1"
|
|
25
26
|
|
|
26
27
|
# Registry of signature method implementations
|
|
27
28
|
@registry = {}
|
|
@@ -66,8 +67,9 @@ module SimpleOAuth
|
|
|
66
67
|
# @api public
|
|
67
68
|
# @return [Array<String>] registered method names
|
|
68
69
|
# @example
|
|
69
|
-
# SimpleOAuth::Signature.
|
|
70
|
-
|
|
70
|
+
# SimpleOAuth::Signature.registered_methods
|
|
71
|
+
# # => ["hmac_sha1", "hmac_sha256", "rsa_sha1", "plaintext"]
|
|
72
|
+
def registered_methods
|
|
71
73
|
@registry.keys
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -76,11 +78,12 @@ module SimpleOAuth
|
|
|
76
78
|
# @api public
|
|
77
79
|
# @param name [String] the signature method name
|
|
78
80
|
# @return [Boolean] true if the method uses RSA
|
|
81
|
+
# @raise [ArgumentError] if the signature method is not registered
|
|
79
82
|
# @example
|
|
80
83
|
# SimpleOAuth::Signature.rsa?("RSA-SHA1") # => true
|
|
81
84
|
# SimpleOAuth::Signature.rsa?("HMAC-SHA1") # => false
|
|
82
85
|
def rsa?(name)
|
|
83
|
-
|
|
86
|
+
fetch(name).fetch(:rsa)
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
# Returns the hash algorithm a signature method signs with
|
|
@@ -159,7 +162,8 @@ module SimpleOAuth
|
|
|
159
162
|
# SimpleOAuth::Signature.decode_base64("AQID")
|
|
160
163
|
# # => "\x01\x02\x03"
|
|
161
164
|
def decode_base64(data)
|
|
162
|
-
Base64
|
|
165
|
+
# "m" is Base64, and is lenient about characters outside the alphabet
|
|
166
|
+
data.unpack1("m") #: String
|
|
163
167
|
end
|
|
164
168
|
|
|
165
169
|
# Encodes binary data as Base64 without newlines
|
|
@@ -171,7 +175,8 @@ module SimpleOAuth
|
|
|
171
175
|
# SimpleOAuth::Signature.encode_base64("\x01\x02\x03")
|
|
172
176
|
# # => "AQID"
|
|
173
177
|
def encode_base64(data)
|
|
174
|
-
Base64
|
|
178
|
+
# "m0" is Base64 with no line breaks
|
|
179
|
+
[data].pack("m0")
|
|
175
180
|
end
|
|
176
181
|
|
|
177
182
|
private
|
data/lib/simple_oauth/version.rb
CHANGED
data/lib/simple_oauth.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative "simple_oauth/header"
|
|
4
|
+
require_relative "simple_oauth/oauth2"
|
|
2
5
|
require_relative "simple_oauth/version"
|
|
3
6
|
|
|
4
|
-
# OAuth 1.0 header
|
|
7
|
+
# OAuth 1.0 header and OAuth 2.0 request building library
|
|
5
8
|
#
|
|
6
9
|
# SimpleOAuth provides a simple interface for building and verifying
|
|
7
|
-
# OAuth 1.0 Authorization headers per RFC 5849
|
|
10
|
+
# OAuth 1.0 Authorization headers per RFC 5849, and for building
|
|
11
|
+
# OAuth 2.0 requests and parsing their responses (see {SimpleOAuth::OAuth2}).
|
|
8
12
|
#
|
|
9
13
|
# @example Building an OAuth header
|
|
10
14
|
# header = SimpleOAuth::Header.new(
|
|
@@ -22,9 +26,4 @@ require_relative "simple_oauth/version"
|
|
|
22
26
|
#
|
|
23
27
|
# @see https://tools.ietf.org/html/rfc5849 RFC 5849 - The OAuth 1.0 Protocol
|
|
24
28
|
module SimpleOAuth
|
|
25
|
-
# Error raised when parsing a malformed OAuth Authorization header
|
|
26
|
-
class ParseError < StandardError; end
|
|
27
|
-
|
|
28
|
-
# Error raised when invalid options are passed to Header
|
|
29
|
-
# (defined in header.rb, exported here for convenience)
|
|
30
29
|
end
|
|
@@ -26,10 +26,10 @@ module SimpleOAuth
|
|
|
26
26
|
|
|
27
27
|
def parse_form_body: (String | _ToS body) -> Header::oauth_options
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# Parses a form-encoded query string or body into parameter pairs
|
|
30
|
+
def form_pairs: (String | _ToS | nil form) -> Array[[String, String]]
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
def form_params: (String? body) -> Array[[String, String]]
|
|
32
|
+
private
|
|
33
33
|
|
|
34
34
|
# Checks whether a request carries a form-encoded body
|
|
35
35
|
def form_encoded?: (Header::_Request request) -> bool
|
|
@@ -41,8 +41,6 @@ module SimpleOAuth
|
|
|
41
41
|
# Generates a random nonce for OAuth requests
|
|
42
42
|
def generate_nonce: () -> String
|
|
43
43
|
|
|
44
|
-
# Encodes binary data as Base64 without newlines
|
|
45
|
-
def encode_base64: (String data) -> String
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module SimpleOAuth
|
|
2
|
+
# OAuth 2.0 request builders and response parsers
|
|
3
|
+
module OAuth2
|
|
4
|
+
# Parses the JSON bodies returned by OAuth 2.0 endpoints
|
|
5
|
+
module ResponseBody
|
|
6
|
+
def self.parse: (String? body) -> Hash[String, untyped]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# The response an authorization server returns to a client's redirect URI
|
|
10
|
+
class AuthorizationResponse
|
|
11
|
+
type response_params = Hash[String | Symbol, String]
|
|
12
|
+
|
|
13
|
+
STATE_MISMATCH: String
|
|
14
|
+
ISSUER_MISMATCH: String
|
|
15
|
+
NO_CODE: String
|
|
16
|
+
DUPLICATE_PARAMETER: String
|
|
17
|
+
|
|
18
|
+
attr_reader code: String
|
|
19
|
+
attr_reader state: String?
|
|
20
|
+
attr_reader issuer: String?
|
|
21
|
+
attr_reader params: Hash[String, String]
|
|
22
|
+
|
|
23
|
+
def self.parse: (String | response_params | nil query, ?state: String?, ?issuer: String?) -> AuthorizationResponse
|
|
24
|
+
def self.parameters: (String | response_params | nil query) -> Hash[String, String]
|
|
25
|
+
def self.reported_error: (Hash[String, String] params) -> Error
|
|
26
|
+
def self.mismatch_reason: (Hash[String, String] params, String? state, String? issuer) -> String?
|
|
27
|
+
def self.matches?: (String? expected, String? actual) -> bool
|
|
28
|
+
def initialize: (response_params params) -> void
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Error returned by an OAuth 2.0 endpoint
|
|
32
|
+
class Error < SimpleOAuth::Error
|
|
33
|
+
INVALID_STATUS: String
|
|
34
|
+
|
|
35
|
+
attr_reader code: String?
|
|
36
|
+
attr_reader description: String?
|
|
37
|
+
attr_reader uri: String?
|
|
38
|
+
attr_reader status: Integer?
|
|
39
|
+
|
|
40
|
+
def self.http_status: (Integer | String value) -> Integer
|
|
41
|
+
def self.from_response: (status: Integer | String, body: String?) -> Error
|
|
42
|
+
def initialize: (code: String?, ?description: String?, ?uri: String?, ?status: Integer?) -> void
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A Proof Key for Code Exchange verifier and challenge
|
|
46
|
+
class PKCE
|
|
47
|
+
S256: String
|
|
48
|
+
PLAIN: String
|
|
49
|
+
VERIFIER_PATTERN: Regexp
|
|
50
|
+
INVALID_VERIFIER: String
|
|
51
|
+
VERIFIER_BYTES: Integer
|
|
52
|
+
|
|
53
|
+
attr_reader verifier: String
|
|
54
|
+
attr_reader challenge_method: String
|
|
55
|
+
attr_reader challenge: String
|
|
56
|
+
|
|
57
|
+
def self.generate: (?challenge_method: String) -> PKCE
|
|
58
|
+
def initialize: (verifier: String, ?challenge_method: String) -> void
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def compute_challenge: () -> String
|
|
63
|
+
def base64_url: (String data) -> String
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# An HTTP request to an OAuth 2.0 endpoint, built but not sent
|
|
67
|
+
class Request
|
|
68
|
+
attr_reader method: String
|
|
69
|
+
attr_reader url: String
|
|
70
|
+
attr_reader headers: Hash[String, String]
|
|
71
|
+
attr_reader body: String
|
|
72
|
+
|
|
73
|
+
def initialize: (method: String, url: String, headers: Hash[String, String], body: String) -> void
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# An access token from a successful token response
|
|
77
|
+
class Token
|
|
78
|
+
NO_ACCESS_TOKEN: String
|
|
79
|
+
INVALID_EXPIRES_IN: String
|
|
80
|
+
INVALID_ACCESS_TOKEN: String
|
|
81
|
+
INVALID_LIFETIME: String
|
|
82
|
+
|
|
83
|
+
attr_reader access_token: String
|
|
84
|
+
attr_reader token_type: String?
|
|
85
|
+
attr_reader expires_in: Integer?
|
|
86
|
+
attr_reader refresh_token: String?
|
|
87
|
+
attr_reader scope: String?
|
|
88
|
+
attr_reader expires_at: Time?
|
|
89
|
+
attr_reader params: Hash[String, untyped]
|
|
90
|
+
|
|
91
|
+
def self.access_token?: (untyped value) -> bool
|
|
92
|
+
def self.expires_in?: (untyped value) -> bool
|
|
93
|
+
def self.rejection_reason: (Hash[String, untyped] params) -> String?
|
|
94
|
+
def self.from_response: (status: Integer | String, body: String?, ?issued_at: Time) -> Token
|
|
95
|
+
def initialize: (Hash[String | Symbol, untyped] params, ?issued_at: Time) -> void
|
|
96
|
+
def scopes: () -> Array[String]
|
|
97
|
+
def expired?: (?leeway: Numeric, ?now: Time) -> bool
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def validated_access_token: () -> String
|
|
102
|
+
def validated_expires_in: () -> Integer?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# An OAuth 2.0 client that builds authorization URLs and endpoint requests
|
|
106
|
+
class Client
|
|
107
|
+
type auth_method = :client_secret_basic | :client_secret_post
|
|
108
|
+
type scope = String | Array[String] | nil
|
|
109
|
+
type form_params = Hash[Symbol, String?]
|
|
110
|
+
type extra_params = Hash[Symbol | String, String?]
|
|
111
|
+
|
|
112
|
+
AUTH_METHODS: Array[Symbol]
|
|
113
|
+
FORM_CONTENT_TYPE: String
|
|
114
|
+
EMPTY_STATE: String
|
|
115
|
+
UNPROTECTED: String
|
|
116
|
+
|
|
117
|
+
attr_reader client_id: String
|
|
118
|
+
attr_reader client_secret: String?
|
|
119
|
+
attr_reader authorization_endpoint: String?
|
|
120
|
+
attr_reader token_endpoint: String?
|
|
121
|
+
attr_reader revocation_endpoint: String?
|
|
122
|
+
attr_reader auth_method: auth_method
|
|
123
|
+
|
|
124
|
+
def initialize: (client_id: String, ?client_secret: String?, ?authorization_endpoint: String?, ?token_endpoint: String?,
|
|
125
|
+
?revocation_endpoint: String?, ?auth_method: auth_method) -> void
|
|
126
|
+
def public?: () -> bool
|
|
127
|
+
def authorization_url: (redirect_uri: String, pkce: PKCE?, ?state: String?, ?scope: scope, ?params: extra_params) -> String
|
|
128
|
+
def authorization_code_request: (code: String, redirect_uri: String, ?code_verifier: String?, ?params: extra_params) -> Request
|
|
129
|
+
def refresh_token_request: (refresh_token: String, ?scope: scope, ?params: extra_params) -> Request
|
|
130
|
+
def client_credentials_request: (?scope: scope, ?params: extra_params) -> Request
|
|
131
|
+
def revocation_request: (token: String, ?token_type_hint: String?, ?params: extra_params) -> Request
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def validate_protection!: (PKCE? pkce, String? state) -> void
|
|
136
|
+
def token_request: (form_params params, extra_params extra) -> Request
|
|
137
|
+
def form_request: (String url, form_params params, extra_params extra) -> Request
|
|
138
|
+
def authenticated: (form_params params) -> [Hash[String, String], form_params]
|
|
139
|
+
def basic_authorization: (String secret) -> String
|
|
140
|
+
def scope_value: (scope scope) -> String?
|
|
141
|
+
def endpoint: (String? url, Symbol name) -> String
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -26,7 +26,7 @@ module SimpleOAuth
|
|
|
26
26
|
def self.registered?: (String | Symbol name) -> bool
|
|
27
27
|
|
|
28
28
|
# Returns list of registered signature method names
|
|
29
|
-
def self.
|
|
29
|
+
def self.registered_methods: () -> Array[String]
|
|
30
30
|
|
|
31
31
|
# Checks if a signature method uses RSA
|
|
32
32
|
def self.rsa?: (String | Symbol name) -> bool
|
data/sig/simple_oauth.rbs
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# OAuth 1.0 header generation library
|
|
2
2
|
module SimpleOAuth
|
|
3
|
+
# The base of every error the library raises
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
3
7
|
# Error raised when parsing a malformed OAuth Authorization header
|
|
4
|
-
class ParseError <
|
|
8
|
+
class ParseError < Error
|
|
5
9
|
end
|
|
6
10
|
|
|
7
11
|
# Error raised when invalid options are passed to Header
|
|
8
|
-
class InvalidOptionsError <
|
|
12
|
+
class InvalidOptionsError < Error
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
# OAuth percent-encoding utilities
|
|
@@ -160,7 +164,7 @@ module SimpleOAuth
|
|
|
160
164
|
end
|
|
161
165
|
|
|
162
166
|
# Version module
|
|
163
|
-
module
|
|
167
|
+
module SimpleOAuth
|
|
164
168
|
# The current version of the SimpleOAuth gem
|
|
165
169
|
VERSION: String
|
|
166
170
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple_oauth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steve Richert
|
|
@@ -9,36 +9,8 @@ authors:
|
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
14
|
-
name: base64
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: cgi
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
description: Simply builds and verifies OAuth headers
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Simply builds and verifies OAuth 1.0 headers and builds OAuth 2.0 requests
|
|
42
14
|
email:
|
|
43
15
|
- steve.richert@gmail.com
|
|
44
16
|
- sferik@gmail.com
|
|
@@ -60,6 +32,14 @@ files:
|
|
|
60
32
|
- lib/simple_oauth/header.rb
|
|
61
33
|
- lib/simple_oauth/header/class_methods.rb
|
|
62
34
|
- lib/simple_oauth/header/params.rb
|
|
35
|
+
- lib/simple_oauth/oauth2.rb
|
|
36
|
+
- lib/simple_oauth/oauth2/authorization_response.rb
|
|
37
|
+
- lib/simple_oauth/oauth2/client.rb
|
|
38
|
+
- lib/simple_oauth/oauth2/error.rb
|
|
39
|
+
- lib/simple_oauth/oauth2/pkce.rb
|
|
40
|
+
- lib/simple_oauth/oauth2/request.rb
|
|
41
|
+
- lib/simple_oauth/oauth2/response_body.rb
|
|
42
|
+
- lib/simple_oauth/oauth2/token.rb
|
|
63
43
|
- lib/simple_oauth/parser.rb
|
|
64
44
|
- lib/simple_oauth/signature.rb
|
|
65
45
|
- lib/simple_oauth/version.rb
|
|
@@ -69,6 +49,7 @@ files:
|
|
|
69
49
|
- sig/simple_oauth.rbs
|
|
70
50
|
- sig/simple_oauth/header/class_methods.rbs
|
|
71
51
|
- sig/simple_oauth/header/params.rbs
|
|
52
|
+
- sig/simple_oauth/oauth2.rbs
|
|
72
53
|
- sig/simple_oauth/parser.rbs
|
|
73
54
|
- sig/simple_oauth/signature.rbs
|
|
74
55
|
- sig/strscan.rbs
|
|
@@ -97,5 +78,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
97
78
|
requirements: []
|
|
98
79
|
rubygems_version: 4.0.20
|
|
99
80
|
specification_version: 4
|
|
100
|
-
summary: Simply builds and verifies OAuth headers
|
|
81
|
+
summary: Simply builds and verifies OAuth 1.0 headers and builds OAuth 2.0 requests
|
|
101
82
|
test_files: []
|