simple_oauth 0.4.2 → 0.5.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 +20 -0
- data/README.md +43 -3
- data/lib/simple_oauth/header/class_methods.rb +63 -3
- data/lib/simple_oauth/header/params.rb +75 -0
- data/lib/simple_oauth/header.rb +40 -66
- data/lib/simple_oauth/signature.rb +70 -10
- data/lib/simple_oauth/version.rb +1 -1
- data/sig/openssl_ext.rbs +6 -0
- data/sig/simple_oauth/header/class_methods.rbs +21 -2
- data/sig/simple_oauth/header/params.rbs +29 -0
- data/sig/simple_oauth/signature.rbs +21 -3
- data/sig/simple_oauth.rbs +20 -14
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7eca3b8d3940706967367e25b8f42261d48b325c8f2cd1c1cc01ad58a800f284
|
|
4
|
+
data.tar.gz: 63cec8ec4204dd64c40fdf61ecf1782df0364687794896199ded8a6dcb890ca3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 967d4c8e1f174da75c8818fa0b94432ca2878d44474e5433930aa5bca728d4636b1ae4594e930be98fc68d6d9d57a40622dd5075ff507242100f424b95213e75
|
|
7
|
+
data.tar.gz: 7b24159117153d934834c2767692be078a78c58ec6fc34775d91bd90794767bbf86cbde9fb6306bcf5640b2a7b212f8bd0866e980ed3a871a3e0212120a7494b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.5.0] - 2026-09-12
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
* `Header.from_request`, which builds a header for a request object such as a `Net::HTTPRequest`, signing its query parameters, its form-encoded body, or hashing any other body
|
|
6
|
+
* `Header.parse_query`, for OAuth credentials sent in a query string
|
|
7
|
+
* `Signature.digest`, and a `digest:` option on `Signature.register`, which gives the hash algorithm a signature method signs with
|
|
8
|
+
* `Signature.verify` and a `verify:` option on `Signature.register`, for signature methods that cannot be verified by recomputing the signature
|
|
9
|
+
* `Signature.decode_base64`
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* Check `oauth_body_hash` against the body a header was built with when verifying, so a body changed after signing no longer verifies against the hash its signature covers
|
|
14
|
+
* Verify signatures without merging the given secrets into the header's own options, where anything else reading the header could see them
|
|
15
|
+
* Compare signatures in constant time when verifying
|
|
16
|
+
* Compute `oauth_body_hash` with the hash algorithm of the signature method, such as SHA-256 for HMAC-SHA256; it was always SHA-1
|
|
17
|
+
* Sign a parameter whose value is an Array as one parameter per value, as a repeated parameter; the Array was previously signed as its Ruby representation
|
|
18
|
+
* Verify RSA signatures with the signer's public key, which is all a verifier has; `Header#valid?` previously recomputed the signature and so needed the private key
|
|
19
|
+
* Match the form-encoded media type exactly when signing a body, rather than by prefix, so a media type such as `application/x-www-form-urlencoded-json` is hashed instead of signed as parameters, and `Application/X-WWW-Form-Urlencoded` is recognized
|
|
20
|
+
|
|
1
21
|
## [0.4.2] - 2026-09-12
|
|
2
22
|
|
|
3
23
|
### Added
|
data/README.md
CHANGED
|
@@ -40,11 +40,29 @@ header.to_s
|
|
|
40
40
|
# => "OAuth oauth_consumer_key=\"consumer_key\", oauth_nonce=\"...\", ..."
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
### Signing a Request
|
|
44
|
+
|
|
45
|
+
`Header.from_request` takes the method, URL, and parameters from a request object, such as a `Net::HTTPRequest`. Query parameters are always signed, a form-encoded body is signed as parameters, and any other body is hashed into `oauth_body_hash`:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
request = Net::HTTP::Post.new(URI("https://api.example.com/statuses"))
|
|
49
|
+
request.set_form_data(status: "Hello")
|
|
50
|
+
request["Authorization"] = SimpleOAuth::Header.from_request(request,
|
|
51
|
+
consumer_key: "key",
|
|
52
|
+
consumer_secret: "secret"
|
|
53
|
+
).to_s
|
|
54
|
+
```
|
|
55
|
+
|
|
43
56
|
### Repeated Parameters
|
|
44
57
|
|
|
45
|
-
Pass
|
|
58
|
+
Pass an Array of values, or an Array of key-value pairs, when a key repeats:
|
|
46
59
|
|
|
47
60
|
```ruby
|
|
61
|
+
header = SimpleOAuth::Header.new(:post, url, {"ids" => %w[1 2]},
|
|
62
|
+
consumer_key: "key",
|
|
63
|
+
consumer_secret: "secret"
|
|
64
|
+
)
|
|
65
|
+
|
|
48
66
|
header = SimpleOAuth::Header.new(:post, url, [["ids", "1"], ["ids", "2"]],
|
|
49
67
|
consumer_key: "key",
|
|
50
68
|
consumer_secret: "secret"
|
|
@@ -87,7 +105,7 @@ SimpleOAuth::Signature.methods # => ["hmac_sha1", "hmac_sha256", "rsa_sha1", "rs
|
|
|
87
105
|
|
|
88
106
|
### OAuth Request Body Hash
|
|
89
107
|
|
|
90
|
-
For non-form-encoded request bodies (e.g., JSON), pass the body as the fifth parameter to compute `oauth_body_hash
|
|
108
|
+
For non-form-encoded request bodies (e.g., JSON), pass the body as the fifth parameter to compute `oauth_body_hash`, which is hashed with the signature method's algorithm. Form-encoded bodies are signed by passing their parameters as `params` instead.
|
|
91
109
|
|
|
92
110
|
```ruby
|
|
93
111
|
json_body = '{"text": "Hello, World!"}'
|
|
@@ -120,11 +138,14 @@ parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key", oauth_signat
|
|
|
120
138
|
# => {consumer_key: "key", signature: "sig"}
|
|
121
139
|
```
|
|
122
140
|
|
|
123
|
-
Parse OAuth credentials from a form-encoded POST body:
|
|
141
|
+
Parse OAuth credentials from a form-encoded POST body, or from a query string:
|
|
124
142
|
|
|
125
143
|
```ruby
|
|
126
144
|
parsed = SimpleOAuth::Header.parse_form_body('oauth_consumer_key=key&oauth_signature=sig&status=hello')
|
|
127
145
|
# => {consumer_key: "key", signature: "sig"}
|
|
146
|
+
|
|
147
|
+
parsed = SimpleOAuth::Header.parse_query("oauth_consumer_key=key&status=hello")
|
|
148
|
+
# => {consumer_key: "key"}
|
|
128
149
|
```
|
|
129
150
|
|
|
130
151
|
### Verifying Signatures
|
|
@@ -138,6 +159,25 @@ header.valid?(consumer_secret: "secret", token_secret: "token_secret")
|
|
|
138
159
|
# => true
|
|
139
160
|
```
|
|
140
161
|
|
|
162
|
+
Verifying compares signatures in constant time and leaves the header's own options untouched, so the secrets stay with the caller.
|
|
163
|
+
|
|
164
|
+
RSA signatures verify with the client's public key, which is all a server has:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
header.valid?(consumer_secret: File.read("client_public_key.pem"))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Custom signature methods that cannot be verified by recomputing the signature register a `verify` block:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
SimpleOAuth::Signature.register("RSA-SHA512", rsa: true,
|
|
174
|
+
verify: ->(key, signature_base, signature) {
|
|
175
|
+
OpenSSL::PKey::RSA.new(key).verify("SHA512", SimpleOAuth::Signature.decode_base64(signature), signature_base)
|
|
176
|
+
}) do |private_key_pem, signature_base|
|
|
177
|
+
SimpleOAuth::Signature.encode_base64(OpenSSL::PKey::RSA.new(private_key_pem).sign("SHA512", signature_base))
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
141
181
|
## Contributing
|
|
142
182
|
|
|
143
183
|
Bug reports and pull requests are welcome on GitHub at https://github.com/laserlemon/simple_oauth.
|
|
@@ -13,17 +13,18 @@ module SimpleOAuth
|
|
|
13
13
|
#
|
|
14
14
|
# @api public
|
|
15
15
|
# @param body [String, nil] optional request body for computing oauth_body_hash
|
|
16
|
+
# @param signature_method [String] the signature method, whose hash algorithm oauth_body_hash uses
|
|
16
17
|
# @return [Hash] default options including nonce, signature_method, timestamp, and version
|
|
17
18
|
# @example
|
|
18
19
|
# SimpleOAuth::Header.default_options
|
|
19
20
|
# # => {nonce: "abc123...", signature_method: "HMAC-SHA1", timestamp: "1234567890", version: "1.0"}
|
|
20
|
-
def default_options(body = nil)
|
|
21
|
+
def default_options(body = nil, signature_method = DEFAULT_SIGNATURE_METHOD)
|
|
21
22
|
{
|
|
22
23
|
nonce: generate_nonce,
|
|
23
|
-
signature_method:
|
|
24
|
+
signature_method: signature_method,
|
|
24
25
|
timestamp: Integer(Time.now).to_s,
|
|
25
26
|
version: OAUTH_VERSION
|
|
26
|
-
}.tap { |opts| opts[:body_hash] = body_hash(body) if body }
|
|
27
|
+
}.tap { |opts| opts[:body_hash] = body_hash(body, Signature.digest(signature_method)) if body }
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Computes the oauth_body_hash for a request body
|
|
@@ -52,6 +53,30 @@ module SimpleOAuth
|
|
|
52
53
|
Parser.new(header).parse(PARSE_KEYS)
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
# Builds a header for an HTTP request, signing the parameters it carries
|
|
57
|
+
#
|
|
58
|
+
# The request's query parameters are always signed. A form-encoded body is signed as
|
|
59
|
+
# parameters, and any other body is hashed into oauth_body_hash.
|
|
60
|
+
#
|
|
61
|
+
# @api public
|
|
62
|
+
# @param request [#method, #uri, #body] the request to sign, such as a Net::HTTPRequest
|
|
63
|
+
# @param oauth [Hash, String] OAuth options hash or an existing Authorization header to parse
|
|
64
|
+
# @return [Header] the header for the request
|
|
65
|
+
# @raise [ArgumentError] if the request has no URI
|
|
66
|
+
# @example
|
|
67
|
+
# request = Net::HTTP::Post.new(URI("https://api.example.com/statuses"))
|
|
68
|
+
# request.set_form_data(status: "Hello")
|
|
69
|
+
# request["Authorization"] = SimpleOAuth::Header.from_request(request,
|
|
70
|
+
# consumer_key: "key", consumer_secret: "secret").to_s
|
|
71
|
+
def from_request(request, oauth = {})
|
|
72
|
+
uri = request.uri || raise(ArgumentError, "The request has no URI")
|
|
73
|
+
body = request.body
|
|
74
|
+
return new(request.method, uri, CGI.parse(body.to_s), oauth) if form_encoded?(request)
|
|
75
|
+
|
|
76
|
+
no_params = {} #: Header::request_params
|
|
77
|
+
new(request.method, uri, no_params, oauth, body)
|
|
78
|
+
end
|
|
79
|
+
|
|
55
80
|
# Parses OAuth parameters from a form-encoded POST body
|
|
56
81
|
#
|
|
57
82
|
# OAuth 1.0 allows credentials to be transmitted in the request body for
|
|
@@ -63,6 +88,9 @@ module SimpleOAuth
|
|
|
63
88
|
# @example
|
|
64
89
|
# SimpleOAuth::Header.parse_form_body('oauth_consumer_key=key&oauth_signature=sig&status=hello')
|
|
65
90
|
# # => {consumer_key: "key", signature: "sig"}
|
|
91
|
+
# @example Parse the credentials from a query string
|
|
92
|
+
# SimpleOAuth::Header.parse_query('oauth_consumer_key=key&status=hello')
|
|
93
|
+
# # => {consumer_key: "key"}
|
|
66
94
|
def parse_form_body(body)
|
|
67
95
|
valid_keys = PARSE_KEYS.map(&:to_s)
|
|
68
96
|
|
|
@@ -76,8 +104,40 @@ module SimpleOAuth
|
|
|
76
104
|
result
|
|
77
105
|
end
|
|
78
106
|
|
|
107
|
+
# @!method parse_query(query)
|
|
108
|
+
# Parses OAuth parameters from a query string, which RFC 5849 Section 3.5.3 also allows
|
|
109
|
+
#
|
|
110
|
+
# @api public
|
|
111
|
+
# @param query [String, #to_s] the query string
|
|
112
|
+
# @return [Hash] parsed OAuth attributes with symbol keys (only valid OAuth keys)
|
|
113
|
+
# @example
|
|
114
|
+
# SimpleOAuth::Header.parse_query("oauth_consumer_key=key&status=hello")
|
|
115
|
+
# # => {consumer_key: "key"}
|
|
116
|
+
alias_method :parse_query, :parse_form_body
|
|
117
|
+
|
|
79
118
|
private
|
|
80
119
|
|
|
120
|
+
# Checks whether a request carries a form-encoded body
|
|
121
|
+
#
|
|
122
|
+
# @api private
|
|
123
|
+
# @param request [#[]] the request
|
|
124
|
+
# @return [Boolean] true if the body is form-encoded
|
|
125
|
+
def form_encoded?(request)
|
|
126
|
+
media_type(request).eql?(FORM_CONTENT_TYPE)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Extracts the media type from a request, without its parameters
|
|
130
|
+
#
|
|
131
|
+
# Per RFC 9110 Section 8.3 the media type is case-insensitive and may carry parameters,
|
|
132
|
+
# such as a charset, that play no part in identifying it.
|
|
133
|
+
#
|
|
134
|
+
# @api private
|
|
135
|
+
# @param request [#[]] the request
|
|
136
|
+
# @return [String] the lowercase media type, or an empty String when the request declares none
|
|
137
|
+
def media_type(request)
|
|
138
|
+
request["Content-Type"].to_s.split(";").first.to_s.strip.downcase
|
|
139
|
+
end
|
|
140
|
+
|
|
81
141
|
# Generates a random nonce for OAuth requests
|
|
82
142
|
#
|
|
83
143
|
# @api private
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
|
|
3
|
+
module SimpleOAuth
|
|
4
|
+
class Header
|
|
5
|
+
# Normalization of the request parameters that are signed, per RFC 5849 Section 3.4.1.3
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module Params
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# Extracts valid OAuth attributes from options
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
# @return [Hash] OAuth attributes without signature or realm
|
|
15
|
+
def attributes
|
|
16
|
+
validate_option_keys!
|
|
17
|
+
options.slice(*ATTRIBUTE_KEYS).transform_keys { |key| :"#{OAUTH_PREFIX}#{key}" }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Validates that no unknown keys are present in options
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
# @raise [InvalidOptionsError] if extra keys are found
|
|
24
|
+
# @return [void]
|
|
25
|
+
def validate_option_keys!
|
|
26
|
+
return if options[:ignore_extra_keys]
|
|
27
|
+
|
|
28
|
+
extra_keys = options.keys - ATTRIBUTE_KEYS - IGNORED_KEYS
|
|
29
|
+
return if extra_keys.empty?
|
|
30
|
+
|
|
31
|
+
raise InvalidOptionsError, "Unknown option keys: #{extra_keys.map(&:inspect).join(", ")}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Extracts query parameters from the request URL
|
|
35
|
+
#
|
|
36
|
+
# @api private
|
|
37
|
+
# @return [Array<Array>] URL query parameters as key-value pairs
|
|
38
|
+
def url_params
|
|
39
|
+
CGI.parse(@uri.query || "").flat_map do |key, values|
|
|
40
|
+
values.sort.map { |value| [key, value] }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Normalizes and sorts all request parameters for signing
|
|
45
|
+
#
|
|
46
|
+
# @api private
|
|
47
|
+
# @return [String] normalized request parameters
|
|
48
|
+
def normalized_params
|
|
49
|
+
signature_params
|
|
50
|
+
.map { |key, value| [Header.escape(key), Header.escape(value)] }
|
|
51
|
+
.sort
|
|
52
|
+
.map { |pair| pair.join("=") }
|
|
53
|
+
.join("&")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Collects all parameters to include in signature
|
|
57
|
+
#
|
|
58
|
+
# @api private
|
|
59
|
+
# @return [Array<Array>] all parameters for signature as key-value pairs
|
|
60
|
+
def signature_params
|
|
61
|
+
attributes.to_a + expanded_params + url_params
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Expands parameters into one pair per value, for parameters with several values
|
|
65
|
+
#
|
|
66
|
+
# @api private
|
|
67
|
+
# @return [Array<Array(Object, Object)>] the parameter pairs
|
|
68
|
+
def expanded_params
|
|
69
|
+
params.flat_map do |key, value|
|
|
70
|
+
value.is_a?(Array) ? value.map { |element| [key, element] } : [[key, value]]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/simple_oauth/header.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "errors"
|
|
|
5
5
|
require_relative "parser"
|
|
6
6
|
require_relative "signature"
|
|
7
7
|
require_relative "header/class_methods"
|
|
8
|
+
require_relative "header/params"
|
|
8
9
|
|
|
9
10
|
module SimpleOAuth
|
|
10
11
|
# Generates OAuth 1.0 Authorization headers for HTTP requests
|
|
@@ -17,6 +18,8 @@ module SimpleOAuth
|
|
|
17
18
|
# Prefix for OAuth parameters
|
|
18
19
|
OAUTH_PREFIX = "oauth_".freeze
|
|
19
20
|
|
|
21
|
+
# The content type whose body parameters are signed, per RFC 5849 Section 3.4.1.3.1
|
|
22
|
+
FORM_CONTENT_TYPE = "application/x-www-form-urlencoded".freeze
|
|
20
23
|
# Default signature method per RFC 5849
|
|
21
24
|
DEFAULT_SIGNATURE_METHOD = "HMAC-SHA1".freeze
|
|
22
25
|
|
|
@@ -60,6 +63,8 @@ module SimpleOAuth
|
|
|
60
63
|
# header.options # => {consumer_key: "key", nonce: "..."}
|
|
61
64
|
attr_reader :options
|
|
62
65
|
|
|
66
|
+
include Params
|
|
67
|
+
|
|
63
68
|
extend ClassMethods
|
|
64
69
|
extend Encoding
|
|
65
70
|
|
|
@@ -118,16 +123,15 @@ module SimpleOAuth
|
|
|
118
123
|
# @api public
|
|
119
124
|
# @param secrets [Hash] the consumer_secret and token_secret for validation
|
|
120
125
|
# @return [Boolean] true if the signature is valid, false otherwise
|
|
126
|
+
# @note When the header was built with a body, the signed oauth_body_hash must match that body,
|
|
127
|
+
# so a tampered body fails verification even though its signature covers the claimed hash
|
|
121
128
|
# @example
|
|
122
129
|
# parsed_header = SimpleOAuth::Header.new(:get, url, {}, authorization_header)
|
|
123
130
|
# parsed_header.valid?(consumer_secret: "secret", token_secret: "token_secret")
|
|
124
131
|
# # => true
|
|
125
132
|
def valid?(secrets = {})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
options.fetch(:signature).eql?(signature)
|
|
129
|
-
ensure
|
|
130
|
-
options.replace(original_options)
|
|
133
|
+
body_hash_valid? && Signature.verify(options.fetch(:signature_method), signing_key(options.merge(secrets)),
|
|
134
|
+
signature_base, options.fetch(:signature))
|
|
131
135
|
end
|
|
132
136
|
|
|
133
137
|
# Returns the OAuth attributes including the signature
|
|
@@ -162,11 +166,10 @@ module SimpleOAuth
|
|
|
162
166
|
# @param body [String, nil] request body for body_hash computation
|
|
163
167
|
# @return [Hash] merged OAuth options with defaults
|
|
164
168
|
def build_options(oauth, body)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
end
|
|
169
|
+
return self.class.parse(oauth) unless oauth.is_a?(Hash)
|
|
170
|
+
|
|
171
|
+
overrides = oauth.transform_keys(&:to_sym)
|
|
172
|
+
self.class.default_options(body, overrides.fetch(:signature_method, DEFAULT_SIGNATURE_METHOD)).merge(overrides)
|
|
170
173
|
end
|
|
171
174
|
|
|
172
175
|
# Builds the normalized OAuth attributes string for the header
|
|
@@ -180,29 +183,6 @@ module SimpleOAuth
|
|
|
180
183
|
.join(", ")
|
|
181
184
|
end
|
|
182
185
|
|
|
183
|
-
# Extracts valid OAuth attributes from options
|
|
184
|
-
#
|
|
185
|
-
# @api private
|
|
186
|
-
# @return [Hash] OAuth attributes without signature or realm
|
|
187
|
-
def attributes
|
|
188
|
-
validate_option_keys!
|
|
189
|
-
options.slice(*ATTRIBUTE_KEYS).transform_keys { |key| :"#{OAUTH_PREFIX}#{key}" }
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Validates that no unknown keys are present in options
|
|
193
|
-
#
|
|
194
|
-
# @api private
|
|
195
|
-
# @raise [InvalidOptionsError] if extra keys are found
|
|
196
|
-
# @return [void]
|
|
197
|
-
def validate_option_keys!
|
|
198
|
-
return if options[:ignore_extra_keys]
|
|
199
|
-
|
|
200
|
-
extra_keys = options.keys - ATTRIBUTE_KEYS - IGNORED_KEYS
|
|
201
|
-
return if extra_keys.empty?
|
|
202
|
-
|
|
203
|
-
raise InvalidOptionsError, "Unknown option keys: #{extra_keys.map(&:inspect).join(", ")}"
|
|
204
|
-
end
|
|
205
|
-
|
|
206
186
|
# Returns OAuth attributes with realm for the Authorization header
|
|
207
187
|
#
|
|
208
188
|
# Per RFC 5849 Section 3.5.1, realm is included in the Authorization header
|
|
@@ -216,60 +196,54 @@ module SimpleOAuth
|
|
|
216
196
|
attrs
|
|
217
197
|
end
|
|
218
198
|
|
|
219
|
-
# Extracts query parameters from the request URL
|
|
220
|
-
#
|
|
221
|
-
# @api private
|
|
222
|
-
# @return [Array<Array>] URL query parameters as key-value pairs
|
|
223
|
-
def url_params
|
|
224
|
-
CGI.parse(@uri.query || "").flat_map do |key, values|
|
|
225
|
-
values.sort.map { |value| [key, value] }
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
199
|
# Computes the OAuth signature using the configured method
|
|
230
200
|
#
|
|
231
201
|
# @api private
|
|
232
202
|
# @return [String] the computed signature based on signature_method
|
|
233
203
|
def signature
|
|
234
|
-
|
|
235
|
-
sig_secret = Signature.rsa?(sig_method) ? options[:consumer_secret] : secret
|
|
236
|
-
Signature.sign(sig_method, sig_secret, signature_base)
|
|
204
|
+
Signature.sign(options.fetch(:signature_method), signing_key(options), signature_base)
|
|
237
205
|
end
|
|
238
206
|
|
|
239
|
-
#
|
|
207
|
+
# The key for signing and verifying: an RSA key, or the escaped secrets
|
|
240
208
|
#
|
|
241
209
|
# @api private
|
|
242
|
-
# @
|
|
243
|
-
|
|
244
|
-
|
|
210
|
+
# @param options [Hash] the options holding the credentials
|
|
211
|
+
# @return [String, nil] the key
|
|
212
|
+
def signing_key(options)
|
|
213
|
+
Signature.rsa?(options.fetch(:signature_method)) ? options[:consumer_secret] : secret(options)
|
|
245
214
|
end
|
|
246
215
|
|
|
247
|
-
#
|
|
216
|
+
# Checks the body against the oauth_body_hash the header carries
|
|
217
|
+
#
|
|
218
|
+
# A header parsed from a request claims a body hash that its signature covers, so the claim must be
|
|
219
|
+
# checked against the body actually received. A signer that omits oauth_body_hash leaves the body
|
|
220
|
+
# unprotected, which its signature already attests to, so there is nothing to check.
|
|
248
221
|
#
|
|
249
222
|
# @api private
|
|
250
|
-
# @return [
|
|
251
|
-
def
|
|
252
|
-
|
|
223
|
+
# @return [Boolean] true unless the body contradicts the signed oauth_body_hash
|
|
224
|
+
def body_hash_valid?
|
|
225
|
+
claimed_body_hash = options[:body_hash]
|
|
226
|
+
return true if body.nil? || claimed_body_hash.nil?
|
|
227
|
+
|
|
228
|
+
digest = Signature.digest(options.fetch(:signature_method))
|
|
229
|
+
OpenSSL.secure_compare(self.class.body_hash(body, digest), claimed_body_hash)
|
|
253
230
|
end
|
|
254
231
|
|
|
255
|
-
#
|
|
232
|
+
# Builds the secret string from consumer and token secrets
|
|
256
233
|
#
|
|
257
234
|
# @api private
|
|
258
|
-
# @
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.sort
|
|
263
|
-
.map { |pair| pair.join("=") }
|
|
264
|
-
.join("&")
|
|
235
|
+
# @param options [Hash] the options holding the secrets
|
|
236
|
+
# @return [String] the secret string for signing
|
|
237
|
+
def secret(options)
|
|
238
|
+
options.values_at(:consumer_secret, :token_secret).map { |v| Header.escape(v) }.join("&")
|
|
265
239
|
end
|
|
266
240
|
|
|
267
|
-
#
|
|
241
|
+
# Builds the signature base string from method, URL, and params
|
|
268
242
|
#
|
|
269
243
|
# @api private
|
|
270
|
-
# @return [
|
|
271
|
-
def
|
|
272
|
-
|
|
244
|
+
# @return [String] the signature base string
|
|
245
|
+
def signature_base
|
|
246
|
+
[method, url, normalized_params].map { |v| Header.escape(v) }.join("&")
|
|
273
247
|
end
|
|
274
248
|
end
|
|
275
249
|
end
|
|
@@ -20,6 +20,9 @@ module SimpleOAuth
|
|
|
20
20
|
# SimpleOAuth::Signature.registered?("HMAC-SHA1") # => true
|
|
21
21
|
# SimpleOAuth::Signature.registered?("CUSTOM") # => false
|
|
22
22
|
module Signature
|
|
23
|
+
# The hash algorithm of the signature methods RFC 5849 defines
|
|
24
|
+
DEFAULT_DIGEST = "SHA1".freeze
|
|
25
|
+
|
|
23
26
|
# Registry of signature method implementations
|
|
24
27
|
@registry = {}
|
|
25
28
|
|
|
@@ -29,6 +32,9 @@ module SimpleOAuth
|
|
|
29
32
|
# @api public
|
|
30
33
|
# @param name [String] the signature method name (e.g., "HMAC-SHA512")
|
|
31
34
|
# @param rsa [Boolean] whether this method uses RSA (raw consumer_secret as key)
|
|
35
|
+
# @param verify [Proc, nil] a block that verifies a signature with a key, for methods whose
|
|
36
|
+
# signature cannot be recomputed from the verifying key, such as RSA with a public key
|
|
37
|
+
# @param digest [String] the hash algorithm this method signs with, which oauth_body_hash also uses
|
|
32
38
|
# @yield [secret, signature_base] block that computes the signature
|
|
33
39
|
# @yieldparam secret [String] the signing secret (or PEM key for RSA methods)
|
|
34
40
|
# @yieldparam signature_base [String] the signature base string
|
|
@@ -40,8 +46,8 @@ module SimpleOAuth
|
|
|
40
46
|
# OpenSSL::HMAC.digest("SHA512", secret, base)
|
|
41
47
|
# )
|
|
42
48
|
# end
|
|
43
|
-
def register(name, rsa: false, &block)
|
|
44
|
-
@registry[normalize_name(name)] = {implementation: block, rsa: rsa}
|
|
49
|
+
def register(name, rsa: false, verify: nil, digest: DEFAULT_DIGEST, &block)
|
|
50
|
+
@registry[normalize_name(name)] = {implementation: block, rsa: rsa, verifier: verify, digest: digest}
|
|
45
51
|
end
|
|
46
52
|
|
|
47
53
|
# Checks if a signature method is registered
|
|
@@ -77,6 +83,18 @@ module SimpleOAuth
|
|
|
77
83
|
@registry.dig(normalize_name(name), :rsa) || false
|
|
78
84
|
end
|
|
79
85
|
|
|
86
|
+
# Returns the hash algorithm a signature method signs with
|
|
87
|
+
#
|
|
88
|
+
# @api public
|
|
89
|
+
# @param name [String] the signature method name
|
|
90
|
+
# @return [String] the hash algorithm, such as SHA1 or SHA256
|
|
91
|
+
# @raise [ArgumentError] if the signature method is not registered
|
|
92
|
+
# @example
|
|
93
|
+
# SimpleOAuth::Signature.digest("HMAC-SHA256") # => "SHA256"
|
|
94
|
+
def digest(name)
|
|
95
|
+
fetch(name).fetch(:digest)
|
|
96
|
+
end
|
|
97
|
+
|
|
80
98
|
# Computes a signature using the specified method
|
|
81
99
|
#
|
|
82
100
|
# @api public
|
|
@@ -88,12 +106,26 @@ module SimpleOAuth
|
|
|
88
106
|
# @example
|
|
89
107
|
# SimpleOAuth::Signature.sign("HMAC-SHA1", "secret&token", "GET&url¶ms")
|
|
90
108
|
def sign(name, secret, signature_base)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
fetch(name).fetch(:implementation).call(secret, signature_base)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Verifies a signature against a key and a signature base string
|
|
113
|
+
#
|
|
114
|
+
# @api public
|
|
115
|
+
# @param name [String] the signature method name
|
|
116
|
+
# @param key [String] the verifying key: a public or private RSA key, or the signing secret
|
|
117
|
+
# @param signature_base [String] the signature base string
|
|
118
|
+
# @param signature [String] the signature to verify
|
|
119
|
+
# @return [Boolean] true if the signature is valid
|
|
120
|
+
# @raise [ArgumentError] if the signature method is not registered
|
|
121
|
+
# @note Signatures are compared in constant time, so verifying leaks no timing information
|
|
122
|
+
# @example
|
|
123
|
+
# SimpleOAuth::Signature.verify("RSA-SHA1", public_key_pem, "GET&url¶ms", signature)
|
|
124
|
+
def verify(name, key, signature_base, signature)
|
|
125
|
+
verifier = fetch(name).fetch(:verifier)
|
|
126
|
+
return OpenSSL.secure_compare(sign(name, key, signature_base), signature) if verifier.nil?
|
|
127
|
+
|
|
128
|
+
verifier.call(key, signature_base, signature)
|
|
97
129
|
end
|
|
98
130
|
|
|
99
131
|
# Unregisters a signature method (useful for testing)
|
|
@@ -118,6 +150,18 @@ module SimpleOAuth
|
|
|
118
150
|
register_builtin_methods
|
|
119
151
|
end
|
|
120
152
|
|
|
153
|
+
# Decodes Base64-encoded data
|
|
154
|
+
#
|
|
155
|
+
# @api public
|
|
156
|
+
# @param data [String] Base64-encoded data
|
|
157
|
+
# @return [String] the decoded binary data
|
|
158
|
+
# @example
|
|
159
|
+
# SimpleOAuth::Signature.decode_base64("AQID")
|
|
160
|
+
# # => "\x01\x02\x03"
|
|
161
|
+
def decode_base64(data)
|
|
162
|
+
Base64.decode64(data)
|
|
163
|
+
end
|
|
164
|
+
|
|
121
165
|
# Encodes binary data as Base64 without newlines
|
|
122
166
|
#
|
|
123
167
|
# @api public
|
|
@@ -132,6 +176,19 @@ module SimpleOAuth
|
|
|
132
176
|
|
|
133
177
|
private
|
|
134
178
|
|
|
179
|
+
# Looks up a registered signature method
|
|
180
|
+
#
|
|
181
|
+
# @api private
|
|
182
|
+
# @param name [String] the signature method name
|
|
183
|
+
# @return [Hash] the registry entry
|
|
184
|
+
# @raise [ArgumentError] if the signature method is not registered
|
|
185
|
+
def fetch(name)
|
|
186
|
+
@registry.fetch(normalize_name(name)) do
|
|
187
|
+
raise ArgumentError, "Unknown signature method: #{name}. " \
|
|
188
|
+
"Registered methods: #{@registry.keys.join(", ")}"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
135
192
|
# Normalizes signature method name for registry lookup
|
|
136
193
|
#
|
|
137
194
|
# @api private
|
|
@@ -157,7 +214,7 @@ module SimpleOAuth
|
|
|
157
214
|
# @return [void]
|
|
158
215
|
def register_hmac_methods
|
|
159
216
|
%w[SHA1 SHA256].each do |digest|
|
|
160
|
-
register("HMAC-#{digest}") do |secret, signature_base|
|
|
217
|
+
register("HMAC-#{digest}", digest: digest) do |secret, signature_base|
|
|
161
218
|
encode_base64(OpenSSL::HMAC.digest(digest, secret, signature_base))
|
|
162
219
|
end
|
|
163
220
|
end
|
|
@@ -169,7 +226,10 @@ module SimpleOAuth
|
|
|
169
226
|
# @return [void]
|
|
170
227
|
def register_rsa_methods
|
|
171
228
|
%w[SHA1 SHA256].each do |digest|
|
|
172
|
-
|
|
229
|
+
verifier = lambda { |key_pem, signature_base, signature|
|
|
230
|
+
OpenSSL::PKey::RSA.new(key_pem).verify(digest, decode_base64(signature), signature_base)
|
|
231
|
+
}
|
|
232
|
+
register("RSA-#{digest}", rsa: true, verify: verifier, digest: digest) do |private_key_pem, signature_base|
|
|
173
233
|
private_key = OpenSSL::PKey::RSA.new(private_key_pem)
|
|
174
234
|
encode_base64(private_key.sign(digest, signature_base))
|
|
175
235
|
end
|
data/lib/simple_oauth/version.rb
CHANGED
data/sig/openssl_ext.rbs
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# Extensions to OpenSSL types
|
|
2
2
|
module OpenSSL
|
|
3
|
+
# Compare two strings in constant time
|
|
4
|
+
def self.secure_compare: (String a, String b) -> bool
|
|
5
|
+
|
|
3
6
|
module PKey
|
|
4
7
|
class PKey
|
|
5
8
|
# Sign with digest name as String (in addition to Digest object)
|
|
6
9
|
def sign: (String | OpenSSL::Digest digest, String data) -> String
|
|
10
|
+
|
|
11
|
+
# Verify with digest name as String (in addition to Digest object)
|
|
12
|
+
def verify: (String | OpenSSL::Digest digest, String signature, String data) -> bool
|
|
7
13
|
end
|
|
8
14
|
end
|
|
9
15
|
end
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
module SimpleOAuth
|
|
2
2
|
class Header
|
|
3
3
|
# Class methods for Header - parsing, defaults, and body hashing
|
|
4
|
-
module
|
|
4
|
+
# What the ClassMethods module needs from the class that extends it
|
|
5
|
+
interface _HeaderFactory
|
|
6
|
+
def new: (String | Symbol method, String | URI::Generic url, Header::request_params params,
|
|
7
|
+
?Header::oauth_options | String oauth, ?String? body) -> Header
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods : _HeaderFactory
|
|
5
11
|
# Returns default OAuth options with generated nonce and timestamp
|
|
6
|
-
def default_options: (?String? body) -> Header::oauth_options
|
|
12
|
+
def default_options: (?String? body, ?String signature_method) -> Header::oauth_options
|
|
7
13
|
|
|
8
14
|
# Computes the oauth_body_hash for a request body
|
|
9
15
|
def body_hash: (String? body, ?String algorithm) -> String
|
|
@@ -12,10 +18,23 @@ module SimpleOAuth
|
|
|
12
18
|
def parse: (String | _ToS header) -> Header::oauth_options
|
|
13
19
|
|
|
14
20
|
# Parses OAuth parameters from a form-encoded POST body
|
|
21
|
+
# Builds a header for an HTTP request
|
|
22
|
+
def from_request: (Header::_Request request, ?Header::oauth_options | String oauth) -> Header
|
|
23
|
+
|
|
24
|
+
# Parses OAuth parameters from a query string
|
|
25
|
+
def parse_query: (String | _ToS body) -> Hash[Symbol, String]
|
|
26
|
+
|
|
15
27
|
def parse_form_body: (String | _ToS body) -> Header::oauth_options
|
|
16
28
|
|
|
17
29
|
private
|
|
18
30
|
|
|
31
|
+
# Checks whether a request carries a form-encoded body
|
|
32
|
+
def form_encoded?: (Header::_Request request) -> bool
|
|
33
|
+
|
|
34
|
+
# Extracts the media type from a request, without its parameters
|
|
35
|
+
def media_type: (Header::_Request request) -> String
|
|
36
|
+
|
|
37
|
+
|
|
19
38
|
# Generates a random nonce for OAuth requests
|
|
20
39
|
def generate_nonce: () -> String
|
|
21
40
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module SimpleOAuth
|
|
2
|
+
class Header
|
|
3
|
+
# What the Params module needs from the header that includes it
|
|
4
|
+
interface _Signable
|
|
5
|
+
def options: () -> Header::oauth_options
|
|
6
|
+
def params: () -> Header::request_params
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Normalization of the parameters that are signed
|
|
10
|
+
module Params : _Signable
|
|
11
|
+
@uri: URI::Generic
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# Extracts query parameters from the request URL
|
|
16
|
+
def url_params: () -> Array[untyped]
|
|
17
|
+
# Normalizes and sorts all request parameters for signing
|
|
18
|
+
def normalized_params: () -> String
|
|
19
|
+
# Collects all parameters to include in signature
|
|
20
|
+
def signature_params: () -> Array[untyped]
|
|
21
|
+
# Expands parameters into one pair per value, for parameters with several values
|
|
22
|
+
def expanded_params: () -> Array[untyped]
|
|
23
|
+
# Extracts valid OAuth attributes from options (excludes realm per RFC 5849)
|
|
24
|
+
def attributes: () -> Header::signed_attributes_hash
|
|
25
|
+
# Validates that no unknown keys are present in options
|
|
26
|
+
def validate_option_keys!: () -> void
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -5,16 +5,22 @@
|
|
|
5
5
|
module SimpleOAuth
|
|
6
6
|
module Signature
|
|
7
7
|
# Type for signature implementation block
|
|
8
|
-
type signature_block = ^(
|
|
8
|
+
type signature_block = ^(untyped secret, String signature_base) -> String
|
|
9
|
+
|
|
10
|
+
# Type for a block that verifies a signature with a key
|
|
11
|
+
type verifier_block = ^(untyped key, String signature_base, String signature) -> bool
|
|
9
12
|
|
|
10
13
|
# Type for registry entry
|
|
11
|
-
type registry_entry = { implementation: signature_block, rsa: bool }
|
|
14
|
+
type registry_entry = { implementation: signature_block, rsa: bool, verifier: verifier_block?, digest: String }
|
|
15
|
+
|
|
16
|
+
# The hash algorithm of the signature methods RFC 5849 defines
|
|
17
|
+
DEFAULT_DIGEST: String
|
|
12
18
|
|
|
13
19
|
# Registry of signature method implementations (class-level instance variable)
|
|
14
20
|
self.@registry: Hash[String, registry_entry]
|
|
15
21
|
|
|
16
22
|
# Registers a custom signature method
|
|
17
|
-
def self.register: (String | Symbol name, ?rsa: bool) { (
|
|
23
|
+
def self.register: (String | Symbol name, ?rsa: bool, ?verify: verifier_block?, ?digest: String) { (untyped, String) -> String } -> void
|
|
18
24
|
|
|
19
25
|
# Checks if a signature method is registered
|
|
20
26
|
def self.registered?: (String | Symbol name) -> bool
|
|
@@ -25,9 +31,15 @@ module SimpleOAuth
|
|
|
25
31
|
# Checks if a signature method uses RSA
|
|
26
32
|
def self.rsa?: (String | Symbol name) -> bool
|
|
27
33
|
|
|
34
|
+
# Returns the hash algorithm a signature method signs with
|
|
35
|
+
def self.digest: (String | Symbol name) -> String
|
|
36
|
+
|
|
28
37
|
# Computes a signature using the specified method
|
|
29
38
|
def self.sign: (String | Symbol name, String? secret, String signature_base) -> String
|
|
30
39
|
|
|
40
|
+
# Verifies a signature against a key and a signature base string
|
|
41
|
+
def self.verify: (String | Symbol name, String? key, String signature_base, String signature) -> bool
|
|
42
|
+
|
|
31
43
|
# Unregisters a signature method
|
|
32
44
|
def self.unregister: (String | Symbol name) -> void
|
|
33
45
|
|
|
@@ -37,8 +49,14 @@ module SimpleOAuth
|
|
|
37
49
|
# Encodes binary data as Base64 without newlines
|
|
38
50
|
def self.encode_base64: (String data) -> String
|
|
39
51
|
|
|
52
|
+
# Decodes Base64-encoded data
|
|
53
|
+
def self.decode_base64: (String data) -> String
|
|
54
|
+
|
|
40
55
|
private
|
|
41
56
|
|
|
57
|
+
# Looks up a registered signature method
|
|
58
|
+
def self.fetch: (String | Symbol name) -> registry_entry
|
|
59
|
+
|
|
42
60
|
# Normalizes signature method name for registry lookup
|
|
43
61
|
def self.normalize_name: (String | Symbol name) -> String
|
|
44
62
|
|
data/sig/simple_oauth.rbs
CHANGED
|
@@ -40,6 +40,9 @@ module SimpleOAuth
|
|
|
40
40
|
# Prefix for OAuth parameters
|
|
41
41
|
OAUTH_PREFIX: String
|
|
42
42
|
|
|
43
|
+
# The content type whose body parameters are signed
|
|
44
|
+
FORM_CONTENT_TYPE: String
|
|
45
|
+
|
|
43
46
|
# Default signature method per RFC 5849
|
|
44
47
|
DEFAULT_SIGNATURE_METHOD: String
|
|
45
48
|
|
|
@@ -55,6 +58,14 @@ module SimpleOAuth
|
|
|
55
58
|
# Valid keys when parsing OAuth parameters (ATTRIBUTE_KEYS + signature)
|
|
56
59
|
PARSE_KEYS: Array[Symbol]
|
|
57
60
|
|
|
61
|
+
# What from_request needs from a request object, such as a Net::HTTPRequest
|
|
62
|
+
interface _Request
|
|
63
|
+
def method: () -> String
|
|
64
|
+
def uri: () -> URI::Generic?
|
|
65
|
+
def body: () -> String?
|
|
66
|
+
def []: (String name) -> String?
|
|
67
|
+
end
|
|
68
|
+
|
|
58
69
|
# Type aliases for clarity
|
|
59
70
|
type oauth_key = :body_hash | :callback | :consumer_key | :nonce | :signature_method | :timestamp | :token | :verifier | :version
|
|
60
71
|
type ignored_key = :consumer_secret | :token_secret | :signature | :realm | :ignore_extra_keys
|
|
@@ -77,6 +88,9 @@ module SimpleOAuth
|
|
|
77
88
|
# The OAuth options including credentials and signature
|
|
78
89
|
attr_reader options: oauth_options
|
|
79
90
|
|
|
91
|
+
# Parameter and attribute normalization from the Params module
|
|
92
|
+
include Params
|
|
93
|
+
|
|
80
94
|
# Class methods from ClassMethods module
|
|
81
95
|
extend ClassMethods
|
|
82
96
|
|
|
@@ -124,32 +138,24 @@ module SimpleOAuth
|
|
|
124
138
|
# Builds the normalized OAuth attributes string for the Authorization header
|
|
125
139
|
def normalized_attributes: () -> String
|
|
126
140
|
|
|
127
|
-
# Extracts valid OAuth attributes from options (excludes realm per RFC 5849)
|
|
128
|
-
def attributes: () -> signed_attributes_hash
|
|
129
|
-
|
|
130
|
-
# Validates that no unknown keys are present in options
|
|
131
|
-
def validate_option_keys!: () -> void
|
|
132
|
-
|
|
133
141
|
# Returns OAuth attributes including realm for Authorization header output
|
|
134
142
|
def header_attributes: () -> signed_attributes_hash
|
|
135
143
|
|
|
136
|
-
#
|
|
137
|
-
def
|
|
144
|
+
# The key for signing and verifying
|
|
145
|
+
def signing_key: (oauth_options options) -> String?
|
|
146
|
+
|
|
147
|
+
# Checks the body against the oauth_body_hash the header carries
|
|
148
|
+
def body_hash_valid?: () -> bool
|
|
138
149
|
|
|
139
150
|
# Computes the OAuth signature using the configured signature method
|
|
140
151
|
def signature: () -> String
|
|
141
152
|
|
|
142
153
|
# Builds the secret string from consumer and token secrets
|
|
143
|
-
def secret: () -> String
|
|
154
|
+
def secret: (oauth_options options) -> String
|
|
144
155
|
|
|
145
156
|
# Builds the signature base string from method, URL, and params
|
|
146
157
|
def signature_base: () -> String
|
|
147
158
|
|
|
148
|
-
# Normalizes and sorts all request parameters for signing
|
|
149
|
-
def normalized_params: () -> String
|
|
150
|
-
|
|
151
|
-
# Collects all parameters to include in signature
|
|
152
|
-
def signature_params: () -> Array[untyped]
|
|
153
159
|
end
|
|
154
160
|
end
|
|
155
161
|
|
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: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steve Richert
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- lib/simple_oauth/errors.rb
|
|
60
60
|
- lib/simple_oauth/header.rb
|
|
61
61
|
- lib/simple_oauth/header/class_methods.rb
|
|
62
|
+
- lib/simple_oauth/header/params.rb
|
|
62
63
|
- lib/simple_oauth/parser.rb
|
|
63
64
|
- lib/simple_oauth/signature.rb
|
|
64
65
|
- lib/simple_oauth/version.rb
|
|
@@ -67,6 +68,7 @@ files:
|
|
|
67
68
|
- sig/openssl_ext.rbs
|
|
68
69
|
- sig/simple_oauth.rbs
|
|
69
70
|
- sig/simple_oauth/header/class_methods.rbs
|
|
71
|
+
- sig/simple_oauth/header/params.rbs
|
|
70
72
|
- sig/simple_oauth/parser.rbs
|
|
71
73
|
- sig/simple_oauth/signature.rbs
|
|
72
74
|
- sig/strscan.rbs
|