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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e33b22e5f3c253796236a61dda5f130f2943c4e2
4
- data.tar.gz: 070a415e6824f5e370668c3253c3431480a216b2
2
+ SHA256:
3
+ metadata.gz: c50ae9a3795998aadd96790e68574680bec0bc5d848ad2fd139327e4123b2872
4
+ data.tar.gz: 6c8d45ae40b2f52846d0c83ce9e4b7d4f1fb53caa0f516734c81abcb5ecc7f3a
5
5
  SHA512:
6
- metadata.gz: e861d3e040c487c501e1c422a9120be8fe51fefad5162956f686d4457fc602bf35f91ea7f2e5633cd18c94f0a039e10b3ffb578d253ff45d76f83d67bb02b85c
7
- data.tar.gz: c4bd1d327734ab9b88d754ada4ce5b2f534a5db640c1f6126941910fe6877c90b39a52d218cd09fc1d77a94a2ddd47b16a60b49f744a537529ee430f72e4b764
6
+ metadata.gz: c9d3a63ea92cd30dec03017175fd3be9611b484a20f79a7810229885f9f4697b81eeccdce3dd399f25d1e9f6ea316ca09dee0440d961be869f71a3f6a120d02c
7
+ data.tar.gz: 2a511446baa6096738e47e93b57eaedf6e53840e570c8664dee0227303ecade3237336e475292108403c791728bac88ab847b17d33ccc5535c2c1fb5c12a2a7a
data/.rubocop.yml CHANGED
@@ -1,57 +1,59 @@
1
- Metrics/AbcSize:
2
- Max: 16
3
-
4
- Metrics/BlockNesting:
5
- Max: 1
6
-
7
- Metrics/LineLength:
8
- AllowURI: true
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
- Max: 4
17
- CountKeywordArgs: true
18
-
19
- Style/AccessModifierIndentation:
20
- EnforcedStyle: outdent
44
+ CountKeywordArgs: false
21
45
 
22
- Style/CollectionMethods:
23
- PreferredMethods:
24
- map: 'collect'
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/Documentation:
50
+ Style/FrozenStringLiteralComment:
30
51
  Enabled: false
31
52
 
32
- Style/DotPosition:
33
- EnforcedStyle: trailing
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/TrailingComma:
57
- EnforcedStyleForMultiline: 'comma'
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
- Copyright (c) 2010-2013 Steve Richert, Erik Michaels-Ober
1
+ The MIT License (MIT)
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
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
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
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
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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
- [![Gem Version](http://img.shields.io/gem/v/simple_oauth.svg)][gem]
4
- [![Build Status](http://img.shields.io/travis/laserlemon/simple_oauth.svg)][travis]
5
- [![Dependency Status](http://img.shields.io/gemnasium/laserlemon/simple_oauth.svg)][gemnasium]
6
- [![Code Climate](http://img.shields.io/codeclimate/github/laserlemon/simple_oauth.svg)][codeclimate]
7
- [![Coverage Status](http://img.shields.io/coveralls/laserlemon/simple_oauth.svg)][coveralls]
8
-
9
- [gem]: https://rubygems.org/gems/simple_oauth
10
- [travis]: http://travis-ci.org/laserlemon/simple_oauth
11
- [gemnasium]: https://gemnasium.com/laserlemon/simple_oauth
12
- [codeclimate]: https://codeclimate.com/github/laserlemon/simple_oauth
13
- [coveralls]: https://coveralls.io/r/laserlemon/simple_oauth
14
-
15
- Simply builds and verifies OAuth headers
16
-
17
- ## Supported Rubies
18
- This library aims to support and is [tested
19
- against](http://travis-ci.org/laserlemon/simple_oauth) the following Ruby
20
- implementations:
21
-
22
- * Ruby 1.8.7
23
- * Ruby 1.9.3
24
- * Ruby 2.0.0
25
- * Ruby 2.1
26
- * [JRuby](http://jruby.org/)
27
- * [Rubinius](http://rubini.us/)
28
-
29
- If something doesn't work on one of these interpreters, it's a bug.
30
-
31
- This library may inadvertently work (or seem to work) on other Ruby
32
- implementations, however support will only be provided for the versions listed
33
- above.
34
-
35
- If you would like this library to support another Ruby version, you may
36
- volunteer to be a maintainer. Being a maintainer entails making sure all tests
37
- run and pass on that implementation. When something breaks on your
38
- implementation, you will be responsible for providing patches in a timely
39
- fashion. If critical issues for a particular implementation exist at the time
40
- of a major release, support for that Ruby version may be dropped.
41
-
42
- ## Copyright
43
- Copyright (c) 2010-2013 Steve Richert, Erik Michaels-Ober. See
44
- [LICENSE](LICENSE.md) for details.
3
+ [![Gem Version](https://badge.fury.io/rb/simple_oauth.svg)](https://badge.fury.io/rb/simple_oauth)
4
+ [![Test](https://github.com/laserlemon/simple_oauth/actions/workflows/test.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/test.yml)
5
+ [![Mutant](https://github.com/laserlemon/simple_oauth/actions/workflows/mutant.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/mutant.yml)
6
+ [![Lint](https://github.com/laserlemon/simple_oauth/actions/workflows/lint.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/lint.yml)
7
+ [![Typecheck](https://github.com/laserlemon/simple_oauth/actions/workflows/typecheck.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/typecheck.yml)
8
+ [![Yardstick](https://github.com/laserlemon/simple_oauth/actions/workflows/yardstick.yml/badge.svg)](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 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ require "rubocop/rake_task"
4
+ require "standard/rake"
3
5
 
4
- RSpec::Core::RakeTask.new(:spec)
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
- task :test => :spec
12
+ RuboCop::RakeTask.new
7
13
 
8
- begin
9
- require 'rubocop/rake_task'
10
- RuboCop::RakeTask.new
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 'yardstick/rake/measurement'
18
- Yardstick::Rake::Measurement.new do |measurement|
19
- measurement.output = 'measurement/report.txt'
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
- require 'yardstick/rake/verify'
23
- Yardstick::Rake::Verify.new do |verify|
24
- verify.threshold = 47
31
+ desc "Run type checker"
32
+ task :steep do
33
+ system("bundle", "exec", "steep", "check") || exit(1)
25
34
  end
26
35
 
27
- task :default => [:spec, :rubocop, :verify_measurements]
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,7 @@
1
+ module SimpleOAuth
2
+ # Error raised when parsing a malformed OAuth Authorization header
3
+ class ParseError < StandardError; end
4
+
5
+ # Error raised when invalid options are passed to Header
6
+ class InvalidOptionsError < StandardError; end
7
+ 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