pax_global_header00006660000000000000000000000064151375512250014520gustar00rootroot0000000000000052 comment=0d0b7f48a432a78feb8f764f61828806f33ee211 simple_oauth-0.4.0/000077500000000000000000000000001513755122500142125ustar00rootroot00000000000000simple_oauth-0.4.0/.github/000077500000000000000000000000001513755122500155525ustar00rootroot00000000000000simple_oauth-0.4.0/.github/workflows/000077500000000000000000000000001513755122500176075ustar00rootroot00000000000000simple_oauth-0.4.0/.github/workflows/lint.yml000066400000000000000000000006561513755122500213070ustar00rootroot00000000000000name: Lint on: push: branches: - master pull_request: jobs: lint: runs-on: ubuntu-latest name: RuboCop & Standard steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true - name: Run RuboCop run: bundle exec rake rubocop - name: Run Standard run: bundle exec rake standard simple_oauth-0.4.0/.github/workflows/mutant.yml000066400000000000000000000005701513755122500216440ustar00rootroot00000000000000name: Mutant on: push: branches: - master pull_request: jobs: mutant: runs-on: ubuntu-latest name: Mutation Testing steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true - name: Run mutation tests run: bundle exec rake mutant simple_oauth-0.4.0/.github/workflows/push.yml000066400000000000000000000017401513755122500213130ustar00rootroot00000000000000name: Push gem to RubyGems on: push: tags: - "v*" permissions: contents: read jobs: push: if: github.repository == 'laserlemon/simple_oauth' runs-on: ubuntu-latest environment: name: rubygems.org url: https://rubygems.org/gems/simple_oauth permissions: contents: write id-token: write steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ruby bundler-cache: true - uses: rubygems/configure-rubygems-credentials@v1.0.0 - name: Update RubyGems run: gem update --system - name: Build gem run: bundle exec rake build - name: Sign gem with Sigstore run: gem exec sigstore-cli sign pkg/*.gem --bundle pkg/simple_oauth.gem.sigstore.json - name: Push gem run: gem push pkg/*.gem --attestation pkg/simple_oauth.gem.sigstore.json - name: Wait for release run: gem exec rubygems-await pkg/*.gem simple_oauth-0.4.0/.github/workflows/test.yml000066400000000000000000000007571513755122500213220ustar00rootroot00000000000000name: Test on: push: branches: - master pull_request: jobs: test: runs-on: ubuntu-latest name: Ruby ${{ matrix.ruby }} strategy: matrix: ruby: - "3.2" - "3.3" - "3.4" - "4.0" steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Run tests run: bundle exec rake test simple_oauth-0.4.0/.github/workflows/typecheck.yml000066400000000000000000000005761513755122500223210ustar00rootroot00000000000000name: Typecheck on: push: branches: - master pull_request: jobs: typecheck: runs-on: ubuntu-latest name: Steep Type Checking steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true - name: Run type checker run: bundle exec rake steep simple_oauth-0.4.0/.github/workflows/yardstick.yml000066400000000000000000000006211513755122500223260ustar00rootroot00000000000000name: Yardstick on: push: branches: - master pull_request: jobs: yardstick: runs-on: ubuntu-latest name: Documentation Coverage steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: "3.4" bundler-cache: true - name: Check documentation coverage run: bundle exec rake yardstick simple_oauth-0.4.0/.gitignore000066400000000000000000000001261513755122500162010ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ Gemfile.lock simple_oauth-0.4.0/.rubocop.yml000066400000000000000000000021141513755122500164620ustar00rootroot00000000000000require: - standard plugins: - standard-performance - rubocop-minitest - rubocop-performance - rubocop-rake AllCops: NewCops: enable TargetRubyVersion: 3.2 Layout/ArgumentAlignment: Enabled: true EnforcedStyle: with_fixed_indentation Layout/LeadingCommentSpace: AllowRBSInlineAnnotation: true Layout/ArrayAlignment: Enabled: true EnforcedStyle: with_fixed_indentation Layout/EndAlignment: Enabled: true EnforcedStyleAlignWith: variable Layout/HashAlignment: Enabled: true EnforcedHashRocketStyle: key EnforcedColonStyle: key EnforcedLastArgumentHashStyle: always_inspect Layout/ParameterAlignment: Enabled: true EnforcedStyle: with_fixed_indentation IndentationWidth: ~ Layout/SpaceInsideHashLiteralBraces: Enabled: false Metrics/ParameterLists: CountKeywordArgs: false Style/Alias: Enabled: true EnforcedStyle: prefer_alias_method Style/FrozenStringLiteralComment: Enabled: false Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes Style/StringLiteralsInInterpolation: Enabled: true EnforcedStyle: double_quotes simple_oauth-0.4.0/.yardopts000066400000000000000000000000711513755122500160560ustar00rootroot00000000000000--markup markdown - CONTRIBUTING.md LICENSE.md README.md simple_oauth-0.4.0/CHANGELOG.md000066400000000000000000000013751513755122500160310ustar00rootroot00000000000000## [0.4.0] - 2026-02-01 ### Added * Extensible signature method registry allowing custom signature methods to be registered at runtime * Support for RSA-SHA256 and HMAC-SHA256 signature methods * OAuth Request Body Hash support (`oauth_body_hash` parameter) for signing requests with non-form-encoded bodies * Support for parsing OAuth credentials from POST body via `Header.parse_form_body` * Support for `realm` parameter in OAuth Authorization header ### Fixed * Avoid symbolizing untrusted input in parse methods for security * Refactored `Header.parse` for improved robustness using StringScanner ### Changed * Supports Ruby 3.2, 3.3, 3.4, and 4.0 * Added `base64` and `cgi` as explicit runtime dependencies * Migrated test suite from RSpec to Minitest simple_oauth-0.4.0/CONTRIBUTING.md000066400000000000000000000004331513755122500164430ustar00rootroot00000000000000## Contributing 1. Fork the project. 2. Create a topic branch. 3. Add failing tests. 4. Add code to pass the failing tests. 5. Run `bundle exec rake`. If failing, repeat step 4. 6. Commit and push your changes. 7. Submit a pull request. Please do not include changes to the gemspec. simple_oauth-0.4.0/Gemfile000066400000000000000000000007211513755122500155050ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem's dependencies in simple_oauth.gemspec gemspec gem "minitest", ">= 6.0" gem "mutant-minitest", ">= 0.13" gem "rake", ">= 13.3.1" gem "rubocop", ">= 1.82.1" gem "rubocop-minitest", ">= 0.36" gem "rubocop-performance", ">= 1.26.1" gem "rubocop-rake", ">= 0.7.1" gem "simplecov", ">= 0.22" gem "standard", ">= 1.53" gem "steep", ">= 1.9" gem "webmock", ">= 3.26.1" gem "yard", ">= 0.9.38" gem "yardstick", ">= 0.9.9" simple_oauth-0.4.0/LICENSE.md000066400000000000000000000021121513755122500156120ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2010-2026 Steve Richert, Erik Berlin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. simple_oauth-0.4.0/README.md000066400000000000000000000103731513755122500154750ustar00rootroot00000000000000# simple_oauth [![Gem Version](https://badge.fury.io/rb/simple_oauth.svg)](https://badge.fury.io/rb/simple_oauth) [![Test](https://github.com/laserlemon/simple_oauth/actions/workflows/test.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/test.yml) [![Mutant](https://github.com/laserlemon/simple_oauth/actions/workflows/mutant.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/mutant.yml) [![Lint](https://github.com/laserlemon/simple_oauth/actions/workflows/lint.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/lint.yml) [![Typecheck](https://github.com/laserlemon/simple_oauth/actions/workflows/typecheck.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/typecheck.yml) [![Yardstick](https://github.com/laserlemon/simple_oauth/actions/workflows/yardstick.yml/badge.svg)](https://github.com/laserlemon/simple_oauth/actions/workflows/yardstick.yml) Simply builds and verifies OAuth headers per [RFC 5849](https://tools.ietf.org/html/rfc5849) ## Installation Install the gem and add to the application's Gemfile by executing: $ bundle add simple_oauth If bundler is not being used to manage dependencies, install the gem by executing: $ gem install simple_oauth ## Usage ### Building an OAuth Header ```ruby require "simple_oauth" header = SimpleOAuth::Header.new( :get, "https://api.example.com/resource", {status: "Hello"}, consumer_key: "consumer_key", consumer_secret: "consumer_secret", token: "access_token", token_secret: "token_secret" ) header.to_s # => "OAuth oauth_consumer_key=\"consumer_key\", oauth_nonce=\"...\", ..." ``` ### Signature Methods Built-in signature methods: `HMAC-SHA1` (default), `HMAC-SHA256`, `RSA-SHA1`, `RSA-SHA256`, and `PLAINTEXT`. ```ruby # Using HMAC-SHA256 header = SimpleOAuth::Header.new(:get, url, params, consumer_key: "key", consumer_secret: "secret", signature_method: "HMAC-SHA256" ) # Using RSA-SHA1 (pass PEM-encoded private key as consumer_secret) header = SimpleOAuth::Header.new(:get, url, params, consumer_key: "key", consumer_secret: File.read("private_key.pem"), signature_method: "RSA-SHA1" ) ``` ### Custom Signature Methods Register custom signature methods at runtime: ```ruby SimpleOAuth::Signature.register("HMAC-SHA512") do |secret, signature_base| Base64.encode64(OpenSSL::HMAC.digest("SHA512", secret, signature_base)).delete("\n") end # Check registered methods SimpleOAuth::Signature.registered?("HMAC-SHA512") # => true SimpleOAuth::Signature.methods # => ["hmac_sha1", "hmac_sha256", "rsa_sha1", "rsa_sha256", "plaintext", "hmac_sha512"] ``` ### OAuth Request Body Hash For non-form-encoded request bodies (e.g., JSON), pass the body as the fifth parameter to compute `oauth_body_hash`: ```ruby json_body = '{"text": "Hello, World!"}' header = SimpleOAuth::Header.new(:post, url, {}, {consumer_key: "key", consumer_secret: "secret"}, json_body ) ``` ### Realm Parameter Include a realm in the Authorization header: ```ruby header = SimpleOAuth::Header.new(:get, url, params, consumer_key: "key", consumer_secret: "secret", realm: "Example" ) # => "OAuth realm=\"Example\", oauth_consumer_key=\"key\", ..." ``` ### Parsing OAuth Headers Parse an OAuth Authorization header: ```ruby parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key", oauth_signature="sig"') # => {consumer_key: "key", signature: "sig"} ``` Parse OAuth credentials from a form-encoded POST body: ```ruby parsed = SimpleOAuth::Header.parse_form_body('oauth_consumer_key=key&oauth_signature=sig&status=hello') # => {consumer_key: "key", signature: "sig"} ``` ### Verifying Signatures ```ruby # Parse incoming Authorization header header = SimpleOAuth::Header.new(:get, request_url, params, authorization_header) # Verify the signature header.valid?(consumer_secret: "secret", token_secret: "token_secret") # => true ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/laserlemon/simple_oauth. This project conforms to [Standard Ruby](https://github.com/standardrb/standard). Patches that don’t maintain that standard will not be accepted. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). simple_oauth-0.4.0/Rakefile000066400000000000000000000020031513755122500156520ustar00rootroot00000000000000require "bundler/gem_tasks" # Override release task to skip gem push (handled by GitHub Actions with attestations) Rake::Task["release"].clear desc "Build gem and create tag (gem push handled by CI)" task release: %w[build release:guard_clean release:source_control_push] require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList["test/**/*_test.rb"] end require "rubocop/rake_task" RuboCop::RakeTask.new require "standard/rake" desc "Run mutation tests" task :mutant do system("bundle", "exec", "mutant", "run") || exit(1) end require "yard" YARD::Rake::YardocTask.new(:yard) desc "Check documentation coverage" task :yardstick do require "yardstick/rake/verify" Yardstick::Rake::Verify.new(:verify_docs) do |verify| verify.threshold = 100 end Rake::Task[:verify_docs].invoke end desc "Run type checker" task :steep do system("bundle", "exec", "steep", "check") || exit(1) end task default: %i[test rubocop standard mutant yardstick steep] simple_oauth-0.4.0/Steepfile000066400000000000000000000005211513755122500160530ustar00rootroot00000000000000D = Steep::Diagnostic target :lib do signature "sig" check "lib" library "base64" library "openssl" library "uri" library "cgi" library "securerandom" configure_code_diagnostics(D::Ruby.strict) do |hash| # Allow FallbackAny warnings for variables in ensure blocks hash[D::Ruby::FallbackAny] = :hint end end simple_oauth-0.4.0/bin/000077500000000000000000000000001513755122500147625ustar00rootroot00000000000000simple_oauth-0.4.0/bin/console000077500000000000000000000004001513755122500163440ustar00rootroot00000000000000#!/usr/bin/env ruby require "bundler/setup" require "simple_oauth" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. require "irb" IRB.start(__FILE__) simple_oauth-0.4.0/bin/setup000077500000000000000000000002031513755122500160430ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here simple_oauth-0.4.0/lib/000077500000000000000000000000001513755122500147605ustar00rootroot00000000000000simple_oauth-0.4.0/lib/simple_oauth.rb000066400000000000000000000017501513755122500200010ustar00rootroot00000000000000require_relative "simple_oauth/header" require_relative "simple_oauth/version" # OAuth 1.0 header generation and parsing library # # SimpleOAuth provides a simple interface for building and verifying # OAuth 1.0 Authorization headers per RFC 5849. # # @example Building an OAuth header # header = SimpleOAuth::Header.new( # :get, # "https://api.example.com/resource", # {status: "Hello"}, # consumer_key: "key", # consumer_secret: "secret" # ) # header.to_s # => "OAuth oauth_consumer_key=\"key\", ..." # # @example Parsing an OAuth header # parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key"') # # => {consumer_key: "key"} # # @see https://tools.ietf.org/html/rfc5849 RFC 5849 - The OAuth 1.0 Protocol module SimpleOAuth # Error raised when parsing a malformed OAuth Authorization header class ParseError < StandardError; end # Error raised when invalid options are passed to Header # (defined in header.rb, exported here for convenience) end simple_oauth-0.4.0/lib/simple_oauth/000077500000000000000000000000001513755122500174515ustar00rootroot00000000000000simple_oauth-0.4.0/lib/simple_oauth/encoding.rb000066400000000000000000000026571513755122500215760ustar00rootroot00000000000000require "uri" module SimpleOAuth # OAuth percent-encoding utilities # # Provides methods for encoding and decoding values according to the OAuth specification. # These methods can be used as module functions or extended into a class. # # @api public # @example Using as module functions # SimpleOAuth::Encoding.escape("hello world") # => "hello%20world" # # @example Extending into a class # class MyClass # extend SimpleOAuth::Encoding # end # MyClass.escape("hello world") # => "hello%20world" module Encoding # Characters that don't need to be escaped per OAuth spec UNRESERVED_CHARS = /[^a-z0-9\-._~]/i # Percent-encodes a value according to OAuth specification # # @api public # @param value [String, #to_s] the value to encode # @return [String] the percent-encoded value # @example # SimpleOAuth::Encoding.escape("hello world") # # => "hello%20world" def escape(value) URI::RFC2396_PARSER.escape(value.to_s, UNRESERVED_CHARS) end alias_method :encode, :escape # Decodes a percent-encoded value # # @api public # @param value [String, #to_s] the value to decode # @return [String] the decoded value # @example # SimpleOAuth::Encoding.unescape("hello%20world") # # => "hello world" def unescape(value) URI::RFC2396_PARSER.unescape(value.to_s) end alias_method :decode, :unescape end end simple_oauth-0.4.0/lib/simple_oauth/errors.rb000066400000000000000000000003611513755122500213120ustar00rootroot00000000000000module SimpleOAuth # Error raised when parsing a malformed OAuth Authorization header class ParseError < StandardError; end # Error raised when invalid options are passed to Header class InvalidOptionsError < StandardError; end end simple_oauth-0.4.0/lib/simple_oauth/header.rb000066400000000000000000000216361513755122500212360ustar00rootroot00000000000000require "cgi" require "uri" require_relative "encoding" require_relative "errors" require_relative "parser" require_relative "signature" require_relative "header/class_methods" module SimpleOAuth # Generates OAuth 1.0 Authorization headers for HTTP requests # # @api public class Header # OAuth header scheme prefix OAUTH_SCHEME = "OAuth".freeze # Prefix for OAuth parameters OAUTH_PREFIX = "oauth_".freeze # Default signature method per RFC 5849 DEFAULT_SIGNATURE_METHOD = "HMAC-SHA1".freeze # OAuth version OAUTH_VERSION = "1.0".freeze # Valid OAuth attribute keys that can be included in the header ATTRIBUTE_KEYS = %i[body_hash callback consumer_key nonce signature_method timestamp token verifier version].freeze # Keys that are used internally but should not appear in attributes IGNORED_KEYS = %i[consumer_secret token_secret signature realm ignore_extra_keys].freeze # Valid keys when parsing OAuth parameters (ATTRIBUTE_KEYS + signature) PARSE_KEYS = [*ATTRIBUTE_KEYS, :signature].freeze # The HTTP method for the request # # @return [String] the HTTP method (GET, POST, etc.) # @example # header.method # => "GET" attr_reader :method # The request parameters to be signed # # @return [Hash] the request parameters # @example # header.params # => {"status" => "Hello"} attr_reader :params # The raw request body for oauth_body_hash computation # # @return [String, nil] the request body # @example # header.body # => '{"text": "Hello"}' attr_reader :body # The OAuth options including credentials and signature # # @return [Hash] the OAuth options # @example # header.options # => {consumer_key: "key", nonce: "..."} attr_reader :options extend ClassMethods extend Encoding # Creates a new OAuth header # # @api public # @param method [String, Symbol] the HTTP method # @param url [String, URI] the request URL # @param params [Hash] the request parameters (for form-encoded bodies) # @param oauth [Hash, String] OAuth options hash or an existing Authorization header to parse # @param body [String, nil] raw request body for oauth_body_hash (for non-form-encoded bodies) # @example Create a header with OAuth options # SimpleOAuth::Header.new(:get, "https://api.example.com/resource", {}, # consumer_key: "key", consumer_secret: "secret") # @example Create a header by parsing an existing Authorization header # SimpleOAuth::Header.new(:get, "https://api.example.com/resource", {}, existing_header) # @example Create a header with a JSON body (oauth_body_hash will be computed) # SimpleOAuth::Header.new(:post, "https://api.example.com/resource", {}, # {consumer_key: "key", consumer_secret: "secret"}, '{"text": "Hello"}') def initialize(method, url, params, oauth = {}, body = nil) @method = method.to_s.upcase @uri = normalize_uri(url) @params = params @body = body @options = build_options(oauth, body) end # Returns the normalized URL without query string or fragment # # @api public # @return [String] the normalized URL # @example # header = SimpleOAuth::Header.new(:get, "https://api.example.com/path?query=1", {}) # header.url # # => "https://api.example.com/path" def url @uri.dup.tap { |uri| uri.query = nil }.to_str end # Returns the OAuth Authorization header string # # @api public # @return [String] the Authorization header value # @example # header = SimpleOAuth::Header.new(:get, "https://api.example.com/", {}, # consumer_key: "key", consumer_secret: "secret") # header.to_s # # => "OAuth oauth_consumer_key=\"key\", oauth_nonce=\"...\", ..." def to_s "#{OAUTH_SCHEME} #{normalized_attributes}" end # Validates the signature in the header against the provided secrets # # @api public # @param secrets [Hash] the consumer_secret and token_secret for validation # @return [Boolean] true if the signature is valid, false otherwise # @example # parsed_header = SimpleOAuth::Header.new(:get, url, {}, authorization_header) # parsed_header.valid?(consumer_secret: "secret", token_secret: "token_secret") # # => true def valid?(secrets = {}) original_options = options.dup #: Hash[Symbol, untyped] options.merge!(secrets) options.fetch(:signature).eql?(signature) ensure options.replace(original_options) end # Returns the OAuth attributes including the signature # # @api public # @return [Hash] OAuth attributes with oauth_signature included # @example # header.signed_attributes # # => {oauth_consumer_key: "key", oauth_signature: "...", ...} def signed_attributes header_attributes.merge(oauth_signature: signature) end private # Normalizes and parses a URL into a URI object # # @api private # @param url [String, URI] the URL to normalize # @return [URI::Generic] normalized URI without fragment def normalize_uri(url) URI.parse(url.to_s).tap do |uri| uri.normalize! uri.fragment = nil end end # Builds OAuth options from input (hash or header string) # # @api private # @param oauth [Hash, String] OAuth options hash or Authorization header # @param body [String, nil] request body for body_hash computation # @return [Hash] merged OAuth options with defaults def build_options(oauth, body) if oauth.is_a?(Hash) self.class.default_options(body).merge(oauth.transform_keys(&:to_sym)) else self.class.parse(oauth) end end # Builds the normalized OAuth attributes string for the header # # @api private # @return [String] normalized OAuth attributes for the header def normalized_attributes signed_attributes .sort_by { |key, _| key } .map { |key, value| "#{key}=\"#{Header.escape(value)}\"" } .join(", ") end # Extracts valid OAuth attributes from options # # @api private # @return [Hash] OAuth attributes without signature or realm def attributes validate_option_keys! options.slice(*ATTRIBUTE_KEYS).transform_keys { |key| :"#{OAUTH_PREFIX}#{key}" } end # Validates that no unknown keys are present in options # # @api private # @raise [InvalidOptionsError] if extra keys are found # @return [void] def validate_option_keys! return if options[:ignore_extra_keys] extra_keys = options.keys - ATTRIBUTE_KEYS - IGNORED_KEYS return if extra_keys.empty? raise InvalidOptionsError, "Unknown option keys: #{extra_keys.map(&:inspect).join(", ")}" end # Returns OAuth attributes with realm for the Authorization header # # Per RFC 5849 Section 3.5.1, realm is included in the Authorization header # but excluded from signature calculation (Section 3.4.1.3.1) # # @api private # @return [Hash] OAuth attributes with realm if present def header_attributes attrs = attributes attrs[:realm] = options.fetch(:realm) if options[:realm] attrs end # Extracts query parameters from the request URL # # @api private # @return [Array] URL query parameters as key-value pairs def url_params CGI.parse(@uri.query || "").flat_map do |key, values| values.sort.map { |value| [key, value] } end end # Computes the OAuth signature using the configured method # # @api private # @return [String] the computed signature based on signature_method def signature sig_method = options.fetch(:signature_method) sig_secret = Signature.rsa?(sig_method) ? options[:consumer_secret] : secret Signature.sign(sig_method, sig_secret, signature_base) end # Builds the secret string from consumer and token secrets # # @api private # @return [String] the secret string for signing def secret options.values_at(:consumer_secret, :token_secret).map { |v| Header.escape(v) }.join("&") end # Builds the signature base string from method, URL, and params # # @api private # @return [String] the signature base string def signature_base [method, url, normalized_params].map { |v| Header.escape(v) }.join("&") end # Normalizes and sorts all request parameters for signing # # @api private # @return [String] normalized request parameters def normalized_params signature_params .map { |key, value| [Header.escape(key), Header.escape(value)] } .sort .map { |pair| pair.join("=") } .join("&") end # Collects all parameters to include in signature # # @api private # @return [Array] all parameters for signature as key-value pairs def signature_params attributes.to_a + params.to_a + url_params end end end simple_oauth-0.4.0/lib/simple_oauth/header/000077500000000000000000000000001513755122500207015ustar00rootroot00000000000000simple_oauth-0.4.0/lib/simple_oauth/header/class_methods.rb000066400000000000000000000067211513755122500240640ustar00rootroot00000000000000require "base64" require "cgi" require "openssl" require "securerandom" module SimpleOAuth class Header # Class methods for Header - parsing, defaults, and body hashing # # @api private module ClassMethods # Returns default OAuth options with generated nonce and timestamp # # @api public # @param body [String, nil] optional request body for computing oauth_body_hash # @return [Hash] default options including nonce, signature_method, timestamp, and version # @example # SimpleOAuth::Header.default_options # # => {nonce: "abc123...", signature_method: "HMAC-SHA1", timestamp: "1234567890", version: "1.0"} def default_options(body = nil) { nonce: generate_nonce, signature_method: DEFAULT_SIGNATURE_METHOD, timestamp: Integer(Time.now).to_s, version: OAUTH_VERSION }.tap { |opts| opts[:body_hash] = body_hash(body) if body } end # Computes the oauth_body_hash for a request body # # @api public # @param body [String] the raw request body # @param algorithm [String] the hash algorithm to use (default: "SHA1") # @return [String] Base64-encoded hash of the body # @example # SimpleOAuth::Header.body_hash('{"text": "Hello"}') # # => "aOjMoMwMP1RZ0hKa1HryYDlCKck=" def body_hash(body, algorithm = "SHA1") encode_base64(OpenSSL::Digest.digest(algorithm, body || "")) end # Parses an OAuth Authorization header string into a hash # # @api public # @param header [String, #to_s] the OAuth Authorization header string # @return [Hash] parsed OAuth attributes with symbol keys (only valid OAuth keys) # @raise [SimpleOAuth::ParseError] if the header is malformed # @example # SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key", oauth_signature="sig"') # # => {consumer_key: "key", signature: "sig"} def parse(header) Parser.new(header).parse(PARSE_KEYS) end # Parses OAuth parameters from a form-encoded POST body # # OAuth 1.0 allows credentials to be transmitted in the request body for # POST requests with Content-Type: application/x-www-form-urlencoded # # @api public # @param body [String, #to_s] the form-encoded request body # @return [Hash] parsed OAuth attributes with symbol keys (only valid OAuth keys) # @example # SimpleOAuth::Header.parse_form_body('oauth_consumer_key=key&oauth_signature=sig&status=hello') # # => {consumer_key: "key", signature: "sig"} def parse_form_body(body) valid_keys = PARSE_KEYS.map(&:to_s) result = {} #: Hash[Symbol, String] CGI.parse(body.to_s).each do |key, values| next unless key.start_with?(OAUTH_PREFIX) parsed_key = key.delete_prefix(OAUTH_PREFIX) result[parsed_key.to_sym] = values.first || "" if valid_keys.include?(parsed_key) end result end private # Generates a random nonce for OAuth requests # # @api private # @return [String] hex-encoded random bytes def generate_nonce SecureRandom.hex end # Encodes binary data as Base64 without newlines # # @api private # @param data [String] binary data to encode # @return [String] Base64-encoded string def encode_base64(data) Base64.strict_encode64(data) end end end end simple_oauth-0.4.0/lib/simple_oauth/parser.rb000066400000000000000000000063651513755122500213040ustar00rootroot00000000000000require "strscan" require_relative "errors" module SimpleOAuth # Parses OAuth Authorization headers # # @api private class Parser # Pattern to match OAuth key-value pairs: key="value" PARAM_PATTERN = /(\w+)="([^"]*)"\s*(,?)\s*/ # OAuth scheme prefix OAUTH_PREFIX = /OAuth\s+/ # The StringScanner instance for parsing the header # # @return [StringScanner] the scanner attr_reader :scanner # The parsed OAuth attributes # # @return [Hash{Symbol => String}] the parsed attributes attr_reader :attributes # Creates a new Parser for the given header string # # @param header [String, #to_s] the OAuth Authorization header string # @return [Parser] a new parser instance def initialize(header) @scanner = StringScanner.new(header.to_s) @attributes = {} end # Parses the OAuth Authorization header # # @param valid_keys [Array] the valid OAuth parameter keys # @return [Hash{Symbol => String}] the parsed attributes # @raise [SimpleOAuth::ParseError] if the header is malformed def parse(valid_keys) scan_oauth_prefix scan_params(valid_keys) verify_complete attributes end private # Scans and validates the OAuth prefix # # @return [void] # @raise [SimpleOAuth::ParseError] if the header doesn't start with "OAuth " def scan_oauth_prefix return if scanner.scan(OAUTH_PREFIX) raise ParseError, "Authorization header must start with 'OAuth '" end # Scans all key-value parameters from the header # # @param valid_keys [Array] the valid OAuth parameter keys # @return [void] def scan_params(valid_keys) while scanner.scan(PARAM_PATTERN) key = scanner[1] #: String value = scanner[2] #: String comma = scanner[3] #: String validate_comma_separator(key, comma) store_if_valid(key, value, valid_keys) end end # Validates that a comma separator exists between parameters # # @param key [String] the parameter key for error messages # @param comma [String] the comma separator (empty string if missing) # @return [void] # @raise [SimpleOAuth::ParseError] if comma is missing and more content follows def validate_comma_separator(key, comma) return if !comma.empty? || scanner.eos? raise ParseError, "Expected comma after '#{key}' parameter at position #{scanner.pos}: #{scanner.rest.inspect}" end # Stores the parameter if it's a valid OAuth key # # @param key [String] the raw parameter key (e.g., "oauth_consumer_key") # @param value [String] the parameter value # @param valid_keys [Array] the valid OAuth parameter keys # @return [void] def store_if_valid(key, value, valid_keys) parsed_key = valid_keys.find { |k| "oauth_#{k}".eql?(key) } attributes[parsed_key] = Header.unescape(value) if parsed_key end # Verifies that the entire header was parsed # # @return [void] # @raise [SimpleOAuth::ParseError] if unparsed content remains def verify_complete return if scanner.eos? raise ParseError, "Could not parse parameter at position #{scanner.pos}: #{scanner.rest.inspect}" end end end simple_oauth-0.4.0/lib/simple_oauth/signature.rb000066400000000000000000000143501513755122500220020ustar00rootroot00000000000000require "base64" require "openssl" module SimpleOAuth # Signature computation methods for OAuth 1.0 # # This module provides a registry of signature methods that can be extended # with custom implementations. Built-in methods include HMAC-SHA1, HMAC-SHA256, # RSA-SHA1, RSA-SHA256, and PLAINTEXT. # # @api public # @example Register a custom signature method # SimpleOAuth::Signature.register("HMAC-SHA512") do |secret, signature_base| # SimpleOAuth::Signature.encode_base64( # OpenSSL::HMAC.digest("SHA512", secret, signature_base) # ) # end # # @example Check if a signature method is registered # SimpleOAuth::Signature.registered?("HMAC-SHA1") # => true # SimpleOAuth::Signature.registered?("CUSTOM") # => false module Signature # Registry of signature method implementations @registry = {} class << self # Registers a custom signature method # # @api public # @param name [String] the signature method name (e.g., "HMAC-SHA512") # @param rsa [Boolean] whether this method uses RSA (raw consumer_secret as key) # @yield [secret, signature_base] block that computes the signature # @yieldparam secret [String] the signing secret (or PEM key for RSA methods) # @yieldparam signature_base [String] the signature base string # @yieldreturn [String] the computed signature # @return [void] # @example # SimpleOAuth::Signature.register("HMAC-SHA512") do |secret, base| # SimpleOAuth::Signature.encode_base64( # OpenSSL::HMAC.digest("SHA512", secret, base) # ) # end def register(name, rsa: false, &block) @registry[normalize_name(name)] = {implementation: block, rsa: rsa} end # Checks if a signature method is registered # # @api public # @param name [String] the signature method name # @return [Boolean] true if the method is registered # @example # SimpleOAuth::Signature.registered?("HMAC-SHA1") # => true def registered?(name) @registry.key?(normalize_name(name)) end # Returns list of registered signature method names # # @api public # @return [Array] registered method names # @example # SimpleOAuth::Signature.methods # => ["hmac_sha1", "hmac_sha256", "rsa_sha1", "plaintext"] def methods @registry.keys end # Checks if a signature method uses RSA (raw key instead of escaped secret) # # @api public # @param name [String] the signature method name # @return [Boolean] true if the method uses RSA # @example # SimpleOAuth::Signature.rsa?("RSA-SHA1") # => true # SimpleOAuth::Signature.rsa?("HMAC-SHA1") # => false def rsa?(name) @registry.dig(normalize_name(name), :rsa) || false end # Computes a signature using the specified method # # @api public # @param name [String] the signature method name # @param secret [String] the signing secret # @param signature_base [String] the signature base string # @return [String] the computed signature # @raise [ArgumentError] if the signature method is not registered # @example # SimpleOAuth::Signature.sign("HMAC-SHA1", "secret&token", "GET&url¶ms") def sign(name, secret, signature_base) normalized = normalize_name(name) entry = @registry.fetch(normalized) do raise ArgumentError, "Unknown signature method: #{name}. " \ "Registered methods: #{@registry.keys.join(", ")}" end entry.fetch(:implementation).call(secret, signature_base) end # Unregisters a signature method (useful for testing) # # @api public # @param name [String] the signature method name to remove # @return [void] # @example # SimpleOAuth::Signature.unregister("HMAC-SHA512") def unregister(name) @registry.delete(normalize_name(name)) end # Resets the registry to only built-in methods (useful for testing) # # @api public # @return [void] # @example # SimpleOAuth::Signature.reset! def reset! @registry.clear register_builtin_methods end # Encodes binary data as Base64 without newlines # # @api public # @param data [String] binary data to encode # @return [String] Base64-encoded string without newlines # @example # SimpleOAuth::Signature.encode_base64("\x01\x02\x03") # # => "AQID" def encode_base64(data) Base64.strict_encode64(data) end private # Normalizes signature method name for registry lookup # # @api private # @param name [String] the signature method name # @return [String] normalized name (lowercase, dashes to underscores) def normalize_name(name) name.to_s.downcase.tr("-", "_") end # Registers the built-in OAuth signature methods # # @api private # @return [void] def register_builtin_methods register_hmac_methods register_rsa_methods register_plaintext_method end # Registers HMAC-based signature methods # # @api private # @return [void] def register_hmac_methods %w[SHA1 SHA256].each do |digest| register("HMAC-#{digest}") do |secret, signature_base| encode_base64(OpenSSL::HMAC.digest(digest, secret, signature_base)) end end end # Registers RSA-based signature methods # # @api private # @return [void] def register_rsa_methods %w[SHA1 SHA256].each do |digest| register("RSA-#{digest}", rsa: true) do |private_key_pem, signature_base| private_key = OpenSSL::PKey::RSA.new(private_key_pem) encode_base64(private_key.sign(digest, signature_base)) end end end # Registers the PLAINTEXT signature method # # @api private # @return [void] def register_plaintext_method register("PLAINTEXT") { |secret, _| secret } end end # Initialize built-in methods on load register_builtin_methods end end simple_oauth-0.4.0/lib/simple_oauth/version.rb000066400000000000000000000002071513755122500214620ustar00rootroot00000000000000# OAuth 1.0 header generation library module SimpleOauth # The current version of the SimpleOAuth gem VERSION = "0.4.0".freeze end simple_oauth-0.4.0/mutant.yml000066400000000000000000000003661513755122500162520ustar00rootroot00000000000000usage: opensource integration: name: minitest includes: - lib - test requires: - simple_oauth mutation: operators: full timeout: 5.0 matcher: subjects: - SimpleOAuth* coverage_criteria: process_abort: true test_result: true simple_oauth-0.4.0/sig/000077500000000000000000000000001513755122500147745ustar00rootroot00000000000000simple_oauth-0.4.0/sig/matchdata_ext.rbs000066400000000000000000000003201513755122500203050ustar00rootroot00000000000000# Extension to MatchData for indexed access with Integer class MatchData # Override [] to return non-nil String for Integer index # (when we know the group exists) def []: (Integer index) -> String end simple_oauth-0.4.0/sig/openssl_ext.rbs000066400000000000000000000003541513755122500200510ustar00rootroot00000000000000# Extensions to OpenSSL types module OpenSSL module PKey class PKey # Sign with digest name as String (in addition to Digest object) def sign: (String | OpenSSL::Digest digest, String data) -> String end end end simple_oauth-0.4.0/sig/simple_oauth.rbs000066400000000000000000000120761513755122500202030ustar00rootroot00000000000000# OAuth 1.0 header generation library module SimpleOAuth # Error raised when parsing a malformed OAuth Authorization header class ParseError < StandardError end # Error raised when invalid options are passed to Header class InvalidOptionsError < StandardError end # OAuth percent-encoding utilities module Encoding # Characters that don't need to be escaped per OAuth spec UNRESERVED_CHARS: Regexp # Percent-encodes a value according to OAuth specification def escape: (String | _ToS value) -> String # Alias for escape def encode: (String | _ToS value) -> String # Decodes a percent-encoded value def unescape: (String | _ToS value) -> String # Alias for unescape def decode: (String | _ToS value) -> String # Module-level methods (via extend self) def self.escape: (String | _ToS value) -> String def self.encode: (String | _ToS value) -> String def self.unescape: (String | _ToS value) -> String def self.decode: (String | _ToS value) -> String end # Generates OAuth 1.0 Authorization headers for HTTP requests class Header # OAuth header scheme prefix OAUTH_SCHEME: String # Prefix for OAuth parameters OAUTH_PREFIX: String # Default signature method per RFC 5849 DEFAULT_SIGNATURE_METHOD: String # OAuth version OAUTH_VERSION: String # Valid OAuth attribute keys that can be included in the header ATTRIBUTE_KEYS: Array[Symbol] # Keys that are used internally but should not appear in attributes IGNORED_KEYS: Array[Symbol] # Valid keys when parsing OAuth parameters (ATTRIBUTE_KEYS + signature) PARSE_KEYS: Array[Symbol] # Type aliases for clarity type oauth_key = :body_hash | :callback | :consumer_key | :nonce | :signature_method | :timestamp | :token | :verifier | :version type ignored_key = :consumer_secret | :token_secret | :signature | :realm | :ignore_extra_keys type signature_method = "HMAC-SHA1" | "HMAC-SHA256" | "RSA-SHA1" | "RSA-SHA256" | "PLAINTEXT" type params_hash = Hash[String | Symbol, untyped] type oauth_options = Hash[Symbol, untyped] type signed_attributes_hash = Hash[Symbol, untyped] # The HTTP method for the request attr_reader method: String # The request parameters to be signed attr_reader params: params_hash # The raw request body for oauth_body_hash computation attr_reader body: String? # The OAuth options including credentials and signature attr_reader options: oauth_options # Class methods from ClassMethods module extend ClassMethods # Encoding methods from Encoding module extend Encoding # Percent-encodes a value according to OAuth specification def self.escape: (String | _ToS value) -> String # Alias for escape def self.encode: (String | _ToS value) -> String # Decodes a percent-encoded value def self.unescape: (String | _ToS value) -> String # Alias for unescape def self.decode: (String | _ToS value) -> String # Creates a new OAuth header def initialize: (String | Symbol method, String | URI::Generic url, params_hash params, ?oauth_options | String oauth, ?String? body) -> void # Returns the normalized URL without query string or fragment def url: () -> String # Returns the OAuth Authorization header string def to_s: () -> String # Validates the signature in the header against the provided secrets def valid?: (?oauth_options secrets) -> bool # Returns the OAuth attributes including the signature def signed_attributes: () -> signed_attributes_hash private # Internal URI instance @uri: URI::Generic # Normalizes and parses a URL into a URI object def normalize_uri: (String | URI::Generic url) -> URI::Generic # Builds OAuth options from input (hash or header string) def build_options: (oauth_options | String oauth, String? body) -> oauth_options # Builds the normalized OAuth attributes string for the Authorization header def normalized_attributes: () -> String # Extracts valid OAuth attributes from options (excludes realm per RFC 5849) def attributes: () -> signed_attributes_hash # Validates that no unknown keys are present in options def validate_option_keys!: () -> void # Returns OAuth attributes including realm for Authorization header output def header_attributes: () -> signed_attributes_hash # Extracts query parameters from the request URL def url_params: () -> Array[untyped] # Computes the OAuth signature using the configured signature method def signature: () -> String # Builds the secret string from consumer and token secrets def secret: () -> String # Builds the signature base string from method, URL, and params def signature_base: () -> String # Normalizes and sorts all request parameters for signing def normalized_params: () -> String # Collects all parameters to include in signature def signature_params: () -> Array[untyped] end end # Version module module SimpleOauth # The current version of the SimpleOAuth gem VERSION: String end simple_oauth-0.4.0/sig/simple_oauth/000077500000000000000000000000001513755122500174655ustar00rootroot00000000000000simple_oauth-0.4.0/sig/simple_oauth/header/000077500000000000000000000000001513755122500207155ustar00rootroot00000000000000simple_oauth-0.4.0/sig/simple_oauth/header/class_methods.rbs000066400000000000000000000015661513755122500242650ustar00rootroot00000000000000module SimpleOAuth class Header # Class methods for Header - parsing, defaults, and body hashing module ClassMethods # Returns default OAuth options with generated nonce and timestamp def default_options: (?String? body) -> Header::oauth_options # Computes the oauth_body_hash for a request body def body_hash: (String? body, ?String algorithm) -> String # Parses an OAuth Authorization header string into a hash def parse: (String | _ToS header) -> Header::oauth_options # Parses OAuth parameters from a form-encoded POST body def parse_form_body: (String | _ToS body) -> Header::oauth_options private # Generates a random nonce for OAuth requests def generate_nonce: () -> String # Encodes binary data as Base64 without newlines def encode_base64: (String data) -> String end end end simple_oauth-0.4.0/sig/simple_oauth/parser.rbs000066400000000000000000000013171513755122500214730ustar00rootroot00000000000000module SimpleOAuth # Parses OAuth Authorization headers class Parser # Pattern to match OAuth key-value pairs PARAM_PATTERN: Regexp # OAuth scheme prefix pattern OAUTH_PREFIX: Regexp attr_reader scanner: StringScanner attr_reader attributes: Hash[Symbol, String] def initialize: (String | _ToS header) -> void def parse: (Array[Symbol] valid_keys) -> Hash[Symbol, String] private def scan_oauth_prefix: () -> void def scan_params: (Array[Symbol] valid_keys) -> void def validate_comma_separator: (String key, String comma) -> void def store_if_valid: (String key, String value, Array[Symbol] valid_keys) -> void def verify_complete: () -> void end end simple_oauth-0.4.0/sig/simple_oauth/signature.rbs000066400000000000000000000036441513755122500222050ustar00rootroot00000000000000# Signature computation methods for OAuth 1.0 # # This module provides a registry of signature methods that can be extended # with custom implementations. module SimpleOAuth module Signature # Type for signature implementation block type signature_block = ^(String secret, String signature_base) -> String # Type for registry entry type registry_entry = { implementation: signature_block, rsa: bool } # Registry of signature method implementations (class-level instance variable) self.@registry: Hash[String, registry_entry] # Registers a custom signature method def self.register: (String | Symbol name, ?rsa: bool) { (String, String) -> String } -> void # Checks if a signature method is registered def self.registered?: (String | Symbol name) -> bool # Returns list of registered signature method names def self.methods: () -> Array[String] # Checks if a signature method uses RSA def self.rsa?: (String | Symbol name) -> bool # Computes a signature using the specified method def self.sign: (String | Symbol name, String? secret, String signature_base) -> String # Unregisters a signature method def self.unregister: (String | Symbol name) -> void # Resets the registry to only built-in methods def self.reset!: () -> void # Encodes binary data as Base64 without newlines def self.encode_base64: (String data) -> String private # Normalizes signature method name for registry lookup def self.normalize_name: (String | Symbol name) -> String # Registers the built-in OAuth signature methods def self.register_builtin_methods: () -> void # Registers HMAC-based signature methods def self.register_hmac_methods: () -> void # Registers RSA-based signature methods def self.register_rsa_methods: () -> void # Registers the PLAINTEXT signature method def self.register_plaintext_method: () -> void end end simple_oauth-0.4.0/sig/strscan.rbs000066400000000000000000000004231513755122500171600ustar00rootroot00000000000000# Minimal StringScanner type declarations for simple_oauth class StringScanner def initialize: (String string) -> void def scan: (Regexp pattern) -> String? def eos?: () -> bool def rest: () -> String def pos: () -> Integer def []: (Integer index) -> String? end simple_oauth-0.4.0/sig/uri_rfc2396_parser.rbs000066400000000000000000000003341513755122500210350ustar00rootroot00000000000000# Stubs for URI module module URI # The RFC2396 parser instance constant RFC2396_PARSER: RFC2396_Parser class Generic # Returns the URI as a String (implicit conversion) def to_str: () -> String end end simple_oauth-0.4.0/simple_oauth.gemspec000066400000000000000000000026021513755122500202500ustar00rootroot00000000000000require_relative "lib/simple_oauth/version" Gem::Specification.new do |spec| spec.name = "simple_oauth" spec.version = SimpleOauth::VERSION spec.authors = ["Steve Richert", "Erik Berlin"] spec.email = ["steve.richert@gmail.com", "sferik@gmail.com"] spec.summary = "Simply builds and verifies OAuth headers" spec.description = spec.summary spec.homepage = "https://github.com/laserlemon/simple_oauth" spec.license = "MIT" spec.required_ruby_version = ">= 3.2" spec.metadata["allowed_push_host"] = "https://rubygems.org" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "https://github.com/laserlemon/simple_oauth" spec.metadata["changelog_uri"] = "https://github.com/laserlemon/simple_oauth/blob/master/CHANGELOG.md" # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile]) end end spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.metadata["rubygems_mfa_required"] = "true" spec.add_dependency "base64" spec.add_dependency "cgi" end simple_oauth-0.4.0/test/000077500000000000000000000000001513755122500151715ustar00rootroot00000000000000simple_oauth-0.4.0/test/fixtures/000077500000000000000000000000001513755122500170425ustar00rootroot00000000000000simple_oauth-0.4.0/test/fixtures/rsa-private-key000066400000000000000000000016231513755122500220120ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY-----simple_oauth-0.4.0/test/simple_oauth/000077500000000000000000000000001513755122500176625ustar00rootroot00000000000000simple_oauth-0.4.0/test/simple_oauth/header_attributes_test.rb000066400000000000000000000060671513755122500247550ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for OAuth attribute handling. class HeaderAttributesTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_attributes_prepends_keys_with_oauth # RFC 5849 Section 3.5.1 - parameter names prefixed with oauth_ header = build_header_with_all_attribute_keys header.options[:ignore_extra_keys] = true assert(header.send(:attributes).keys.all? { |k| k.to_s =~ /^oauth_/ }) end def test_attributes_has_only_symbol_keys header = build_header_with_all_attribute_keys header.options[:ignore_extra_keys] = true assert(header.send(:attributes).keys.all?(Symbol)) end def test_attributes_excludes_invalid_keys header = build_header_with_all_attribute_keys header.options[:ignore_extra_keys] = true refute header.send(:attributes).key?(:oauth_other) end def test_attributes_preserves_values_for_valid_keys header = build_header_with_all_attribute_keys header.options[:ignore_extra_keys] = true assert(header.send(:attributes).all? { |k, v| k.to_s == "oauth_#{v.downcase}" }) end def test_attributes_has_same_count_as_attribute_keys header = build_header_with_all_attribute_keys header.options[:ignore_extra_keys] = true assert_equal SimpleOAuth::Header::ATTRIBUTE_KEYS.size, header.send(:attributes).size end def test_attributes_raises_for_extra_keys header = build_header_with_all_attribute_keys error = assert_raises(SimpleOAuth::InvalidOptionsError) { header.send(:attributes) } assert_equal "Unknown option keys: :other", error.message end def test_attributes_does_not_raise_when_ignore_extra_keys_is_true header = build_header_with_all_attribute_keys header.options[:ignore_extra_keys] = true assert header.send(:attributes) end def test_attributes_does_not_raise_when_no_extra_keys header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_key: RFC5849::CONSUMER_KEY) assert header.send(:attributes) end def test_attributes_raises_when_ignore_extra_keys_is_explicitly_false header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, other: "OTHER") header.options[:ignore_extra_keys] = false assert_raises(SimpleOAuth::InvalidOptionsError) { header.send(:attributes) } end def test_attributes_error_message_includes_comma_separator_for_multiple_extra_keys header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, extra1: "EXTRA1", extra2: "EXTRA2") error = assert_raises(SimpleOAuth::InvalidOptionsError) { header.send(:attributes) } assert_includes error.message, ", " end private def build_header_with_all_attribute_keys options = {} SimpleOAuth::Header::ATTRIBUTE_KEYS.each { |k| options[k] = k.to_s.upcase } options[:other] = "OTHER" SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, options) end end end simple_oauth-0.4.0/test/simple_oauth/header_body_hash_test.rb000066400000000000000000000110201513755122500245100ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for oauth_body_hash extension (OAuth Body Hash, draft-eaton-oauth-bodyhash). class HeaderBodyHashTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # .body_hash class method tests def test_body_hash_computes_sha1_hash_of_body body = '{"text": "Hello, World!"}' expected = Base64.strict_encode64(OpenSSL::Digest.digest("SHA1", body)) assert_equal expected, SimpleOAuth::Header.body_hash(body) end def test_body_hash_returns_hash_of_empty_string_for_nil expected = Base64.strict_encode64(OpenSSL::Digest.digest("SHA1", "")) assert_equal expected, SimpleOAuth::Header.body_hash(nil) end def test_body_hash_supports_sha256_algorithm body = '{"text": "Hello, World!"}' expected = Base64.strict_encode64(OpenSSL::Digest.digest("SHA256", body)) assert_equal expected, SimpleOAuth::Header.body_hash(body, "SHA256") end def test_body_hash_contains_no_newlines body = "x" * 1000 hash = SimpleOAuth::Header.body_hash(body) refute_includes hash, "\n" end # .default_options with body tests def test_default_options_includes_body_hash_when_body_provided options = SimpleOAuth::Header.default_options('{"text": "test"}') assert_includes options.keys, :body_hash end def test_default_options_excludes_body_hash_when_no_body options = SimpleOAuth::Header.default_options refute_includes options.keys, :body_hash end def test_default_options_body_hash_matches_body_hash_method body = '{"status": "testing oauth_body_hash"}' options = SimpleOAuth::Header.default_options(body) expected = SimpleOAuth::Header.body_hash(body) assert_equal expected, options[:body_hash] end # Header#initialize with body tests def test_initialize_stores_body body = '{"text": "Hello"}' header = SimpleOAuth::Header.new(:post, "https://photos.example.net/upload", {}, {}, body) assert_equal body, header.body end def test_initialize_without_body_has_nil_body header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert_nil header.body end def test_initialize_with_body_includes_body_hash_in_options body = '{"text": "Hello"}' header = SimpleOAuth::Header.new(:post, "https://photos.example.net/upload", {}, {}, body) assert_includes header.options.keys, :body_hash end def test_initialize_without_body_excludes_body_hash_from_options header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) refute_includes header.options.keys, :body_hash end # oauth_body_hash in Authorization header tests def test_to_s_includes_oauth_body_hash_when_body_provided body = '{"text": "Hello"}' header = SimpleOAuth::Header.new(:post, "https://photos.example.net/upload", {}, {consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET}, body) assert_includes header.to_s, "oauth_body_hash=" end def test_to_s_excludes_oauth_body_hash_when_no_body header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET) refute_includes header.to_s, "oauth_body_hash=" end def test_body_hash_is_included_in_signature_computation body = '{"text": "Hello"}' header1 = SimpleOAuth::Header.new(:post, "https://photos.example.net/upload", {}, {consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, nonce: "chapoH", timestamp: "137131202"}, body) different_body = '{"text": "Different"}' header2 = SimpleOAuth::Header.new(:post, "https://photos.example.net/upload", {}, {consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, nonce: "chapoH", timestamp: "137131202"}, different_body) # Different bodies should produce different signatures refute_equal header1.to_s, header2.to_s end # User can override body_hash in options def test_user_provided_body_hash_overrides_computed_hash body = '{"text": "Hello"}' custom_hash = "custom_hash_value" header = SimpleOAuth::Header.new(:post, "https://photos.example.net/upload", {}, {consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, body_hash: custom_hash}, body) assert_includes header.to_s, custom_hash end end end simple_oauth-0.4.0/test/simple_oauth/header_class_methods_test.rb000066400000000000000000000032701513755122500254100ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for Header class methods. class HeaderClassMethodsTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # .default_options tests def test_default_options_is_different_every_time first = SimpleOAuth::Header.default_options second = SimpleOAuth::Header.default_options refute_equal first, second end def test_default_options_is_used_for_new_headers header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert_includes header.options.keys, :nonce assert_includes header.options.keys, :signature_method assert_includes header.options.keys, :timestamp end def test_default_options_includes_version_key header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert_includes header.options.keys, :version end def test_default_options_includes_signature_method refute_nil SimpleOAuth::Header.default_options[:signature_method] end def test_default_options_includes_oauth_version refute_nil SimpleOAuth::Header.default_options[:version] end def test_default_options_nonce_is_32_hex_characters # RFC 5849 Section 3.3 - nonce is a random string nonce = SimpleOAuth::Header.default_options[:nonce] assert_match(/\A[0-9a-f]{32}\z/, nonce) end def test_default_options_timestamp_is_integer_string # RFC 5849 Section 3.3 - timestamp is seconds since epoch timestamp = SimpleOAuth::Header.default_options[:timestamp] assert_match(/\A\d+\z/, timestamp) assert_equal Time.now.to_i.to_s.length, timestamp.length end end end simple_oauth-0.4.0/test/simple_oauth/header_escape_test.rb000066400000000000000000000030341513755122500240160ustar00rootroot00000000000000require "test_helper" module SimpleOAuth class HeaderEscapeTest < Minitest::Test cover "SimpleOAuth::Header*" # .escape tests def test_escape_escapes_non_word_characters [" ", "!", "@", "#", "$", "%", "^", "&"].each do |character| escaped = SimpleOAuth::Header.escape(character) refute_equal character, escaped assert_equal URI::RFC2396_PARSER.escape(character, /.*/), escaped end end def test_escape_does_not_escape_dash_dot_or_tilde ["-", ".", "~"].each do |character| escaped = SimpleOAuth::Header.escape(character) assert_equal character, escaped end end def test_escape_escapes_non_ascii_characters assert_equal "%C3%A9", SimpleOAuth::Header.escape("é") end def test_escape_escapes_multibyte_characters assert_equal "%E3%81%82", SimpleOAuth::Header.escape("あ") end # .unescape tests def test_unescape_unescapes_percent_encoded_characters assert_equal "é", SimpleOAuth::Header.unescape("%C3%A9") end def test_unescape_unescapes_multibyte_characters assert_equal "あ", SimpleOAuth::Header.unescape("%E3%81%82") end def test_unescape_returns_unencoded_characters_as_is assert_equal "hello", SimpleOAuth::Header.unescape("hello") end def test_unescape_converts_non_string_to_string assert_equal "123", SimpleOAuth::Header.unescape(123) end def test_escape_converts_non_string_to_string assert_equal "123", SimpleOAuth::Header.escape(123) end end end simple_oauth-0.4.0/test/simple_oauth/header_hmac_sha256_integration_test.rb000066400000000000000000000037751513755122500271750ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # HMAC-SHA256 integration tests using RFC 5849 credentials. # Note: HMAC-SHA256 is not defined in RFC 5849, but is a common extension. class HeaderHmacSha256IntegrationTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_hmac_sha256_signature_produces_valid_signature_for_get # Using RFC 5849 Section 1.2 credentials with HMAC-SHA256 header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {file: "vacation.jpg", size: "original"}, hmac_sha256_get_options) parsed = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {file: "vacation.jpg", size: "original"}, header.to_s) assert parsed.valid?(consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TOKEN_SECRET) end def test_hmac_sha256_signature_produces_valid_signature_for_post # Using RFC 5849 Section 1.2 credentials with HMAC-SHA256 header = SimpleOAuth::Header.new(:post, "https://photos.example.net/token", {}, hmac_sha256_post_options) parsed = SimpleOAuth::Header.new(:post, "https://photos.example.net/token", {}, header.to_s) assert parsed.valid?(consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TEMP_TOKEN_SECRET) end private def hmac_sha256_get_options { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, token: RFC5849::TOKEN, token_secret: RFC5849::TOKEN_SECRET, signature_method: "HMAC-SHA256", timestamp: "137131202", nonce: "chapoH" } end def hmac_sha256_post_options { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, token: RFC5849::TEMP_TOKEN, token_secret: RFC5849::TEMP_TOKEN_SECRET, signature_method: "HMAC-SHA256", timestamp: "137131201", nonce: "walatlh", verifier: RFC5849::VERIFIER } end end end simple_oauth-0.4.0/test/simple_oauth/header_instance_test.rb000066400000000000000000000145301513755122500243650ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for Header instance methods using RFC 5849 examples. class HeaderInstanceTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # RFC 5849 Section 3.4.1.2 - Base String URI construction # Uses example URLs from the spec for normalization tests # #initialize tests def test_initialize_stringifies_and_uppercases_request_method # RFC 5849 Section 3.4.1.1 - HTTP request method MUST be uppercase header = SimpleOAuth::Header.new(:get, "HTTPS://PHOTOS.example.net:443/photos?file=vacation.jpg#anchor", {}) assert_equal "GET", header.method end def test_initialize_downcases_scheme_and_authority # RFC 5849 Section 3.4.1.2 - scheme and host are case-insensitive, normalized to lowercase header = SimpleOAuth::Header.new(:get, "HTTPS://PHOTOS.Example.Net:443/photos?file=vacation.jpg#anchor", {}) assert_match %r{^https://photos\.example\.net/}, header.url end def test_initialize_ignores_query_and_fragment # RFC 5849 Section 3.4.1.2 - query and fragment excluded from base string URI header = SimpleOAuth::Header.new(:get, "HTTPS://PHOTOS.Example.Net:443/photos?file=vacation.jpg#anchor", {}) assert_match %r{/photos$}, header.url end def test_initialize_stores_downcased_scheme_in_uri header = SimpleOAuth::Header.new(:get, "HTTPS://photos.example.net/photos", {}) assert header.url.start_with?("https://") end def test_scheme_is_downcased_from_mixed_case header = SimpleOAuth::Header.new(:get, "HtTpS://photos.example.net/photos", {}) refute_includes header.url, "HtTpS" assert_includes header.url, "https" end def test_initialize_accepts_object_with_to_s_method url_object = Object.new url_object.define_singleton_method(:to_s) { "https://photos.example.net/photos" } header = SimpleOAuth::Header.new(:get, url_object, {}) assert_equal "https://photos.example.net/photos", header.url end def test_initialize_with_hash_subclass_uses_default_options class_with_hash_ancestor = Class.new(Hash) options = class_with_hash_ancestor.new options[:consumer_key] = RFC5849::CONSUMER_KEY header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, options) assert header.options.key?(:nonce) assert header.options.key?(:timestamp) end def test_initialize_converts_string_keys_to_symbols options = {"consumer_key" => RFC5849::CONSUMER_KEY, "consumer_secret" => RFC5849::CONSUMER_SECRET} header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, options) assert header.options.key?(:consumer_key) assert header.options.key?(:consumer_secret) end def test_initialize_preserves_values_when_converting_string_keys options = {"consumer_key" => RFC5849::CONSUMER_KEY, "consumer_secret" => RFC5849::CONSUMER_SECRET} header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, options) assert_equal RFC5849::CONSUMER_KEY, header.options[:consumer_key] assert_equal RFC5849::CONSUMER_SECRET, header.options[:consumer_secret] end # #normalized_attributes tests def test_normalized_attributes_returns_sorted_quoted_comma_separated_list header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) stubbed_attrs = {d: 1, c: 2, b: 3, a: 4} header.define_singleton_method(:signed_attributes) { stubbed_attrs } assert_equal 'a="4", b="3", c="2", d="1"', header.send(:normalized_attributes) end def test_normalized_attributes_uri_encodes_values header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) stubbed_attrs = {1 => "!", 2 => "@", 3 => "#", 4 => "$"} header.define_singleton_method(:signed_attributes) { stubbed_attrs } assert_equal '1="%21", 2="%40", 3="%23", 4="%24"', header.send(:normalized_attributes) end def test_normalized_attributes_converts_symbol_keys_to_strings_for_sorting header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) stubbed_attrs = {z_key: "z", a_key: "a"} header.define_singleton_method(:signed_attributes) { stubbed_attrs } result = header.send(:normalized_attributes) assert_match(/^a_key=.*z_key=/, result) end # #signed_attributes tests def test_signed_attributes_includes_oauth_signature header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert header.send(:signed_attributes).key?(:oauth_signature) end # #secret tests def test_secret_combines_consumer_and_token_secrets_with_ampersand # RFC 5849 Section 3.4.2 - HMAC-SHA1 key is consumer_secret&token_secret header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TOKEN_SECRET) assert_equal "#{RFC5849::CONSUMER_SECRET}&#{RFC5849::TOKEN_SECRET}", header.send(:secret) end def test_secret_uri_encodes_each_value_before_combination # Secrets with special characters should be percent-encoded header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_secret: "CONSUM#R_SECRET", token_secret: "TOKEN_S#CRET") assert_equal "CONSUM%23R_SECRET&TOKEN_S%23CRET", header.send(:secret) end # #signature_base tests def test_signature_base_combines_method_url_and_params_with_ampersands # RFC 5849 Section 3.4.1 - signature base string format header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:method) { "METHOD" } header.define_singleton_method(:url) { "URL" } header.define_singleton_method(:normalized_params) { "NORMALIZED_PARAMS" } assert_equal "METHOD&URL&NORMALIZED_PARAMS", header.send(:signature_base) end def test_signature_base_uri_encodes_each_value header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:method) { "ME#HOD" } header.define_singleton_method(:url) { "U#L" } header.define_singleton_method(:normalized_params) { "NORMAL#ZED_PARAMS" } assert_equal "ME%23HOD&U%23L&NORMAL%23ZED_PARAMS", header.send(:signature_base) end end end simple_oauth-0.4.0/test/simple_oauth/header_params_test.rb000066400000000000000000000132551513755122500240470ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for parameter normalization per RFC 5849 Section 3.4.1.3.2. class HeaderParamsTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # #normalized_params tests def test_normalized_params_returns_a_string header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:signature_params) { [%w[A 4], %w[B 3], %w[B 2], %w[C 1], ["D[]", "0 "]] } assert_kind_of String, header.send(:normalized_params) end def test_normalized_params_joins_pairs_with_ampersands # RFC 5849 Section 3.4.1.3.2 - parameters joined with & header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) signature_params = [%w[A 4], %w[B 3], %w[B 2], %w[C 1], ["D[]", "0 "]] header.define_singleton_method(:signature_params) { signature_params } parts = header.send(:normalized_params).split("&") assert_equal signature_params.size, parts.size end def test_normalized_params_joins_key_value_with_equal_signs # RFC 5849 Section 3.4.1.3.2 - name=value pairs header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:signature_params) { [%w[A 4], %w[B 3], %w[B 2], %w[C 1], ["D[]", "0 "]] } pairs = header.send(:normalized_params).split("&").collect { |p| p.split("=") } assert(pairs.all? { |p| p.size == 2 }) end # #signature_params tests def test_signature_params_combines_attributes_params_and_url_params header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:attributes) { {attribute: "ATTRIBUTE"} } header.define_singleton_method(:params) { {"param" => "PARAM"} } header.define_singleton_method(:url_params) { [%w[url_param 1], %w[url_param 2]] } expected = [[:attribute, "ATTRIBUTE"], %w[param PARAM], %w[url_param 1], %w[url_param 2]] assert_equal expected, header.send(:signature_params) end # #url_params tests def test_url_params_returns_empty_array_when_no_query_parameters header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert_empty header.send(:url_params) end def test_url_params_returns_key_value_pairs_for_query_parameters # RFC 5849 Section 1.2 - file and size query parameters header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?file=vacation.jpg", {}) assert_equal [%w[file vacation.jpg]], header.send(:url_params) end def test_url_params_sorts_values_for_repeated_keys # RFC 5849 Section 3.4.1.3.2 - values for same key sorted header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?size=3&size=1&size=2", {}) assert_equal [%w[size 1], %w[size 2], %w[size 3]], header.send(:url_params) end def test_url_params_handles_empty_query_string header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?", {}) assert_empty header.send(:url_params) end def test_normalized_params_sorts_params_alphabetically # RFC 5849 Section 3.4.1.3.2 - parameters sorted by name header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:signature_params) { [%w[z last], %w[a first], %w[m middle]] } result = header.send(:normalized_params) assert_equal "a=first&m=middle&z=last", result end def test_url_params_accumulates_multiple_keys # RFC 5849 Section 1.2 - multiple query parameters header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?file=vacation.jpg&size=original", {}) url_params = header.send(:url_params) expected = [%w[file vacation.jpg], %w[size original]] assert_equal expected, url_params.sort end def test_normalized_params_escapes_special_characters_in_keys # RFC 5849 Section 3.6 - percent-encoding header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:signature_params) { [["a[]", "value"]] } result = header.send(:normalized_params) assert_equal "a%5B%5D=value", result end def test_normalized_params_escapes_special_characters_in_values # RFC 5849 Section 3.6 - percent-encoding header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) header.define_singleton_method(:signature_params) { [["key", "value with spaces"]] } result = header.send(:normalized_params) assert_equal "key=value%20with%20spaces", result end def test_params_are_included_in_signature_calculation # Verify that params passed to initialize affect the signature header1 = SimpleOAuth::Header.new(:post, "https://photos.example.net/photos", {status: "Hello"}, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, nonce: "chapoH", timestamp: "137131202") header2 = SimpleOAuth::Header.new(:post, "https://photos.example.net/photos", {status: "Goodbye"}, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, nonce: "chapoH", timestamp: "137131202") refute_equal header1.signed_attributes[:oauth_signature], header2.signed_attributes[:oauth_signature] end def test_params_accessor_returns_initialized_params params = {status: "Hello", count: "5"} header = SimpleOAuth::Header.new(:post, "https://photos.example.net/photos", params, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET) assert_equal params, header.params end end end simple_oauth-0.4.0/test/simple_oauth/header_parse_form_body_test.rb000066400000000000000000000173541513755122500257420ustar00rootroot00000000000000require "test_helper" module SimpleOAuth class HeaderParseFormBodyBasicTest < Minitest::Test cover "SimpleOAuth::Header*" def test_parse_form_body_returns_a_hash body = "oauth_consumer_key=key123&oauth_token=token456" parsed = SimpleOAuth::Header.parse_form_body(body) assert_kind_of Hash, parsed end def test_parse_form_body_extracts_oauth_parameters body = "oauth_consumer_key=key123&oauth_token=token456&oauth_signature=sig789" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "key123", parsed[:consumer_key] assert_equal "token456", parsed[:token] assert_equal "sig789", parsed[:signature] end def test_parse_form_body_decodes_percent_encoded_values body = "oauth_consumer_key=key%2B123&oauth_signature=sig%3D%26value" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "key+123", parsed[:consumer_key] assert_equal "sig=&value", parsed[:signature] end def test_parse_form_body_handles_empty_values body = "oauth_consumer_key=key123&oauth_callback=&oauth_token=token456" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "", parsed[:callback] end def test_parse_form_body_handles_plus_signs_as_spaces body = "oauth_consumer_key=key+with+spaces" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "key with spaces", parsed[:consumer_key] end def test_parse_form_body_calls_to_s_on_input body = Object.new def body.to_s "oauth_consumer_key=key123" end parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "key123", parsed[:consumer_key] end def test_parse_form_body_uses_first_value_for_duplicate_keys body = "oauth_consumer_key=first&oauth_consumer_key=second" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "first", parsed[:consumer_key] end def test_parse_form_body_handles_key_without_equals_sign body = "oauth_callback&oauth_consumer_key=key123" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "", parsed[:callback] assert_equal "key123", parsed[:consumer_key] end end class HeaderParseFormBodyFilteringTest < Minitest::Test cover "SimpleOAuth::Header*" def test_parse_form_body_ignores_non_oauth_parameters_count body = "oauth_consumer_key=key123&status=hello%20world&oauth_token=token456" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal 2, parsed.keys.size end def test_parse_form_body_ignores_non_oauth_parameters_values body = "oauth_consumer_key=key123&status=hello%20world&oauth_token=token456" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "key123", parsed[:consumer_key] assert_equal "token456", parsed[:token] end def test_parse_form_body_excludes_non_oauth_key body = "oauth_consumer_key=key123&status=hello%20world&oauth_token=token456" parsed = SimpleOAuth::Header.parse_form_body(body) refute parsed.key?(:status) end def test_parse_form_body_strips_oauth_prefix_includes_correct_keys body = "oauth_consumer_key=key123&oauth_signature_method=HMAC-SHA1" parsed = SimpleOAuth::Header.parse_form_body(body) assert parsed.key?(:consumer_key) assert parsed.key?(:signature_method) end def test_parse_form_body_strips_oauth_prefix_excludes_prefixed_keys body = "oauth_consumer_key=key123&oauth_signature_method=HMAC-SHA1" parsed = SimpleOAuth::Header.parse_form_body(body) refute parsed.key?(:oauth_consumer_key) refute parsed.key?(:oauth_signature_method) end def test_parse_form_body_returns_empty_hash_for_empty_body parsed = SimpleOAuth::Header.parse_form_body("") assert_empty parsed end def test_parse_form_body_returns_empty_hash_for_body_without_oauth_params body = "status=hello&text=world" parsed = SimpleOAuth::Header.parse_form_body(body) assert_empty parsed end def test_parse_form_body_ignores_invalid_oauth_keys_count body = "oauth_consumer_key=key123&oauth_invalid_key=bad&oauth_signature=sig" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal 2, parsed.keys.size end def test_parse_form_body_ignores_invalid_oauth_keys_values body = "oauth_consumer_key=key123&oauth_invalid_key=bad&oauth_signature=sig" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal "key123", parsed[:consumer_key] assert_equal "sig", parsed[:signature] end def test_parse_form_body_ignores_invalid_oauth_keys_exclusion body = "oauth_consumer_key=key123&oauth_invalid_key=bad&oauth_signature=sig" parsed = SimpleOAuth::Header.parse_form_body(body) refute parsed.key?(:invalid_key) end def test_parse_form_body_requires_oauth_prefix body = "oauth_consumer_key=real&consumer_key=sneaky" parsed = SimpleOAuth::Header.parse_form_body(body) assert_equal 1, parsed.keys.size assert_equal "real", parsed[:consumer_key] end end class HeaderParseFormBodyStandardParamsTest < Minitest::Test cover "SimpleOAuth::Header*" def setup @body = "oauth_consumer_key=ck&oauth_token=tk&oauth_signature_method=HMAC-SHA1&" \ "oauth_signature=sig&oauth_timestamp=123456&oauth_nonce=abc&" \ "oauth_version=1.0&oauth_callback=http%3A%2F%2Fexample.com&oauth_verifier=ver" @parsed = SimpleOAuth::Header.parse_form_body(@body) end def test_parses_consumer_key assert_equal "ck", @parsed[:consumer_key] end def test_parses_token assert_equal "tk", @parsed[:token] end def test_parses_signature_method assert_equal "HMAC-SHA1", @parsed[:signature_method] end def test_parses_signature assert_equal "sig", @parsed[:signature] end def test_parses_timestamp assert_equal "123456", @parsed[:timestamp] end def test_parses_nonce assert_equal "abc", @parsed[:nonce] end def test_parses_version assert_equal "1.0", @parsed[:version] end def test_parses_and_decodes_callback assert_equal "http://example.com", @parsed[:callback] end def test_parses_verifier assert_equal "ver", @parsed[:verifier] end end class HeaderParseFormBodyIntegrationTest < Minitest::Test cover "SimpleOAuth::Header*" def test_header_can_be_created_from_parsed_form_body secrets = {consumer_secret: "CONSUMER_SECRET", token_secret: "TOKEN_SECRET"} original_header = SimpleOAuth::Header.new(:post, "https://api.example.com/resource", {}, secrets) form_body = build_form_body(original_header.signed_attributes) parsed_oauth = SimpleOAuth::Header.parse_form_body(form_body) reconstructed_header = SimpleOAuth::Header.new(:post, "https://api.example.com/resource", {}, parsed_oauth) assert reconstructed_header.valid?(secrets) end def test_parsed_form_body_validation_with_request_params secrets = {consumer_secret: "CONSUMER_SECRET", token_secret: "TOKEN_SECRET"} request_params = {status: "Hello world!"} original_header = SimpleOAuth::Header.new(:post, "https://api.example.com/update", request_params, secrets) form_body = build_form_body(original_header.signed_attributes.merge(request_params)) parsed_oauth = SimpleOAuth::Header.parse_form_body(form_body) url = "https://api.example.com/update" reconstructed_header = SimpleOAuth::Header.new(:post, url, request_params, parsed_oauth) assert reconstructed_header.valid?(secrets) end private def build_form_body(params) params.map do |k, v| "#{k}=#{SimpleOAuth::Header.escape(v)}" end.join("&") end end end simple_oauth-0.4.0/test/simple_oauth/header_parse_test.rb000066400000000000000000000204071513755122500236730ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for parsing OAuth Authorization headers per RFC 5849 Section 3.5.1. class HeaderParseBasicTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_parse_returns_a_hash header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert_kind_of Hash, SimpleOAuth::Header.parse(header) end def test_parse_includes_options_used_to_build_header header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) parsed_options = SimpleOAuth::Header.parse(header) assert_equal header.options, parsed_options.except(:signature) end def test_parse_header_options_does_not_include_signature header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) refute header.options.key?(:signature) end def test_parse_includes_signature_in_parsed_options header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) assert SimpleOAuth::Header.parse(header).key?(:signature) end def test_parse_has_non_nil_signature header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}) refute_nil SimpleOAuth::Header.parse(header)[:signature] end def test_parse_handles_empty_value parsed = SimpleOAuth::Header.parse('OAuth oauth_callback=""') assert_equal "", parsed[:callback] end def test_parse_strips_oauth_prefix_from_keys parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="dpf43f3p2l4k3l03"') assert parsed.key?(:consumer_key) refute parsed.key?(:oauth_consumer_key) end def test_parse_silently_ignores_params_without_oauth_prefix parsed = SimpleOAuth::Header.parse('OAuth consumer_key="dpf43f3p2l4k3l03"') assert_empty parsed end end class HeaderParseWhitespaceTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_parse_handles_spaces_after_commas header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="wIjqoS", ' \ 'oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D", oauth_signature_method="HMAC-SHA1", ' \ 'oauth_timestamp="137131200", oauth_token="hh5s93j4hdidpola", oauth_version="1.0"' assert_equal 7, SimpleOAuth::Header.parse(header).keys.size end def test_parse_handles_multiple_spaces_after_commas header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="wIjqoS", ' \ 'oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D", oauth_signature_method="HMAC-SHA1", ' \ 'oauth_timestamp="137131200", oauth_token="hh5s93j4hdidpola", oauth_version="1.0"' assert_equal 7, SimpleOAuth::Header.parse(header).keys.size end def test_parse_handles_no_spaces_after_commas header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03",oauth_nonce="wIjqoS",' \ 'oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D",oauth_signature_method="HMAC-SHA1",' \ 'oauth_timestamp="137131200",oauth_token="hh5s93j4hdidpola",oauth_version="1.0"' assert_equal 7, SimpleOAuth::Header.parse(header).keys.size end def test_parse_handles_trailing_whitespace parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="dpf43f3p2l4k3l03", ') assert_equal RFC5849::CONSUMER_KEY, parsed[:consumer_key] end def test_parse_handles_multiple_spaces_after_oauth_scheme parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="dpf43f3p2l4k3l03"') assert_equal RFC5849::CONSUMER_KEY, parsed[:consumer_key] end end class HeaderParseFilteringTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_parse_ignores_unrecognized_oauth_keys_count header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_invalid_key="bad", oauth_signature="sig"' parsed = SimpleOAuth::Header.parse(header) assert_equal 2, parsed.keys.size end def test_parse_ignores_unrecognized_oauth_keys_values header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_invalid_key="bad", oauth_signature="sig"' parsed = SimpleOAuth::Header.parse(header) assert_equal RFC5849::CONSUMER_KEY, parsed[:consumer_key] assert_equal "sig", parsed[:signature] end def test_parse_ignores_unrecognized_oauth_keys_exclusion header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", oauth_invalid_key="bad", oauth_signature="sig"' parsed = SimpleOAuth::Header.parse(header) refute parsed.key?(:invalid_key) end def test_parse_ignores_non_oauth_prefixed_keys header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", custom_key="ignored", oauth_signature="sig"' parsed = SimpleOAuth::Header.parse(header) assert_equal 2, parsed.keys.size refute parsed.key?(:custom_key) end def test_parse_handles_unescaped_comma_in_value parsed = SimpleOAuth::Header.parse('OAuth oauth_consumer_key="key,with,commas", oauth_signature="sig"') assert_equal "key,with,commas", parsed[:consumer_key] assert_equal "sig", parsed[:signature] end end class HeaderParseErrorTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_parse_raises_on_malformed_pair_position header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", malformed_without_quotes, oauth_token="token"' error = assert_raises(SimpleOAuth::ParseError) { SimpleOAuth::Header.parse(header) } assert_match(/Could not parse parameter at position 45/, error.message) end def test_parse_raises_on_malformed_pair_content header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", malformed_without_quotes, oauth_token="token"' error = assert_raises(SimpleOAuth::ParseError) { SimpleOAuth::Header.parse(header) } assert_match(/malformed_without_quotes/, error.message) end def test_parse_raises_on_malformed_pair_inspect_format header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03", malformed_without_quotes, oauth_token="token"' error = assert_raises(SimpleOAuth::ParseError) { SimpleOAuth::Header.parse(header) } # Verify .inspect is used (shows escaped quotes) assert_match(/\\"token\\"/, error.message) end def test_parse_raises_on_missing_opening_quote_position error = assert_raises(SimpleOAuth::ParseError) do SimpleOAuth::Header.parse("OAuth oauth_consumer_key=dpf43f3p2l4k3l03") end assert_match(/Could not parse parameter at position 6/, error.message) end def test_parse_raises_on_missing_opening_quote_inspect_format error = assert_raises(SimpleOAuth::ParseError) do SimpleOAuth::Header.parse("OAuth oauth_consumer_key=dpf43f3p2l4k3l03") end assert_match(/"oauth_consumer_key=dpf43f3p2l4k3l03"/, error.message) end def test_parse_raises_on_missing_comma_position header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03" oauth_signature="sig"' error = assert_raises(SimpleOAuth::ParseError) { SimpleOAuth::Header.parse(header) } assert_match(/Expected comma after 'oauth_consumer_key' parameter at position 44/, error.message) end def test_parse_raises_on_missing_comma_content header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03" oauth_signature="sig"' error = assert_raises(SimpleOAuth::ParseError) { SimpleOAuth::Header.parse(header) } assert_match(/oauth_signature/, error.message) end def test_parse_raises_on_missing_comma_inspect_format header = 'OAuth oauth_consumer_key="dpf43f3p2l4k3l03" oauth_signature="sig"' error = assert_raises(SimpleOAuth::ParseError) { SimpleOAuth::Header.parse(header) } assert_match(/\\"sig\\"/, error.message) end def test_parse_raises_on_missing_oauth_prefix error = assert_raises(SimpleOAuth::ParseError) do SimpleOAuth::Header.parse('oauth_consumer_key="dpf43f3p2l4k3l03"') end assert_match(/Authorization header must start with 'OAuth '/, error.message) end def test_parse_raises_on_invalid_scheme error = assert_raises(SimpleOAuth::ParseError) do SimpleOAuth::Header.parse("Bearer token123") end assert_match(/Authorization header must start with 'OAuth '/, error.message) end end end simple_oauth-0.4.0/test/simple_oauth/header_realm_test.rb000066400000000000000000000056641513755122500236710ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for realm parameter handling per RFC 5849. # # Examples use values from RFC 5849 Section 1.2 and Section 3.5.1: # - realm="Photos" with consumer_key="dpf43f3p2l4k3l03" # - realm="Example" with consumer_key="0685bd9184jfhq22" class HeaderRealmTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_realm_is_included_in_header # RFC 5849 Section 3.5.1 example: realm="Example" header = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS.merge(realm: "Example")) assert_includes header.to_s, 'realm="Example"' end def test_realm_is_included_in_signed_attributes # RFC 5849 Section 1.2 example: realm="Photos" header = SimpleOAuth::Header.new(:get, "https://photos.example.net/initiate", {}, RFC5849::PHOTOS_OPTIONS.merge(realm: "Photos")) assert_equal "Photos", header.signed_attributes[:realm] end def test_realm_is_excluded_from_signature_computation # Per RFC 5849 Section 3.4.1.3.1, realm MUST be excluded from signature base string. # Using both RFC examples: "Photos" (Section 1.2) and "Example" (Section 3.5.1) header_photos = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS.merge(realm: "Photos")) header_example = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS.merge(realm: "Example")) header_no_realm = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS) # Different realm values MUST produce identical signatures photos_sig = header_photos.signed_attributes[:oauth_signature] assert_equal photos_sig, header_example.signed_attributes[:oauth_signature] assert_equal photos_sig, header_no_realm.signed_attributes[:oauth_signature] end def test_no_realm_by_default header = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS) refute_includes header.to_s, "realm" end def test_realm_does_not_raise_extra_keys_error header = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS.merge(realm: "Example")) assert header.to_s end def test_realm_value_is_escaped # RFC 5849 Section 3.5.1: realm values are percent-encoded per RFC 3986 header = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS.merge(realm: "Example Realm")) assert_includes header.to_s, 'realm="Example%20Realm"' end def test_realm_nil_is_not_included header = SimpleOAuth::Header.new(:get, "http://example.com/request", {}, RFC5849::HeaderExample::OPTIONS.merge(realm: nil)) refute_includes header.to_s, "realm" end end end simple_oauth-0.4.0/test/simple_oauth/header_signature_format_test.rb000066400000000000000000000033571513755122500261370ustar00rootroot00000000000000require "test_helper" module SimpleOAuth class HeaderSignatureFormatTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_hmac_sha1_signature_contains_no_newlines signature = Signature.sign("HMAC-SHA1", "secret", "signature_base_string") refute_includes signature, "\n" end def test_hmac_sha256_signature_contains_no_newlines signature = Signature.sign("HMAC-SHA256", "secret", "signature_base_string") refute_includes signature, "\n" end def test_rsa_sha1_signature_contains_no_newlines signature = Signature.sign("RSA-SHA1", rsa_private_key, "signature_base_string") refute_includes signature, "\n" end def test_hmac_sha1_signature_is_base64_encoded signature = Signature.sign("HMAC-SHA1", "secret", "signature_base_string") assert_match %r{\A[A-Za-z0-9+/]+=*\z}, signature end def test_hmac_sha256_signature_is_base64_encoded signature = Signature.sign("HMAC-SHA256", "secret", "signature_base_string") assert_match %r{\A[A-Za-z0-9+/]+=*\z}, signature end def test_rsa_sha1_signature_is_base64_encoded signature = Signature.sign("RSA-SHA1", rsa_private_key, "signature_base_string") assert_match %r{\A[A-Za-z0-9+/]+=*\z}, signature end def test_plaintext_signature_returns_secret_unchanged signature = Signature.sign("PLAINTEXT", "secret&token_secret", "signature_base_string") assert_equal "secret&token_secret", signature end def test_plaintext_signature_ignores_signature_base sig1 = Signature.sign("PLAINTEXT", "secret&token_secret", "base1") sig2 = Signature.sign("PLAINTEXT", "secret&token_secret", "base2") assert_equal sig1, sig2 end end end simple_oauth-0.4.0/test/simple_oauth/header_signature_integration_test.rb000066400000000000000000000125461513755122500271720ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Integration tests using credentials and URLs from RFC 5849. # See https://www.rfc-editor.org/rfc/rfc5849 # # Note: Our library includes oauth_version="1.0" by default, which the # RFC examples omit. This means our signatures differ from the RFC # examples, but we verify consistency and correctness. class HeaderSignatureIntegrationTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # HMAC-SHA1 tests using RFC 5849 Section 1.2 credentials def test_hmac_sha1_produces_valid_signature_for_get # RFC 5849 Section 1.2 - Accessing Protected Resource header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {file: "vacation.jpg", size: "original"}, rfc_resource_request_options) parsed = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {file: "vacation.jpg", size: "original"}, header.to_s) assert parsed.valid?(consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TOKEN_SECRET) end def test_hmac_sha1_produces_valid_signature_for_post # RFC 5849 Section 1.2 - Token Request header = SimpleOAuth::Header.new(:post, "https://photos.example.net/token", {}, rfc_token_request_options) parsed = SimpleOAuth::Header.new(:post, "https://photos.example.net/token", {}, header.to_s) assert parsed.valid?(consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TEMP_TOKEN_SECRET) end def test_hmac_sha1_includes_callback_in_signature # RFC 5849 Section 1.2 - Temporary Credentials Request includes callback header = SimpleOAuth::Header.new(:post, "https://photos.example.net/initiate", {}, rfc_temporary_credentials_options) assert_includes header.to_s, 'oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready"' end def test_hmac_sha1_includes_verifier_in_signature # RFC 5849 Section 1.2 - Token Request includes verifier header = SimpleOAuth::Header.new(:post, "https://photos.example.net/token", {}, rfc_token_request_options) assert_includes header.to_s, "oauth_verifier=\"#{RFC5849::VERIFIER}\"" end # RSA-SHA1 tests using RFC 5849 Section 1.2 credentials def test_rsa_sha1_produces_valid_signature header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {file: "vacation.jpg", size: "original"}, rsa_sha1_options) parsed = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {file: "vacation.jpg", size: "original"}, header.to_s) assert parsed.valid?(consumer_secret: rsa_private_key) end # PLAINTEXT tests using RFC 5849 Section 2.1 credentials def test_plaintext_produces_valid_signature # RFC 5849 Section 2.1 - PLAINTEXT Temporary Credentials Request header = SimpleOAuth::Header.new(:post, "http://server.example.com/request_temp_credentials", {}, rfc_plaintext_options) parsed = SimpleOAuth::Header.new(:post, "http://server.example.com/request_temp_credentials", {}, header.to_s) assert parsed.valid?(consumer_secret: RFC5849::PlaintextExample::CONSUMER_SECRET) end def test_plaintext_signature_is_escaped_secret header = SimpleOAuth::Header.new(:post, "http://server.example.com/request_temp_credentials", {}, rfc_plaintext_options) # PLAINTEXT signature is consumer_secret&token_secret (no token_secret here) assert_equal "#{RFC5849::PlaintextExample::CONSUMER_SECRET}&", header.signed_attributes[:oauth_signature] end private # RFC 5849 Section 1.2 - Temporary Credentials Request def rfc_temporary_credentials_options { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, signature_method: "HMAC-SHA1", timestamp: "137131200", nonce: "wIjqoS", callback: RFC5849::PRINTER_CALLBACK } end # RFC 5849 Section 1.2 - Token Request def rfc_token_request_options { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, token: RFC5849::TEMP_TOKEN, token_secret: RFC5849::TEMP_TOKEN_SECRET, signature_method: "HMAC-SHA1", timestamp: "137131201", nonce: "walatlh", verifier: RFC5849::VERIFIER } end # RFC 5849 Section 1.2 - Accessing Protected Resource def rfc_resource_request_options { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, token: RFC5849::TOKEN, token_secret: RFC5849::TOKEN_SECRET, signature_method: "HMAC-SHA1", timestamp: "137131202", nonce: "chapoH" } end # RSA-SHA1 using RFC 5849 credentials def rsa_sha1_options { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: rsa_private_key, nonce: "13917289812797014437", signature_method: "RSA-SHA1", timestamp: "1196666512" } end # RFC 5849 Section 2.1 - PLAINTEXT example def rfc_plaintext_options { consumer_key: RFC5849::PlaintextExample::CONSUMER_KEY, consumer_secret: RFC5849::PlaintextExample::CONSUMER_SECRET, signature_method: "PLAINTEXT", timestamp: "137131200", nonce: "7d8f3e4a", callback: RFC5849::PlaintextExample::CALLBACK } end end end simple_oauth-0.4.0/test/simple_oauth/header_signature_test.rb000066400000000000000000000075231513755122500245660ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for signature computation using RFC 5849 credentials. class HeaderSignatureTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # #signature tests - dispatches to correct Signature method def test_signature_uses_hmac_sha1_for_hmac_sha1_method # RFC 5849 Section 3.4.2 - HMAC-SHA1 signature method header = build_header_with_fixed_credentials(signature_method: "HMAC-SHA1") assert_kind_of String, header.send(:signature) refute_empty header.send(:signature) end def test_signature_uses_hmac_sha256_for_hmac_sha256_method header = build_header_with_fixed_credentials(signature_method: "HMAC-SHA256") assert_kind_of String, header.send(:signature) refute_empty header.send(:signature) end def test_signature_uses_rsa_sha1_for_rsa_sha1_method # RFC 5849 Section 3.4.3 - RSA-SHA1 signature method header = build_header_with_fixed_credentials( consumer_secret: rsa_private_key, signature_method: "RSA-SHA1" ) assert_kind_of String, header.send(:signature) refute_empty header.send(:signature) end def test_signature_uses_plaintext_for_plaintext_method # RFC 5849 Section 3.4.4 - PLAINTEXT signature method header = SimpleOAuth::Header.new(:get, "http://server.example.com/resource", {}, consumer_key: RFC5849::PlaintextExample::CONSUMER_KEY, consumer_secret: RFC5849::PlaintextExample::CONSUMER_SECRET, token_secret: RFC5849::PlaintextExample::TOKEN_SECRET, signature_method: "PLAINTEXT", nonce: "7d8f3e4a", timestamp: "137131200") # PLAINTEXT signature is just the escaped secrets joined with & assert_equal "#{RFC5849::PlaintextExample::CONSUMER_SECRET}&#{RFC5849::PlaintextExample::TOKEN_SECRET}", header.send(:signature) end def test_signature_method_converts_dashes_to_underscores header1 = build_header_with_fixed_credentials(signature_method: "HMAC-SHA1") header2 = build_header_with_fixed_credentials(signature_method: "HMAC-SHA256") # Different signature methods produce different signatures refute_equal header1.send(:signature), header2.send(:signature) end def test_signature_method_dispatches_correctly_regardless_of_case # Both uppercase and lowercase signature methods should work header_upper = build_header_with_fixed_credentials(signature_method: "HMAC-SHA1") header_lower = build_header_with_fixed_credentials(signature_method: "hmac-sha1") # Both should produce valid signatures (non-empty base64 strings) assert_match %r{\A[A-Za-z0-9+/]+=*\z}, header_upper.send(:signature) assert_match %r{\A[A-Za-z0-9+/]+=*\z}, header_lower.send(:signature) end def test_same_inputs_produce_same_signature header1 = build_header_with_fixed_credentials(signature_method: "HMAC-SHA1") header2 = build_header_with_fixed_credentials(signature_method: "HMAC-SHA1") assert_equal header1.send(:signature), header2.send(:signature) end def test_different_secrets_produce_different_signatures header1 = build_header_with_fixed_credentials(consumer_secret: "secret1") header2 = build_header_with_fixed_credentials(consumer_secret: "secret2") refute_equal header1.send(:signature), header2.send(:signature) end def test_rsa_sha1_uses_raw_consumer_secret_not_escaped_secret # RSA-SHA1 needs the raw PEM key, not the escaped secret string header = build_header_with_fixed_credentials( consumer_secret: rsa_private_key, token_secret: "ignored", signature_method: "RSA-SHA1" ) # If the secret was escaped (like for HMAC), this would fail because # the PEM key would be mangled assert_match %r{\A[A-Za-z0-9+/]+=*\z}, header.send(:signature) end end end simple_oauth-0.4.0/test/simple_oauth/header_url_test.rb000066400000000000000000000040211513755122500233550ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for URL handling per RFC 5849 Section 3.4.1.2 (Base String URI). class HeaderUrlTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" def test_initialize_accepts_uri_object # RFC 5849 Section 1.2 - photos.example.net endpoint uri = URI.parse("https://photos.example.net/photos") header = SimpleOAuth::Header.new(:get, uri, {}) assert_equal "https://photos.example.net/photos", header.url end def test_initialize_normalizes_scheme_to_lowercase # RFC 5849 Section 3.4.1.2 - scheme is case-insensitive, normalized to lowercase header = SimpleOAuth::Header.new(:get, "HTTPS://photos.example.net/photos", {}) assert header.url.start_with?("https://") end def test_url_removes_query_string # RFC 5849 Section 3.4.1.2 - query component excluded from base string URI header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?file=vacation.jpg", {}) refute_includes header.url, "?" refute_includes header.url, "file" end def test_url_removes_fragment # RFC 5849 Section 3.4.1.2 - fragment excluded from base string URI header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos#section", {}) refute_includes header.url, "#" refute_includes header.url, "section" end def test_url_can_be_called_multiple_times_consistently header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?file=vacation.jpg", {}) first_result = header.url second_result = header.url assert_equal first_result, second_result end def test_url_preserves_internal_uri_with_query # Query params should still be available for signature even though url() strips them header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos?file=vacation.jpg", {}) header.url url_params = header.send(:url_params) assert_equal [%w[file vacation.jpg]], url_params end end end simple_oauth-0.4.0/test/simple_oauth/header_validation_test.rb000066400000000000000000000066371513755122500247240ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for signature validation using RFC 5849 credentials. class HeaderValidationTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Header*" # #valid? tests - HMAC-SHA1 def test_valid_hmac_sha1_is_not_valid_without_secrets header = build_header(token_secret: RFC5849::TOKEN_SECRET) parsed_header = SimpleOAuth::Header.new(:get, RFC5849::PHOTOS_URL, {}, header) refute_predicate parsed_header, :valid? end def test_valid_hmac_sha1_is_valid_with_secrets secrets = {consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TOKEN_SECRET} header = build_header(token_secret: RFC5849::TOKEN_SECRET) parsed_header = SimpleOAuth::Header.new(:get, RFC5849::PHOTOS_URL, {}, header) assert parsed_header.valid?(secrets) end # #valid? tests - RSA-SHA1 def test_valid_rsa_sha1_raises_type_error_without_private_key header = build_header(consumer_secret: rsa_private_key, signature_method: "RSA-SHA1") parsed_header = SimpleOAuth::Header.new(:get, RFC5849::PHOTOS_URL, {}, header) assert_raises(TypeError) { parsed_header.valid? } end def test_valid_rsa_sha1_is_valid_with_private_key secrets = {consumer_secret: rsa_private_key} header = build_header(consumer_secret: rsa_private_key, signature_method: "RSA-SHA1") parsed_header = SimpleOAuth::Header.new(:get, RFC5849::PHOTOS_URL, {}, header) assert parsed_header.valid?(secrets) end # #valid? tests - PLAINTEXT def test_valid_plaintext_is_not_valid_without_secrets # RFC 5849 Section 2.1 - PLAINTEXT example credentials secrets = {consumer_secret: RFC5849::PlaintextExample::CONSUMER_SECRET, token_secret: RFC5849::PlaintextExample::TOKEN_SECRET} header = SimpleOAuth::Header.new(:get, "http://server.example.com/resource", {}, secrets.merge(signature_method: "PLAINTEXT")) parsed_header = SimpleOAuth::Header.new(:get, "http://server.example.com/resource", {}, header) refute_predicate parsed_header, :valid? end def test_valid_plaintext_is_valid_with_secrets # RFC 5849 Section 2.1 - PLAINTEXT example credentials secrets = {consumer_secret: RFC5849::PlaintextExample::CONSUMER_SECRET, token_secret: RFC5849::PlaintextExample::TOKEN_SECRET} header = SimpleOAuth::Header.new(:get, "http://server.example.com/resource", {}, secrets.merge(signature_method: "PLAINTEXT")) parsed_header = SimpleOAuth::Header.new(:get, "http://server.example.com/resource", {}, header) assert parsed_header.valid?(secrets) end def test_valid_restores_original_options_after_validation secrets = {consumer_secret: RFC5849::CONSUMER_SECRET, token_secret: RFC5849::TOKEN_SECRET} header = build_header(token_secret: RFC5849::TOKEN_SECRET) parsed_header = SimpleOAuth::Header.new(:get, RFC5849::PHOTOS_URL, {}, header) original_options = parsed_header.options.dup parsed_header.valid?(secrets) assert_equal original_options, parsed_header.options end def test_valid_returns_false_when_signature_does_not_match header = build_header(token_secret: RFC5849::TOKEN_SECRET) parsed_header = SimpleOAuth::Header.new(:get, RFC5849::PHOTOS_URL, {}, header) refute parsed_header.valid?(consumer_secret: "WRONG_SECRET", token_secret: "WRONG_TOKEN") end end end simple_oauth-0.4.0/test/simple_oauth/signature_registry_test.rb000066400000000000000000000206221513755122500252010ustar00rootroot00000000000000require "test_helper" module SimpleOAuth # Tests for Signature method registration. class SignatureRegistrationTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Signature*" def teardown Signature.reset! end def test_register_adds_custom_signature_method Signature.register("CUSTOM-METHOD") { |secret, base| "custom:#{secret}:#{base}" } assert Signature.registered?("CUSTOM-METHOD") end def test_register_normalizes_method_name Signature.register("My-Custom-Method") { |_s, _b| "sig" } assert Signature.registered?("my_custom_method") assert Signature.registered?("MY-CUSTOM-METHOD") end def test_register_accepts_symbol_name Signature.register(:custom_symbol_method) { |_s, _b| "sig" } assert Signature.registered?("custom_symbol_method") assert Signature.registered?(:custom_symbol_method) end def test_register_with_rsa_flag Signature.register("RSA-CUSTOM", rsa: true) { |_key, base| "rsa:#{base}" } assert Signature.rsa?("RSA-CUSTOM") end def test_register_without_rsa_flag_defaults_to_false Signature.register("NON-RSA") { |_s, _b| "sig" } refute Signature.rsa?("NON-RSA") end def test_unregister_removes_method Signature.register("TEMP") { |_s, _b| "sig" } assert Signature.registered?("TEMP") Signature.unregister("TEMP") refute Signature.registered?("TEMP") end def test_unregister_normalizes_name Signature.register("TEMP-METHOD") { |_s, _b| "sig" } Signature.unregister("temp_method") refute Signature.registered?("TEMP-METHOD") end def test_reset_restores_builtin_methods Signature.unregister("HMAC-SHA1") refute Signature.registered?("HMAC-SHA1") Signature.reset! assert Signature.registered?("HMAC-SHA1") end def test_reset_removes_custom_methods Signature.register("CUSTOM") { |_s, _b| "sig" } assert Signature.registered?("CUSTOM") Signature.reset! refute Signature.registered?("CUSTOM") end end # Tests for Signature method queries. class SignatureQueryTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Signature*" def teardown Signature.reset! end # RFC 5849 Section 3.4.2 - HMAC-SHA1, Section 3.4.3 - RSA-SHA1, Section 3.4.4 - PLAINTEXT def test_registered_returns_true_for_builtin_methods %w[HMAC-SHA1 HMAC-SHA256 RSA-SHA1 RSA-SHA256 PLAINTEXT].each do |method| assert Signature.registered?(method), "Expected #{method} to be registered" end end def test_registered_returns_false_for_unknown_methods refute Signature.registered?("UNKNOWN-METHOD") end def test_registered_is_case_insensitive assert Signature.registered?("hmac-sha1") assert Signature.registered?("Hmac-Sha1") assert Signature.registered?("HMAC-SHA1") end def test_methods_returns_array_of_strings methods = Signature.methods assert_kind_of Array, methods methods.each { |m| assert_kind_of String, m } end def test_methods_includes_all_builtin_methods %w[hmac_sha1 hmac_sha256 rsa_sha1 rsa_sha256 plaintext].each do |method| assert_includes Signature.methods, method end end def test_methods_includes_custom_registered_methods Signature.register("CUSTOM") { |_s, _b| "sig" } assert_includes Signature.methods, "custom" end def test_rsa_returns_true_for_rsa_methods assert Signature.rsa?("RSA-SHA1") assert Signature.rsa?("RSA-SHA256") end def test_rsa_returns_false_for_non_rsa_methods refute Signature.rsa?("HMAC-SHA1") refute Signature.rsa?("HMAC-SHA256") refute Signature.rsa?("PLAINTEXT") end def test_rsa_returns_false_for_unknown_method result = Signature.rsa?("UNKNOWN") assert_instance_of FalseClass, result end end # Tests for Signature.sign method. class SignatureSignTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Signature*" def teardown Signature.reset! end def test_sign_computes_hmac_sha1_signature signature = Signature.sign("HMAC-SHA1", "secret", "base") expected = Base64.strict_encode64(OpenSSL::HMAC.digest("SHA1", "secret", "base")) assert_equal expected, signature end def test_sign_computes_hmac_sha256_signature signature = Signature.sign("HMAC-SHA256", "secret", "base") expected = Base64.strict_encode64(OpenSSL::HMAC.digest("SHA256", "secret", "base")) assert_equal expected, signature end def test_sign_computes_rsa_sha1_signature signature = Signature.sign("RSA-SHA1", rsa_private_key, "base") private_key = OpenSSL::PKey::RSA.new(rsa_private_key) expected = Base64.strict_encode64(private_key.sign("SHA1", "base")) assert_equal expected, signature end def test_sign_computes_rsa_sha256_signature signature = Signature.sign("RSA-SHA256", rsa_private_key, "base") private_key = OpenSSL::PKey::RSA.new(rsa_private_key) expected = Base64.strict_encode64(private_key.sign("SHA256", "base")) assert_equal expected, signature end def test_sign_rsa_sha256_differs_from_rsa_sha1 sha1_sig = Signature.sign("RSA-SHA1", rsa_private_key, "base") sha256_sig = Signature.sign("RSA-SHA256", rsa_private_key, "base") refute_equal sha1_sig, sha256_sig end # RFC 5849 Section 3.4.4 - PLAINTEXT returns secret unchanged def test_sign_computes_plaintext_signature signature = Signature.sign("PLAINTEXT", "secret&token", "base") assert_equal "secret&token", signature end def test_sign_uses_custom_registered_method Signature.register("CUSTOM") { |secret, base| "custom:#{secret}:#{base}" } assert_equal "custom:mysecret:mybase", Signature.sign("CUSTOM", "mysecret", "mybase") end def test_sign_raises_for_unknown_method error = assert_raises(ArgumentError) { Signature.sign("UNKNOWN-METHOD", "secret", "base") } assert_includes error.message, "Unknown signature method: UNKNOWN-METHOD" assert_includes error.message, "Registered methods:" end def test_sign_is_case_insensitive sig1 = Signature.sign("HMAC-SHA1", "secret", "base") sig2 = Signature.sign("hmac-sha1", "secret", "base") assert_equal sig1, sig2 end def test_sign_normalizes_dashes_to_underscores sig1 = Signature.sign("HMAC-SHA1", "secret", "base") sig2 = Signature.sign("HMAC_SHA1", "secret", "base") assert_equal sig1, sig2 end def test_sign_accepts_symbol_method_name sig1 = Signature.sign("HMAC-SHA1", "secret", "base") sig2 = Signature.sign(:hmac_sha1, "secret", "base") assert_equal sig1, sig2 end end # Tests for Header integration with Signature registry. class SignatureHeaderIntegrationTest < Minitest::Test include TestHelpers cover "SimpleOAuth::Signature*" def teardown Signature.reset! end def test_header_uses_custom_signature_method Signature.register("HMAC-SHA512") do |secret, signature_base| Base64.strict_encode64(OpenSSL::HMAC.digest("SHA512", secret, signature_base)) end header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, signature_method: "HMAC-SHA512", nonce: "chapoH", timestamp: "137131202") assert_match %r{\A[A-Za-z0-9+/]+=*\z}, header.signed_attributes[:oauth_signature] end def test_header_uses_builtin_rsa_sha256_method header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: rsa_private_key, signature_method: "RSA-SHA256", nonce: "chapoH", timestamp: "137131202") private_key = OpenSSL::PKey::RSA.new(rsa_private_key) expected = Base64.strict_encode64(private_key.sign("SHA256", header.send(:signature_base))) assert_equal expected, header.signed_attributes[:oauth_signature] end def test_header_raises_for_unregistered_method header = SimpleOAuth::Header.new(:get, "https://photos.example.net/photos", {}, consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, signature_method: "UNKNOWN", nonce: "chapoH", timestamp: "137131202") assert_raises(ArgumentError) { header.to_s } end end end simple_oauth-0.4.0/test/test_helper.rb000066400000000000000000000077151513755122500200460ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "simplecov" SimpleCov.start do enable_coverage :branch add_filter "/test/" minimum_coverage line: 100, branch: 100 end require "minitest/autorun" require "simple_oauth" # Define a no-op cover method for regular test runs (mutant-minitest defines this when running mutations) Minitest::Test.define_singleton_method(:cover) { |*| nil } unless Minitest::Test.respond_to?(:cover) module TestHelpers PRIVATE_KEY_PATH = File.expand_path("fixtures/rsa-private-key", __dir__) def rsa_private_key @rsa_private_key ||= File.read(PRIVATE_KEY_PATH) end # Factory method to build a Header with common defaults def build_header(method = :get, url = RFC5849::PHOTOS_URL, params = {}, **options) defaults = { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET } SimpleOAuth::Header.new(method, url, params, defaults.merge(options)) end # Factory method to build a Header with fixed nonce/timestamp for deterministic tests def build_header_with_fixed_credentials(method = :get, url = RFC5849::PHOTOS_URL, params = {}, **options) defaults = { consumer_key: RFC5849::CONSUMER_KEY, consumer_secret: RFC5849::CONSUMER_SECRET, nonce: "chapoH", timestamp: "137131202" } SimpleOAuth::Header.new(method, url, params, defaults.merge(options)) end # RFC 5849 Example Constants # See https://www.rfc-editor.org/rfc/rfc5849 for complete examples module RFC5849 # Section 1.2 - Printer/Photos example endpoints PHOTOS_HOST = "photos.example.net".freeze PHOTOS_BASE_URL = "https://#{PHOTOS_HOST}".freeze PHOTOS_URL = "#{PHOTOS_BASE_URL}/photos".freeze PRINTER_HOST = "printer.example.com".freeze PRINTER_CALLBACK = "http://#{PRINTER_HOST}/ready".freeze # Section 1.2 - Client credentials (printer application) CONSUMER_KEY = "dpf43f3p2l4k3l03".freeze CONSUMER_SECRET = "kd94hf93k423kf44".freeze # Section 1.2 - Temporary credentials TEMP_TOKEN = "hh5s93j4hdidpola".freeze TEMP_TOKEN_SECRET = "hdhd0244k9j7ao03".freeze # Section 1.2 - Token credentials TOKEN = "nnch734d00sl2jdk".freeze TOKEN_SECRET = "pfkkdhi9sl3r4s00".freeze # Section 1.2 - Verifier VERIFIER = "hfdp7dh39dks9884".freeze # Section 3.1 / 3.4.1 - Signature example module SignatureExample HOST = "example.com".freeze BASE_URL = "http://#{HOST}".freeze CONSUMER_KEY = "9djdj82h48djs9d2".freeze CONSUMER_SECRET = "j49sk3j29djd".freeze TOKEN = "kkk9d7dh3k39sjv7".freeze TOKEN_SECRET = "dh893hdasih9".freeze TIMESTAMP = "137131201".freeze NONCE = "7d8f3e4a".freeze end # Section 3.5.1 - Authorization header example module HeaderExample CONSUMER_KEY = "0685bd9184jfhq22".freeze CONSUMER_SECRET = "kd94hf93k423kf44".freeze TOKEN = "ad180jjd733klru7".freeze TOKEN_SECRET = "pfkkdhi9sl3r4s00".freeze TIMESTAMP = "137131200".freeze NONCE = "4572616e48616d6d65724c61686176".freeze # Complete options hash for tests OPTIONS = { consumer_key: CONSUMER_KEY, consumer_secret: CONSUMER_SECRET, token: TOKEN, token_secret: TOKEN_SECRET, nonce: NONCE, timestamp: TIMESTAMP }.freeze end # Complete options hash for printer/photos example (Section 1.2) PHOTOS_OPTIONS = { consumer_key: CONSUMER_KEY, consumer_secret: CONSUMER_SECRET, nonce: "wIjqoS", timestamp: "137131200", callback: PRINTER_CALLBACK }.freeze # Section 2.1 - PLAINTEXT example module PlaintextExample HOST = "server.example.com".freeze BASE_URL = "http://#{HOST}".freeze CONSUMER_KEY = "jd83jd92dhsh93js".freeze CONSUMER_SECRET = "ja893SD9".freeze CALLBACK = "http://client.example.net/cb?x=1".freeze TOKEN = "hdk48Djdsa".freeze TOKEN_SECRET = "xyz4992k83j47x0b".freeze VERIFIER = "473f82d3".freeze end end end