├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── coverage.yml │ ├── heads.yml │ ├── macos.yml │ ├── style.yml │ ├── supported.yml │ └── windows.yml ├── .gitignore ├── .overcommit.yml ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── CHANGELOG.md ├── CNAME ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── SECURITY.md ├── TODO ├── bin ├── bundle ├── rake └── rubocop ├── certs └── pboling.pem ├── docs └── images │ └── logo │ ├── Oauth_logo.svg │ ├── README.txt │ └── ruby-logo-198px.svg ├── examples ├── twitter.rb └── yql.rb ├── gemfiles ├── README.md ├── a6.gemfile └── a7.gemfile ├── lib ├── oauth.rb └── oauth │ ├── client.rb │ ├── client │ ├── action_controller_request.rb │ ├── em_http.rb │ ├── helper.rb │ └── net_http.rb │ ├── consumer.rb │ ├── errors.rb │ ├── errors │ ├── error.rb │ ├── problem.rb │ └── unauthorized.rb │ ├── helper.rb │ ├── oauth.rb │ ├── oauth_test_helper.rb │ ├── request_proxy.rb │ ├── request_proxy │ ├── action_controller_request.rb │ ├── action_dispatch_request.rb │ ├── base.rb │ ├── curb_request.rb │ ├── em_http_request.rb │ ├── jabber_request.rb │ ├── mock_request.rb │ ├── net_http.rb │ ├── rack_request.rb │ ├── rest_client_request.rb │ └── typhoeus_request.rb │ ├── server.rb │ ├── signature.rb │ ├── signature │ ├── base.rb │ ├── hmac │ │ ├── sha1.rb │ │ └── sha256.rb │ ├── plaintext.rb │ └── rsa │ │ └── sha1.rb │ ├── token.rb │ ├── tokens │ ├── access_token.rb │ ├── consumer_token.rb │ ├── request_token.rb │ ├── server_token.rb │ └── token.rb │ └── version.rb ├── oauth.gemspec └── test ├── cases └── spec │ └── 1_0-final │ ├── construct_request_url_test.rb │ ├── normalize_request_parameters_test.rb │ ├── parameter_encodings_test.rb │ └── signature_base_strings_test.rb ├── keys ├── rsa.cert └── rsa.pem ├── support ├── minitest_helpers.rb └── oauth_case.rb ├── test_helper.rb └── units ├── access_token_test.rb ├── action_controller_request_proxy_test.rb ├── action_dispatch_request_proxy_test.rb ├── cli_test.rb ├── client_helper_test.rb ├── consumer_integration_test.rb ├── consumer_test.rb ├── curb_request_proxy_test.rb ├── em_http_client_test.rb ├── em_http_request_proxy_test.rb ├── hmac_sha1_test.rb ├── hmac_sha256_test.rb ├── net_http_client_test.rb ├── net_http_request_proxy_test.rb ├── oauth_helper_test.rb ├── rack_request_proxy_test.rb ├── request_token_test.rb ├── rest_client_request_proxy_test.rb ├── rsa_sha1_test.rb ├── server_test.rb ├── signature_base_test.rb ├── signature_hmac_sha1_test.rb ├── signature_hmac_sha256_test.rb ├── signature_plain_text_test.rb ├── signature_test.rb ├── token_test.rb └── typhoeus_request_proxy_test.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | buy_me_a_coffee: pboling 4 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 5 | github: [pboling] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 6 | issuehunt: pboling # Replace with a single IssueHunt username 7 | ko_fi: pboling # Replace with a single Ko-fi username 8 | liberapay: pboling # Replace with a single Liberapay username 9 | open_collective: # Replace with a single Open Collective username 10 | patreon: galtzo # Replace with a single Patreon username 11 | polar: pboling 12 | thanks_dev: gh/pboling 13 | tidelift: rubygems/oauth # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:30" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: "rubocop-lts" 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main, "*-maintenance" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main, "*-maintenance" ] 20 | schedule: 21 | - cron: '35 1 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'ruby' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | env: 4 | CI_CODECOV: true 5 | COVER_ALL: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'main' 11 | - '*-maintenance' 12 | - '*-dev' 13 | - '*-stable' 14 | tags: 15 | - '!*' # Do not execute on tags 16 | pull_request: 17 | branches: 18 | - '*' 19 | # Allow manually triggering the workflow. 20 | workflow_dispatch: 21 | 22 | # Cancels all previous workflow runs for the same branch that have not yet completed. 23 | concurrency: 24 | # The concurrency group contains the workflow name and the branch name. 25 | group: ${{ github.workflow }}-${{ github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | test: 30 | name: Specs with Coverage - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} 31 | env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps 32 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 33 | if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | experimental: [false] 38 | gemfile: 39 | - a7 40 | rubygems: 41 | - latest 42 | bundler: 43 | - latest 44 | ruby: 45 | - "2.7" 46 | 47 | runs-on: ubuntu-latest 48 | continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v3 52 | 53 | - name: Install cURL Headers 54 | run: | 55 | sudo apt-get update 56 | sudo apt-get -y --fix-missing install libcurl4-openssl-dev 57 | 58 | - uses: amancevice/setup-code-climate@v0 59 | name: CodeClimate Install 60 | if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() 61 | with: 62 | cc_test_reporter_id: ${{ secrets.CC_TEST_REPORTER_ID }} 63 | 64 | - name: Setup Ruby & Bundle 65 | uses: ruby/setup-ruby@v1 66 | with: 67 | ruby-version: ${{ matrix.ruby }} 68 | rubygems: ${{ matrix.rubygems }} 69 | bundler: ${{ matrix.bundler }} 70 | bundler-cache: true 71 | 72 | - name: CodeClimate Pre-build Notification 73 | run: cc-test-reporter before-build 74 | if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() 75 | continue-on-error: ${{ matrix.experimental != 'false' }} 76 | 77 | - name: Run tests 78 | run: bundle exec rake test 79 | 80 | - name: CodeClimate Post-build Notification 81 | run: cc-test-reporter after-build 82 | if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() 83 | continue-on-error: ${{ matrix.experimental != 'false' }} 84 | 85 | - name: Code Coverage Summary Report 86 | uses: irongut/CodeCoverageSummary@v1.2.0 87 | with: 88 | filename: ./coverage/coverage.xml 89 | badge: true 90 | fail_below_min: true 91 | format: markdown 92 | hide_branch_rate: true 93 | hide_complexity: true 94 | indicators: true 95 | output: both 96 | thresholds: '78 40' 97 | continue-on-error: ${{ matrix.experimental != 'false' }} 98 | 99 | - name: Add Coverage PR Comment 100 | uses: marocchino/sticky-pull-request-comment@v2 101 | if: matrix.ruby == '2.7' && always() 102 | with: 103 | recreate: true 104 | path: code-coverage-results.md 105 | continue-on-error: ${{ matrix.experimental != 'false' }} 106 | 107 | - name: Coveralls 108 | uses: coverallsapp/github-action@master 109 | if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() 110 | with: 111 | github-token: ${{ secrets.GITHUB_TOKEN }} 112 | continue-on-error: ${{ matrix.experimental != 'false' }} 113 | 114 | # Using the codecov gem instead. 115 | # - name: CodeCov 116 | # uses: codecov/codecov-action@v2 117 | # if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() 118 | # with: 119 | # files: ./coverage/coverage.xml 120 | # flags: unittests 121 | # name: codecov-upload 122 | # fail_ci_if_error: true 123 | # continue-on-error: ${{ matrix.experimental != 'false' }} 124 | -------------------------------------------------------------------------------- /.github/workflows/heads.yml: -------------------------------------------------------------------------------- 1 | name: Heads 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - '*-maintenance' 8 | - '*-dev' 9 | - '*-stable' 10 | tags: 11 | - '!*' # Do not execute on tags 12 | pull_request: 13 | branches: 14 | - '*' 15 | # Allow manually triggering the workflow. 16 | workflow_dispatch: 17 | 18 | # Cancels all previous workflow runs for the same branch that have not yet completed. 19 | concurrency: 20 | # The concurrency group contains the workflow name and the branch name. 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | test: 26 | name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} 27 | env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps 28 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 29 | if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | experimental: [true] 34 | gemfile: 35 | - a7 36 | rubygems: 37 | - latest 38 | bundler: 39 | - latest 40 | ruby: 41 | - truffleruby+graalvm-head 42 | - truffleruby-head 43 | - ruby-head 44 | 45 | runs-on: ubuntu-latest 46 | continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v3 50 | - name: Install cURL Headers 51 | run: | 52 | sudo apt-get update 53 | sudo apt-get -y --fix-missing install libcurl4-openssl-dev 54 | - name: Setup Ruby & Bundle 55 | uses: ruby/setup-ruby@v1 56 | with: 57 | ruby-version: ${{ matrix.ruby }} 58 | rubygems: ${{ matrix.rubygems }} 59 | bundler: ${{ matrix.bundler }} 60 | bundler-cache: true 61 | - name: Run tests 62 | run: bundle exec rake test 63 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - '*-maintenance' 8 | - '*-dev' 9 | - '*-stable' 10 | tags: 11 | - '!*' # Do not execute on tags 12 | pull_request: 13 | branches: 14 | - '*' 15 | # Allow manually triggering the workflow. 16 | workflow_dispatch: 17 | 18 | # Cancels all previous workflow runs for the same branch that have not yet completed. 19 | concurrency: 20 | # The concurrency group contains the workflow name and the branch name. 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | test: 26 | name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} 27 | env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps 28 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 29 | if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | experimental: [true] 34 | gemfile: 35 | - a7 36 | rubygems: 37 | - latest 38 | bundler: 39 | - latest 40 | ruby: 41 | - "2.7" 42 | - "3.0" 43 | - "3.1" 44 | - truffleruby 45 | 46 | runs-on: macos-latest 47 | continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} 48 | steps: 49 | - name: Checkout 50 | uses: actions/checkout@v3 51 | - name: Setup Ruby & Bundle 52 | uses: ruby/setup-ruby@v1 53 | with: 54 | ruby-version: ${{ matrix.ruby }} 55 | rubygems: ${{ matrix.rubygems }} 56 | bundler: ${{ matrix.bundler }} 57 | bundler-cache: true 58 | - name: Run tests 59 | run: bundle exec rake test 60 | -------------------------------------------------------------------------------- /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | name: Code Style Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - '*-maintenance' 8 | - '*-dev' 9 | - '*-stable' 10 | tags: 11 | - '!*' # Do not execute on tags 12 | pull_request: 13 | branches: 14 | - '*' 15 | 16 | jobs: 17 | rubocop: 18 | name: Rubocop 19 | env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps 20 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 21 | if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | experimental: [false] 26 | gemfile: 27 | - a7 28 | rubygems: 29 | - latest 30 | bundler: 31 | - latest 32 | ruby: 33 | - "2.7" 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | - name: Install cURL Headers 39 | run: | 40 | sudo apt-get update 41 | sudo apt-get -y --fix-missing install libcurl4-openssl-dev 42 | - name: Setup Ruby & Bundle 43 | uses: ruby/setup-ruby@v1 44 | with: 45 | ruby-version: ${{ matrix.ruby }} 46 | rubygems: ${{ matrix.rubygems }} 47 | bundler: ${{ matrix.bundler }} 48 | bundler-cache: true 49 | - name: Run Rubocop 50 | run: bundle exec rubocop -DESP 51 | -------------------------------------------------------------------------------- /.github/workflows/supported.yml: -------------------------------------------------------------------------------- 1 | name: Official Support 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - '*-maintenance' 8 | - '*-dev' 9 | - '*-stable' 10 | tags: 11 | - '!*' # Do not execute on tags 12 | pull_request: 13 | branches: 14 | - '*' 15 | # Allow manually triggering the workflow. 16 | workflow_dispatch: 17 | 18 | # Cancels all previous workflow runs for the same branch that have not yet completed. 19 | concurrency: 20 | # The concurrency group contains the workflow name and the branch name. 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | test: 26 | name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} 27 | env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps 28 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 29 | if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | experimental: [false] 34 | rubygems: 35 | - latest 36 | bundler: 37 | - latest 38 | gemfile: 39 | - a6 40 | - a7 41 | ruby: 42 | - "2.7" 43 | - "3.0" 44 | - "3.1" 45 | exclude: 46 | - ruby: "3.0" 47 | gemfile: "a7" 48 | - ruby: "3.1" 49 | gemfile: "a5" 50 | - ruby: "3.1" 51 | gemfile: "a6" 52 | 53 | runs-on: ubuntu-latest 54 | continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v3 58 | - name: Install cURL Headers 59 | run: | 60 | sudo apt-get update 61 | sudo apt-get -y --fix-missing install libcurl4-openssl-dev 62 | - name: Setup Ruby & Bundle 63 | uses: ruby/setup-ruby@v1 64 | with: 65 | ruby-version: ${{ matrix.ruby }} 66 | rubygems: ${{ matrix.rubygems }} 67 | bundler: ${{ matrix.bundler }} 68 | bundler-cache: true 69 | - name: Run tests 70 | run: bundle exec rake test 71 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | - '*-maintenance' 8 | - '*-dev' 9 | - '*-stable' 10 | tags: 11 | - '!*' # Do not execute on tags 12 | pull_request: 13 | branches: 14 | - '*' 15 | # Allow manually triggering the workflow. 16 | workflow_dispatch: 17 | 18 | # Cancels all previous workflow runs for the same branch that have not yet completed. 19 | concurrency: 20 | # The concurrency group contains the workflow name and the branch name. 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | test: 26 | name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} 27 | env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps 28 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 29 | if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | experimental: [true] 34 | gemfile: 35 | - a7 36 | rubygems: 37 | - latest 38 | bundler: 39 | - latest 40 | ruby: 41 | - "2.7" 42 | - "3.0" 43 | - "3.1" 44 | 45 | runs-on: windows-latest 46 | continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v3 50 | - name: Setup Ruby & Bundle 51 | uses: ruby/setup-ruby@v1 52 | with: 53 | ruby-version: ${{ matrix.ruby }} 54 | rubygems: ${{ matrix.rubygems }} 55 | bundler: ${{ matrix.bundler }} 56 | bundler-cache: true 57 | - name: Run tests 58 | run: bundle exec rake test 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Artifacts 2 | /pkg/ 3 | /tmp/ 4 | *.gem 5 | 6 | # Bundler 7 | /.bundle/ 8 | /gemfiles/*.lock 9 | /gemfiles/.bundle/ 10 | /gemfiles/.bundle/config 11 | /gemfiles/vendor/ 12 | 13 | # Specs 14 | /coverage/ 15 | /test/reports/ 16 | 17 | # Documentation 18 | /.yardoc 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | # Editors 24 | .idea 25 | *~ 26 | 27 | # Other 28 | /measurement/ 29 | /.byebug_history 30 | .DS_Store 31 | 32 | # Version Managers 33 | .rvmrc 34 | .ruby-version 35 | .tool-versions 36 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Use this file to configure the Overcommit hooks you wish to use. This will 2 | # extend the default configuration defined in: 3 | # https://github.com/sds/overcommit/blob/master/config/default.yml 4 | # 5 | # At the topmost level of this YAML file is a key representing type of hook 6 | # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can 7 | # customize each hook, such as whether to only run it on certain files (via 8 | # `include`), whether to only display output if it fails (via `quiet`), etc. 9 | # 10 | # For a complete list of hooks, see: 11 | # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook 12 | # 13 | # For a complete list of options that you can use to customize hooks, see: 14 | # https://github.com/sds/overcommit#configuration 15 | # 16 | # Uncomment the following lines to make the configuration take effect. 17 | 18 | PreCommit: 19 | # RuboCop: 20 | # enabled: true 21 | # on_warn: fail # Treat all warnings as failures 22 | 23 | TrailingWhitespace: 24 | enabled: true 25 | 26 | PostCheckout: 27 | ALL: # Special hook name that customizes all hooks of this type 28 | quiet: true # Change all post-checkout hooks to only display output on failure 29 | # 30 | # IndexTags: 31 | # enabled: true # Generate a tags file with `ctags` each time HEAD changes 32 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | inherit_gem: 4 | rubocop-lts: rubocop-lts.yml 5 | 6 | require: 7 | # See: https://github.com/rubocop/rubocop-md/issues/14 8 | # - 'rubocop-md' 9 | - 'rubocop-minitest' 10 | - 'rubocop-packaging' 11 | - 'rubocop-performance' 12 | - 'rubocop-rake' 13 | - 'rubocop-thread_safety' 14 | 15 | AllCops: 16 | DisplayCopNames: true # Display the name of the failing cops 17 | 18 | Layout/DotPosition: 19 | Enabled: true 20 | EnforcedStyle: trailing 21 | Metrics/BlockLength: 22 | IgnoredMethods: 23 | - context 24 | - describe 25 | - it 26 | - shared_context 27 | - shared_examples 28 | - shared_examples_for 29 | Style/StringLiterals: 30 | Enabled: true 31 | EnforcedStyle: double_quotes 32 | Style/StringLiteralsInInterpolation: 33 | Enabled: true 34 | EnforcedStyle: double_quotes 35 | 36 | Lint/RaiseException: # (0.81) 37 | Enabled: true 38 | Lint/StructNewOverride: # (0.81) 39 | Enabled: true 40 | Style/HashEachMethods: # (0.80) 41 | Enabled: true 42 | Style/HashTransformKeys: # (0.80) 43 | Enabled: true 44 | Style/HashTransformValues: # (0.80) 45 | Enabled: true 46 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2022-08-23 21:37:33 UTC using RuboCop version 1.30.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Configuration parameters: AllowComments. 11 | Lint/EmptyWhen: 12 | Exclude: 13 | - 'lib/oauth/consumer.rb' 14 | 15 | # Offense count: 2 16 | # Configuration parameters: AllowKeywordBlockArguments. 17 | Lint/UnderscorePrefixedVariableName: 18 | Exclude: 19 | - 'lib/oauth/consumer.rb' 20 | 21 | # Offense count: 38 22 | # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. 23 | Metrics/AbcSize: 24 | Max: 62 25 | 26 | # Offense count: 8 27 | # Configuration parameters: CountComments, CountAsOne. 28 | Metrics/ClassLength: 29 | Max: 302 30 | 31 | # Offense count: 8 32 | # Configuration parameters: IgnoredMethods. 33 | Metrics/CyclomaticComplexity: 34 | Max: 18 35 | 36 | # Offense count: 66 37 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. 38 | Metrics/MethodLength: 39 | Max: 43 40 | 41 | # Offense count: 1 42 | # Configuration parameters: Max, CountKeywordArgs. 43 | Metrics/ParameterLists: 44 | MaxOptionalParameters: 4 45 | 46 | # Offense count: 7 47 | # Configuration parameters: IgnoredMethods. 48 | Metrics/PerceivedComplexity: 49 | Max: 20 50 | 51 | # Offense count: 39 52 | Minitest/MultipleAssertions: 53 | Max: 18 54 | 55 | # Offense count: 1 56 | # Configuration parameters: EnforcedStyleForLeadingUnderscores. 57 | # SupportedStylesForLeadingUnderscores: disallowed, required, optional 58 | Naming/MemoizedInstanceVariableName: 59 | Exclude: 60 | - 'lib/oauth/client/em_http.rb' 61 | 62 | # Offense count: 2 63 | Style/ClassVars: 64 | Exclude: 65 | - 'lib/oauth/consumer.rb' 66 | - 'lib/oauth/server.rb' 67 | 68 | # Offense count: 60 69 | # Configuration parameters: AllowedConstants. 70 | Style/Documentation: 71 | Enabled: false 72 | 73 | # Offense count: 2 74 | # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. 75 | Style/GuardClause: 76 | Exclude: 77 | - 'lib/oauth/consumer.rb' 78 | - 'lib/oauth/signature/base.rb' 79 | 80 | # Offense count: 2 81 | # This cop supports safe autocorrection (--autocorrect). 82 | Style/IfUnlessModifier: 83 | Exclude: 84 | - 'bin/bundle' 85 | - 'lib/oauth/request_proxy/net_http.rb' 86 | 87 | # Offense count: 2 88 | # This cop supports unsafe autocorrection (--autocorrect-all). 89 | # Configuration parameters: InverseMethods, InverseBlocks. 90 | Style/InverseMethods: 91 | Exclude: 92 | - 'lib/oauth/request_proxy/base.rb' 93 | 94 | # Offense count: 1 95 | ThreadSafety/ClassAndModuleAttributes: 96 | Exclude: 97 | - 'lib/oauth/client/action_controller_request.rb' 98 | 99 | # Offense count: 6 100 | ThreadSafety/InstanceVariableInClassMethod: 101 | Exclude: 102 | - 'lib/oauth/client/action_controller_request.rb' 103 | - 'lib/oauth/request_proxy.rb' 104 | - 'lib/oauth/signature.rb' 105 | - 'lib/oauth/signature/base.rb' 106 | 107 | # Offense count: 76 108 | # This cop supports safe autocorrection (--autocorrect). 109 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. 110 | # URISchemes: http, https 111 | # AllowedPatterns: (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#), (?-mix:^\#) 112 | # IgnoredPatterns: (?-mix:^\#) 113 | Layout/LineLength: 114 | Max: 429 115 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # To get coverage 4 | # On Local, default (HTML) output, it just works, coverage is turned on: 5 | # bundle exec rspec spec 6 | # On Local, all output formats: 7 | # COVER_ALL=true bundle exec rspec spec 8 | # 9 | # On CI, all output formats, the ENV variables CI is always set, 10 | # and COVER_ALL, and CI_CODECOV, are set in the coverage.yml workflow only, 11 | # so coverage only runs in that workflow, and outputs all formats. 12 | # 13 | 14 | if RUN_COVERAGE 15 | SimpleCov.start do 16 | enable_coverage :branch 17 | primary_coverage :branch 18 | add_filter "test" 19 | add_filter "lib/oauth/version.rb" 20 | track_files "**/*.rb" 21 | 22 | if ALL_FORMATTERS 23 | command_name "#{ENV.fetch("GITHUB_WORKFLOW", 24 | nil)} Job #{ENV.fetch("GITHUB_RUN_ID", nil)}:#{ENV.fetch("GITHUB_RUN_NUMBER", nil)}" 25 | else 26 | formatter SimpleCov::Formatter::HTMLFormatter 27 | end 28 | 29 | minimum_coverage(65) 30 | end 31 | else 32 | puts "Not running coverage on #{RUBY_ENGINE} #{RUBY_VERSION}" 33 | end 34 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | oauth.galtzo.com -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at peter.boling@gmail.com. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Bug reports and pull requests are welcome on GitLab at [https://gitlab.com/oauth-xx/oauth][🚎src-main]. This project is 4 | intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to 5 | the [code of conduct][🚎code-conduct]. 6 | 7 | Everyone interacting in the OAuth::TTY project's codebases, issue trackers, chat 8 | rooms and mailing lists is expected to follow the [code of conduct][🚎code-conduct]. 9 | 10 | To submit a patch, please fork the project and create a patch with 11 | tests. Once you're happy with it send a pull request and post a message to the 12 | [google group][mailinglist] or on the [gitter chat][🏘chat]. 13 | 14 | ## Create a Patch 15 | 16 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 17 | 18 | To install this gem onto your local machine, run `bundle exec rake install`. 19 | 20 | ## Run tests 21 | 22 | ### Against Rails 6 23 | 24 | ```bash 25 | BUNDLE_GEMFILE=gemfiles/a6.gemfile bundle install 26 | BUNDLE_GEMFILE=gemfiles/a6.gemfile bundle exec rake 27 | ``` 28 | 29 | ### Against Rails 7 30 | 31 | ```bash 32 | BUNDLE_GEMFILE=gemfiles/a7.gemfile bundle install 33 | BUNDLE_GEMFILE=gemfiles/a7.gemfile bundle exec rake 34 | ``` 35 | 36 | ## Release 37 | 38 | To release a new version: 39 | 40 | 1. update the version number in `version.rb` 41 | 2. run `bundle exec rake build:checksum` 42 | 3. move the built gem to project root 43 | 4. run `bin/checksum` to create the missing SHA256 checksum 44 | 5. move the built gem back to `pkg/` 45 | 6. run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 46 | 47 | NOTE: You will need to have a public key in `certs/`, and list your cert in the 48 | `gemspec`, in order to sign the new release. 49 | See: [RubyGems Security Guide][rubygems-security-guide] 50 | 51 | ## Contributors 52 | 53 | [![Contributors][🖐contributors-img]][🖐contributors] 54 | 55 | [comment]: <> (Following links are used by README, CONTRIBUTING, Homepage) 56 | 57 | [🚎code-conduct]: https://gitlab.com/oauth-xx/oauth/-/blob/main/CODE_OF_CONDUCT.md 58 | [🖐contributors]: https://gitlab.com/oauth-xx/oauth/-/graphs/main 59 | [🖐contributors-img]: https://img.shields.io/github/contributors-anon/oauth-xx/oauth-ruby 60 | [mailinglist]: http://groups.google.com/group/oauth-ruby 61 | [🚎src-main]: https://gitlab.com/oauth-xx/oauth/-/tree/main 62 | [🏘chat]: https://gitter.im/oauth-xx/oauth-ruby 63 | [rubygems-security-guide]: https://guides.rubygems.org/security/#building-gems 64 | [rubygems]: https://rubygems.org 65 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 8 | 9 | # Curb has trouble building native extentions on Windows platform 10 | curb = !Gem.win_platform? 11 | 12 | gem "pry", platforms: %i[mri] 13 | platforms :mri do 14 | gem "codecov", "~> 0.6" # For CodeCov 15 | gem "overcommit", "~> 0.67" 16 | # See: https://github.com/rubocop/rubocop-md/issues/14 17 | # gem "rubocop-md" 18 | gem "rubocop-minitest" 19 | gem "rubocop-packaging" 20 | gem "rubocop-performance" 21 | gem "rubocop-rake" 22 | gem "rubocop-thread_safety" 23 | gem "simplecov", "~> 0.21", require: false 24 | gem "simplecov-cobertura" # XML for Jenkins 25 | gem "simplecov-json" # For CodeClimate 26 | gem "simplecov-lcov", "~> 0.8", require: false 27 | 28 | # Add `byebug` to your code where you want to drop to REPL, and add DEBUG=true when running tests 29 | gem "byebug" 30 | # WebMock is known to work with Curb >= 0.7.16, < 1.1, except versions 0.8.7 31 | gem "curb", ">= 0.7.16", "!= 0.8.7", "< 1.1" if curb 32 | gem "pry-byebug" 33 | end 34 | 35 | ### deps for documentation and rdoc.info 36 | group :documentation do 37 | gem "github-markup", platform: :mri 38 | gem "redcarpet", platform: :mri 39 | gem "yard", require: false 40 | end 41 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | oauth (1.1.1) 5 | oauth-tty (~> 1.0, >= 1.0.1) 6 | snaky_hash (~> 2.0) 7 | version_gem (~> 1.1) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | addressable (2.8.7) 13 | public_suffix (>= 2.0.2, < 7.0) 14 | ast (2.4.3) 15 | bigdecimal (3.1.9) 16 | byebug (11.1.3) 17 | childprocess (5.1.0) 18 | logger (~> 1.5) 19 | codecov (0.6.0) 20 | simplecov (>= 0.15, < 0.22) 21 | coderay (1.1.3) 22 | cookiejar (0.3.3) 23 | crack (1.0.0) 24 | bigdecimal 25 | rexml 26 | curb (1.0.9) 27 | diff-lcs (1.5.1) 28 | diffy (3.4.2) 29 | docile (1.4.0) 30 | domain_name (0.5.20190701) 31 | unf (>= 0.0.5, < 1.0.0) 32 | em-http-request (1.1.7) 33 | addressable (>= 2.3.4) 34 | cookiejar (!= 0.3.1) 35 | em-socksify (>= 0.3) 36 | eventmachine (>= 1.0.3) 37 | http_parser.rb (>= 0.6.0) 38 | em-socksify (0.3.2) 39 | eventmachine (>= 1.0.0.beta.4) 40 | ethon (0.16.0) 41 | ffi (>= 1.15.0) 42 | eventmachine (1.2.7) 43 | ffi (1.17.2-x86_64-darwin) 44 | ffi (1.17.2-x86_64-linux-gnu) 45 | github-markup (4.0.2) 46 | hashdiff (1.1.2) 47 | hashie (5.0.0) 48 | http-accept (1.7.0) 49 | http-cookie (1.0.5) 50 | domain_name (~> 0.5) 51 | http_parser.rb (0.8.0) 52 | iconv (1.1.0) 53 | iniparse (1.5.0) 54 | json (2.12.2) 55 | language_server-protocol (3.17.0.5) 56 | lint_roller (1.1.0) 57 | logger (1.7.0) 58 | method_source (1.1.0) 59 | mime-types (3.4.1) 60 | mime-types-data (~> 3.2015) 61 | mime-types-data (3.2022.0105) 62 | minitest (5.15.0) 63 | mocha (2.7.1) 64 | ruby2_keywords (>= 0.0.5) 65 | netrc (0.11.0) 66 | oauth-tty (1.0.5) 67 | version_gem (~> 1.1, >= 1.1.1) 68 | overcommit (0.67.1) 69 | childprocess (>= 0.6.3, < 6) 70 | iniparse (~> 1.4) 71 | rexml (>= 3.3.9) 72 | parallel (1.27.0) 73 | parser (3.3.8.0) 74 | ast (~> 2.4.1) 75 | racc 76 | prism (1.4.0) 77 | pry (0.14.2) 78 | coderay (~> 1.1) 79 | method_source (~> 1.0) 80 | pry-byebug (3.10.1) 81 | byebug (~> 11.0) 82 | pry (>= 0.13, < 0.15) 83 | public_suffix (5.1.1) 84 | racc (1.8.1) 85 | rack (3.1.16) 86 | rack-test (2.2.0) 87 | rack (>= 1.3) 88 | rainbow (3.1.1) 89 | rake (13.3.0) 90 | redcarpet (3.6.0) 91 | regexp_parser (2.10.0) 92 | rest-client (2.1.0) 93 | http-accept (>= 1.7.0, < 2.0) 94 | http-cookie (>= 1.0.2, < 2.0) 95 | mime-types (>= 1.16, < 4.0) 96 | netrc (~> 0.8) 97 | rexml (3.4.1) 98 | rubocop (1.75.8) 99 | json (~> 2.3) 100 | language_server-protocol (~> 3.17.0.2) 101 | lint_roller (~> 1.1.0) 102 | parallel (~> 1.10) 103 | parser (>= 3.3.0.2) 104 | rainbow (>= 2.2.2, < 4.0) 105 | regexp_parser (>= 2.9.3, < 3.0) 106 | rubocop-ast (>= 1.44.0, < 2.0) 107 | ruby-progressbar (~> 1.7) 108 | unicode-display_width (>= 2.4.0, < 4.0) 109 | rubocop-ast (1.44.1) 110 | parser (>= 3.3.7.2) 111 | prism (~> 1.4) 112 | rubocop-gradual (0.3.6) 113 | diff-lcs (>= 1.2.0, < 2.0) 114 | diffy (~> 3.0) 115 | parallel (~> 1.10) 116 | rainbow (>= 2.2.2, < 4.0) 117 | rubocop (~> 1.0) 118 | rubocop-lts (18.2.1) 119 | rubocop-ruby2_7 (>= 2.0.4, < 3) 120 | standard-rubocop-lts (>= 1.0.3, < 3) 121 | version_gem (>= 1.1.2, < 3) 122 | rubocop-md (1.2.2) 123 | rubocop (>= 1.0) 124 | rubocop-minitest (0.38.1) 125 | lint_roller (~> 1.1) 126 | rubocop (>= 1.75.0, < 2.0) 127 | rubocop-ast (>= 1.38.0, < 2.0) 128 | rubocop-packaging (0.6.0) 129 | lint_roller (~> 1.1.0) 130 | rubocop (>= 1.72.1, < 2.0) 131 | rubocop-performance (1.21.1) 132 | rubocop (>= 1.48.1, < 2.0) 133 | rubocop-ast (>= 1.31.1, < 2.0) 134 | rubocop-rake (0.7.1) 135 | lint_roller (~> 1.1) 136 | rubocop (>= 1.72.1) 137 | rubocop-ruby2_7 (2.0.6) 138 | rubocop-gradual (~> 0.3, >= 0.3.1) 139 | rubocop-md (~> 1.2) 140 | rubocop-rake (~> 0.6) 141 | rubocop-shopify (~> 2.14) 142 | rubocop-thread_safety (~> 0.5, >= 0.5.1) 143 | standard-rubocop-lts (~> 1.0, >= 1.0.7) 144 | version_gem (>= 1.1.3, < 3) 145 | rubocop-shopify (2.15.1) 146 | rubocop (~> 1.51) 147 | rubocop-thread_safety (0.7.2) 148 | lint_roller (~> 1.1) 149 | rubocop (~> 1.72, >= 1.72.1) 150 | ruby-progressbar (1.13.0) 151 | ruby2_keywords (0.0.5) 152 | simplecov (0.21.2) 153 | docile (~> 1.1) 154 | simplecov-html (~> 0.11) 155 | simplecov_json_formatter (~> 0.1) 156 | simplecov-cobertura (2.1.0) 157 | rexml 158 | simplecov (~> 0.19) 159 | simplecov-html (0.12.3) 160 | simplecov-json (0.2.3) 161 | json 162 | simplecov 163 | simplecov-lcov (0.8.0) 164 | simplecov_json_formatter (0.1.4) 165 | snaky_hash (2.0.3) 166 | hashie (>= 0.1.0, < 6) 167 | version_gem (>= 1.1.8, < 3) 168 | standard (1.35.0.1) 169 | language_server-protocol (~> 3.17.0.2) 170 | lint_roller (~> 1.0) 171 | rubocop (~> 1.62) 172 | standard-custom (~> 1.0.0) 173 | standard-performance (~> 1.3) 174 | standard-custom (1.0.2) 175 | lint_roller (~> 1.0) 176 | rubocop (~> 1.50) 177 | standard-performance (1.4.0) 178 | lint_roller (~> 1.1) 179 | rubocop-performance (~> 1.21.0) 180 | standard-rubocop-lts (1.0.9) 181 | standard (>= 1.31.1, < 2) 182 | standard-custom (>= 1.0.1, < 2) 183 | standard-performance (>= 1.2, < 2) 184 | version_gem (>= 1.1.3, < 4) 185 | typhoeus (1.4.1) 186 | ethon (>= 0.9.0) 187 | unf (0.1.4) 188 | unf_ext 189 | unf_ext (0.0.8.2) 190 | unicode-display_width (3.1.4) 191 | unicode-emoji (~> 4.0, >= 4.0.4) 192 | unicode-emoji (4.0.4) 193 | version_gem (1.1.8) 194 | webmock (3.25.1) 195 | addressable (>= 2.8.0) 196 | crack (>= 0.3.2) 197 | hashdiff (>= 0.4.0, < 2.0.0) 198 | yard (0.9.37) 199 | 200 | PLATFORMS 201 | x86_64-darwin-21 202 | x86_64-linux 203 | 204 | DEPENDENCIES 205 | byebug 206 | codecov (~> 0.6) 207 | curb (>= 0.7.16, < 1.1, != 0.8.7) 208 | em-http-request (~> 1.1.7) 209 | github-markup 210 | iconv 211 | minitest (~> 5.15.0) 212 | mocha 213 | oauth! 214 | overcommit (~> 0.67) 215 | pry 216 | pry-byebug 217 | rack (~> 3.1) 218 | rack-test 219 | rake (~> 13.0) 220 | redcarpet 221 | rest-client 222 | rubocop-lts (~> 18.0) 223 | rubocop-minitest 224 | rubocop-packaging 225 | rubocop-performance 226 | rubocop-rake 227 | rubocop-thread_safety 228 | simplecov (~> 0.21) 229 | simplecov-cobertura 230 | simplecov-json 231 | simplecov-lcov (~> 0.8) 232 | typhoeus (>= 0.1.13) 233 | webmock (<= 3.26.0) 234 | yard 235 | 236 | BUNDLED WITH 237 | 2.3.22 238 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2007-2012, 2016-2017 Blaine Cook, Larry Halff, Pelle Braendgaard 4 | Copyright (c) 2020-2022 oauth-xx organization, https://gitlab.com/oauth-xx 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | %w[rake/testtask fileutils].each { |f| require f } 5 | 6 | Bundler::GemHelper.install_tasks 7 | 8 | Rake::TestTask.new do |t| 9 | t.libs << "test" 10 | t.test_files = FileList["test/**/*test*.rb"] 11 | t.verbose = true 12 | end 13 | 14 | begin 15 | require "rubocop/rake_task" 16 | RuboCop::RakeTask.new 17 | rescue LoadError 18 | task :rubocop do 19 | warn "RuboCop is disabled on Ruby #{RUBY_VERSION}" 20 | end 21 | end 22 | 23 | task default: %i[test rubocop] 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | EOL | Post-EOL / Enterprise | 6 | |---------|----------|---------|---------------------------------------| 7 | | 1.1.x | ✅ | 04/2023 | [Tidelift Subscription][tidelift-ref] | 8 | | 1.0.x | ✅ | 04/2023 | [Tidelift Subscription][tidelift-ref] | 9 | | 0.6.x | 🚨 | 04/2023 | [Tidelift Subscription][tidelift-ref] | 10 | | 0.5.x | 🚨 | 04/2023 | [Tidelift Subscription][tidelift-ref] | 11 | | <= 0.5 | ⛔ | ⛔ | ⛔ | 12 | 13 | LEGEND: 14 | ✅ - Supported 15 | 🚨 - Will only receive critical bug and security updates. 16 | ⛔ - No Support 17 | 18 | ### EOL Policy 19 | 20 | Non-commercial support for the oldest version of Ruby (which itself is going EOL) will be dropped each year in April. 21 | 22 | ## Reporting a Vulnerability 23 | 24 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 25 | Tidelift will coordinate the fix and disclosure. 26 | 27 | ## OAuth for Enterprise 28 | 29 | Available as part of the Tidelift Subscription. 30 | 31 | The maintainers of oauth and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. [Learn more.][tidelift-ref] 32 | 33 | [tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-oauth?utm_source=rubygems-oauth&utm_medium=referral&utm_campaign=enterprise&utm_term=repo 34 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Common use-cases should be streamlined: 2 | 3 | * I have a URL that I want to sign (given consumer key/secret, optional 4 | token/secret, optional nonce/timestamp). 5 | * I have a URL that I want to sign AND I want to see what the components 6 | (e.g. signature base string, etc.) are while it's being signed (i.e. verbose 7 | signing). 8 | * I have a URL that I want to sign and I only want the signature. 9 | * I have a URL that I want to sign and I want something suitable to put in 10 | {the header, the querystring, XMPP}. 11 | * I want to make a query to an OAuth-enabled web service (with sensible 12 | errors, if available). 13 | * I want to host an OAuth-enabled web service. 14 | * I want to test my OAuth-enabled web service (i.e. test helpers) 15 | 16 | Example applications for: 17 | * Ning 18 | * Fire Eagle 19 | * Google (blogger, contacts) 20 | * Twitter 21 | * YOS / YQL 22 | * Netflix 23 | 24 | In addition to providing best practices of use, these can also be part of 25 | the pre-release checks to make sure that there have been no regressions. 26 | 27 | Random TODOs: 28 | * finish CLI 29 | * sensible Exception hierarchy 30 | * Tokens as Modules 31 | * don't tie to Net::HTTP 32 | * Take a look at Curb HTTP Verbs -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV.fetch("BUNDLER_VERSION", nil) 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | 28 | bundler_version = nil 29 | update_index = nil 30 | ARGV.each_with_index do |a, i| 31 | bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 32 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/o 33 | 34 | bundler_version = Regexp.last_match(1) 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV.fetch("BUNDLE_GEMFILE", nil) 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | 59 | lockfile_contents = File.read(lockfile) 60 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/o 61 | 62 | Regexp.last_match(1) 63 | end 64 | 65 | def bundler_requirement 66 | @bundler_requirement ||= 67 | env_var_version || cli_arg_version || 68 | bundler_requirement_for(lockfile_version) 69 | end 70 | 71 | def bundler_requirement_for(version) 72 | return "#{Gem::Requirement.default}.a" unless version 73 | 74 | bundler_gem_version = Gem::Version.new(version) 75 | 76 | requirement = bundler_gem_version.approximate_recommendation 77 | 78 | return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") 79 | 80 | requirement += ".a" if bundler_gem_version.prerelease? 81 | 82 | requirement 83 | end 84 | 85 | def load_bundler! 86 | ENV["BUNDLE_GEMFILE"] ||= gemfile 87 | 88 | activate_bundler 89 | end 90 | 91 | def activate_bundler 92 | gem_error = activation_error_handling do 93 | gem "bundler", bundler_requirement 94 | end 95 | return if gem_error.nil? 96 | 97 | require_error = activation_error_handling do 98 | require "bundler/version" 99 | end 100 | if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 101 | return 102 | end 103 | 104 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 105 | exit 42 106 | end 107 | 108 | def activation_error_handling 109 | yield 110 | nil 111 | rescue StandardError, LoadError => e 112 | e 113 | end 114 | end 115 | 116 | m.load_bundler! 117 | 118 | load Gem.bin_path("bundler", "bundle") if m.invoked_as_script? 119 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../gemfiles/a7.gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("rake", "rake") 28 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("rubocop", "rubocop") 28 | -------------------------------------------------------------------------------- /certs/pboling.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEgDCCAuigAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMRUwEwYDVQQDDAxwZXRl 3 | ci5ib2xpbmcxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkW 4 | A2NvbTAeFw0yMjA5MTgyMzEyMzBaFw0yMzA5MTgyMzEyMzBaMEMxFTATBgNVBAMM 5 | DHBldGVyLmJvbGluZzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy 6 | LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2Dn1GM3W 7 | 8K2/rvN1zz+06bQMcxD16ZKTihVwi7Pb1v3T98rM4Omnxohm3s+CwpDWGeiB9pj6 8 | 0I/CTce0e4e3s8GKJSOrg93veImPSoH2PfsMsRsuB8wtqyiOCjLbF5o6S29x87r0 9 | LA5EawH+Lh4xqrkkPjdffsmLk7TaCig/vlmNvnzxXKBdey/X/aEJZXzzBiWRfVdh 10 | O1fmMbVKyieGv9HK7+pLotIoT08bjDv8NP6V7zZslwQRqW27bQc6cqC2LGIbTYO3 11 | 3jt1kQxfMWmhOictS6SzG9VtKSrXf0L4Neq0Gh7CLBZBvJFWJYZPfb92YNITDbd8 12 | emPOAQlXXNMN4mMXsEqtEhCPZRMnmwO+fOk/cC4AyglKi9lnQugCQoFV1XDMZST/ 13 | CYbzdQyadOdPDInTntG6V+Uw51d2QGXZ6PDDfrx9+toc/3sl5h68rCUGgE6Q3jPz 14 | srinqmBsxv2vTpmd4FjmiAtEnwH5/ooLpQYL8UdAjEoeysxS3AwIh+5dAgMBAAGj 15 | fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQWU6D156a2cle+ 16 | lb5RBfvVXlxTwjAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG 17 | A1UdEgQaMBiBFnBldGVyLmJvbGluZ0BnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD 18 | ggGBAJ4SqhPlgUiLYIrphGXIaxXScHyvx4kixuvdrwhI4VoQV2qXvO7R6ZjOXVwX 19 | f/z84BWPiTZ8lzThPbt1UV/BGwkvLw9I4RjOdzvUz3J42j9Ly6q63isall07bo3F 20 | QWe/OBvIMBF1IbjC3q5vKPg4rq8+TkNRJNoE86U2gfR+PkW3jYYs9uiy0GloHDCP 21 | k5xgaj0vSL0Uy5mTOPdk3K6a/sUGZyYniWK05zdhIi956ynhfGaFO988FFdVw5Jq 22 | LHtXfIpAU8F7ES04syZSslxOluw7VlcSKyRdVIr737J92ZTduppB4PRGSKRgBsWV 23 | hXTahRE72Kyw53Q7FAuzF3v102WxAAQ7BuMjW+MyCUT75fwPm3W4ELPL8HYkNGE7 24 | 2oA5CPghFitRnvYS3GNrDG+9bNiRMEskeaBYwZ9UgReBQIwGYVj7LZk3UhiAsn44 25 | gwGrEXGQGDZ0NIgBcmvMOqlXjkGQwQvugKycJ024z89+fz2332vdZIKTrSxJrXGk 26 | 4/bR9A== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /docs/images/logo/README.txt: -------------------------------------------------------------------------------- 1 | The OAuth 1.0 Logo - Oauth_logo.svg 2 | 3 | https://commons.wikimedia.org/wiki/File:Oauth_logo.svg 4 | 5 | Chris Messina, CC BY-SA 3.0, via Wikimedia Commons 6 | 7 | --consumer-secret 6 | 7 | require "oauth" 8 | require "optparse" 9 | require "json" 10 | require "pp" 11 | 12 | options = {} 13 | 14 | option_parser = OptionParser.new do |opts| 15 | opts.banner = "Usage: #{$PROGRAM_NAME} [options] " 16 | 17 | opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v| 18 | options[:consumer_key] = v 19 | end 20 | 21 | opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v| 22 | options[:consumer_secret] = v 23 | end 24 | end 25 | 26 | option_parser.parse! 27 | query = ARGV.pop 28 | query = $stdin.read if query == "-" 29 | 30 | if options[:consumer_key].nil? || options[:consumer_secret].nil? || query.nil? 31 | puts option_parser.help 32 | exit 1 33 | end 34 | 35 | consumer = OAuth::Consumer.new \ 36 | options[:consumer_key], 37 | options[:consumer_secret], 38 | site: "https://api.twitter.com" 39 | 40 | access_token = OAuth::AccessToken.new(consumer) 41 | 42 | response = access_token.request(:get, "/1.1/statuses/show/#{OAuth::Helper.escape(query)}.json") 43 | rsp = JSON.parse(response.body) 44 | pp rsp 45 | -------------------------------------------------------------------------------- /examples/yql.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -r rubygems 2 | # frozen_string_literal: true 3 | 4 | # Sample queries: 5 | # ./yql.rb --consumer-key --consumer-secret "show tables" 6 | # ./yql.rb --consumer-key --consumer-secret "select * from flickr.photos.search where text='Cat' limit 10" 7 | 8 | require "oauth" 9 | require "optparse" 10 | require "json" 11 | require "pp" 12 | 13 | options = {} 14 | 15 | option_parser = OptionParser.new do |opts| 16 | opts.banner = "Usage: #{$PROGRAM_NAME} [options] " 17 | 18 | opts.on("--consumer-key KEY", "Specifies the consumer key to use.") do |v| 19 | options[:consumer_key] = v 20 | end 21 | 22 | opts.on("--consumer-secret SECRET", "Specifies the consumer secret to use.") do |v| 23 | options[:consumer_secret] = v 24 | end 25 | end 26 | 27 | option_parser.parse! 28 | query = ARGV.pop 29 | query = $stdin.read if query == "-" 30 | 31 | if options[:consumer_key].nil? || options[:consumer_secret].nil? || query.nil? 32 | puts option_parser.help 33 | exit 1 34 | end 35 | 36 | consumer = OAuth::Consumer.new \ 37 | options[:consumer_key], 38 | options[:consumer_secret], 39 | site: "http://query.yahooapis.com" 40 | 41 | access_token = OAuth::AccessToken.new(consumer) 42 | 43 | response = access_token.request(:get, "/v1/yql?q=#{OAuth::Helper.escape(query)}&format=json") 44 | rsp = JSON.parse(response.body) 45 | pp rsp 46 | -------------------------------------------------------------------------------- /gemfiles/README.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | This is a ruby library which is intended to be used in creating Ruby Consumer 4 | and Service Provider applications. It is NOT a Rails plugin, but could easily 5 | be used for the foundation for such a Rails plugin. 6 | 7 | This gem was originally extracted from @pelle's [oauth-plugin](https://github.com/pelle/oauth-plugin) 8 | gem. After extraction that gem was made to depend on this gem. 9 | 10 | Unfortunately, this gem does have some Rails related bits that are 11 | **optional** to load. You don't need Rails! The Rails bits may be pulled out 12 | into a separate gem after the release of version 1.0 of this gem. 13 | 14 | These `gemfiles` help with testing this gem against various versions of Rails-ish-ness. 15 | 16 | ```ruby 17 | gem "actionpack", [">= 6", "< 8"] 18 | ``` 19 | 20 | # *.gemfile Naming 21 | 22 | In the naming of gemfiles, we will use the below shorthand for actionpack and version 23 | 24 | | Gem | Version | Gemfile | 25 | |------------|---------|------------| 26 | | actionpack | ~> 6.0 | a6.gemfile | 27 | | actionpack | ~> 7.0 | a7.gemfile | 28 | 29 | # References 30 | 31 | Compatibility Matrix for Ruby and Rails: 32 | * https://www.fastruby.io/blog/ruby/rails/versions/compatibility-table.html 33 | -------------------------------------------------------------------------------- /gemfiles/a6.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # See README.md in this directory 6 | 7 | gem "actionpack", "~> 6.0" 8 | 9 | eval_gemfile "../Gemfile" 10 | -------------------------------------------------------------------------------- /gemfiles/a7.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # See README.md in this directory 6 | 7 | gem "actionpack", "~> 7.0" 8 | 9 | eval_gemfile "../Gemfile" 10 | -------------------------------------------------------------------------------- /lib/oauth.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # third party gems 4 | require "snaky_hash" 5 | require "version_gem" 6 | 7 | require "oauth/version" 8 | 9 | require "oauth/oauth" 10 | 11 | require "oauth/client/helper" 12 | require "oauth/signature/plaintext" 13 | require "oauth/signature/hmac/sha1" 14 | require "oauth/signature/hmac/sha256" 15 | require "oauth/signature/rsa/sha1" 16 | require "oauth/request_proxy/mock_request" 17 | 18 | OAuth::Version.class_eval do 19 | extend VersionGem::Basic 20 | end 21 | -------------------------------------------------------------------------------- /lib/oauth/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | module Client 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/oauth/client/action_controller_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | if defined? ActionDispatch 4 | require "oauth/request_proxy/rack_request" 5 | require "oauth/request_proxy/action_dispatch_request" 6 | require "action_dispatch/testing/test_process" 7 | else 8 | require "oauth/request_proxy/action_controller_request" 9 | require "action_controller/test_process" 10 | end 11 | 12 | module ActionController 13 | class Base 14 | if defined? ActionDispatch 15 | def process_with_new_base_test(request, response = nil) 16 | request.apply_oauth! if request.respond_to?(:apply_oauth!) 17 | super(request, response) 18 | end 19 | else 20 | def process_with_oauth(request, response = nil) 21 | request.apply_oauth! if request.respond_to?(:apply_oauth!) 22 | process_without_oauth(request, response) 23 | end 24 | alias_method_chain :process, :oauth 25 | end 26 | end 27 | 28 | class TestRequest 29 | class << self 30 | attr_writer :use_oauth 31 | end 32 | 33 | def self.use_oauth? 34 | @use_oauth 35 | end 36 | 37 | def configure_oauth(consumer = nil, token = nil, options = {}) 38 | @oauth_options = { consumer: consumer, 39 | token: token, 40 | scheme: "header", 41 | signature_method: nil, 42 | nonce: nil, 43 | timestamp: nil }.merge(options) 44 | end 45 | 46 | def apply_oauth! 47 | return unless ActionController::TestRequest.use_oauth? && @oauth_options 48 | 49 | @oauth_helper = OAuth::Client::Helper.new(self, 50 | @oauth_options.merge(request_uri: (respond_to?(:fullpath) ? fullpath : request_uri))) 51 | @oauth_helper.amend_user_agent_header(env) 52 | 53 | send("set_oauth_#{@oauth_options[:scheme]}") 54 | end 55 | 56 | def set_oauth_header 57 | env["Authorization"] = @oauth_helper.header 58 | end 59 | 60 | def set_oauth_parameters 61 | @query_parameters = @oauth_helper.parameters_with_oauth 62 | @query_parameters.merge!(oauth_signature: @oauth_helper.signature) 63 | end 64 | 65 | def set_oauth_query_string; end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/oauth/client/em_http.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "em-http" 4 | require "oauth/helper" 5 | require "oauth/request_proxy/em_http_request" 6 | 7 | # Extensions for em-http so that we can use consumer.sign! with an EventMachine::HttpClient 8 | # instance. This is purely syntactic sugar. 9 | module EventMachine 10 | class HttpClient 11 | attr_reader :oauth_helper 12 | 13 | # Add the OAuth information to an HTTP request. Depending on the options[:scheme] setting 14 | # this may add a header, additional query string parameters, or additional POST body parameters. 15 | # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 16 | # header. 17 | # 18 | # * http - Configured Net::HTTP instance, ignored in this scenario except for getting host. 19 | # * consumer - OAuth::Consumer instance 20 | # * token - OAuth::Token instance 21 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 22 | # +signature_method+, +nonce+, +timestamp+) 23 | # 24 | # This method also modifies the User-Agent header to add the OAuth gem version. 25 | # 26 | # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1] 27 | def oauth!(http, consumer = nil, token = nil, options = {}) 28 | options = { request_uri: normalized_oauth_uri(http), 29 | consumer: consumer, 30 | token: token, 31 | scheme: "header", 32 | signature_method: nil, 33 | nonce: nil, 34 | timestamp: nil }.merge(options) 35 | 36 | @oauth_helper = OAuth::Client::Helper.new(self, options) 37 | __send__(:"set_oauth_#{options[:scheme]}") 38 | end 39 | 40 | # Create a string suitable for signing for an HTTP request. This process involves parameter 41 | # normalization as specified in the OAuth specification. The exact normalization also depends 42 | # on the options[:scheme] being used so this must match what will be used for the request 43 | # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 44 | # header. 45 | # 46 | # * http - Configured Net::HTTP instance 47 | # * consumer - OAuth::Consumer instance 48 | # * token - OAuth::Token instance 49 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 50 | # +signature_method+, +nonce+, +timestamp+) 51 | # 52 | # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] 53 | def signature_base_string(http, consumer = nil, token = nil, options = {}) 54 | options = { request_uri: normalized_oauth_uri(http), 55 | consumer: consumer, 56 | token: token, 57 | scheme: "header", 58 | signature_method: nil, 59 | nonce: nil, 60 | timestamp: nil }.merge(options) 61 | 62 | OAuth::Client::Helper.new(self, options).signature_base_string 63 | end 64 | 65 | # This code was lifted from the em-http-request because it was removed from 66 | # the gem June 19, 2010 67 | # see: http://github.com/igrigorik/em-http-request/commit/d536fc17d56dbe55c487eab01e2ff9382a62598b 68 | def normalize_uri 69 | @normalized_uri ||= begin 70 | uri = @conn.dup 71 | encoded_query = encode_query(@conn, @req[:query]) 72 | path, query = encoded_query.split("?", 2) 73 | uri.query = query unless encoded_query.empty? 74 | uri.path = path 75 | uri 76 | end 77 | end 78 | 79 | protected 80 | 81 | def combine_query(path, query, uri_query) 82 | combined_query = if query.is_a?(Hash) 83 | query.map { |k, v| encode_param(k, v) }.join("&") 84 | else 85 | query.to_s 86 | end 87 | combined_query = [combined_query, uri_query].reject(&:empty?).join("&") unless uri_query.to_s.empty? 88 | combined_query.to_s.empty? ? path : "#{path}?#{combined_query}" 89 | end 90 | 91 | # Since we expect to get the host etc details from the http instance (...), 92 | # we create a fake url here. Surely this is a horrible, horrible idea? 93 | def normalized_oauth_uri(http) 94 | uri = URI.parse(normalize_uri.path) 95 | uri.host = http.address 96 | uri.port = http.port 97 | 98 | uri.scheme = if http.respond_to?(:use_ssl?) && http.use_ssl? 99 | "https" 100 | else 101 | "http" 102 | end 103 | uri.to_s 104 | end 105 | 106 | def set_oauth_header 107 | req[:head] ||= {} 108 | req[:head].merge!("Authorization" => @oauth_helper.header) 109 | end 110 | 111 | def set_oauth_body 112 | raise NotImplementedError, "please use the set_oauth_header method instead" 113 | end 114 | 115 | def set_oauth_query_string 116 | raise NotImplementedError, "please use the set_oauth_header method instead" 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/oauth/client/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/client" 4 | require "oauth/consumer" 5 | require "oauth/helper" 6 | require "oauth/token" 7 | require "oauth/signature/hmac/sha1" 8 | 9 | module OAuth 10 | module Client 11 | class Helper 12 | include OAuth::Helper 13 | 14 | def initialize(request, options = {}) 15 | @request = request 16 | @options = options 17 | @options[:signature_method] ||= "HMAC-SHA1" 18 | end 19 | 20 | attr_reader :options 21 | 22 | def nonce 23 | options[:nonce] ||= generate_key 24 | end 25 | 26 | def timestamp 27 | options[:timestamp] ||= generate_timestamp 28 | end 29 | 30 | def oauth_parameters 31 | out = { 32 | "oauth_body_hash" => options[:body_hash], 33 | "oauth_callback" => options[:oauth_callback], 34 | "oauth_consumer_key" => options[:consumer].key, 35 | "oauth_token" => options[:token] ? options[:token].token : "", 36 | "oauth_signature_method" => options[:signature_method], 37 | "oauth_timestamp" => timestamp, 38 | "oauth_nonce" => nonce, 39 | "oauth_verifier" => options[:oauth_verifier], 40 | "oauth_version" => (options[:oauth_version] || "1.0"), 41 | "oauth_session_handle" => options[:oauth_session_handle] 42 | } 43 | allowed_empty_params = options[:allow_empty_params] 44 | if allowed_empty_params != true && !allowed_empty_params.is_a?(Array) 45 | allowed_empty_params = allowed_empty_params == false ? [] : [allowed_empty_params] 46 | end 47 | out.select! { |k, v| v.to_s != "" || allowed_empty_params == true || allowed_empty_params.include?(k) } 48 | out 49 | end 50 | 51 | def signature(extra_options = {}) 52 | OAuth::Signature.sign(@request, { uri: options[:request_uri], 53 | consumer: options[:consumer], 54 | token: options[:token], 55 | unsigned_parameters: options[:unsigned_parameters] }.merge(extra_options)) 56 | end 57 | 58 | def signature_base_string(extra_options = {}) 59 | OAuth::Signature.signature_base_string(@request, { uri: options[:request_uri], 60 | consumer: options[:consumer], 61 | token: options[:token], 62 | parameters: oauth_parameters }.merge(extra_options)) 63 | end 64 | 65 | def token_request? 66 | @options[:token_request].eql?(true) 67 | end 68 | 69 | def hash_body 70 | @options[:body_hash] = OAuth::Signature.body_hash(@request, parameters: oauth_parameters) 71 | end 72 | 73 | def amend_user_agent_header(headers) 74 | @oauth_ua_string ||= "OAuth gem v#{OAuth::Version::VERSION}" 75 | # Net::HTTP in 1.9 appends Ruby 76 | if headers["User-Agent"] && headers["User-Agent"] != "Ruby" 77 | headers["User-Agent"] += " (#{@oauth_ua_string})" 78 | else 79 | headers["User-Agent"] = @oauth_ua_string 80 | end 81 | end 82 | 83 | def header 84 | parameters = oauth_parameters 85 | parameters["oauth_signature"] = signature(options.merge(parameters: parameters)) 86 | 87 | header_params_str = parameters.sort.map { |k, v| "#{k}=\"#{escape(v)}\"" }.join(", ") 88 | 89 | realm = "realm=\"#{options[:realm]}\", " if options[:realm] 90 | "OAuth #{realm}#{header_params_str}" 91 | end 92 | 93 | def parameters 94 | OAuth::RequestProxy.proxy(@request).parameters 95 | end 96 | 97 | def parameters_with_oauth 98 | oauth_parameters.merge(parameters) 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/oauth/client/net_http.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/helper" 4 | require "oauth/request_proxy/net_http" 5 | 6 | module Net 7 | class HTTPGenericRequest 8 | include OAuth::Helper 9 | 10 | attr_reader :oauth_helper 11 | 12 | # Add the OAuth information to an HTTP request. Depending on the options[:scheme] setting 13 | # this may add a header, additional query string parameters, or additional POST body parameters. 14 | # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 15 | # header. 16 | # 17 | # * http - Configured Net::HTTP instance 18 | # * consumer - OAuth::Consumer instance 19 | # * token - OAuth::Token instance 20 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 21 | # +signature_method+, +nonce+, +timestamp+, +body_hash+) 22 | # 23 | # This method also modifies the User-Agent header to add the OAuth gem version. 24 | # 25 | # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1], 26 | # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html, 27 | # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#when_to_include] 28 | def oauth!(http, consumer = nil, token = nil, options = {}) 29 | helper_options = oauth_helper_options(http, consumer, token, options) 30 | @oauth_helper = OAuth::Client::Helper.new(self, helper_options) 31 | @oauth_helper.amend_user_agent_header(self) 32 | @oauth_helper.hash_body if oauth_body_hash_required?(helper_options) 33 | send("set_oauth_#{helper_options[:scheme]}") 34 | end 35 | 36 | # Create a string suitable for signing for an HTTP request. This process involves parameter 37 | # normalization as specified in the OAuth specification. The exact normalization also depends 38 | # on the options[:scheme] being used so this must match what will be used for the request 39 | # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+ 40 | # header. 41 | # 42 | # * http - Configured Net::HTTP instance 43 | # * consumer - OAuth::Consumer instance 44 | # * token - OAuth::Token instance 45 | # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+, 46 | # +signature_method+, +nonce+, +timestamp+) 47 | # 48 | # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1], 49 | # {OAuth Request Body Hash 1.0 Draft 4}[http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/drafts/4/spec.html, 50 | # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html#when_to_include] 51 | def signature_base_string(http, consumer = nil, token = nil, options = {}) 52 | helper_options = oauth_helper_options(http, consumer, token, options) 53 | @oauth_helper = OAuth::Client::Helper.new(self, helper_options) 54 | @oauth_helper.hash_body if oauth_body_hash_required?(helper_options) 55 | @oauth_helper.signature_base_string 56 | end 57 | 58 | private 59 | 60 | def oauth_helper_options(http, consumer, token, options) 61 | { request_uri: oauth_full_request_uri(http, options), 62 | consumer: consumer, 63 | token: token, 64 | scheme: "header", 65 | signature_method: nil, 66 | nonce: nil, 67 | timestamp: nil, 68 | body_hash_enabled: true }.merge(options) 69 | end 70 | 71 | def oauth_full_request_uri(http, options) 72 | uri = URI.parse(path) 73 | uri.host = http.address 74 | uri.port = http.port 75 | 76 | if options[:request_endpoint] && options[:site] 77 | is_https = options[:site].match(%r{^https://}) 78 | uri.host = options[:site].gsub(%r{^https?://}, "") 79 | uri.port ||= is_https ? 443 : 80 80 | end 81 | 82 | uri.scheme = if http.respond_to?(:use_ssl?) && http.use_ssl? 83 | "https" 84 | else 85 | "http" 86 | end 87 | 88 | uri.to_s 89 | end 90 | 91 | def oauth_body_hash_required?(options) 92 | !@oauth_helper.token_request? && request_body_permitted? && !content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded") && options[:body_hash_enabled] 93 | end 94 | 95 | def set_oauth_header 96 | self["Authorization"] = @oauth_helper.header 97 | end 98 | 99 | # FIXME: if you're using a POST body and query string parameters, this method 100 | # will move query string parameters into the body unexpectedly. This may 101 | # cause problems with non-x-www-form-urlencoded bodies submitted to URLs 102 | # containing query string params. If duplicate parameters are present in both 103 | # places, all instances should be included when calculating the signature 104 | # base string. 105 | 106 | def set_oauth_body 107 | # NOTE: OAuth::Helper and @oauth_helper are not the same, despite sharing all methods defined in OAuth::Helper 108 | # see: https://stackoverflow.com/a/53447775/213191 109 | set_form_data(OAuth::Helper.stringify_keys(@oauth_helper.parameters_with_oauth)) 110 | params_with_sig = @oauth_helper.parameters.merge(oauth_signature: @oauth_helper.signature) 111 | set_form_data(OAuth::Helper.stringify_keys(params_with_sig)) 112 | end 113 | 114 | def set_oauth_query_string 115 | oauth_params_str = @oauth_helper.oauth_parameters.map { |k, v| [escape(k), escape(v)].join("=") }.join("&") 116 | uri = URI.parse(path) 117 | uri.query = if uri.query.to_s == "" 118 | oauth_params_str 119 | else 120 | "#{uri.query}&#{oauth_params_str}" 121 | end 122 | 123 | @path = uri.to_s 124 | 125 | @path << "&oauth_signature=#{escape(oauth_helper.signature)}" 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/oauth/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/errors/error" 4 | require "oauth/errors/unauthorized" 5 | require "oauth/errors/problem" 6 | -------------------------------------------------------------------------------- /lib/oauth/errors/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | class Error < StandardError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/oauth/errors/problem.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | class Problem < OAuth::Unauthorized 5 | attr_reader :problem, :params 6 | 7 | def initialize(problem, request = nil, params = {}) 8 | super(request) 9 | @problem = problem 10 | @params = params 11 | end 12 | 13 | def to_s 14 | problem 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/oauth/errors/unauthorized.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | class Unauthorized < OAuth::Error 5 | attr_reader :request 6 | 7 | def initialize(request = nil) 8 | super() 9 | @request = request 10 | end 11 | 12 | def to_s 13 | return "401 Unauthorized" if request.nil? 14 | 15 | "#{request.code} #{request.message}" 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/oauth/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "time" 4 | require "openssl" 5 | require "base64" 6 | 7 | module OAuth 8 | module Helper 9 | module_function 10 | 11 | # Escape +value+ by URL encoding all non-reserved character. 12 | # 13 | # See Also: {OAuth core spec version 1.0, section 5.1}[http://oauth.net/core/1.0#rfc.section.5.1] 14 | def escape(value) 15 | _escape(value.to_s.to_str) 16 | rescue ArgumentError 17 | _escape(value.to_s.to_str.force_encoding(Encoding::UTF_8)) 18 | end 19 | 20 | def _escape(string) 21 | URI::DEFAULT_PARSER.escape(string, OAuth::RESERVED_CHARACTERS) 22 | end 23 | 24 | def unescape(value) 25 | URI::DEFAULT_PARSER.unescape(value.gsub("+", "%2B")) 26 | end 27 | 28 | # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word 29 | # characters removed. 30 | def generate_key(size = 32) 31 | Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, "") 32 | end 33 | 34 | alias generate_nonce generate_key 35 | 36 | def generate_timestamp # :nodoc: 37 | Time.now.to_i.to_s 38 | end 39 | 40 | # Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical 41 | # byte value ordering. If two or more parameters share the same name, they are sorted by their value. 42 | # Parameters are concatenated in their sorted order into a single string. For each parameter, the name 43 | # is separated from the corresponding value by an "=" character, even if the value is empty. Each 44 | # name-value pair is separated by an "&" character. 45 | # 46 | # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] 47 | def normalize(params) 48 | params.sort.map do |k, values| 49 | case values 50 | when Array 51 | # make sure the array has an element so we don't lose the key 52 | values << nil if values.empty? 53 | # multiple values were provided for a single key 54 | if values[0].is_a?(Hash) 55 | normalize_nested_query(values, k) 56 | else 57 | values.sort.collect do |v| 58 | [escape(k), escape(v)].join("=") 59 | end 60 | end 61 | when Hash 62 | normalize_nested_query(values, k) 63 | else 64 | [escape(k), escape(values)].join("=") 65 | end 66 | end * "&" 67 | end 68 | 69 | # Returns a string representation of the Hash like in URL query string 70 | # build_nested_query({:level_1 => {:level_2 => ['value_1','value_2']}}, 'prefix')) 71 | # #=> ["prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_1", "prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_2"] 72 | def normalize_nested_query(value, prefix = nil) 73 | case value 74 | when Array 75 | value.map do |v| 76 | normalize_nested_query(v, "#{prefix}[]") 77 | end.flatten.sort 78 | when Hash 79 | value.map do |k, v| 80 | normalize_nested_query(v, prefix ? "#{prefix}[#{k}]" : k) 81 | end.flatten.sort 82 | else 83 | [escape(prefix), escape(value)].join("=") 84 | end 85 | end 86 | 87 | # Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and 88 | # removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a 89 | # valid hash. Does not validate the keys or values. 90 | # 91 | # hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate']) 92 | # hash['oauth_timestamp'] 93 | # #=>"1234567890" 94 | # 95 | def parse_header(header) 96 | # decompose 97 | params = header[6, header.length].split(/[,=&]/) 98 | 99 | # odd number of arguments - must be a malformed header. 100 | raise OAuth::Problem, "Invalid authorization header" if params.size.odd? 101 | 102 | params.map! do |v| 103 | # strip and unescape 104 | val = unescape(v.strip) 105 | # strip quotes 106 | val.sub(/^"(.*)"$/, '\1') 107 | end 108 | 109 | # convert into a Hash 110 | Hash[*params.flatten] 111 | end 112 | 113 | def stringify_keys(hash) 114 | new_h = {} 115 | hash.each do |k, v| 116 | new_h[k.to_s] = v.is_a?(Hash) ? stringify_keys(v) : v 117 | end 118 | new_h 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/oauth/oauth.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | # request tokens are passed between the consumer and the provider out of 5 | # band (i.e. callbacks cannot be used), per section 6.1.1 6 | OUT_OF_BAND = "oob" 7 | 8 | # required parameters, per sections 6.1.1, 6.3.1, and 7 9 | PARAMETERS = %w[oauth_callback oauth_consumer_key oauth_token 10 | oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier 11 | oauth_version oauth_signature oauth_body_hash].freeze 12 | 13 | # reserved character regexp, per section 5.1 14 | RESERVED_CHARACTERS = /[^a-zA-Z0-9\-._~]/.freeze 15 | end 16 | -------------------------------------------------------------------------------- /lib/oauth/oauth_test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "action_controller" 4 | require "action_controller/test_process" 5 | 6 | module OAuth 7 | module OAuthTestHelper 8 | def mock_incoming_request_with_query(request) 9 | incoming = ActionController::TestRequest.new(request.to_hash) 10 | incoming.request_uri = request.path 11 | incoming.host = request.uri.host 12 | incoming.env["SERVER_PORT"] = request.uri.port 13 | incoming.env["REQUEST_METHOD"] = request.http_method 14 | incoming 15 | end 16 | 17 | def mock_incoming_request_with_authorize_header(request) 18 | incoming = ActionController::TestRequest.new 19 | incoming.request_uri = request.path 20 | incoming.host = request.uri.host 21 | incoming.env["HTTP_AUTHORIZATION"] = request.to_auth_string 22 | incoming.env["SERVER_PORT"] = request.uri.port 23 | incoming.env["REQUEST_METHOD"] = request.http_method 24 | incoming 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | module RequestProxy 5 | def self.available_proxies # :nodoc: 6 | @available_proxies ||= {} 7 | end 8 | 9 | def self.proxy(request, options = {}) 10 | return request if request.is_a?(OAuth::RequestProxy::Base) 11 | 12 | klass = available_proxies[request.class] 13 | 14 | # Search for possible superclass matches. 15 | if klass.nil? 16 | request_parent = available_proxies.keys.find { |rc| request.is_a?(rc) } 17 | klass = available_proxies[request_parent] 18 | end 19 | 20 | raise UnknownRequestType, request.class.to_s unless klass 21 | 22 | klass.new(request, options) 23 | end 24 | 25 | class UnknownRequestType < RuntimeError; end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/action_controller_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support" 4 | require "action_controller" 5 | require "uri" 6 | 7 | require "action_dispatch/http/request" 8 | 9 | module OAuth 10 | module RequestProxy 11 | class ActionControllerRequest < OAuth::RequestProxy::Base 12 | proxies(::ActionDispatch::Request) 13 | 14 | def method 15 | request.method.to_s.upcase 16 | end 17 | 18 | def uri 19 | request.url 20 | end 21 | 22 | def parameters 23 | if options[:clobber_request] 24 | options[:parameters] || {} 25 | else 26 | params = request_params.merge(query_params).merge(header_params) 27 | params.stringify_keys! if params.respond_to?(:stringify_keys!) 28 | params.merge(options[:parameters] || {}) 29 | end 30 | end 31 | 32 | # Override from OAuth::RequestProxy::Base to avoid round-trip 33 | # conversion to Hash or Array and thus preserve the original 34 | # parameter names 35 | def parameters_for_signature 36 | params = [] 37 | params << options[:parameters].to_query if options[:parameters] 38 | 39 | unless options[:clobber_request] 40 | params << header_params.to_query 41 | params << request.query_string unless query_string_blank? 42 | 43 | params << request.raw_post if raw_post_signature? 44 | end 45 | 46 | params. 47 | join("&").split("&"). 48 | reject { |s| s.match(/\A\s*\z/) }. 49 | map { |p| p.split("=").map { |esc| CGI.unescape(esc) } }. 50 | reject { |kv| kv[0] == "oauth_signature" } 51 | end 52 | 53 | def raw_post_signature? 54 | (request.post? || request.put?) && request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded") 55 | end 56 | 57 | protected 58 | 59 | def query_params 60 | request.query_parameters 61 | end 62 | 63 | def request_params 64 | request.request_parameters 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/action_dispatch_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/rack_request" 4 | 5 | module OAuth 6 | module RequestProxy 7 | class ActionDispatchRequest < OAuth::RequestProxy::RackRequest 8 | proxies ::ActionDispatch::Request 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy" 4 | require "oauth/helper" 5 | 6 | module OAuth 7 | module RequestProxy 8 | class Base 9 | include OAuth::Helper 10 | 11 | def self.proxies(klass) 12 | OAuth::RequestProxy.available_proxies[klass] = self 13 | end 14 | 15 | attr_accessor :request, :options, :unsigned_parameters 16 | 17 | def initialize(request, options = {}) 18 | @request = request 19 | @unsigned_parameters = (options[:unsigned_parameters] || []).map(&:to_s) 20 | @options = options 21 | end 22 | 23 | ## OAuth parameters 24 | 25 | def oauth_callback 26 | parameters["oauth_callback"] 27 | end 28 | 29 | def oauth_consumer_key 30 | parameters["oauth_consumer_key"] 31 | end 32 | 33 | def oauth_nonce 34 | parameters["oauth_nonce"] 35 | end 36 | 37 | def oauth_signature 38 | # TODO: can this be nil? 39 | [parameters["oauth_signature"]].flatten.first || "" 40 | end 41 | 42 | def oauth_signature_method 43 | case parameters["oauth_signature_method"] 44 | when Array 45 | parameters["oauth_signature_method"].first 46 | else 47 | parameters["oauth_signature_method"] 48 | end 49 | end 50 | 51 | def oauth_timestamp 52 | parameters["oauth_timestamp"] 53 | end 54 | 55 | def oauth_token 56 | parameters["oauth_token"] 57 | end 58 | 59 | def oauth_verifier 60 | parameters["oauth_verifier"] 61 | end 62 | 63 | def oauth_version 64 | parameters["oauth_version"] 65 | end 66 | 67 | # TODO: deprecate these 68 | alias consumer_key oauth_consumer_key 69 | alias token oauth_token 70 | alias nonce oauth_nonce 71 | alias timestamp oauth_timestamp 72 | alias signature oauth_signature 73 | alias signature_method oauth_signature_method 74 | 75 | ## Parameter accessors 76 | 77 | def parameters 78 | raise NotImplementedError, "Must be implemented by subclasses" 79 | end 80 | 81 | def parameters_for_signature 82 | parameters.select { |k, _v| !signature_and_unsigned_parameters.include?(k) } 83 | end 84 | 85 | def oauth_parameters 86 | parameters.select { |k, v| OAuth::PARAMETERS.include?(k) && !v.nil? && v != "" } 87 | end 88 | 89 | def non_oauth_parameters 90 | parameters.select { |k, _v| !OAuth::PARAMETERS.include?(k) } 91 | end 92 | 93 | def signature_and_unsigned_parameters 94 | unsigned_parameters + ["oauth_signature"] 95 | end 96 | 97 | # See 9.1.2 in specs 98 | def normalized_uri 99 | u = URI.parse(uri) 100 | "#{u.scheme.downcase}://#{u.host.downcase}#{(u.scheme.casecmp("http").zero? && u.port != 80) || (u.scheme.casecmp("https").zero? && u.port != 443) ? ":#{u.port}" : ""}#{u.path && u.path != "" ? u.path : "/"}" 101 | end 102 | 103 | # See 9.1.1. in specs Normalize Request Parameters 104 | def normalized_parameters 105 | normalize(parameters_for_signature) 106 | end 107 | 108 | def sign(options = {}) 109 | OAuth::Signature.sign(self, options) 110 | end 111 | 112 | def sign!(options = {}) 113 | parameters["oauth_signature"] = sign(options) 114 | @signed = true 115 | signature 116 | end 117 | 118 | # See 9.1 in specs 119 | def signature_base_string 120 | base = [method, normalized_uri, normalized_parameters] 121 | base.map { |v| escape(v) }.join("&") 122 | end 123 | 124 | # Has this request been signed yet? 125 | def signed? 126 | @signed 127 | end 128 | 129 | # URI, including OAuth parameters 130 | def signed_uri(with_oauth: true) 131 | if signed? 132 | params = if with_oauth 133 | parameters 134 | else 135 | non_oauth_parameters 136 | end 137 | 138 | [uri, normalize(params)].join("?") 139 | else 140 | warn "This request has not yet been signed!" 141 | end 142 | end 143 | 144 | # Authorization header for OAuth 145 | def oauth_header(options = {}) 146 | header_params_str = oauth_parameters.map { |k, v| "#{k}=\"#{escape(v)}\"" }.join(", ") 147 | 148 | realm = "realm=\"#{options[:realm]}\", " if options[:realm] 149 | "OAuth #{realm}#{header_params_str}" 150 | end 151 | 152 | def query_string_blank? 153 | if (uri = request.env["REQUEST_URI"]) 154 | uri.split("?", 2)[1].nil? 155 | else 156 | request.query_string.match(/\A\s*\z/) 157 | end 158 | end 159 | 160 | protected 161 | 162 | def header_params 163 | %w[X-HTTP_AUTHORIZATION Authorization HTTP_AUTHORIZATION].each do |header| 164 | next unless request.env.include?(header) 165 | 166 | header = request.env[header] 167 | next unless header[0, 6] == "OAuth " 168 | 169 | # parse the header into a Hash 170 | oauth_params = OAuth::Helper.parse_header(header) 171 | 172 | # remove non-OAuth parameters 173 | oauth_params.select! { |k, _v| k =~ /^oauth_/ } 174 | 175 | return oauth_params 176 | end 177 | 178 | {} 179 | end 180 | end 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/curb_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | require "curb" 5 | require "uri" 6 | require "cgi" 7 | 8 | module OAuth 9 | module RequestProxy 10 | module Curl 11 | class Easy < OAuth::RequestProxy::Base 12 | # Proxy for signing Curl::Easy requests 13 | # Usage example: 14 | # oauth_params = {:consumer => oauth_consumer, :token => access_token} 15 | # req = Curl::Easy.new(uri) 16 | # oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri)) 17 | # req.headers.merge!({"Authorization" => oauth_helper.header}) 18 | # req.http_get 19 | # response = req.body_str 20 | proxies ::Curl::Easy 21 | 22 | def method 23 | nil 24 | end 25 | 26 | def uri 27 | options[:uri].to_s 28 | end 29 | 30 | def parameters 31 | if options[:clobber_request] 32 | options[:parameters] 33 | else 34 | post_parameters.merge(query_parameters).merge(options[:parameters] || {}) 35 | end 36 | end 37 | 38 | private 39 | 40 | def query_parameters 41 | query = URI.parse(request.url).query 42 | (query ? CGI.parse(query) : {}) 43 | end 44 | 45 | def post_parameters 46 | post_body = {} 47 | 48 | # Post params are only used if posting form data 49 | if request.headers["Content-Type"] && request.headers["Content-Type"].to_s.downcase.start_with?("application/x-www-form-urlencoded") 50 | 51 | request.post_body.split("&").each do |str| 52 | param = str.split("=") 53 | post_body[param[0]] = param[1] 54 | end 55 | end 56 | post_body 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/em_http_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | # em-http also uses adddressable so there is no need to require uri. 5 | require "em-http" 6 | require "cgi" 7 | 8 | module OAuth 9 | module RequestProxy 10 | module EventMachine 11 | class HttpRequest < OAuth::RequestProxy::Base 12 | # A Proxy for use when you need to sign EventMachine::HttpClient instances. 13 | # It needs to be called once the client is construct but before data is sent. 14 | # Also see oauth/client/em-http 15 | proxies ::EventMachine::HttpClient 16 | 17 | # Request in this con 18 | 19 | def method 20 | request.req[:method] 21 | end 22 | 23 | def uri 24 | request.conn.normalize.to_s 25 | end 26 | 27 | def parameters 28 | if options[:clobber_request] 29 | options[:parameters] 30 | else 31 | all_parameters 32 | end 33 | end 34 | 35 | protected 36 | 37 | def all_parameters 38 | merged_parameters({}, post_parameters, query_parameters, options[:parameters]) 39 | end 40 | 41 | def query_parameters 42 | quer = request.req[:query] 43 | hash_quer = if quer.respond_to?(:merge) 44 | quer 45 | else 46 | CGI.parse(quer.to_s) 47 | end 48 | CGI.parse(request.conn.query.to_s).merge(hash_quer) 49 | end 50 | 51 | def post_parameters 52 | headers = request.req[:head] || {} 53 | form_encoded = headers["Content-Type"].to_s.downcase.start_with?("application/x-www-form-urlencoded") 54 | if %w[POST PUT].include?(method) && form_encoded 55 | CGI.parse(request.normalize_body(request.req[:body]).to_s) 56 | else 57 | {} 58 | end 59 | end 60 | 61 | def merged_parameters(params, *extra_params) 62 | extra_params.compact.each do |params_pairs| 63 | params_pairs.each_pair do |key, value| 64 | if params.key?(key) 65 | params[key.to_s] += value 66 | else 67 | params[key.to_s] = [value].flatten 68 | end 69 | end 70 | end 71 | params 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/jabber_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "xmpp4r" 4 | require "oauth/request_proxy/base" 5 | 6 | module OAuth 7 | module RequestProxy 8 | class JabberRequest < OAuth::RequestProxy::Base 9 | proxies ::Jabber::Iq 10 | proxies ::Jabber::Presence 11 | proxies ::Jabber::Message 12 | 13 | def parameters 14 | return @params if @params 15 | 16 | @params = {} 17 | 18 | oauth = @request.get_elements("//oauth").first 19 | return @params unless oauth 20 | 21 | %w[ oauth_token oauth_consumer_key oauth_signature_method oauth_signature 22 | oauth_timestamp oauth_nonce oauth_version ].each do |param| 23 | next unless (element = oauth.first_element(param)) 24 | 25 | @params[param] = element.text 26 | end 27 | 28 | @params 29 | end 30 | 31 | def method 32 | @request.name 33 | end 34 | 35 | def uri 36 | [@request.from.strip.to_s, @request.to.strip.to_s].join("&") 37 | end 38 | 39 | def normalized_uri 40 | uri 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/mock_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | 5 | module OAuth 6 | module RequestProxy 7 | # RequestProxy for Hashes to facilitate simpler signature creation. 8 | # Usage: 9 | # request = OAuth::RequestProxy.proxy \ 10 | # "method" => "iq", 11 | # "uri" => [from, to] * "&", 12 | # "parameters" => { 13 | # "oauth_consumer_key" => oauth_consumer_key, 14 | # "oauth_token" => oauth_token, 15 | # "oauth_signature_method" => "HMAC-SHA1" 16 | # } 17 | # 18 | # signature = OAuth::Signature.sign \ 19 | # request, 20 | # :consumer_secret => oauth_consumer_secret, 21 | # :token_secret => oauth_token_secret, 22 | class MockRequest < OAuth::RequestProxy::Base 23 | proxies ::Hash 24 | 25 | def parameters 26 | @request["parameters"] 27 | end 28 | 29 | def method 30 | @request["method"] 31 | end 32 | 33 | def normalized_uri 34 | super 35 | rescue StandardError 36 | # if this is a non-standard URI, it may not parse properly 37 | # in that case, assume that it's already been normalized 38 | uri 39 | end 40 | 41 | def uri 42 | @request["uri"] 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/net_http.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | require "net/http" 5 | require "uri" 6 | require "cgi" 7 | 8 | module OAuth 9 | module RequestProxy 10 | module Net 11 | module HTTP 12 | class HTTPRequest < OAuth::RequestProxy::Base 13 | proxies ::Net::HTTPGenericRequest 14 | 15 | def method 16 | request.method 17 | end 18 | 19 | def uri 20 | options[:uri].to_s 21 | end 22 | 23 | def parameters 24 | if options[:clobber_request] 25 | options[:parameters] 26 | else 27 | all_parameters 28 | end 29 | end 30 | 31 | def body 32 | request.body 33 | end 34 | 35 | private 36 | 37 | def all_parameters 38 | request_params = CGI.parse(query_string) 39 | # request_params.each{|k,v| request_params[k] = [nil] if v == []} 40 | 41 | options[:parameters]&.each do |k, v| 42 | if request_params.key?(k) && v 43 | request_params[k] << v 44 | else 45 | request_params[k] = [v] 46 | end 47 | end 48 | request_params 49 | end 50 | 51 | def query_string 52 | params = [query_params, auth_header_params] 53 | if (method.to_s.casecmp("POST").zero? || method.to_s.casecmp("PUT").zero?) && form_url_encoded? 54 | params << post_params 55 | end 56 | params.compact.join("&") 57 | end 58 | 59 | def form_url_encoded? 60 | !request["Content-Type"].nil? && request["Content-Type"].to_s.downcase.start_with?("application/x-www-form-urlencoded") 61 | end 62 | 63 | def query_params 64 | URI.parse(request.path).query 65 | end 66 | 67 | def post_params 68 | request.body 69 | end 70 | 71 | def auth_header_params 72 | return nil unless request["Authorization"] && request["Authorization"][0, 5] == "OAuth" 73 | 74 | request["Authorization"] 75 | end 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/rack_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | require "uri" 5 | require "rack" 6 | 7 | module OAuth 8 | module RequestProxy 9 | class RackRequest < OAuth::RequestProxy::Base 10 | proxies ::Rack::Request 11 | 12 | def method 13 | request.env["rack.methodoverride.original_method"] || request.request_method 14 | end 15 | 16 | def uri 17 | request.url 18 | end 19 | 20 | def parameters 21 | if options[:clobber_request] 22 | options[:parameters] || {} 23 | else 24 | params = request_params.merge(query_params).merge(header_params) 25 | params.merge(options[:parameters] || {}) 26 | end 27 | end 28 | 29 | def signature 30 | parameters["oauth_signature"] 31 | end 32 | 33 | protected 34 | 35 | def query_params 36 | request.GET 37 | end 38 | 39 | def request_params 40 | if request.content_type && request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded") 41 | request.POST 42 | else 43 | {} 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/rest_client_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | require "rest-client" 5 | require "uri" 6 | require "cgi" 7 | 8 | module OAuth 9 | module RequestProxy 10 | module RestClient 11 | class Request < OAuth::RequestProxy::Base 12 | proxies ::RestClient::Request 13 | 14 | def method 15 | request.method.to_s.upcase 16 | end 17 | 18 | def uri 19 | request.url 20 | end 21 | 22 | def parameters 23 | if options[:clobber_request] 24 | options[:parameters] || {} 25 | else 26 | post_parameters.merge(query_params).merge(options[:parameters] || {}) 27 | end 28 | end 29 | 30 | protected 31 | 32 | def query_params 33 | query = URI.parse(request.url).query 34 | query ? CGI.parse(query) : {} 35 | end 36 | 37 | def request_params; end 38 | 39 | def post_parameters 40 | # Post params are only used if posting form data 41 | is_form_data = request.payload && request.payload.headers["Content-Type"] == "application/x-www-form-urlencoded" 42 | if is_form_data && (method == "POST" || method == "PUT") 43 | OAuth::Helper.stringify_keys(query_string_to_hash(request.payload.to_s) || {}) 44 | else 45 | {} 46 | end 47 | end 48 | 49 | private 50 | 51 | def query_string_to_hash(query) 52 | query.split("&").inject({}) do |result, q| 53 | k, v = q.split("=") 54 | if !v.nil? 55 | result.merge({ k => v }) 56 | elsif !result.key?(k) 57 | result.merge({ k => true }) 58 | else 59 | result 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/oauth/request_proxy/typhoeus_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/request_proxy/base" 4 | require "typhoeus" 5 | require "typhoeus/request" 6 | require "uri" 7 | require "cgi" 8 | 9 | module OAuth 10 | module RequestProxy 11 | module Typhoeus 12 | class Request < OAuth::RequestProxy::Base 13 | # Proxy for signing Typhoeus::Request requests 14 | # Usage example: 15 | # oauth_params = {:consumer => oauth_consumer, :token => access_token} 16 | # req = Typhoeus::Request.new(uri, options) 17 | # oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri)) 18 | # req.options[:headers].merge!({"Authorization" => oauth_helper.header}) 19 | # hydra = Typhoeus::Hydra.new() 20 | # hydra.queue(req) 21 | # hydra.run 22 | # response = req.response 23 | proxies ::Typhoeus::Request 24 | 25 | def method 26 | request_method = request.options[:method].to_s.upcase 27 | request_method.empty? ? "GET" : request_method 28 | end 29 | 30 | def uri 31 | options[:uri].to_s 32 | end 33 | 34 | def parameters 35 | if options[:clobber_request] 36 | options[:parameters] 37 | else 38 | post_parameters.merge(query_parameters).merge(options[:parameters] || {}) 39 | end 40 | end 41 | 42 | private 43 | 44 | def query_parameters 45 | query = URI.parse(request.url).query 46 | query ? CGI.parse(query) : {} 47 | end 48 | 49 | def post_parameters 50 | # Post params are only used if posting form data 51 | if method == "POST" 52 | OAuth::Helper.stringify_keys(request.options[:params] || {}) 53 | else 54 | {} 55 | end 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/oauth/server.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/helper" 4 | require "oauth/consumer" 5 | 6 | module OAuth 7 | # This is mainly used to create consumer credentials and can pretty much be ignored if you want to create your own 8 | class Server 9 | include OAuth::Helper 10 | attr_accessor :base_url 11 | 12 | @@server_paths = { 13 | request_token_path: "/oauth/request_token", 14 | authorize_path: "/oauth/authorize", 15 | access_token_path: "/oauth/access_token" 16 | } 17 | 18 | # Create a new server instance 19 | def initialize(base_url, paths = {}) 20 | @base_url = base_url 21 | @paths = @@server_paths.merge(paths) 22 | end 23 | 24 | def generate_credentials 25 | [generate_key(16), generate_key] 26 | end 27 | 28 | def generate_consumer_credentials(_params = {}) 29 | Consumer.new(*generate_credentials) 30 | end 31 | 32 | # mainly for testing purposes 33 | def create_consumer 34 | creds = generate_credentials 35 | Consumer.new(creds[0], creds[1], 36 | { 37 | site: base_url, 38 | request_token_path: request_token_path, 39 | authorize_path: authorize_path, 40 | access_token_path: access_token_path 41 | }) 42 | end 43 | 44 | def request_token_path 45 | @paths[:request_token_path] 46 | end 47 | 48 | def request_token_url 49 | base_url + request_token_path 50 | end 51 | 52 | def authorize_path 53 | @paths[:authorize_path] 54 | end 55 | 56 | def authorize_url 57 | base_url + authorize_path 58 | end 59 | 60 | def access_token_path 61 | @paths[:access_token_path] 62 | end 63 | 64 | def access_token_url 65 | base_url + access_token_path 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/oauth/signature.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | module Signature 5 | # Returns a list of available signature methods 6 | def self.available_methods 7 | @available_methods ||= {} 8 | end 9 | 10 | # Build a signature from a +request+. 11 | # 12 | # Raises UnknownSignatureMethod exception if the signature method is unknown. 13 | def self.build(request, options = {}, &block) 14 | request = OAuth::RequestProxy.proxy(request, options) 15 | klass = available_methods[ 16 | (request.signature_method || 17 | ((c = request.options[:consumer]) && c.options[:signature_method]) || 18 | "").downcase] 19 | raise UnknownSignatureMethod, request.signature_method unless klass 20 | 21 | klass.new(request, options, &block) 22 | end 23 | 24 | # Sign a +request+ 25 | def self.sign(request, options = {}, &block) 26 | build(request, options, &block).signature 27 | end 28 | 29 | # Verify the signature of +request+ 30 | def self.verify(request, options = {}, &block) 31 | build(request, options, &block).verify 32 | end 33 | 34 | # Create the signature base string for +request+. This string is the normalized parameter information. 35 | # 36 | # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] 37 | def self.signature_base_string(request, options = {}, &block) 38 | build(request, options, &block).signature_base_string 39 | end 40 | 41 | # Create the body hash for a request 42 | def self.body_hash(request, options = {}, &block) 43 | build(request, options, &block).body_hash 44 | end 45 | 46 | class UnknownSignatureMethod < RuntimeError; end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/oauth/signature/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/signature" 4 | require "oauth/helper" 5 | require "oauth/request_proxy/base" 6 | require "base64" 7 | 8 | module OAuth 9 | module Signature 10 | class Base 11 | include OAuth::Helper 12 | 13 | attr_accessor :options 14 | attr_reader :token_secret, :consumer_secret, :request 15 | 16 | def self.implements(signature_method = nil) 17 | return @implements if signature_method.nil? 18 | 19 | @implements = signature_method 20 | OAuth::Signature.available_methods[@implements] = self 21 | end 22 | 23 | def initialize(request, options = {}, &block) 24 | raise TypeError unless request.is_a?(OAuth::RequestProxy::Base) 25 | 26 | @request = request 27 | @options = options 28 | 29 | ## consumer secret was determined beforehand 30 | 31 | @consumer_secret = options[:consumer].secret if options[:consumer] 32 | 33 | # presence of :consumer_secret option will override any Consumer that's provided 34 | @consumer_secret = options[:consumer_secret] if options[:consumer_secret] 35 | 36 | ## token secret was determined beforehand 37 | 38 | @token_secret = options[:token].secret if options[:token] 39 | 40 | # presence of :token_secret option will override any Token that's provided 41 | @token_secret = options[:token_secret] if options[:token_secret] 42 | 43 | # override secrets based on the values returned from the block (if any) 44 | if block 45 | # consumer secret and token secret need to be looked up based on pieces of the request 46 | secrets = yield block.arity == 1 ? request : [token, consumer_key, nonce, request.timestamp] 47 | if secrets.is_a?(Array) && secrets.size == 2 48 | @token_secret = secrets[0] 49 | @consumer_secret = secrets[1] 50 | end 51 | end 52 | end 53 | 54 | def signature 55 | Base64.encode64(digest).chomp.delete("\n") 56 | end 57 | 58 | def ==(other) 59 | check = signature.bytesize ^ other.bytesize 60 | signature.bytes.zip(other.bytes) { |x, y| check |= x ^ y.to_i } 61 | check.zero? 62 | end 63 | 64 | def verify 65 | self == request.signature 66 | end 67 | 68 | def signature_base_string 69 | request.signature_base_string 70 | end 71 | 72 | def body_hash 73 | raise_instantiation_error 74 | end 75 | 76 | private 77 | 78 | def token 79 | request.token 80 | end 81 | 82 | def consumer_key 83 | request.consumer_key 84 | end 85 | 86 | def nonce 87 | request.nonce 88 | end 89 | 90 | def secret 91 | "#{escape(consumer_secret)}&#{escape(token_secret)}" 92 | end 93 | 94 | def digest 95 | raise_instantiation_error 96 | end 97 | 98 | def raise_instantiation_error 99 | raise NotImplementedError, "Cannot instantiate #{self.class.name} class directly." 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/oauth/signature/hmac/sha1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/signature/base" 4 | 5 | module OAuth 6 | module Signature 7 | module HMAC 8 | class SHA1 < OAuth::Signature::Base 9 | implements "hmac-sha1" 10 | 11 | def body_hash 12 | Base64.encode64(OpenSSL::Digest.digest("SHA1", request.body || "")).chomp.delete("\n") 13 | end 14 | 15 | private 16 | 17 | def digest 18 | OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha1"), secret, signature_base_string) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/oauth/signature/hmac/sha256.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/signature/base" 4 | 5 | module OAuth 6 | module Signature 7 | module HMAC 8 | class SHA256 < OAuth::Signature::Base 9 | implements "hmac-sha256" 10 | 11 | def body_hash 12 | Base64.encode64(OpenSSL::Digest.digest("SHA256", request.body || "")).chomp.delete("\n") 13 | end 14 | 15 | private 16 | 17 | def digest 18 | OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), secret, signature_base_string) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/oauth/signature/plaintext.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/signature/base" 4 | 5 | module OAuth 6 | module Signature 7 | class PLAINTEXT < Base 8 | implements "plaintext" 9 | 10 | def signature 11 | signature_base_string 12 | end 13 | 14 | def ==(other) 15 | signature.to_s == other.to_s 16 | end 17 | 18 | def signature_base_string 19 | secret 20 | end 21 | 22 | def body_hash 23 | nil 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/oauth/signature/rsa/sha1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/signature/base" 4 | 5 | module OAuth 6 | module Signature 7 | module RSA 8 | class SHA1 < OAuth::Signature::Base 9 | implements "rsa-sha1" 10 | 11 | def ==(other) 12 | public_key.verify(OpenSSL::Digest.new("SHA1"), 13 | Base64.decode64(other.is_a?(Array) ? other.first : other), signature_base_string) 14 | end 15 | 16 | def public_key 17 | case consumer_secret 18 | when String 19 | decode_public_key 20 | when OpenSSL::X509::Certificate 21 | consumer_secret.public_key 22 | else 23 | consumer_secret 24 | end 25 | end 26 | 27 | def body_hash 28 | Base64.encode64(OpenSSL::Digest.digest("SHA1", request.body || "")).chomp.delete("\n") 29 | end 30 | 31 | private 32 | 33 | def decode_public_key 34 | case consumer_secret 35 | when /-----BEGIN CERTIFICATE-----/ 36 | OpenSSL::X509::Certificate.new(consumer_secret).public_key 37 | else 38 | OpenSSL::PKey::RSA.new(consumer_secret) 39 | end 40 | end 41 | 42 | def digest 43 | private_key = OpenSSL::PKey::RSA.new( 44 | if options[:private_key_file] 45 | File.read(options[:private_key_file]) 46 | elsif options[:private_key] 47 | options[:private_key] 48 | else 49 | consumer_secret 50 | end 51 | ) 52 | 53 | private_key.sign(OpenSSL::Digest.new("SHA1"), signature_base_string) 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/oauth/token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # this exists for backwards-compatibility 4 | 5 | require "oauth/tokens/token" 6 | require "oauth/tokens/server_token" 7 | require "oauth/tokens/consumer_token" 8 | require "oauth/tokens/request_token" 9 | require "oauth/tokens/access_token" 10 | -------------------------------------------------------------------------------- /lib/oauth/tokens/access_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | # The Access Token is used for the actual "real" web service calls that you perform against the server 5 | class AccessToken < ConsumerToken 6 | # The less intrusive way. Otherwise, if we are to do it correctly inside consumer, 7 | # we need to restructure and touch more methods: request(), sign!(), etc. 8 | def request(http_method, path, *arguments) 9 | request_uri = URI.parse(path) 10 | site_uri = consumer.uri 11 | is_service_uri_different = (request_uri.absolute? && request_uri != site_uri) 12 | begin 13 | consumer.uri(request_uri) if is_service_uri_different 14 | @response = super(http_method, path, *arguments) 15 | ensure 16 | # NOTE: reset for wholesomeness? meaning that we admit only AccessToken service calls may use different URIs? 17 | # so reset in case consumer is still used for other token-management tasks subsequently? 18 | consumer.uri(site_uri) if is_service_uri_different 19 | end 20 | @response 21 | end 22 | 23 | # Make a regular GET request using AccessToken 24 | # 25 | # @response = @token.get('/people') 26 | # @response = @token.get('/people', { 'Accept'=>'application/xml' }) 27 | # 28 | def get(path, headers = {}) 29 | request(:get, path, headers) 30 | end 31 | 32 | # Make a regular HEAD request using AccessToken 33 | # 34 | # @response = @token.head('/people') 35 | # 36 | def head(path, headers = {}) 37 | request(:head, path, headers) 38 | end 39 | 40 | # Make a regular POST request using AccessToken 41 | # 42 | # @response = @token.post('/people') 43 | # @response = @token.post('/people', { :name => 'Bob', :email => 'bob@mailinator.com' }) 44 | # @response = @token.post('/people', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' }) 45 | # @response = @token.post('/people', nil, {'Accept' => 'application/xml' }) 46 | # @response = @token.post('/people', @person.to_xml, { 'Accept'=>'application/xml', 'Content-Type' => 'application/xml' }) 47 | # 48 | def post(path, body = "", headers = {}) 49 | request(:post, path, body, headers) 50 | end 51 | 52 | # Make a regular PUT request using AccessToken 53 | # 54 | # @response = @token.put('/people/123') 55 | # @response = @token.put('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }) 56 | # @response = @token.put('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' }) 57 | # @response = @token.put('/people/123', nil, { 'Accept' => 'application/xml' }) 58 | # @response = @token.put('/people/123', @person.to_xml, { 'Accept' => 'application/xml', 'Content-Type' => 'application/xml' }) 59 | # 60 | def put(path, body = "", headers = {}) 61 | request(:put, path, body, headers) 62 | end 63 | 64 | # Make a regular PATCH request using AccessToken 65 | # 66 | # @response = @token.patch('/people/123') 67 | # @response = @token.patch('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }) 68 | # @response = @token.patch('/people/123', { :name => 'Bob', :email => 'bob@mailinator.com' }, { 'Accept' => 'application/xml' }) 69 | # @response = @token.patch('/people/123', nil, { 'Accept' => 'application/xml' }) 70 | # @response = @token.patch('/people/123', @person.to_xml, { 'Accept' => 'application/xml', 'Content-Type' => 'application/xml' }) 71 | # 72 | def patch(path, body = "", headers = {}) 73 | request(:patch, path, body, headers) 74 | end 75 | 76 | # Make a regular DELETE request using AccessToken 77 | # 78 | # @response = @token.delete('/people/123') 79 | # @response = @token.delete('/people/123', { 'Accept' => 'application/xml' }) 80 | # 81 | def delete(path, headers = {}) 82 | request(:delete, path, headers) 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/oauth/tokens/consumer_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | # Superclass for tokens used by OAuth Clients 5 | class ConsumerToken < Token 6 | attr_accessor :consumer, :params 7 | attr_reader :response 8 | 9 | def self.from_hash(consumer, hash) 10 | token = new(consumer, hash[:oauth_token], hash[:oauth_token_secret]) 11 | token.params = hash 12 | token 13 | end 14 | 15 | def initialize(consumer, token = "", secret = "") 16 | super(token, secret) 17 | @consumer = consumer 18 | @params = {} 19 | end 20 | 21 | # Make a signed request using given http_method to the path 22 | # 23 | # @token.request(:get, '/people') 24 | # @token.request(:post, '/people', @person.to_xml, { 'Content-Type' => 'application/xml' }) 25 | # 26 | def request(http_method, path, *arguments) 27 | @response = consumer.request(http_method, path, self, {}, *arguments) 28 | end 29 | 30 | # Sign a request generated elsewhere using Net:HTTP::Post.new or friends 31 | def sign!(request, options = {}) 32 | consumer.sign!(request, self, options) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/oauth/tokens/request_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | # The RequestToken is used for the initial Request. 5 | # This is normally created by the Consumer object. 6 | class RequestToken < ConsumerToken 7 | # Generate an authorization URL for user authorization 8 | def authorize_url(params = nil) 9 | return nil if token.nil? 10 | 11 | params = (params || {}).merge(oauth_token: token) 12 | build_url(consumer.authorize_url, params) 13 | end 14 | 15 | def authenticate_url(params = nil) 16 | return nil if token.nil? 17 | 18 | params = (params || {}).merge(oauth_token: token) 19 | build_url(consumer.authenticate_url, params) 20 | end 21 | 22 | def callback_confirmed? 23 | params[:oauth_callback_confirmed] == "true" 24 | end 25 | 26 | # exchange for AccessToken on server 27 | def get_access_token(options = {}, *arguments) 28 | response = consumer.token_request(consumer.http_method, 29 | (consumer.access_token_url? ? consumer.access_token_url : consumer.access_token_path), self, options, *arguments) 30 | OAuth::AccessToken.from_hash(consumer, response) 31 | end 32 | 33 | protected 34 | 35 | # construct an authorization or authentication url 36 | def build_url(base_url, params) 37 | uri = URI.parse(base_url.to_s) 38 | queries = {} 39 | queries = URI.decode_www_form(uri.query).to_h if uri.query 40 | # TODO: doesn't handle array values correctly 41 | queries.merge!(params) if params 42 | uri.query = URI.encode_www_form(queries) unless queries.empty? 43 | uri.to_s 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/oauth/tokens/server_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | # Used on the server for generating tokens 5 | class ServerToken < Token 6 | def initialize 7 | super(generate_key(16), generate_key) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/oauth/tokens/token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | # Superclass for the various tokens used by OAuth 5 | class Token 6 | include OAuth::Helper 7 | 8 | attr_accessor :token, :secret 9 | 10 | def initialize(token, secret) 11 | @token = token 12 | @secret = secret 13 | end 14 | 15 | def to_query 16 | "oauth_token=#{escape(token)}&oauth_token_secret=#{escape(secret)}" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/oauth/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | module Version 5 | VERSION = "1.1.1" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /oauth.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/oauth/version" 4 | 5 | Gem::Specification.new do |spec| 6 | # "oauth-tty" was extracted from this gem with release 1.1 of this gem 7 | # It is now a dependency for backward compatibility. 8 | # The dependency will be removed with release 2.0, by April 2023. 9 | spec.add_dependency("oauth-tty", ["~> 1.0", ">= 1.0.1"]) 10 | spec.add_dependency("snaky_hash", "~> 2.0") 11 | spec.add_dependency("version_gem", "~> 1.1") 12 | 13 | spec.cert_chain = ["certs/pboling.pem"] 14 | spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $PROGRAM_NAME.end_with?("gem") 15 | 16 | spec.name = "oauth" 17 | spec.version = OAuth::Version::VERSION 18 | spec.license = "MIT" 19 | 20 | spec.authors = ["Pelle Braendgaard", "Blaine Cook", "Larry Halff", "Jesse Clark", "Jon Crosby", 21 | "Seth Fitzsimmons", "Matt Sanford", "Aaron Quint", "Peter Boling"] 22 | spec.email = ["peter.boling@gmail.com", "oauth-ruby@googlegroups.com"] 23 | spec.summary = "OAuth 1.0 Core Ruby implementation" 24 | spec.description = "A Ruby wrapper for the original OAuth 1.0 spec." 25 | 26 | spec.homepage = "https://gitlab.com/oauth-xx/oauth" 27 | spec.metadata["homepage_uri"] = spec.homepage 28 | spec.metadata["source_code_uri"] = "#{spec.homepage}/-/tree/v#{spec.version}" 29 | spec.metadata["changelog_uri"] = "#{spec.homepage}/-/blob/v#{spec.version}/CHANGELOG.md" 30 | spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/-/issues" 31 | spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/#{spec.name}/#{spec.version}" 32 | spec.metadata["wiki_uri"] = "#{spec.homepage}/-/wiki" 33 | spec.metadata["mailing_list_uri"] = "https://groups.google.com/g/oauth-ruby" 34 | spec.metadata["funding_uri"] = "https://liberapay.com/pboling" 35 | spec.metadata["rubygems_mfa_required"] = "true" 36 | 37 | spec.files = Dir[ 38 | "lib/**/*", 39 | "CHANGELOG.md", 40 | "CODE_OF_CONDUCT.md", 41 | "CONTRIBUTING.md", 42 | "LICENSE.txt", 43 | "README.md", 44 | "SECURITY.md", 45 | ] 46 | 47 | spec.required_ruby_version = ">= 2.7" 48 | spec.post_install_message = " 49 | You have installed oauth version #{OAuth::Version::VERSION}, congratulations! 50 | 51 | Please see: 52 | • #{spec.homepage}/-/blob/main/SECURITY.md 53 | • #{spec.homepage}/-/blob/v#{spec.version}/CHANGELOG.md#111-2022-09-19 54 | 55 | Major updates: 56 | 1. master branch renamed to main 57 | • Update your local: git checkout master; git branch -m master main; git branch --unset-upstream; git branch -u origin/main 58 | 2. Github has been replaced with Gitlab; I wrote about some of the reasons here: 59 | • https://dev.to/galtzo/im-leaving-github-50ba 60 | • Update your local: git remote set-url origin git@gitlab.com:oauth-xx/oauth.git 61 | 3. Google Group is active (again)! 62 | • https://groups.google.com/g/oauth-ruby/c/QA_dtrXWXaE 63 | 4. Gitter Chat is active (still)! 64 | • https://gitter.im/oauth-xx/ 65 | 5. Non-commercial support for the 1.x series will end by April, 2025. Please make a plan to upgrade to the next version prior to that date. 66 | Support will be dropped for Ruby 2.7 and any other Ruby versions which will also have reached EOL by then. 67 | 6. Gem releases are now cryptographically signed for security. 68 | 69 | If you are a human, please consider a donation as I move toward supporting myself with Open Source work: 70 | • https://liberapay.com/pboling 71 | • https://ko-fi.com/pboling 72 | • https://patreon.com/galtzo 73 | 74 | If you are a corporation, please consider supporting this project, and open source work generally, with a TideLift subscription. 75 | • https://tidelift.com/funding/github/rubygems/oauth 76 | • Or hire me. I am looking for a job! 77 | 78 | Please report issues, and support the project! 79 | 80 | Thanks, |7eter l-|. l3oling 81 | " 82 | spec.add_development_dependency("em-http-request", "~> 1.1.7") 83 | spec.add_development_dependency("iconv") 84 | spec.add_development_dependency("minitest", "~> 5.15.0") 85 | spec.add_development_dependency("mocha") 86 | spec.add_development_dependency("rack", "~> 3.1") 87 | spec.add_development_dependency("rack-test") 88 | spec.add_development_dependency("rake", "~> 13.0") 89 | spec.add_development_dependency("rest-client") 90 | spec.add_development_dependency("rubocop-lts", "~> 18.0") 91 | spec.add_development_dependency("typhoeus", ">= 0.1.13") 92 | spec.add_development_dependency("webmock", "<= 3.26.0") 93 | end 94 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/construct_request_url_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | # See http://oauth.net/core/1.0/#anchor14 6 | # 7 | # 9.1.2. Construct Request URL 8 | # 9 | # The Signature Base String includes the request absolute URL, tying the signature to a specific endpoint. The URL used in the Signature Base String MUST include the scheme, authority, and path, and MUST exclude the query and fragment as defined by [RFC3986] section 3. 10 | # 11 | # If the absolute request URL is not available to the Service Provider (it is always available to the Consumer), it can be constructed by combining the scheme being used, the HTTP Host header, and the relative HTTP request URL. If the Host header is not available, the Service Provider SHOULD use the host name communicated to the Consumer in the documentation or other means. 12 | # 13 | # The Service Provider SHOULD document the form of URL used in the Signature Base String to avoid ambiguity due to URL normalization. Unless specified, URL scheme and authority MUST be lowercase and include the port number; http default port 80 and https default port 443 MUST be excluded. 14 | # 15 | # For example, the request: 16 | # 17 | # HTTP://Example.com:80/resource?id=123 18 | # Is included in the Signature Base String as: 19 | # 20 | # http://example.com/resource 21 | 22 | class ConstructRequestUrlTest < OAuthCase 23 | def test_from_spec 24 | assert_request_url("http://example.com/resource", "HTTP://Example.com:80/resource?id=123") 25 | end 26 | 27 | def test_simple_url_with_ending_slash 28 | assert_request_url("http://example.com/", "http://example.com/") 29 | end 30 | 31 | def test_simple_url_without_ending_slash 32 | assert_request_url("http://example.com/", "http://example.com") 33 | end 34 | 35 | def test_of_normalized_http 36 | assert_request_url("http://example.com/resource", "http://example.com/resource") 37 | end 38 | 39 | def test_of_https 40 | assert_request_url("https://example.com/resource", "HTTPS://Example.com:443/resource?id=123") 41 | end 42 | 43 | def test_of_normalized_https 44 | assert_request_url("https://example.com/resource", "https://example.com/resource") 45 | end 46 | 47 | def test_of_http_with_non_standard_port 48 | assert_request_url("http://example.com:8080/resource", "http://example.com:8080/resource") 49 | end 50 | 51 | def test_of_https_with_non_standard_port 52 | assert_request_url("https://example.com:8080/resource", "https://example.com:8080/resource") 53 | end 54 | 55 | protected 56 | 57 | def assert_request_url(expected, given, message = nil) 58 | assert_equal expected, request({}, "GET", given).normalized_uri, message 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/normalize_request_parameters_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | # See http://oauth.net/core/1.0/#anchor14 6 | # 7 | # 9.1.1. Normalize Request Parameters 8 | # 9 | # The request parameters are collected, sorted and concatenated into a normalized string: 10 | # 11 | # Parameters in the OAuth HTTP Authorization header excluding the realm parameter. 12 | # Parameters in the HTTP POST request body (with a content-type of application/x-www-form-urlencoded). 13 | # HTTP GET parameters added to the URLs in the query part (as defined by [RFC3986] section 3). 14 | # The oauth_signature parameter MUST be excluded. 15 | # 16 | # The parameters are normalized into a single string as follows: 17 | # 18 | # Parameters are sorted by name, using lexicographical byte value ordering. 19 | # If two or more parameters share the same name, they are sorted by their value. For example: 20 | # 21 | # a=1, c=hi%20there, f=25, f=50, f=a, z=p, z=t 22 | # Parameters are concatenated in their sorted order into a single string. For each parameter, 23 | # the name is separated from the corresponding value by an ‘=’ character (ASCII code 61), even 24 | # if the value is empty. Each name-value pair is separated by an ‘&’ character (ASCII code 38). For example: 25 | # a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t 26 | # 27 | 28 | class NormalizeRequestParametersTest < OAuthCase 29 | def test_parameters_for_signature 30 | params = { "a" => 1, "c" => "hi there", "f" => "a", "z" => "t" } 31 | assert_equal params, request(params).parameters_for_signature 32 | end 33 | 34 | def test_parameters_for_signature_removes_oauth_signature 35 | params = { "a" => 1, "c" => "hi there", "f" => "a", "z" => "t" } 36 | assert_equal params, request(params.merge({ "oauth_signature" => "blalbla" })).parameters_for_signature 37 | end 38 | 39 | def test_spec_example 40 | assert_normalized "a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t", 41 | { "a" => 1, "c" => "hi there", "f" => %w[25 50 a], "z" => %w[p t] } 42 | end 43 | 44 | def test_sorts_parameters_correctly 45 | # values for 'f' are scrambled 46 | assert_normalized "a=1&c=hi%20there&f=5&f=70&f=a&z=p&z=t", 47 | { "a" => 1, "c" => "hi there", "f" => %w[a 70 5], "z" => %w[p t] } 48 | end 49 | 50 | def test_empty 51 | assert_normalized "", {} 52 | end 53 | 54 | # These are from the wiki http://wiki.oauth.net/TestCases 55 | # in the section Normalize Request Parameters 56 | # Parameters have already been x-www-form-urlencoded (i.e. + = ) 57 | def test_wiki1 58 | assert_normalized "name=", { "name" => nil } 59 | end 60 | 61 | def test_wiki2 62 | assert_normalized "a=b", { "a" => "b" } 63 | end 64 | 65 | def test_wiki3 66 | assert_normalized "a=b&c=d", { "a" => "b", "c" => "d" } 67 | end 68 | 69 | def test_wiki4 70 | assert_normalized "a=x%20y&a=x%21y", { "a" => ["x!y", "x y"] } 71 | end 72 | 73 | def test_wiki5 74 | assert_normalized "x=a&x%21y=a", { "x!y" => "a", "x" => "a" } 75 | end 76 | 77 | protected 78 | 79 | def assert_normalized(expected, params, message = nil) 80 | assert_equal expected, normalize_request_parameters(params), message 81 | end 82 | 83 | def normalize_request_parameters(params = {}) 84 | request(params).normalized_parameters 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/parameter_encodings_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | # See http://oauth.net/core/1.0/#encoding_parameters 6 | # 7 | # 5.1. Parameter Encoding 8 | # 9 | # All parameter names and values are escaped using the [RFC3986] percent-encoding (%xx) mechanism. 10 | # Characters not in the unreserved character set ([RFC3986] section 2.3) MUST be encoded. Characters 11 | # in the unreserved character set MUST NOT be encoded. Hexadecimal characters in encodings MUST be 12 | # upper case. Text names and values MUST be encoded as UTF-8 octets before percent-encoding them per [RFC3629]. 13 | # 14 | # unreserved = ALPHA, DIGIT, '-', '.', '_', '~' 15 | # 16 | 17 | class ParameterEncodingTest < OAuthCase 18 | def test_encodings_alpha_num 19 | assert_encoding "abcABC123", "abcABC123" 20 | end 21 | 22 | def test_encodings_non_escaped 23 | assert_encoding "-._~", "-._~" 24 | end 25 | 26 | def test_encodings_percent 27 | assert_encoding "%25", "%" 28 | end 29 | 30 | def test_encodings_plus 31 | assert_encoding "%2B", "+" 32 | end 33 | 34 | def test_encodings_space 35 | assert_encoding "%20", " " 36 | end 37 | 38 | def test_encodings_query_param_symbols 39 | assert_encoding "%26%3D%2A", "&=*" 40 | end 41 | 42 | def test_encodings_unicode_lf 43 | assert_encoding "%0A", unicode_to_utf8("U+000A") 44 | end 45 | 46 | def test_encodings_unicode_space 47 | assert_encoding "%20", unicode_to_utf8("U+0020") 48 | end 49 | 50 | def test_encodings_unicode007f 51 | assert_encoding "%7F", unicode_to_utf8("U+007F") 52 | end 53 | 54 | def test_encodings_unicode0080 55 | assert_encoding "%C2%80", unicode_to_utf8("U+0080") 56 | end 57 | 58 | def test_encoding_unicode2708 59 | assert_encoding "%E2%9C%88", unicode_to_utf8("U+2708") 60 | end 61 | 62 | def test_encodings_unicode3001 63 | assert_encoding "%E3%80%81", unicode_to_utf8("U+3001") 64 | end 65 | 66 | protected 67 | 68 | def unicode_to_utf8(unicode) 69 | return unicode if unicode =~ /\A[[:space:]]*\z/m 70 | 71 | str = "" 72 | 73 | # :xdigit: character set (hexadecimal) includes the character set of :digit: (decimal) 74 | unicode.scan(/(U\+(?:[[:xdigit:]]{4,5}|10[[:xdigit:]]{4})|.)/mu) do 75 | c = Regexp.last_match(1) 76 | str += if /^U\+/.match?(c) 77 | [c[2..].hex].pack("U*") 78 | else 79 | c 80 | end 81 | end 82 | 83 | str 84 | end 85 | 86 | def assert_encoding(expected, given, message = nil) 87 | assert_equal expected, OAuth::Helper.escape(given), message 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /test/cases/spec/1_0-final/signature_base_strings_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../../../test_helper" 4 | 5 | # See http://oauth.net/core/1.0/#anchor14 6 | # 7 | # 9.1. Signature Base String 8 | # 9 | # The Signature Base String is a consistent reproducible concatenation of the request elements 10 | # into a single string. The string is used as an input in hashing or signing algorithms. The 11 | # HMAC-SHA1 signature method provides both a standard and an example of using the Signature 12 | # Base String with a signing algorithm to generate signatures. All the request parameters MUST 13 | # be encoded as described in Parameter Encoding prior to constructing the Signature Base String. 14 | # 15 | 16 | class SignatureBaseStringTest < OAuthCase 17 | def test_a51 18 | parameters = { 19 | "oauth_consumer_key" => "dpf43f3p2l4k3l03", 20 | "oauth_token" => "nnch734d00sl2jdk", 21 | "oauth_signature_method" => "HMAC-SHA1", 22 | "oauth_timestamp" => "1191242096", 23 | "oauth_nonce" => "kllo9940pd9333jh", 24 | "oauth_version" => "1.0", 25 | "file" => "vacation.jpg", 26 | "size" => "original" 27 | } 28 | sbs = "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal" 29 | 30 | assert_signature_base_string sbs, parameters, "GET", "http://photos.example.net/photos" 31 | end 32 | 33 | # These are from the wiki http://wiki.oauth.net/TestCases 34 | # in the section Concatenate Test Elements 35 | 36 | def test_wiki_1_simple_with_ending_slash 37 | parameters = { 38 | "n" => "v" 39 | } 40 | sbs = "GET&http%3A%2F%2Fexample.com%2F&n%3Dv" 41 | 42 | assert_signature_base_string sbs, parameters, "GET", "http://example.com/" 43 | end 44 | 45 | def test_wiki_2_simple_without_ending_slash 46 | parameters = { 47 | "n" => "v" 48 | } 49 | sbs = "GET&http%3A%2F%2Fexample.com%2F&n%3Dv" 50 | 51 | assert_signature_base_string sbs, parameters, "GET", "http://example.com" 52 | end 53 | 54 | def test_wiki_2_request_token 55 | parameters = { 56 | "oauth_version" => "1.0", 57 | "oauth_consumer_key" => "dpf43f3p2l4k3l03", 58 | "oauth_timestamp" => "1191242090", 59 | "oauth_nonce" => "hsu94j3884jdopsl", 60 | "oauth_signature_method" => "PLAINTEXT", 61 | "oauth_signature" => "ignored" 62 | } 63 | sbs = "POST&https%3A%2F%2Fphotos.example.net%2Frequest_token&oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dhsu94j3884jdopsl%26oauth_signature_method%3DPLAINTEXT%26oauth_timestamp%3D1191242090%26oauth_version%3D1.0" 64 | 65 | assert_signature_base_string sbs, parameters, "POST", "https://photos.example.net/request_token" 66 | end 67 | 68 | protected 69 | 70 | def assert_signature_base_string(expected, params = {}, method = "GET", uri = "http://photos.example.net/photos", message = "Signature Base String does not match") 71 | assert_equal expected, signature_base_string(params, method, uri), message 72 | end 73 | 74 | def signature_base_string(params = {}, method = "GET", uri = "http://photos.example.net/photos") 75 | request(params, method, uri).signature_base_string 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /test/keys/rsa.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0 3 | IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV 4 | BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 5 | gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY 6 | zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb 7 | mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3 8 | DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d 9 | 4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb 10 | WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J 11 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /test/keys/rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V 3 | A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 4 | 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ 5 | hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H 6 | X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm 7 | uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw 8 | rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z 9 | zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn 10 | qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG 11 | WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno 12 | cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 13 | 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 14 | AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 15 | Lw03eHTNQghS0A== 16 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /test/support/minitest_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OAuth 4 | module MinitestHelpers 5 | def assert_matching_headers(expected, actual) 6 | # transform into sorted arrays 7 | auth_intro, auth_params = actual.split(" ", 2) 8 | assert_equal("OAuth", auth_intro) 9 | expected = expected.split(/(,|\s)/).reject { |v| v == "" || v =~ /^[,\s]+/ }.sort 10 | auth_params = auth_params.split(/(,|\s)/).reject { |v| v == "" || v =~ /^[,\s]+/ }.sort 11 | assert_equal expected, auth_params 12 | end 13 | 14 | def stub_test_ie 15 | stub_request(:any, 16 | "http://term.ie/oauth/example/request_token.php").to_return(body: "oauth_token=requestkey&oauth_token_secret=requestsecret") 17 | stub_request(:post, 18 | "http://term.ie/oauth/example/access_token.php").to_return(body: "oauth_token=accesskey&oauth_token_secret=accesssecret") 19 | stub_request(:get, %r{http://term\.ie/oauth/example/echo_api\.php\?.+}).to_return(lambda { |request| 20 | { body: request.uri.query } 21 | }) 22 | stub_request(:post, "http://term.ie/oauth/example/echo_api.php").to_return(lambda { |request| 23 | { body: request.body } 24 | }) 25 | end 26 | end 27 | end 28 | 29 | Minitest::Test.include OAuth::MinitestHelpers 30 | -------------------------------------------------------------------------------- /test/support/oauth_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "oauth/signature" 4 | require "oauth/request_proxy/mock_request" 5 | 6 | class OAuthCase < Minitest::Test 7 | # avoid whining about a lack of tests 8 | def run(*args) 9 | @method_name ||= nil 10 | return if @method_name.to_s == "default_test" 11 | 12 | super 13 | end 14 | 15 | protected 16 | 17 | # Creates a fake request 18 | def request(params = {}, method = "GET", uri = "http://photos.example.net/photos") 19 | OAuth::RequestProxy.proxy({ "parameters" => params, "method" => method, "uri" => uri }) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # ensure test env 4 | ENV["RACK_ENV"] = "test" 5 | 6 | # Third Party Libraries 7 | require "stringio" 8 | require "minitest" 9 | require "mocha/minitest" 10 | require "rack/test" 11 | require "webmock/minitest" 12 | 13 | DEBUG = ENV["DEBUG"] == "true" 14 | 15 | ruby_version = Gem::Version.new(RUBY_VERSION) 16 | minimum_version = ->(version, engine = "ruby") { ruby_version >= Gem::Version.new(version) && RUBY_ENGINE == engine } 17 | actual_version = lambda do |major, minor| 18 | actual = Gem::Version.new(ruby_version) 19 | major == actual.segments[0] && minor == actual.segments[1] && RUBY_ENGINE == "ruby" 20 | end 21 | debugging = minimum_version.call("2.7") && DEBUG 22 | RUN_COVERAGE = minimum_version.call("2.7") && (ENV.fetch("COVER_ALL") { ENV.fetch("CI_CODECOV") { ENV["CI"].nil? } }) 23 | ALL_FORMATTERS = actual_version.call(2, 7) && (ENV.fetch("COVER_ALL") do 24 | ENV.fetch("CI_CODECOV") do 25 | ENV.fetch("CI", nil) 26 | end 27 | end) 28 | 29 | require "byebug" if debugging 30 | 31 | if RUN_COVERAGE 32 | require "simplecov" # Config file `.simplecov` is run immediately when simplecov loads 33 | require "codecov" 34 | require "simplecov-json" 35 | require "simplecov-lcov" 36 | require "simplecov-cobertura" 37 | # This will override the formatter set in .simplecov 38 | if ALL_FORMATTERS 39 | SimpleCov::Formatter::LcovFormatter.config do |c| 40 | c.report_with_single_file = true 41 | c.single_report_path = "coverage/lcov.info" 42 | end 43 | 44 | SimpleCov.formatters = [ 45 | SimpleCov::Formatter::HTMLFormatter, 46 | SimpleCov::Formatter::CoberturaFormatter, # XML for Jenkins 47 | SimpleCov::Formatter::LcovFormatter, 48 | SimpleCov::Formatter::JSONFormatter, # For CodeClimate 49 | SimpleCov::Formatter::Codecov # For CodeCov 50 | ] 51 | end 52 | end 53 | 54 | # This gem 55 | require "oauth" 56 | 57 | # Test Support Code 58 | require "support/minitest_helpers" 59 | require "support/oauth_case" 60 | 61 | # Run the tests! 62 | require "minitest/autorun" 63 | -------------------------------------------------------------------------------- /test/units/access_token_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class AccessTokenTest < Minitest::Test 6 | def setup 7 | @fake_response = { 8 | user_id: 5_734_758_743_895, 9 | oauth_token: "key", 10 | oauth_token_secret: "secret" 11 | } 12 | # setup a fake req. token. mocking Consumer would be more appropriate... 13 | @access_token = OAuth::AccessToken.from_hash( 14 | OAuth::Consumer.new("key", "secret", {}), 15 | @fake_response 16 | ) 17 | end 18 | 19 | def test_provides_response_parameters 20 | assert @access_token 21 | assert_respond_to @access_token, :params 22 | end 23 | 24 | def test_access_token_makes_non_oauth_response_params_available 25 | assert @access_token.params[:user_id] 26 | assert_equal 5_734_758_743_895, @access_token.params[:user_id] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/units/action_controller_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/request_proxy/action_controller_request" 6 | 7 | class ActionControllerRequestProxyTest < Minitest::Test 8 | def request_proxy(request_method = :get, uri_params = {}, body_params = {}) 9 | request = ActionDispatch::TestRequest.create 10 | request.request_uri = "/" 11 | 12 | case request_method 13 | when :post 14 | request.env["REQUEST_METHOD"] = "POST" 15 | when :put 16 | request.env["REQUEST_METHOD"] = "PUT" 17 | when :patch 18 | request.env["REQUEST_METHOD"] = "PATCH" 19 | end 20 | 21 | request.env["REQUEST_URI"] = "/" 22 | request.env["RAW_POST_DATA"] = body_params.to_query 23 | request.env["QUERY_STRING"] = body_params.to_query 24 | request.env["CONTENT_TYPE"] = "application/x-www-form-urlencoded" 25 | 26 | yield request if block_given? 27 | OAuth::RequestProxy::ActionControllerRequest.new(request, parameters: uri_params) 28 | end 29 | 30 | def test_that_proxy_simple_get_request_works_with_query_params 31 | request_proxy = request_proxy(:get, { "key" => "value" }) 32 | 33 | expected_parameters = [%w[key value]] 34 | assert_equal expected_parameters, request_proxy.parameters_for_signature 35 | assert_equal "GET", request_proxy.method 36 | end 37 | 38 | def test_that_proxy_simple_post_request_works_with_query_params 39 | request_proxy = request_proxy(:post, { "key" => "value" }) 40 | 41 | expected_parameters = [%w[key value]] 42 | assert_equal expected_parameters, request_proxy.parameters_for_signature 43 | assert_equal "POST", request_proxy.method 44 | end 45 | 46 | def test_that_proxy_simple_put_request_works_with_query_params 47 | request_proxy = request_proxy(:put, { "key" => "value" }) 48 | 49 | expected_parameters = [%w[key value]] 50 | assert_equal expected_parameters, request_proxy.parameters_for_signature 51 | assert_equal "PUT", request_proxy.method 52 | end 53 | 54 | def test_that_proxy_simple_patch_request_works_with_query_params 55 | request_proxy = request_proxy(:patch, { "key" => "value" }) 56 | 57 | expected_parameters = [%w[key value]] 58 | assert_equal expected_parameters, request_proxy.parameters_for_signature 59 | assert_equal "PATCH", request_proxy.method 60 | end 61 | 62 | def test_that_proxy_simple_get_request_works_with_post_params 63 | request_proxy = request_proxy(:get, {}, { "key" => "value" }) 64 | 65 | expected_parameters = [] 66 | assert_equal expected_parameters, request_proxy.parameters_for_signature 67 | assert_equal "GET", request_proxy.method 68 | end 69 | 70 | def test_that_proxy_simple_post_request_works_with_post_params 71 | request_proxy = request_proxy(:post, {}, { "key" => "value" }) 72 | 73 | expected_parameters = [%w[key value]] 74 | assert_equal expected_parameters, request_proxy.parameters_for_signature 75 | assert_equal "POST", request_proxy.method 76 | end 77 | 78 | def test_that_proxy_simple_put_request_works_with_post_params 79 | request_proxy = request_proxy(:put, {}, { "key" => "value" }) 80 | 81 | expected_parameters = [%w[key value]] 82 | assert_equal expected_parameters, request_proxy.parameters_for_signature 83 | assert_equal "PUT", request_proxy.method 84 | end 85 | 86 | def test_that_proxy_simple_patch_request_works_with_post_params 87 | request_proxy = request_proxy(:patch, {}, { "key" => "value" }) 88 | 89 | expected_parameters = [] 90 | assert_equal expected_parameters, request_proxy.parameters_for_signature 91 | assert_equal "PATCH", request_proxy.method 92 | end 93 | 94 | def test_that_proxy_simple_get_request_works_with_mixed_params 95 | request_proxy = request_proxy(:get, { "key" => "value" }, { "key2" => "value2" }) 96 | 97 | expected_parameters = [%w[key value]] 98 | assert_equal expected_parameters, request_proxy.parameters_for_signature 99 | assert_equal "GET", request_proxy.method 100 | end 101 | 102 | def test_that_proxy_simple_post_request_works_with_mixed_params 103 | request_proxy = request_proxy(:post, { "key" => "value" }, { "key2" => "value2" }) 104 | 105 | expected_parameters = [%w[key value], %w[key2 value2]] 106 | assert_equal expected_parameters, request_proxy.parameters_for_signature 107 | assert_equal "POST", request_proxy.method 108 | end 109 | 110 | def test_that_proxy_simple_put_request_works_with_mixed_params 111 | request_proxy = request_proxy(:put, { "key" => "value" }, { "key2" => "value2" }) 112 | 113 | expected_parameters = [%w[key value], %w[key2 value2]] 114 | assert_equal expected_parameters, request_proxy.parameters_for_signature 115 | assert_equal "PUT", request_proxy.method 116 | end 117 | 118 | def test_that_proxy_simple_patch_request_works_with_mixed_params 119 | request_proxy = request_proxy(:patch, { "key" => "value" }, { "key2" => "value2" }) 120 | 121 | expected_parameters = [%w[key value]] 122 | assert_equal expected_parameters, request_proxy.parameters_for_signature 123 | assert_equal "PATCH", request_proxy.method 124 | end 125 | 126 | def test_parameter_keys_should_preserve_brackets_from_hash 127 | assert_equal( 128 | [["message[body]", "This is a test"]], 129 | request_proxy(:post, { message: { body: "This is a test" } }).parameters_for_signature 130 | ) 131 | end 132 | 133 | def test_parameter_values_with_amps_should_not_break_parameter_parsing 134 | assert_equal( 135 | [["message[body]", "http://foo.com/?a=b&c=d"]], 136 | request_proxy(:post, { message: { body: "http://foo.com/?a=b&c=d" } }).parameters_for_signature 137 | ) 138 | end 139 | 140 | def test_parameter_keys_should_preserve_brackets_from_array 141 | assert_equal( 142 | [["foo[]", "123"], ["foo[]", "456"]], 143 | request_proxy(:post, { foo: [123, 456] }).parameters_for_signature.sort 144 | ) 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /test/units/action_dispatch_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/request_proxy/action_dispatch_request" 6 | 7 | class ActionRequestProxyTest < Minitest::Test 8 | def test_that_proxy_simple_get_request_works 9 | request = ActionDispatch::Request.new(Rack::MockRequest.env_for("http://example.com/test?key=value")) 10 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test?key=value" }) 11 | 12 | expected_parameters = { "key" => "value" } 13 | assert_equal expected_parameters, request_proxy.parameters 14 | assert_equal "http://example.com/test", request_proxy.normalized_uri 15 | assert_equal "GET", request_proxy.method 16 | end 17 | 18 | def test_that_proxy_simple_post_request_works 19 | request = ActionDispatch::Request.new(Rack::MockRequest.env_for("http://example.com/test", method: "POST")) 20 | params = { "key" => "value" } 21 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 22 | 23 | expected_parameters = { "key" => "value" } 24 | assert_equal expected_parameters, request_proxy.parameters 25 | assert_equal "http://example.com/test", request_proxy.normalized_uri 26 | assert_equal "POST", request_proxy.method 27 | end 28 | 29 | def test_that_proxy_post_and_get_request_works 30 | request = ActionDispatch::Request.new(Rack::MockRequest.env_for("http://example.com/test?key=value", 31 | method: "POST", input: "key2=value2")) 32 | params = { "key2" => "value2" } 33 | request_proxy = OAuth::RequestProxy.proxy(request, 34 | { uri: "http://example.com/test?key=value", parameters: params }) 35 | 36 | expected_parameters = { "key" => "value", "key2" => "value2" } 37 | assert_equal expected_parameters, request_proxy.parameters 38 | assert_equal "http://example.com/test", request_proxy.normalized_uri 39 | assert_equal "POST", request_proxy.method 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/units/client_helper_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/client" 6 | 7 | class ClientHelperTest < Minitest::Test 8 | def setup 9 | @consumer = OAuth::Consumer.new( 10 | "consumer_key_86cad9", "5888bf0345e5d237", 11 | { 12 | site: "http://blabla.bla", 13 | proxy: "http://user:password@proxy.bla:8080", 14 | request_token_path: "/oauth/example/request_token.php", 15 | access_token_path: "/oauth/example/access_token.php", 16 | authorize_path: "/oauth/example/authorize.php", 17 | scheme: :header, 18 | http_method: :get 19 | } 20 | ) 21 | end 22 | 23 | def test_oauth_parameters_allow_empty_params_default 24 | helper = OAuth::Client::Helper.new(nil, { 25 | consumer: @consumer 26 | }) 27 | helper.stub :timestamp, "0" do 28 | helper.stub :nonce, "nonce" do 29 | expected = { 30 | "oauth_consumer_key" => "consumer_key_86cad9", 31 | "oauth_signature_method" => "HMAC-SHA1", 32 | "oauth_timestamp" => "0", 33 | "oauth_nonce" => "nonce", 34 | "oauth_version" => "1.0" 35 | } 36 | assert_equal expected, helper.oauth_parameters 37 | end 38 | end 39 | end 40 | 41 | def test_oauth_parameters_allow_empty_params_true 42 | input = true 43 | helper = OAuth::Client::Helper.new(nil, { 44 | consumer: @consumer, 45 | allow_empty_params: input 46 | }) 47 | helper.stub :timestamp, "0" do 48 | helper.stub :nonce, "nonce" do 49 | expected = { 50 | "oauth_body_hash" => nil, 51 | "oauth_callback" => nil, 52 | "oauth_consumer_key" => "consumer_key_86cad9", 53 | "oauth_token" => "", 54 | "oauth_signature_method" => "HMAC-SHA1", 55 | "oauth_timestamp" => "0", 56 | "oauth_nonce" => "nonce", 57 | "oauth_verifier" => nil, 58 | "oauth_version" => "1.0", 59 | "oauth_session_handle" => nil 60 | } 61 | assert_equal expected, helper.oauth_parameters 62 | end 63 | end 64 | end 65 | 66 | def test_oauth_parameters_allow_empty_params_false 67 | input = false 68 | helper = OAuth::Client::Helper.new(nil, { 69 | consumer: @consumer, 70 | allow_empty_params: input 71 | }) 72 | helper.stub :timestamp, "0" do 73 | helper.stub :nonce, "nonce" do 74 | expected = { 75 | "oauth_consumer_key" => "consumer_key_86cad9", 76 | "oauth_signature_method" => "HMAC-SHA1", 77 | "oauth_timestamp" => "0", 78 | "oauth_nonce" => "nonce", 79 | "oauth_version" => "1.0" 80 | } 81 | assert_equal expected, helper.oauth_parameters 82 | end 83 | end 84 | end 85 | 86 | def test_oauth_parameters_allow_empty_params_only_oauth_token_as_string 87 | input = "oauth_token" 88 | helper = OAuth::Client::Helper.new(nil, { 89 | consumer: @consumer, 90 | allow_empty_params: input 91 | }) 92 | helper.stub :timestamp, "0" do 93 | helper.stub :nonce, "nonce" do 94 | expected = { 95 | "oauth_consumer_key" => "consumer_key_86cad9", 96 | "oauth_token" => "", 97 | "oauth_signature_method" => "HMAC-SHA1", 98 | "oauth_timestamp" => "0", 99 | "oauth_nonce" => "nonce", 100 | "oauth_version" => "1.0" 101 | } 102 | assert_equal expected, helper.oauth_parameters 103 | end 104 | end 105 | end 106 | 107 | def test_oauth_parameters_allow_empty_params_only_oauth_token_as_array 108 | input = ["oauth_token"] 109 | helper = OAuth::Client::Helper.new(nil, { 110 | consumer: @consumer, 111 | allow_empty_params: input 112 | }) 113 | helper.stub :timestamp, "0" do 114 | helper.stub :nonce, "nonce" do 115 | expected = { 116 | "oauth_consumer_key" => "consumer_key_86cad9", 117 | "oauth_token" => "", 118 | "oauth_signature_method" => "HMAC-SHA1", 119 | "oauth_timestamp" => "0", 120 | "oauth_nonce" => "nonce", 121 | "oauth_version" => "1.0" 122 | } 123 | assert_equal expected, helper.oauth_parameters 124 | end 125 | end 126 | end 127 | 128 | def test_oauth_parameters_allow_empty_params_oauth_token_and_oauth_session_handle 129 | input = %w[oauth_token oauth_session_handle] 130 | helper = OAuth::Client::Helper.new(nil, { 131 | consumer: @consumer, 132 | allow_empty_params: input 133 | }) 134 | helper.stub :timestamp, "0" do 135 | helper.stub :nonce, "nonce" do 136 | expected = { 137 | "oauth_consumer_key" => "consumer_key_86cad9", 138 | "oauth_token" => "", 139 | "oauth_signature_method" => "HMAC-SHA1", 140 | "oauth_timestamp" => "0", 141 | "oauth_nonce" => "nonce", 142 | "oauth_version" => "1.0", 143 | "oauth_session_handle" => nil 144 | } 145 | assert_equal expected, helper.oauth_parameters 146 | end 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /test/units/curb_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | begin 6 | require "oauth/request_proxy/curb_request" 7 | require "curb" 8 | 9 | class CurbRequestProxyTest < Minitest::Test 10 | def test_that_proxy_simple_get_request_works 11 | request = Curl::Easy.new("/test?key=value") 12 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test?key=value" }) 13 | 14 | expected_parameters = { "key" => ["value"] } 15 | assert_equal expected_parameters, request_proxy.parameters_for_signature 16 | assert_equal "http://example.com/test", request_proxy.normalized_uri 17 | end 18 | 19 | def test_that_proxy_simple_post_request_works_with_arguments 20 | request = Curl::Easy.new("/test") 21 | params = { "key" => "value" } 22 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 23 | 24 | expected_parameters = { "key" => "value" } 25 | assert_equal expected_parameters, request_proxy.parameters_for_signature 26 | assert_equal "http://example.com/test", request_proxy.normalized_uri 27 | end 28 | 29 | def test_that_proxy_simple_post_request_works_with_form_data 30 | request = Curl::Easy.new("/test") 31 | request.post_body = "key=value" 32 | request.headers["Content-Type"] = "application/x-www-form-urlencoded" 33 | 34 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 35 | 36 | expected_parameters = { "key" => "value" } 37 | assert_equal expected_parameters, request_proxy.parameters_for_signature 38 | assert_equal "http://example.com/test", request_proxy.normalized_uri 39 | end 40 | 41 | def test_that_proxy_simple_put_request_works_with_arguments 42 | request = Curl::Easy.new("/test") 43 | params = { "key" => "value" } 44 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 45 | 46 | expected_parameters = { "key" => "value" } 47 | assert_equal expected_parameters, request_proxy.parameters_for_signature 48 | assert_equal "http://example.com/test", request_proxy.normalized_uri 49 | end 50 | 51 | def test_that_proxy_simple_put_request_works_with_form_data 52 | request = Curl::Easy.new("/test") 53 | request.post_body = "key=value" 54 | 55 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 56 | 57 | expected_parameters = {} 58 | assert_equal expected_parameters, request_proxy.parameters_for_signature 59 | assert_equal "http://example.com/test", request_proxy.normalized_uri 60 | end 61 | 62 | def test_that_proxy_post_request_works_with_mixed_parameter_sources 63 | request = Curl::Easy.new("/test?key=value") 64 | request.post_body = "key2=value2" 65 | request.headers["Content-Type"] = "application/x-www-form-urlencoded" 66 | request_proxy = OAuth::RequestProxy.proxy(request, 67 | { uri: "http://example.com/test?key=value", 68 | parameters: { "key3" => "value3" } }) 69 | 70 | expected_parameters = { "key" => ["value"], "key2" => "value2", "key3" => "value3" } 71 | assert_equal expected_parameters, request_proxy.parameters_for_signature 72 | assert_equal "http://example.com/test", request_proxy.normalized_uri 73 | end 74 | end 75 | rescue LoadError => e 76 | warn "! problems loading curb, skipping these tests: #{e}" 77 | end 78 | -------------------------------------------------------------------------------- /test/units/em_http_client_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | begin 5 | require "oauth/client/em_http" 6 | 7 | class EmHttpClientTest < Minitest::Test 8 | def setup 9 | @consumer = OAuth::Consumer.new("consumer_key_86cad9", "5888bf0345e5d237") 10 | @token = OAuth::Token.new("token_411a7f", "3196ffd991c8ebdb") 11 | @request_uri = URI.parse("http://example.com/test?key=value") 12 | @request_parameters = { "key" => "value" } 13 | @nonce = 225_579_211_881_198_842_005_988_698_334_675_835_446 14 | @timestamp = "1199645624" 15 | # This is really unneeded I guess. 16 | @http = Net::HTTP.new(@request_uri.host, @request_uri.port) 17 | end 18 | 19 | def test_that_using_auth_headers_on_get_requests_works 20 | request = create_client 21 | request.oauth!(@http, @consumer, @token, { nonce: @nonce, timestamp: @timestamp }) 22 | 23 | assert_equal "GET", request.req[:method] 24 | assert_equal "/test", request.normalize_uri.path 25 | assert_equal "key=value", request.normalize_uri.query 26 | correct_headers = "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"1oO2izFav1GP4kEH2EskwXkCRFg%3D\", oauth_version=\"1.0\"" 27 | generated_headers = authz_header(request) 28 | assert_equal_authz_headers correct_headers, generated_headers 29 | end 30 | 31 | def test_that_using_auth_headers_on_get_requests_works_with_plaintext 32 | c = OAuth::Consumer.new("consumer_key_86cad9", "5888bf0345e5d237", { 33 | signature_method: "PLAINTEXT" 34 | }) 35 | request = create_client 36 | request.oauth!(@http, c, @token, { nonce: @nonce, timestamp: @timestamp, signature_method: "PLAINTEXT" }) 37 | 38 | assert_equal "GET", request.req[:method] 39 | assert_equal "/test", request.conn.path 40 | assert_equal "key=value", request.conn.query 41 | assert_equal_authz_headers "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"PLAINTEXT\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"5888bf0345e5d237%263196ffd991c8ebdb\", oauth_version=\"1.0\"", 42 | authz_header(request) 43 | end 44 | 45 | def test_that_using_auth_headers_on_post_requests_works 46 | request = create_client(uri: "http://example.com/test", method: "POST", body: @request_parameters, 47 | head: { "Content-Type" => "application/x-www-form-urlencoded" }) 48 | request.oauth!(@http, @consumer, @token, { nonce: @nonce, timestamp: @timestamp }) 49 | 50 | assert_equal "POST", request.req[:method] 51 | assert_equal "/test", request.conn.path 52 | assert_equal_authz_headers "OAuth oauth_nonce=\"225579211881198842005988698334675835446\", oauth_signature_method=\"HMAC-SHA1\", oauth_token=\"token_411a7f\", oauth_timestamp=\"1199645624\", oauth_consumer_key=\"consumer_key_86cad9\", oauth_signature=\"26g7wHTtNO6ZWJaLltcueppHYiI%3D\", oauth_version=\"1.0\"", 53 | authz_header(request) 54 | assert_equal "key=value", request.normalize_body(request.req[:body]) 55 | end 56 | 57 | protected 58 | 59 | def create_client(options = {}) 60 | options[:method] = options.key?(:method) ? options[:method].upcase : "GET" 61 | uri = options.delete(:uri) || @request_uri.to_s 62 | EventMachine::HttpClient.new(URI.parse(uri), options) 63 | end 64 | 65 | def authz_header(request) 66 | request.req[:head]["Authorization"] 67 | end 68 | 69 | def assert_equal_authz_headers(expected, actual) 70 | refute_nil actual 71 | assert_equal expected[0, 6], actual[0, 6] 72 | assert_equal expected[6..1].split(", ").sort, actual[6..1].split(", ").sort 73 | end 74 | end 75 | rescue LoadError => e 76 | warn "! problem loading em-http, skipping these tests: #{e}" 77 | end 78 | -------------------------------------------------------------------------------- /test/units/em_http_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | begin 6 | require "em-http" 7 | require "oauth/request_proxy/em_http_request" 8 | 9 | class EmHttpRequestProxyTest < Minitest::Test 10 | def test_request_proxy_works_with_simple_request 11 | proxy = create_request_proxy 12 | assert_empty(proxy.parameters) 13 | end 14 | 15 | def test_request_proxy_works_with_query_string_params 16 | assert_equal({ "name" => ["Fred"] }, create_request_proxy(query: "name=Fred").parameters) 17 | assert_equal({ "name" => ["Fred"] }, create_request_proxy(query: { name: "Fred" }).parameters) 18 | proxy = create_request_proxy(query: { name: "Fred" }, uri: "http://example.com/?awesome=true") 19 | assert_equal({ "name" => ["Fred"], "awesome" => ["true"] }, proxy.parameters) 20 | end 21 | 22 | def test_request_proxy_works_with_post_body_params_with_correct_content_type 23 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "POST" 24 | assert_empty(proxy.parameters) 25 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "POST", 26 | body: "a=1" 27 | assert_equal({ "a" => ["1"] }, proxy.parameters) 28 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "POST", 29 | body: { "a" => 1 } 30 | assert_equal({ "a" => ["1"] }, proxy.parameters) 31 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "PUT" 32 | assert_empty(proxy.parameters) 33 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "PUT", 34 | body: "a=1" 35 | assert_equal({ "a" => ["1"] }, proxy.parameters) 36 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "PUT", 37 | body: { "a" => 1 } 38 | assert_equal({ "a" => ["1"] }, proxy.parameters) 39 | end 40 | 41 | def test_request_proxy_ignore_post_body_with_invalid_content_type 42 | proxy = create_request_proxy head: { "Content-Type" => "text/plain" }, method: "POST" 43 | assert_empty(proxy.parameters) 44 | proxy = create_request_proxy head: { "Content-Type" => "text/plain" }, method: "POST", body: "a=1" 45 | assert_empty(proxy.parameters) 46 | proxy = create_request_proxy head: { "Content-Type" => "text/plain" }, method: "POST", body: { "a" => 1 } 47 | assert_empty(proxy.parameters) 48 | proxy = create_request_proxy head: { "Content-Type" => "text/plain" }, method: "PUT" 49 | assert_empty(proxy.parameters) 50 | proxy = create_request_proxy head: { "Content-Type" => "text/plain" }, method: "PUT", body: "a=1" 51 | assert_empty(proxy.parameters) 52 | proxy = create_request_proxy head: { "Content-Type" => "text/plain" }, method: "PUT", body: { "a" => 1 } 53 | assert_empty(proxy.parameters) 54 | end 55 | 56 | def test_request_proxy_ignores_post_body_with_invalid_method 57 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, 58 | method: "DELETE" 59 | assert_empty(proxy.parameters) 60 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "DELETE", 61 | body: "a=1" 62 | assert_empty(proxy.parameters) 63 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "DELETE", 64 | body: { "a" => 1 } 65 | assert_empty(proxy.parameters) 66 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "GET" 67 | assert_empty(proxy.parameters) 68 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "GET", 69 | body: "a=1" 70 | assert_empty(proxy.parameters) 71 | proxy = create_request_proxy head: { "Content-Type" => "application/x-www-form-urlencoded" }, method: "GET", 72 | body: { "a" => 1 } 73 | assert_empty(proxy.parameters) 74 | end 75 | 76 | def test_request_proxy_works_with_argument_params 77 | assert_equal({ "a" => ["1"] }, 78 | create_request_proxy(proxy_options: { parameters: { "a" => "1" } }).parameters) 79 | end 80 | 81 | def test_request_proxy_works_with_mixed_params 82 | proxy = create_request_proxy(proxy_options: { parameters: { "a" => "1" } }, query: { "c" => "1" }, 83 | uri: "http://example.com/test?b=1") 84 | assert_equal({ "a" => ["1"], "b" => ["1"], "c" => ["1"] }, proxy.parameters) 85 | proxy = create_request_proxy(proxy_options: { parameters: { "a" => "1" } }, body: { "b" => "1" }, query: { "c" => "1" }, 86 | uri: "http://example.com/test?d=1", method: "POST", head: { "Content-Type" => "application/x-www-form-urlencoded" }) 87 | assert_equal({ "a" => ["1"], "b" => ["1"], "c" => ["1"], "d" => ["1"] }, proxy.parameters) 88 | end 89 | 90 | def test_request_has_the_correct_uri 91 | assert_equal "http://example.com/", create_request_proxy.uri 92 | assert_equal "http://example.com/?a=1", create_request_proxy(query: "a=1").request.normalize_uri.to_s 93 | assert_equal "http://example.com/?a=1", create_request_proxy(query: { "a" => "1" }).request.normalize_uri.to_s 94 | end 95 | 96 | def test_request_proxy_has_correct_method 97 | assert_equal "GET", create_request_proxy(method: "GET").request.req[:method] 98 | assert_equal "PUT", create_request_proxy(method: "PUT").request.req[:method] 99 | assert_equal "POST", create_request_proxy(method: "POST").request.req[:method] 100 | assert_equal "DELETE", create_request_proxy(method: "DELETE").request.req[:method] 101 | end 102 | 103 | protected 104 | 105 | def create_client(options = {}) 106 | options[:method] = options.key?(:method) ? options[:method].upcase : "GET" 107 | uri = options.delete(:uri) || "http://example.com/" 108 | EventMachine::HttpClient.new(URI.parse(uri), options) 109 | end 110 | 111 | def create_request_proxy(opts = {}) 112 | arguments = opts.delete(:proxy_options) || {} 113 | OAuth::RequestProxy.proxy(create_client(opts), arguments) 114 | end 115 | end 116 | rescue LoadError => e 117 | warn "! problem loading em-http, skipping these tests: #{e}" 118 | end 119 | -------------------------------------------------------------------------------- /test/units/hmac_sha1_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class TestSignatureHmacSha1 < Minitest::Test 6 | def test_that_hmac_sha1_implements_hmac_sha1 7 | assert_includes OAuth::Signature.available_methods, "hmac-sha1" 8 | end 9 | 10 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 11 | request = Net::HTTP::Get.new("/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1") 12 | 13 | consumer = OAuth::Consumer.new("dpf43f3p2l4k3l03", "kd94hf93k423kf44") 14 | token = OAuth::Token.new("nnch734d00sl2jdk", "pfkkdhi9sl3r4s00") 15 | 16 | signature = OAuth::Signature.sign(request, { consumer: consumer, 17 | token: token, 18 | uri: "http://photos.example.net/photos" }) 19 | 20 | assert_equal "tR3+Ty81lMeYAr/Fid0kMTYa/WM=", signature 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/units/hmac_sha256_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class TestSignatureHmacSha256 < Minitest::Test 6 | def test_that_hmac_sha256_implements_hmac_sha256 7 | assert_includes OAuth::Signature.available_methods, "hmac-sha256" 8 | end 9 | 10 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 11 | request = Net::HTTP::Get.new("/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA256") 12 | 13 | consumer = OAuth::Consumer.new("dpf43f3p2l4k3l03", "kd94hf93k423kf44") 14 | token = OAuth::Token.new("nnch734d00sl2jdk", "pfkkdhi9sl3r4s00") 15 | 16 | signature = OAuth::Signature.sign(request, { consumer: consumer, 17 | token: token, 18 | uri: "http://photos.example.net/photos", 19 | signature_method: "HMAC-SHA256" }) 20 | 21 | assert_equal "WVPzl1j6ZsnkIjWr7e3OZ3jkenL57KwaLFhYsroX1hg=", signature 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/units/net_http_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class NetHTTPRequestProxyTest < Minitest::Test 6 | def test_that_proxy_simple_get_request_works 7 | request = Net::HTTP::Get.new("/test?key=value") 8 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test?key=value" }) 9 | 10 | expected_parameters = { "key" => ["value"] } 11 | assert_equal expected_parameters, request_proxy.parameters_for_signature 12 | assert_equal "http://example.com/test", request_proxy.normalized_uri 13 | assert_equal "GET", request_proxy.method 14 | end 15 | 16 | def test_that_proxy_simple_post_request_works_with_arguments 17 | request = Net::HTTP::Post.new("/test") 18 | params = { "key" => "value" } 19 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 20 | 21 | expected_parameters = { "key" => ["value"] } 22 | assert_equal expected_parameters, request_proxy.parameters_for_signature 23 | assert_equal "http://example.com/test", request_proxy.normalized_uri 24 | assert_equal "POST", request_proxy.method 25 | end 26 | 27 | def test_that_proxy_simple_post_request_works_with_form_data 28 | request = Net::HTTP::Post.new("/test") 29 | params = { "key" => "value" } 30 | request.set_form_data(params) 31 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 32 | 33 | expected_parameters = { "key" => ["value"] } 34 | assert_equal expected_parameters, request_proxy.parameters_for_signature 35 | assert_equal "http://example.com/test", request_proxy.normalized_uri 36 | assert_equal "POST", request_proxy.method 37 | end 38 | 39 | def test_that_proxy_simple_put_request_works_with_argugments 40 | request = Net::HTTP::Put.new("/test") 41 | params = { "key" => "value" } 42 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 43 | 44 | expected_parameters = { "key" => ["value"] } 45 | assert_equal expected_parameters, request_proxy.parameters_for_signature 46 | assert_equal "http://example.com/test", request_proxy.normalized_uri 47 | assert_equal "PUT", request_proxy.method 48 | end 49 | 50 | def test_that_proxy_simple_put_request_works_with_form_data 51 | request = Net::HTTP::Put.new("/test") 52 | params = { "key" => "value" } 53 | request.set_form_data(params) 54 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 55 | 56 | expected_parameters = { "key" => ["value"] } 57 | assert_equal expected_parameters, request_proxy.parameters_for_signature 58 | assert_equal "http://example.com/test", request_proxy.normalized_uri 59 | assert_equal "PUT", request_proxy.method 60 | end 61 | 62 | def test_that_proxy_post_request_uses_post_parameters 63 | request = Net::HTTP::Post.new("/test?key=value") 64 | request.set_form_data({ "key2" => "value2" }) 65 | request_proxy = OAuth::RequestProxy.proxy(request, 66 | { uri: "http://example.com/test?key=value", 67 | parameters: { "key3" => "value3" } }) 68 | 69 | expected_parameters = { "key" => ["value"], "key2" => ["value2"], "key3" => ["value3"] } 70 | assert_equal expected_parameters, request_proxy.parameters_for_signature 71 | assert_equal "http://example.com/test", request_proxy.normalized_uri 72 | assert_equal "POST", request_proxy.method 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/units/oauth_helper_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class TestOAuthHelper < Minitest::Test 6 | def test_parse_valid_header 7 | header = "OAuth " \ 8 | 'realm="http://example.com/method", ' \ 9 | 'oauth_consumer_key="vince_clortho", ' \ 10 | 'oauth_token="token_value", ' \ 11 | 'oauth_signature_method="HMAC-SHA1", ' \ 12 | 'oauth_signature="signature_here", ' \ 13 | 'oauth_timestamp="1240004133", oauth_nonce="nonce", ' \ 14 | 'oauth_version="1.0" ' 15 | 16 | params = OAuth::Helper.parse_header(header) 17 | 18 | assert_equal "http://example.com/method", params["realm"] 19 | assert_equal "vince_clortho", params["oauth_consumer_key"] 20 | assert_equal "token_value", params["oauth_token"] 21 | assert_equal "HMAC-SHA1", params["oauth_signature_method"] 22 | assert_equal "signature_here", params["oauth_signature"] 23 | assert_equal "1240004133", params["oauth_timestamp"] 24 | assert_equal "nonce", params["oauth_nonce"] 25 | assert_equal "1.0", params["oauth_version"] 26 | end 27 | 28 | def test_parse_header_ill_formed 29 | header = "OAuth garbage" 30 | 31 | assert_raises OAuth::Problem do 32 | OAuth::Helper.parse_header(header) 33 | end 34 | end 35 | 36 | def test_parse_header_contains_equals 37 | header = "OAuth " \ 38 | 'realm="http://example.com/method", ' \ 39 | 'oauth_consumer_key="vince_clortho", ' \ 40 | 'oauth_token="token_value", ' \ 41 | 'oauth_signature_method="HMAC-SHA1", ' \ 42 | 'oauth_signature="signature_here_with_=", ' \ 43 | 'oauth_timestamp="1240004133", oauth_nonce="nonce", ' \ 44 | 'oauth_version="1.0" ' 45 | 46 | assert_raises OAuth::Problem do 47 | OAuth::Helper.parse_header(header) 48 | end 49 | end 50 | 51 | def test_parse_valid_header_with_and_signs 52 | header = "OAuth " \ 53 | 'realm="http://example.com/method"&' \ 54 | 'oauth_consumer_key="vince_clortho"&' \ 55 | 'oauth_token="token_value"&' \ 56 | 'oauth_signature_method="HMAC-SHA1"&' \ 57 | 'oauth_signature="signature_here"&' \ 58 | 'oauth_timestamp="1240004133"&oauth_nonce="nonce"&' \ 59 | 'oauth_version="1.0"' 60 | 61 | params = OAuth::Helper.parse_header(header) 62 | 63 | assert_equal "http://example.com/method", params["realm"] 64 | assert_equal "vince_clortho", params["oauth_consumer_key"] 65 | assert_equal "token_value", params["oauth_token"] 66 | assert_equal "HMAC-SHA1", params["oauth_signature_method"] 67 | assert_equal "signature_here", params["oauth_signature"] 68 | assert_equal "1240004133", params["oauth_timestamp"] 69 | assert_equal "nonce", params["oauth_nonce"] 70 | assert_equal "1.0", params["oauth_version"] 71 | end 72 | 73 | def test_normalize 74 | params = { 75 | "oauth_nonce" => "nonce", 76 | "weight" => { value: "65" }, 77 | "oauth_signature_method" => "HMAC-SHA1", 78 | "oauth_timestamp" => "1240004133", 79 | "oauth_consumer_key" => "vince_clortho", 80 | "oauth_token" => "token_value", 81 | "oauth_version" => "1.0" 82 | } 83 | assert_equal( 84 | "oauth_consumer_key=vince_clortho&oauth_nonce=nonce&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1240004133&oauth_token=token_value&oauth_version=1.0&weight%5Bvalue%5D=65", OAuth::Helper.normalize(params) 85 | ) 86 | end 87 | 88 | def test_normalize_with_nested_array_of_hashes 89 | params = { 90 | "oauth_nonce" => "nonce", 91 | "weight" => { value: "65" }, 92 | "items" => [{ "a" => 1 }, { "b" => 2 }], 93 | "oauth_signature_method" => "HMAC-SHA1", 94 | "oauth_timestamp" => "1240004133", 95 | "oauth_consumer_key" => "vince_clortho", 96 | "oauth_token" => "token_value", 97 | "oauth_version" => "1.0" 98 | } 99 | assert_equal( 100 | "items%5B%5D%5Ba%5D=1&items%5B%5D%5Bb%5D=2&oauth_consumer_key=vince_clortho&oauth_nonce=nonce&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1240004133&oauth_token=token_value&oauth_version=1.0&weight%5Bvalue%5D=65", OAuth::Helper.normalize(params) 101 | ) 102 | end 103 | 104 | def test_normalize_nested_query 105 | assert_empty(OAuth::Helper.normalize_nested_query({})) 106 | assert_equal(["foo=bar"], OAuth::Helper.normalize_nested_query({ foo: "bar" })) 107 | assert_equal(["prefix%5Bfoo%5D=bar"], OAuth::Helper.normalize_nested_query({ foo: "bar" }, "prefix")) 108 | assert_equal(["prefix%5Buser%5D%5Bage%5D=12", 109 | "prefix%5Buser%5D%5Bdate%5D=2011-10-05", 110 | "prefix%5Buser%5D%5Btwitter_id%5D=123"], OAuth::Helper.normalize_nested_query({ user: { twitter_id: 123, date: "2011-10-05", age: 12 } }, "prefix")) 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /test/units/rack_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/request_proxy/rack_request" 6 | 7 | class RackRequestProxyTest < Minitest::Test 8 | def test_that_proxy_simple_get_request_works 9 | request = Rack::Request.new(Rack::MockRequest.env_for("http://example.com/test?key=value")) 10 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test?key=value" }) 11 | 12 | expected_parameters = { "key" => "value" } 13 | assert_equal expected_parameters, request_proxy.parameters 14 | assert_equal "http://example.com/test", request_proxy.normalized_uri 15 | assert_equal "GET", request_proxy.method 16 | end 17 | 18 | def test_that_proxy_simple_post_request_works 19 | request = Rack::Request.new(Rack::MockRequest.env_for("http://example.com/test", method: "POST")) 20 | params = { "key" => "value" } 21 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 22 | 23 | expected_parameters = { "key" => "value" } 24 | assert_equal expected_parameters, request_proxy.parameters 25 | assert_equal "http://example.com/test", request_proxy.normalized_uri 26 | assert_equal "POST", request_proxy.method 27 | end 28 | 29 | def test_that_proxy_post_and_get_request_works 30 | request = Rack::Request.new(Rack::MockRequest.env_for("http://example.com/test?key=value", method: "POST", 31 | input: "key2=value2")) 32 | params = { "key2" => "value2" } 33 | request_proxy = OAuth::RequestProxy.proxy(request, 34 | { uri: "http://example.com/test?key=value", parameters: params }) 35 | 36 | expected_parameters = { "key" => "value", "key2" => "value2" } 37 | assert_equal expected_parameters, request_proxy.parameters 38 | assert_equal "http://example.com/test", request_proxy.normalized_uri 39 | assert_equal "POST", request_proxy.method 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/units/request_token_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class StubbedToken < OAuth::RequestToken 6 | define_method :build_url_promoted do |root_domain, params| 7 | build_url root_domain, params 8 | end 9 | end 10 | 11 | class TestRequestToken < Minitest::Test 12 | def setup 13 | # setup a fake req. token. mocking Consumer would be more appropriate... 14 | @request_token = OAuth::RequestToken.new( 15 | OAuth::Consumer.new("key", "secret", {}), 16 | "key", 17 | "secret" 18 | ) 19 | end 20 | 21 | def test_request_token_builds_authorize_url_connectly_with_additional_params 22 | auth_url = @request_token.authorize_url({ oauth_callback: "github.com" }) 23 | assert auth_url 24 | assert_match(/oauth_token/, auth_url) 25 | assert_match(/oauth_callback/, auth_url) 26 | end 27 | 28 | def test_request_token_builds_authorize_url_connectly_with_no_or_nil_params 29 | # we should only have 1 key in the url returned if we didn't pass anything. 30 | # this is the only required param to authenticate the client. 31 | auth_url = @request_token.authorize_url(nil) 32 | assert auth_url 33 | assert_match(/\?oauth_token=/, auth_url) 34 | 35 | auth_url = @request_token.authorize_url 36 | assert auth_url 37 | assert_match(/\?oauth_token=/, auth_url) 38 | end 39 | 40 | def test_request_token_returns_nil_authorize_url_when_token_is_nil 41 | @request_token.token = nil 42 | assert_nil @request_token.authorize_url 43 | end 44 | 45 | def test_request_token_builds_authenticate_url_connectly_with_additional_params 46 | authenticate_url = @request_token.authenticate_url({ oauth_callback: "github.com" }) 47 | assert authenticate_url 48 | assert_match(/oauth_token/, authenticate_url) 49 | assert_match(/oauth_callback/, authenticate_url) 50 | end 51 | 52 | def test_request_token_builds_authenticate_url_connectly_with_no_or_nil_params 53 | # we should only have 1 key in the url returned if we didn't pass anything. 54 | # this is the only required param to authenticate the client. 55 | authenticate_url = @request_token.authenticate_url(nil) 56 | assert authenticate_url 57 | assert_match(/\?oauth_token=/, authenticate_url) 58 | 59 | authenticate_url2 = @request_token.authenticate_url 60 | assert authenticate_url2 61 | assert_match(/\?oauth_token=/, authenticate_url2) 62 | end 63 | 64 | def test_request_token_returns_nil_authenticate_url_when_token_is_nil 65 | @request_token.token = nil 66 | assert_nil @request_token.authenticate_url 67 | end 68 | 69 | # TODO: mock out the Consumer to test the Consumer/AccessToken interaction. 70 | def test_get_access_token; end 71 | 72 | def test_build_url 73 | @stubbed_token = StubbedToken.new(nil, nil, nil) 74 | assert_respond_to @stubbed_token, :build_url_promoted 75 | url = @stubbed_token.build_url_promoted( 76 | "http://github.com/oauth/authorize", 77 | { foo: "bar bar" } 78 | ) 79 | assert url 80 | assert_equal "http://github.com/oauth/authorize?foo=bar+bar", url 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/units/rest_client_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/request_proxy/rest_client_request" 6 | require "rest-client" 7 | 8 | class RestlClientRequestProxyTest < Minitest::Test 9 | def test_that_proxy_simple_get_request_works 10 | request = ::RestClient::Request.new(method: :get, url: "http://example.com/test?key=value") 11 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test?key=value" }) 12 | 13 | expected_parameters = { "key" => ["value"] } 14 | assert_equal expected_parameters, request_proxy.parameters_for_signature 15 | assert_equal "http://example.com/test", request_proxy.normalized_uri 16 | assert_equal "GET", request_proxy.method 17 | end 18 | 19 | def test_that_proxy_simple_post_request_works_with_arguments 20 | request = ::RestClient::Request.new(method: :post, url: "http://example.com/test") 21 | params = { "key" => "value" } 22 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 23 | 24 | expected_parameters = { "key" => "value" } 25 | assert_equal expected_parameters, request_proxy.parameters_for_signature 26 | assert_equal "http://example.com/test", request_proxy.normalized_uri 27 | assert_equal "POST", request_proxy.method 28 | end 29 | 30 | def test_that_proxy_simple_post_request_works_with_form_data 31 | request = ::RestClient::Request.new(method: :post, url: "http://example.com/test", 32 | payload: { "key" => "value" }, 33 | headers: { "Content-Type" => "application/x-www-form-urlencoded" }) 34 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 35 | 36 | expected_parameters = { "key" => "value" } 37 | assert_equal expected_parameters, request_proxy.parameters_for_signature 38 | assert_equal "http://example.com/test", request_proxy.normalized_uri 39 | assert_equal "POST", request_proxy.method 40 | end 41 | 42 | def test_that_proxy_simple_post_request_ignores_non_form_data_payload 43 | request = ::RestClient::Request.new( 44 | method: :post, url: "http://example.com/test", payload: '{"key": "value"}', headers: { content_type: :json } 45 | ) 46 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 47 | 48 | assert_empty request_proxy.parameters_for_signature 49 | assert_equal "http://example.com/test", request_proxy.normalized_uri 50 | assert_equal "POST", request_proxy.method 51 | end 52 | 53 | def test_that_proxy_simple_put_request_works_with_arguments 54 | request = ::RestClient::Request.new(method: :put, url: "http://example.com/test") 55 | params = { "key" => "value" } 56 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 57 | 58 | expected_parameters = { "key" => "value" } 59 | assert_equal expected_parameters, request_proxy.parameters_for_signature 60 | assert_equal "http://example.com/test", request_proxy.normalized_uri 61 | assert_equal "PUT", request_proxy.method 62 | end 63 | 64 | def test_that_proxy_simple_put_request_works_with_form_data 65 | request = ::RestClient::Request.new(method: :put, url: "http://example.com/test", 66 | payload: { "key" => "value" }, 67 | headers: { "Content-Type" => "application/x-www-form-urlencoded" }) 68 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 69 | 70 | expected_parameters = { "key" => "value" } 71 | assert_equal expected_parameters, request_proxy.parameters_for_signature 72 | assert_equal "http://example.com/test", request_proxy.normalized_uri 73 | assert_equal "PUT", request_proxy.method 74 | end 75 | 76 | def test_that_proxy_post_request_works_with_mixed_parameter_sources 77 | request = ::RestClient::Request.new(url: "http://example.com/test?key=value", 78 | method: :post, 79 | payload: { "key2" => "value2" }, 80 | headers: { "Content-Type" => "application/x-www-form-urlencoded" }) 81 | request_proxy = OAuth::RequestProxy.proxy(request, 82 | { uri: "http://example.com/test?key=value", 83 | parameters: { "key3" => "value3" } }) 84 | 85 | expected_parameters = { "key" => ["value"], "key2" => "value2", "key3" => "value3" } 86 | assert_equal expected_parameters, request_proxy.parameters_for_signature 87 | assert_equal "http://example.com/test", request_proxy.normalized_uri 88 | assert_equal "POST", request_proxy.method 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/units/rsa_sha1_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/consumer" 6 | require "oauth/signature/rsa/sha1" 7 | 8 | class TestSignatureRsaSha1 < Minitest::Test 9 | def setup 10 | @request = Net::HTTP::Get.new("/photos?file=vacaction.jpg&size=original&oauth_version=1.0&oauth_consumer_key=#{consumer_key}&oauth_timestamp=1196666512&oauth_nonce=13917289812797014437&oauth_signature_method=RSA-SHA1") 11 | 12 | @consumer = OAuth::Consumer.new(consumer_key, pkey_rsa) 13 | end 14 | 15 | def test_that_rsa_sha1_implements_rsa_sha1 16 | assert_includes OAuth::Signature.available_methods, "rsa-sha1" 17 | end 18 | 19 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature_base_string 20 | sbs = OAuth::Signature.signature_base_string(@request, { consumer: @consumer, 21 | uri: "http://photos.example.net/photos" }) 22 | 23 | assert_equal "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacaction.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3D13917289812797014437%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1196666512%26oauth_version%3D1.0%26size%3Doriginal", 24 | sbs 25 | end 26 | 27 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 28 | signature = OAuth::Signature.sign(@request, { consumer: @consumer, 29 | uri: "http://photos.example.net/photos" }) 30 | 31 | assert_equal "jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=", 32 | signature 33 | end 34 | 35 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature_using_private_key_file 36 | @consumer = OAuth::Consumer.new(consumer_key, nil) 37 | 38 | signature = OAuth::Signature.sign(@request, { consumer: @consumer, 39 | private_key_file: pem_path, 40 | uri: "http://photos.example.net/photos" }) 41 | 42 | assert_equal "jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=", 43 | signature 44 | end 45 | 46 | def test_that_get_request_from_oauth_test_cases_verifies_signature 47 | @request = Net::HTTP::Get.new("/photos?oauth_signature_method=RSA-SHA1&oauth_version=1.0&oauth_consumer_key=#{consumer_key}&oauth_timestamp=1196666512&oauth_nonce=13917289812797014437&file=vacaction.jpg&size=original&oauth_signature=jvTp%2FwX1TYtByB1m%2BPbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2%2F9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW%2F%2Fe%2BRinhejgCuzoH26dyF8iY2ZZ%2F5D1ilgeijhV%2FvBka5twt399mXwaYdCwFYE%3D") 48 | @consumer = OAuth::Consumer.new(consumer_key, x509_certificate) 49 | 50 | assert OAuth::Signature.verify(@request, { consumer: @consumer, 51 | uri: "http://photos.example.net/photos" }) 52 | end 53 | 54 | def test_that_get_request_from_oauth_test_cases_verifies_signature_with_pem 55 | @request = Net::HTTP::Get.new("/photos?oauth_signature_method=RSA-SHA1&oauth_version=1.0&oauth_consumer_key=#{consumer_key}&oauth_timestamp=1196666512&oauth_nonce=13917289812797014437&file=vacaction.jpg&size=original&oauth_signature=jvTp%2FwX1TYtByB1m%2BPbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2%2F9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW%2F%2Fe%2BRinhejgCuzoH26dyF8iY2ZZ%2F5D1ilgeijhV%2FvBka5twt399mXwaYdCwFYE%3D") 56 | assert OAuth::Signature.verify(@request, { consumer: @consumer, 57 | uri: "http://photos.example.net/photos" }) 58 | end 59 | 60 | private 61 | 62 | def consumer_key 63 | "dpf43f3p2l4k3l03" 64 | end 65 | 66 | def x509_certificate 67 | OpenSSL::X509::Certificate.new(File.read(cert_path)) 68 | end 69 | 70 | def pkey_rsa 71 | OpenSSL::PKey::RSA.new(File.read(pem_path)) 72 | end 73 | 74 | def cert_path 75 | "#{File.dirname(__FILE__)}/../keys/rsa.cert" 76 | end 77 | 78 | def pem_path 79 | "#{File.dirname(__FILE__)}/../keys/rsa.pem" 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/units/server_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/server" 6 | 7 | class ServerTest < Minitest::Test 8 | def setup 9 | @server = OAuth::Server.new "http://test.com" 10 | end 11 | 12 | def test_default_paths 13 | assert_equal "/oauth/request_token", @server.request_token_path 14 | assert_equal "/oauth/authorize", @server.authorize_path 15 | assert_equal "/oauth/access_token", @server.access_token_path 16 | end 17 | 18 | def test_default_urls 19 | assert_equal "http://test.com/oauth/request_token", @server.request_token_url 20 | assert_equal "http://test.com/oauth/authorize", @server.authorize_url 21 | assert_equal "http://test.com/oauth/access_token", @server.access_token_url 22 | end 23 | 24 | def test_generate_consumer_credentials 25 | consumer = @server.generate_consumer_credentials 26 | assert consumer.key 27 | assert consumer.secret 28 | end 29 | 30 | def test_create_consumer 31 | @consumer = @server.create_consumer 32 | assert @consumer 33 | assert @consumer.key 34 | assert @consumer.secret 35 | assert_equal "http://test.com", @consumer.site 36 | assert_equal "/oauth/request_token", @consumer.request_token_path 37 | assert_equal "/oauth/authorize", @consumer.authorize_path 38 | assert_equal "/oauth/access_token", @consumer.access_token_path 39 | assert_equal "http://test.com/oauth/request_token", @consumer.request_token_url 40 | assert_equal "http://test.com/oauth/authorize", @consumer.authorize_url 41 | assert_equal "http://test.com/oauth/access_token", @consumer.access_token_url 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/units/signature_base_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/signature/base" 6 | require "net/http" 7 | 8 | class SignatureBaseTest < Minitest::Test 9 | def test_that_initialize_requires_one_request_argument 10 | assert_raises ArgumentError do 11 | OAuth::Signature::Base.new 12 | end 13 | end 14 | 15 | def test_that_initialize_requires_a_valid_request_argument 16 | request = nil 17 | assert_raises TypeError do 18 | OAuth::Signature::Base.new(request) do |token| 19 | # just a stub 20 | end 21 | end 22 | end 23 | 24 | def test_that_initialize_succeeds_when_the_request_proxy_is_valid 25 | # this isn't quite valid, but it will do. 26 | raw_request = Net::HTTP::Get.new("/test") 27 | request = OAuth::RequestProxy.proxy(raw_request) 28 | 29 | OAuth::Signature::Base.new(request) do |token| 30 | # just a stub 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/units/signature_hmac_sha1_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/signature/hmac/sha1" 6 | 7 | class SignatureHMACSHA1Test < Minitest::Test 8 | def test_that_verify_returns_true_when_the_request_signature_is_right 9 | request = OAuth::RequestProxy::MockRequest.new( 10 | "method" => "POST", 11 | "uri" => "https://photos.example.net/initialize", 12 | "parameters" => { 13 | "oauth_consumer_key" => "dpf43f3p2l4k3l03", 14 | "oauth_signature_method" => "HMAC-SHA1", 15 | "oauth_timestamp" => "137131200", 16 | "oauth_nonce" => "wIjqoS", 17 | "oauth_callback" => "http://printer.example.com/ready", 18 | "oauth_version" => "1.0", 19 | "oauth_signature" => "xcHYBV3AbyoDz7L4dV10P3oLCjY=" 20 | } 21 | ) 22 | assert OAuth::Signature::HMAC::SHA1.new(request, consumer_secret: "kd94hf93k423kf44").verify 23 | end 24 | 25 | def test_that_verify_returns_false_when_the_request_signature_is_wrong 26 | # Test a bug in the OAuth::Signature::Base#== method: when the Base64.decode64 method is 27 | # used on the "self" and "other" signature (as in version 0.4.7), the result may be incorrectly "true". 28 | request = OAuth::RequestProxy::MockRequest.new( 29 | "method" => "POST", 30 | "uri" => "https://photos.example.net/initialize", 31 | "parameters" => { 32 | "oauth_consumer_key" => "dpf43f3p2l4k3l03", 33 | "oauth_signature_method" => "HMAC-SHA1", 34 | "oauth_timestamp" => "137131200", 35 | "oauth_nonce" => "wIjqoS", 36 | "oauth_callback" => "http://printer.example.com/ready", 37 | "oauth_version" => "1.0", 38 | "oauth_signature" => "xcHYBV3AbyoDz7L4dV10P3oLCjZ=" 39 | } 40 | ) 41 | refute OAuth::Signature::HMAC::SHA1.new(request, consumer_secret: "kd94hf93k423kf44").verify 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/units/signature_hmac_sha256_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/signature/hmac/sha256" 6 | 7 | class SignatureHMACSHA256Test < Minitest::Test 8 | def test_that_verify_returns_true_when_the_request_signature_is_right 9 | request = OAuth::RequestProxy::MockRequest.new( 10 | "method" => "POST", 11 | "uri" => "https://photos.example.net/initialize", 12 | "parameters" => { 13 | "oauth_consumer_key" => "dpf43f3p2l4k3l03", 14 | "oauth_signature_method" => "HMAC-SHA256", 15 | "oauth_timestamp" => "137131200", 16 | "oauth_nonce" => "wIjqoS", 17 | "oauth_callback" => "http://printer.example.com/ready", 18 | "oauth_version" => "1.0", 19 | "oauth_signature" => "tkpCGNHi3laWBHQ9+Ka5IOeixEuhxg12LTMlLJxQxKc=" 20 | } 21 | ) 22 | assert OAuth::Signature::HMAC::SHA256.new(request, consumer_secret: "kd94hf93k423kf44").verify 23 | end 24 | 25 | def test_that_verify_returns_false_when_the_request_signature_is_wrong 26 | # Test a bug in the OAuth::Signature::Base#== method: when the Base64.decode64 method is 27 | # used on the "self" and "other" signature (as in version 0.4.7), the result may be incorrectly "true". 28 | request = OAuth::RequestProxy::MockRequest.new( 29 | "method" => "POST", 30 | "uri" => "https://photos.example.net/initialize", 31 | "parameters" => { 32 | "oauth_consumer_key" => "dpf43f3p2l4k3l03", 33 | "oauth_signature_method" => "HMAC-SHA256", 34 | "oauth_timestamp" => "137131200", 35 | "oauth_nonce" => "wIjqoS", 36 | "oauth_callback" => "http://printer.example.com/ready", 37 | "oauth_version" => "1.0", 38 | "oauth_signature" => "tkpCGNHi3laWBHQ9+Ka5IOeixEuhxg12LTMlLJxQxKZ=" 39 | } 40 | ) 41 | refute OAuth::Signature::HMAC::SHA256.new(request, consumer_secret: "kd94hf93k423kf44").verify 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/units/signature_plain_text_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class TestSignaturePlaintext < Minitest::Test 6 | def test_that_plaintext_implements_plaintext 7 | assert_includes OAuth::Signature.available_methods, "plaintext" 8 | end 9 | 10 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature 11 | request = Net::HTTP::Get.new("/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_signature=kd94hf93k423kf44%26&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=PLAINTEXT") 12 | 13 | consumer = OAuth::Consumer.new("dpf43f3p2l4k3l03", "kd94hf93k423kf44") 14 | token = OAuth::Token.new("nnch734d00sl2jdk", nil) 15 | 16 | assert OAuth::Signature.verify(request, { consumer: consumer, 17 | token: token, 18 | uri: "http://photos.example.net/photos" }) 19 | end 20 | 21 | def test_that_get_request_from_oauth_test_cases_produces_matching_signature_part_two 22 | request = Net::HTTP::Get.new("/photos?file=vacation.jpg&size=original&oauth_version=1.0&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_token=nnch734d00sl2jdk&oauth_signature=kd94hf93k423kf44%26pfkkdhi9sl3r4s00&oauth_timestamp=1191242096&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=PLAINTEXT") 23 | 24 | consumer = OAuth::Consumer.new("dpf43f3p2l4k3l03", "kd94hf93k423kf44") 25 | token = OAuth::Token.new("nnch734d00sl2jdk", "pfkkdhi9sl3r4s00") 26 | 27 | assert OAuth::Signature.verify(request, { consumer: consumer, 28 | token: token, 29 | uri: "http://photos.example.net/photos" }) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/units/signature_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | class TestOauth < Minitest::Test 6 | def test_parameter_escaping_kcode_invariant 7 | %w[n N e E s S u U].each do |kcode| 8 | assert_equal "%E3%81%82", OAuth::Helper.escape("あ"), 9 | "Failed to correctly escape Japanese under $KCODE = #{kcode}" 10 | assert_equal "%C3%A9", OAuth::Helper.escape("é"), 11 | "Failed to correctly escape e+acute under $KCODE = #{kcode}" 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/units/token_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | require "oauth/token" 6 | 7 | class TokenTest < Minitest::Test 8 | def setup; end 9 | 10 | def test_token_constructor_produces_valid_token 11 | token = OAuth::Token.new("xyz", "123") 12 | assert_equal "xyz", token.token 13 | assert_equal "123", token.secret 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/units/typhoeus_request_proxy_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | begin 6 | require "oauth/request_proxy/typhoeus_request" 7 | require "typhoeus" 8 | 9 | class TyphoeusRequestProxyTest < Minitest::Test 10 | def test_that_proxy_simple_get_request_works 11 | request = ::Typhoeus::Request.new("/test?key=value") 12 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test?key=value" }) 13 | 14 | expected_parameters = { "key" => ["value"] } 15 | assert_equal expected_parameters, request_proxy.parameters_for_signature 16 | assert_equal "http://example.com/test", request_proxy.normalized_uri 17 | assert_equal "GET", request_proxy.method 18 | end 19 | 20 | def test_that_proxy_simple_post_request_works_with_arguments 21 | request = Typhoeus::Request.new("/test", method: :post) 22 | params = { "key" => "value" } 23 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 24 | 25 | expected_parameters = { "key" => "value" } 26 | assert_equal expected_parameters, request_proxy.parameters_for_signature 27 | assert_equal "http://example.com/test", request_proxy.normalized_uri 28 | assert_equal "POST", request_proxy.method 29 | end 30 | 31 | def test_that_proxy_simple_post_request_works_with_form_data 32 | request = Typhoeus::Request.new("/test", method: :post, 33 | params: { "key" => "value" }, 34 | headers: { "Content-Type" => "application/x-www-form-urlencoded" }) 35 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 36 | 37 | expected_parameters = { "key" => ["value"] } 38 | assert_equal expected_parameters, request_proxy.parameters_for_signature 39 | assert_equal "http://example.com/test", request_proxy.normalized_uri 40 | assert_equal "POST", request_proxy.method 41 | end 42 | 43 | def test_that_proxy_simple_put_request_works_with_arguments 44 | request = Typhoeus::Request.new("/test", method: :put) 45 | params = { "key" => "value" } 46 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 47 | 48 | expected_parameters = { "key" => "value" } 49 | assert_equal expected_parameters, request_proxy.parameters_for_signature 50 | assert_equal "http://example.com/test", request_proxy.normalized_uri 51 | assert_equal "PUT", request_proxy.method 52 | end 53 | 54 | def test_that_proxy_simple_put_request_works_with_form_data 55 | request = Typhoeus::Request.new("/test", method: :put, params: { "key" => "value" }) 56 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 57 | 58 | expected_parameters = { "key" => ["value"] } 59 | assert_equal expected_parameters, request_proxy.parameters_for_signature 60 | assert_equal "http://example.com/test", request_proxy.normalized_uri 61 | assert_equal "PUT", request_proxy.method 62 | end 63 | 64 | def test_that_proxy_simple_patch_request_works_with_arguments 65 | request = Typhoeus::Request.new("/test", method: :patch) 66 | params = { "key" => "value" } 67 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test", parameters: params }) 68 | 69 | expected_parameters = { "key" => "value" } 70 | assert_equal expected_parameters, request_proxy.parameters_for_signature 71 | assert_equal "http://example.com/test", request_proxy.normalized_uri 72 | assert_equal "PATCH", request_proxy.method 73 | end 74 | 75 | def test_that_proxy_simple_patch_request_works_with_form_data 76 | request = Typhoeus::Request.new("/test", method: :patch, params: { "key" => "value" }) 77 | request_proxy = OAuth::RequestProxy.proxy(request, { uri: "http://example.com/test" }) 78 | 79 | expected_parameters = { "key" => ["value"] } 80 | assert_equal expected_parameters, request_proxy.parameters_for_signature 81 | assert_equal "http://example.com/test", request_proxy.normalized_uri 82 | assert_equal "PATCH", request_proxy.method 83 | end 84 | 85 | def test_that_proxy_post_request_works_with_mixed_parameter_sources 86 | request = Typhoeus::Request.new("/test?key=value", 87 | method: :post, 88 | params: { "key2" => "value2" }, 89 | headers: { "Content-Type" => "application/x-www-form-urlencoded" }) 90 | request_proxy = OAuth::RequestProxy.proxy(request, 91 | { uri: "http://example.com/test?key=value", 92 | parameters: { "key3" => "value3" } }) 93 | 94 | expected_parameters = { "key" => ["value"], "key2" => ["value2"], "key3" => "value3" } 95 | assert_equal expected_parameters, request_proxy.parameters_for_signature 96 | assert_equal "http://example.com/test", request_proxy.normalized_uri 97 | assert_equal "POST", request_proxy.method 98 | end 99 | end 100 | rescue LoadError => e 101 | warn "! problem loading typhoeus, skipping these tests: #{e}" 102 | end 103 | --------------------------------------------------------------------------------