simple_oauth 0.3.1 → 0.4.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 +5 -5
- data/.rubocop.yml +51 -49
- data/CHANGELOG.md +21 -0
- data/LICENSE.md +18 -17
- data/README.md +136 -42
- data/Rakefile +27 -18
- data/Steepfile +18 -0
- data/lib/simple_oauth/encoding.rb +48 -0
- data/lib/simple_oauth/errors.rb +7 -0
- data/lib/simple_oauth/header/class_methods.rb +99 -0
- data/lib/simple_oauth/header.rb +218 -77
- data/lib/simple_oauth/parser.rb +107 -0
- data/lib/simple_oauth/signature.rb +191 -0
- data/lib/simple_oauth/version.rb +5 -0
- data/lib/simple_oauth.rb +30 -1
- data/mutant.yml +17 -0
- data/sig/matchdata_ext.rbs +6 -0
- data/sig/openssl_ext.rbs +9 -0
- data/sig/simple_oauth/header/class_methods.rbs +26 -0
- data/sig/simple_oauth/parser.rbs +24 -0
- data/sig/simple_oauth/signature.rbs +57 -0
- data/sig/simple_oauth.rbs +158 -0
- data/sig/strscan.rbs +9 -0
- data/sig/uri_rfc2396_parser.rbs +10 -0
- metadata +49 -23
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.travis.yml +0 -21
- data/Gemfile +0 -17
- data/simple_oauth.gemspec +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c50ae9a3795998aadd96790e68574680bec0bc5d848ad2fd139327e4123b2872
|
|
4
|
+
data.tar.gz: 6c8d45ae40b2f52846d0c83ce9e4b7d4f1fb53caa0f516734c81abcb5ecc7f3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9d3a63ea92cd30dec03017175fd3be9611b484a20f79a7810229885f9f4697b81eeccdce3dd399f25d1e9f6ea316ca09dee0440d961be869f71a3f6a120d02c
|
|
7
|
+
data.tar.gz: 2a511446baa6096738e47e93b57eaedf6e53840e570c8664dee0227303ecade3237336e475292108403c791728bac88ab847b17d33ccc5535c2c1fb5c12a2a7a
|
data/.rubocop.yml
CHANGED
|
@@ -1,57 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
require:
|
|
2
|
+
- standard
|
|
3
|
+
|
|
4
|
+
plugins:
|
|
5
|
+
- standard-performance
|
|
6
|
+
- rubocop-minitest
|
|
7
|
+
- rubocop-performance
|
|
8
|
+
- rubocop-rake
|
|
9
|
+
|
|
10
|
+
AllCops:
|
|
11
|
+
NewCops: enable
|
|
12
|
+
TargetRubyVersion: 3.2
|
|
13
|
+
|
|
14
|
+
Layout/ArgumentAlignment:
|
|
15
|
+
Enabled: true
|
|
16
|
+
EnforcedStyle: with_fixed_indentation
|
|
17
|
+
|
|
18
|
+
Layout/LeadingCommentSpace:
|
|
19
|
+
AllowRBSInlineAnnotation: true
|
|
20
|
+
|
|
21
|
+
Layout/ArrayAlignment:
|
|
22
|
+
Enabled: true
|
|
23
|
+
EnforcedStyle: with_fixed_indentation
|
|
24
|
+
|
|
25
|
+
Layout/EndAlignment:
|
|
26
|
+
Enabled: true
|
|
27
|
+
EnforcedStyleAlignWith: variable
|
|
28
|
+
|
|
29
|
+
Layout/HashAlignment:
|
|
30
|
+
Enabled: true
|
|
31
|
+
EnforcedHashRocketStyle: key
|
|
32
|
+
EnforcedColonStyle: key
|
|
33
|
+
EnforcedLastArgumentHashStyle: always_inspect
|
|
34
|
+
|
|
35
|
+
Layout/ParameterAlignment:
|
|
36
|
+
Enabled: true
|
|
37
|
+
EnforcedStyle: with_fixed_indentation
|
|
38
|
+
IndentationWidth: ~
|
|
39
|
+
|
|
40
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
9
41
|
Enabled: false
|
|
10
42
|
|
|
11
|
-
Metrics/MethodLength:
|
|
12
|
-
CountComments: false
|
|
13
|
-
Max: 7
|
|
14
|
-
|
|
15
43
|
Metrics/ParameterLists:
|
|
16
|
-
|
|
17
|
-
CountKeywordArgs: true
|
|
18
|
-
|
|
19
|
-
Style/AccessModifierIndentation:
|
|
20
|
-
EnforcedStyle: outdent
|
|
44
|
+
CountKeywordArgs: false
|
|
21
45
|
|
|
22
|
-
Style/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
reduce: 'inject'
|
|
26
|
-
find: 'detect'
|
|
27
|
-
find_all: 'select'
|
|
46
|
+
Style/Alias:
|
|
47
|
+
Enabled: true
|
|
48
|
+
EnforcedStyle: prefer_alias_method
|
|
28
49
|
|
|
29
|
-
Style/
|
|
50
|
+
Style/FrozenStringLiteralComment:
|
|
30
51
|
Enabled: false
|
|
31
52
|
|
|
32
|
-
Style/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Style/DoubleNegation:
|
|
36
|
-
Enabled: false
|
|
37
|
-
|
|
38
|
-
Style/EachWithObject:
|
|
39
|
-
Enabled: false
|
|
40
|
-
|
|
41
|
-
Style/Encoding:
|
|
42
|
-
Enabled: false
|
|
43
|
-
|
|
44
|
-
Style/HashSyntax:
|
|
45
|
-
EnforcedStyle: hash_rockets
|
|
46
|
-
|
|
47
|
-
Style/Lambda:
|
|
48
|
-
Enabled: false
|
|
49
|
-
|
|
50
|
-
Style/RaiseArgs:
|
|
51
|
-
EnforcedStyle: compact
|
|
52
|
-
|
|
53
|
-
Style/SpaceInsideHashLiteralBraces:
|
|
54
|
-
EnforcedStyle: no_space
|
|
53
|
+
Style/StringLiterals:
|
|
54
|
+
Enabled: true
|
|
55
|
+
EnforcedStyle: double_quotes
|
|
55
56
|
|
|
56
|
-
Style/
|
|
57
|
-
|
|
57
|
+
Style/StringLiteralsInInterpolation:
|
|
58
|
+
Enabled: true
|
|
59
|
+
EnforcedStyle: double_quotes
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## [0.4.0] - 2026-02-01
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
* Extensible signature method registry allowing custom signature methods to be registered at runtime
|
|
6
|
+
* Support for RSA-SHA256 and HMAC-SHA256 signature methods
|
|
7
|
+
* OAuth Request Body Hash support (`oauth_body_hash` parameter) for signing requests with non-form-encoded bodies
|
|
8
|
+
* Support for parsing OAuth credentials from POST body via `Header.parse_form_body`
|
|
9
|
+
* Support for `realm` parameter in OAuth Authorization header
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* Avoid symbolizing untrusted input in parse methods for security
|
|
14
|
+
* Refactored `Header.parse` for improved robustness using StringScanner
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
* Supports Ruby 3.2, 3.3, 3.4, and 4.0
|
|
19
|
+
* Added `base64` and `cgi` as explicit runtime dependencies
|
|
20
|
+
* Migrated test suite from RSpec to Minitest
|
|
21
|
+
|
data/LICENSE.md
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
a copy of this software and associated documentation files (the
|
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
-
the following conditions:
|
|
3
|
+
Copyright (c) 2010-2026 Steve Richert, Erik Berlin
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,44 +1,138 @@
|
|
|
1
1
|
# simple_oauth
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/simple_oauth)
|
|
4
|
+
[](https://github.com/laserlemon/simple_oauth/actions/workflows/test.yml)
|
|
5
|
+
[](https://github.com/laserlemon/simple_oauth/actions/workflows/mutant.yml)
|
|
6
|
+
[](https://github.com/laserlemon/simple_oauth/actions/workflows/lint.yml)
|
|
7
|
+
[](https://github.com/laserlemon/simple_oauth/actions/workflows/typecheck.yml)
|
|
8
|
+
[](https://github.com/laserlemon/simple_oauth/actions/workflows/yardstick.yml)
|
|
9
|
+
|
|
10
|
+
Simply builds and verifies OAuth headers per [RFC 5849](https://tools.ietf.org/html/rfc5849)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
15
|
+
|
|
16
|
+
$ bundle add simple_oauth
|
|
17
|
+
|
|
18
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
19
|
+
|
|
20
|
+
$ gem install simple_oauth
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Building an OAuth Header
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require "simple_oauth"
|
|
28
|
+
|
|
29
|
+
header = SimpleOAuth::Header.new(
|
|
30
|
+
:get,
|
|
31
|
+
"https://api.example.com/resource",
|
|
32
|
+
{status: "Hello"},
|
|
33
|
+
consumer_key: "consumer_key",
|
|
34
|
+
consumer_secret: "consumer_secret",
|
|
35
|
+
token: "access_token",
|
|
36
|
+
token_secret: "token_secret"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
header.to_s
|
|
40
|
+
# => "OAuth oauth_consumer_key=\"consumer_key\", oauth_nonce=\"...\", ..."
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Signature Methods
|
|
44
|
+
|
|
45
|
+
Built-in signature methods: `HMAC-SHA1` (default), `HMAC-SHA256`, `RSA-SHA1`, `RSA-SHA256`, and `PLAINTEXT`.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# Using HMAC-SHA256
|
|
49
|
+
header = SimpleOAuth::Header.new(:get, url, params,
|
|
50
|
+
consumer_key: "key",
|
|
51
|
+
consumer_secret: "secret",
|
|
52
|
+
signature_method: "HMAC-SHA256"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Using RSA-SHA1 (pass PEM-encoded private key as consumer_secret)
|
|
56
|
+
header = SimpleOAuth::Header.new(:get, url, params,
|
|
57
|
+
consumer_key: "key",
|
|
58
|
+
consumer_secret: File.read("private_key.pem"),
|
|
59
|
+
signature_method: "RSA-SHA1"
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Custom Signature Methods
|
|
64
|
+
|
|
65
|
+
Register custom signature methods at runtime:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
SimpleOAuth::Signature.register("HMAC-SHA512") do |secret, signature_base|
|
|
69
|
+
Base64.encode64(OpenSSL::HMAC.digest("SHA512", secret, signature_base)).delete("\n")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check registered methods
|
|
73
|
+
SimpleOAuth::Signature.registered?("HMAC-SHA512") # => true
|
|
74
|
+
SimpleOAuth::Signature.methods # => ["hmac_sha1", "hmac_sha256", "rsa_sha1", "rsa_sha256", "plaintext", "hmac_sha512"]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### OAuth Request Body Hash
|
|
78
|
+
|
|
79
|
+
For non-form-encoded request bodies (e.g., JSON), pass the body as the fifth parameter to compute `oauth_body_hash`:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
json_body = '{"text": "Hello, World!"}'
|
|
83
|
+
|
|
84
|
+
header = SimpleOAuth::Header.new(:post, url, {},
|
|
85
|
+
{consumer_key: "key", consumer_secret: "secret"},
|
|
86
|
+
json_body
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Realm Parameter
|
|
91
|
+
|
|
92
|
+
Include a realm in the Authorization header:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
header = SimpleOAuth::Header.new(:get, url, params,
|
|
96
|
+
consumer_key: "key",
|
|
97
|
+
consumer_secret: "secret",
|
|
98
|
+
realm: "Example"
|
|
99
|
+
)
|
|
100
|
+
# => "OAuth realm=\"Example\", oauth_consumer_key=\"key\", ..."
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Parsing OAuth Headers
|
|
104
|
+
|
|
105
|
+
Parse an OAuth Authorization header:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key", oauth_signature="sig"')
|
|
109
|
+
# => {consumer_key: "key", signature: "sig"}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Parse OAuth credentials from a form-encoded POST body:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
parsed = SimpleOAuth::Header.parse_form_body('oauth_consumer_key=key&oauth_signature=sig&status=hello')
|
|
116
|
+
# => {consumer_key: "key", signature: "sig"}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Verifying Signatures
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# Parse incoming Authorization header
|
|
123
|
+
header = SimpleOAuth::Header.new(:get, request_url, params, authorization_header)
|
|
124
|
+
|
|
125
|
+
# Verify the signature
|
|
126
|
+
header.valid?(consumer_secret: "secret", token_secret: "token_secret")
|
|
127
|
+
# => true
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Contributing
|
|
131
|
+
|
|
132
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/laserlemon/simple_oauth.
|
|
133
|
+
|
|
134
|
+
This project conforms to [Standard Ruby](https://github.com/standardrb/standard). Patches that don’t maintain that standard will not be accepted.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require "rake/testtask"
|
|
3
|
+
require "rubocop/rake_task"
|
|
4
|
+
require "standard/rake"
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
5
11
|
|
|
6
|
-
|
|
12
|
+
RuboCop::RakeTask.new
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
rescue LoadError
|
|
12
|
-
task :rubocop do
|
|
13
|
-
$stderr.puts 'RuboCop is disabled'
|
|
14
|
-
end
|
|
14
|
+
desc "Run mutation tests"
|
|
15
|
+
task :mutant do
|
|
16
|
+
system("bundle", "exec", "mutant", "run") || exit(1)
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
require
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
require "yard"
|
|
20
|
+
YARD::Rake::YardocTask.new(:yard)
|
|
21
|
+
|
|
22
|
+
desc "Check documentation coverage"
|
|
23
|
+
task :yardstick do
|
|
24
|
+
require "yardstick/rake/verify"
|
|
25
|
+
Yardstick::Rake::Verify.new(:verify_docs) do |verify|
|
|
26
|
+
verify.threshold = 100
|
|
27
|
+
end
|
|
28
|
+
Rake::Task[:verify_docs].invoke
|
|
20
29
|
end
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
desc "Run type checker"
|
|
32
|
+
task :steep do
|
|
33
|
+
system("bundle", "exec", "steep", "check") || exit(1)
|
|
25
34
|
end
|
|
26
35
|
|
|
27
|
-
task :
|
|
36
|
+
task default: %i[test rubocop standard mutant yardstick steep]
|
data/Steepfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
D = Steep::Diagnostic
|
|
2
|
+
|
|
3
|
+
target :lib do
|
|
4
|
+
signature "sig"
|
|
5
|
+
|
|
6
|
+
check "lib"
|
|
7
|
+
|
|
8
|
+
library "base64"
|
|
9
|
+
library "openssl"
|
|
10
|
+
library "uri"
|
|
11
|
+
library "cgi"
|
|
12
|
+
library "securerandom"
|
|
13
|
+
|
|
14
|
+
configure_code_diagnostics(D::Ruby.strict) do |hash|
|
|
15
|
+
# Allow FallbackAny warnings for variables in ensure blocks
|
|
16
|
+
hash[D::Ruby::FallbackAny] = :hint
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
3
|
+
module SimpleOAuth
|
|
4
|
+
# OAuth percent-encoding utilities
|
|
5
|
+
#
|
|
6
|
+
# Provides methods for encoding and decoding values according to the OAuth specification.
|
|
7
|
+
# These methods can be used as module functions or extended into a class.
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
# @example Using as module functions
|
|
11
|
+
# SimpleOAuth::Encoding.escape("hello world") # => "hello%20world"
|
|
12
|
+
#
|
|
13
|
+
# @example Extending into a class
|
|
14
|
+
# class MyClass
|
|
15
|
+
# extend SimpleOAuth::Encoding
|
|
16
|
+
# end
|
|
17
|
+
# MyClass.escape("hello world") # => "hello%20world"
|
|
18
|
+
module Encoding
|
|
19
|
+
# Characters that don't need to be escaped per OAuth spec
|
|
20
|
+
UNRESERVED_CHARS = /[^a-z0-9\-._~]/i
|
|
21
|
+
|
|
22
|
+
# Percent-encodes a value according to OAuth specification
|
|
23
|
+
#
|
|
24
|
+
# @api public
|
|
25
|
+
# @param value [String, #to_s] the value to encode
|
|
26
|
+
# @return [String] the percent-encoded value
|
|
27
|
+
# @example
|
|
28
|
+
# SimpleOAuth::Encoding.escape("hello world")
|
|
29
|
+
# # => "hello%20world"
|
|
30
|
+
def escape(value)
|
|
31
|
+
URI::RFC2396_PARSER.escape(value.to_s, UNRESERVED_CHARS)
|
|
32
|
+
end
|
|
33
|
+
alias_method :encode, :escape
|
|
34
|
+
|
|
35
|
+
# Decodes a percent-encoded value
|
|
36
|
+
#
|
|
37
|
+
# @api public
|
|
38
|
+
# @param value [String, #to_s] the value to decode
|
|
39
|
+
# @return [String] the decoded value
|
|
40
|
+
# @example
|
|
41
|
+
# SimpleOAuth::Encoding.unescape("hello%20world")
|
|
42
|
+
# # => "hello world"
|
|
43
|
+
def unescape(value)
|
|
44
|
+
URI::RFC2396_PARSER.unescape(value.to_s)
|
|
45
|
+
end
|
|
46
|
+
alias_method :decode, :unescape
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
require "cgi"
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module SimpleOAuth
|
|
7
|
+
class Header
|
|
8
|
+
# Class methods for Header - parsing, defaults, and body hashing
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
module ClassMethods
|
|
12
|
+
# Returns default OAuth options with generated nonce and timestamp
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
15
|
+
# @param body [String, nil] optional request body for computing oauth_body_hash
|
|
16
|
+
# @return [Hash] default options including nonce, signature_method, timestamp, and version
|
|
17
|
+
# @example
|
|
18
|
+
# SimpleOAuth::Header.default_options
|
|
19
|
+
# # => {nonce: "abc123...", signature_method: "HMAC-SHA1", timestamp: "1234567890", version: "1.0"}
|
|
20
|
+
def default_options(body = nil)
|
|
21
|
+
{
|
|
22
|
+
nonce: generate_nonce,
|
|
23
|
+
signature_method: DEFAULT_SIGNATURE_METHOD,
|
|
24
|
+
timestamp: Integer(Time.now).to_s,
|
|
25
|
+
version: OAUTH_VERSION
|
|
26
|
+
}.tap { |opts| opts[:body_hash] = body_hash(body) if body }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Computes the oauth_body_hash for a request body
|
|
30
|
+
#
|
|
31
|
+
# @api public
|
|
32
|
+
# @param body [String] the raw request body
|
|
33
|
+
# @param algorithm [String] the hash algorithm to use (default: "SHA1")
|
|
34
|
+
# @return [String] Base64-encoded hash of the body
|
|
35
|
+
# @example
|
|
36
|
+
# SimpleOAuth::Header.body_hash('{"text": "Hello"}')
|
|
37
|
+
# # => "aOjMoMwMP1RZ0hKa1HryYDlCKck="
|
|
38
|
+
def body_hash(body, algorithm = "SHA1")
|
|
39
|
+
encode_base64(OpenSSL::Digest.digest(algorithm, body || ""))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Parses an OAuth Authorization header string into a hash
|
|
43
|
+
#
|
|
44
|
+
# @api public
|
|
45
|
+
# @param header [String, #to_s] the OAuth Authorization header string
|
|
46
|
+
# @return [Hash] parsed OAuth attributes with symbol keys (only valid OAuth keys)
|
|
47
|
+
# @raise [SimpleOAuth::ParseError] if the header is malformed
|
|
48
|
+
# @example
|
|
49
|
+
# SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key", oauth_signature="sig"')
|
|
50
|
+
# # => {consumer_key: "key", signature: "sig"}
|
|
51
|
+
def parse(header)
|
|
52
|
+
Parser.new(header).parse(PARSE_KEYS)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Parses OAuth parameters from a form-encoded POST body
|
|
56
|
+
#
|
|
57
|
+
# OAuth 1.0 allows credentials to be transmitted in the request body for
|
|
58
|
+
# POST requests with Content-Type: application/x-www-form-urlencoded
|
|
59
|
+
#
|
|
60
|
+
# @api public
|
|
61
|
+
# @param body [String, #to_s] the form-encoded request body
|
|
62
|
+
# @return [Hash] parsed OAuth attributes with symbol keys (only valid OAuth keys)
|
|
63
|
+
# @example
|
|
64
|
+
# SimpleOAuth::Header.parse_form_body('oauth_consumer_key=key&oauth_signature=sig&status=hello')
|
|
65
|
+
# # => {consumer_key: "key", signature: "sig"}
|
|
66
|
+
def parse_form_body(body)
|
|
67
|
+
valid_keys = PARSE_KEYS.map(&:to_s)
|
|
68
|
+
|
|
69
|
+
result = {} #: Hash[Symbol, String]
|
|
70
|
+
CGI.parse(body.to_s).each do |key, values|
|
|
71
|
+
next unless key.start_with?(OAUTH_PREFIX)
|
|
72
|
+
|
|
73
|
+
parsed_key = key.delete_prefix(OAUTH_PREFIX)
|
|
74
|
+
result[parsed_key.to_sym] = values.first || "" if valid_keys.include?(parsed_key)
|
|
75
|
+
end
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Generates a random nonce for OAuth requests
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
# @return [String] hex-encoded random bytes
|
|
85
|
+
def generate_nonce
|
|
86
|
+
SecureRandom.hex
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Encodes binary data as Base64 without newlines
|
|
90
|
+
#
|
|
91
|
+
# @api private
|
|
92
|
+
# @param data [String] binary data to encode
|
|
93
|
+
# @return [String] Base64-encoded string
|
|
94
|
+
def encode_base64(data)
|
|
95
|
+
Base64.strict_encode64(data)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|