pax_global_header00006660000000000000000000000064143006622450014514gustar00rootroot0000000000000052 comment=d499c582ae505c4cec53836a5cfef0e034b29f33 oauth2-2.0.7/000077500000000000000000000000001430066224500127245ustar00rootroot00000000000000oauth2-2.0.7/.github/000077500000000000000000000000001430066224500142645ustar00rootroot00000000000000oauth2-2.0.7/.github/FUNDING.yml000066400000000000000000000015341430066224500161040ustar00rootroot00000000000000# These are supported funding model platforms github: [pboling] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: galtzo # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: pboling # Replace with a single Ko-fi username tidelift: rubygems/oauth2 # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: pboling # Replace with a single Liberapay username issuehunt: pboling # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] oauth2-2.0.7/.github/dependabot.yml000066400000000000000000000003221430066224500171110ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: bundler directory: "/" schedule: interval: daily time: "04:28" open-pull-requests-limit: 10 ignore: - dependency-name: "rubocop-lts" oauth2-2.0.7/.github/workflows/000077500000000000000000000000001430066224500163215ustar00rootroot00000000000000oauth2-2.0.7/.github/workflows/codeql-analysis.yml000066400000000000000000000045031430066224500221360ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master, main, "*-stable" ] pull_request: # The branches below must be a subset of the branches above branches: [ master, main, "*-stable" ] schedule: - cron: '35 1 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'ruby' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 oauth2-2.0.7/.github/workflows/coverage.yml000066400000000000000000000070541430066224500206450ustar00rootroot00000000000000name: Code Coverage env: CI_CODECOV: true COVER_ALL: true on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs with Coverage - Ruby ${{ matrix.ruby }} ${{ matrix.name_extra || '' }} if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [false] rubygems: - latest bundler: - latest ruby: - "2.7" runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - uses: amancevice/setup-code-climate@v0 name: CodeClimate Install if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() with: cc_test_reporter_id: ${{ secrets.CC_TEST_REPORTER_ID }} - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: CodeClimate Pre-build Notification run: cc-test-reporter before-build if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() continue-on-error: ${{ matrix.experimental != 'false' }} - name: Run tests run: bundle exec rake test - name: CodeClimate Post-build Notification run: cc-test-reporter after-build if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() continue-on-error: ${{ matrix.experimental != 'false' }} - name: Code Coverage Summary Report uses: irongut/CodeCoverageSummary@v1.2.0 with: filename: ./coverage/coverage.xml badge: true fail_below_min: true format: markdown hide_branch_rate: true hide_complexity: true indicators: true output: both thresholds: '100 95' continue-on-error: ${{ matrix.experimental != 'false' }} - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 if: matrix.ruby == '2.7' && always() with: recreate: true path: code-coverage-results.md continue-on-error: ${{ matrix.experimental != 'false' }} - name: Coveralls uses: coverallsapp/github-action@master if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() with: github-token: ${{ secrets.GITHUB_TOKEN }} continue-on-error: ${{ matrix.experimental != 'false' }} # Using the codecov gem instead. # - name: CodeCov # uses: codecov/codecov-action@v2 # if: matrix.ruby == '2.7' && github.event_name != 'pull_request' && always() # with: # files: ./coverage/coverage.xml # flags: unittests # name: codecov-upload # fail_ci_if_error: true # continue-on-error: ${{ matrix.experimental != 'false' }} oauth2-2.0.7/.github/workflows/danger.yml000066400000000000000000000023031430066224500203020ustar00rootroot00000000000000name: What's up Danger? on: pull_request: branches: - 'main' - 'master' - '*-stable' jobs: danger: runs-on: ubuntu-latest env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: false # if: github.event_name == 'pull_request' # if only run pull request when multiple trigger workflow strategy: fail-fast: false matrix: gemfile: - f2 rubygems: - latest bundler: - latest ruby: - "2.7" steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - uses: MeilCli/danger-action@v5 with: plugins_file: 'Gemfile' install_path: 'vendor/bundle' danger_file: 'Dangerfile' danger_id: 'danger-pr' env: DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} oauth2-2.0.7/.github/workflows/heads.yml000066400000000000000000000043201430066224500201270ustar00rootroot00000000000000name: Heads on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [true] gemfile: - f0 - f1 - f2 rubygems: - latest bundler: - latest ruby: - truffleruby+graalvm-head - truffleruby-head - ruby-head include: # Includes a new variable experimental with a value of false # for the matrix legs matching rubygems: latest, which is all of them. # This is here for parity with the unsupported.yml # This is a hack. Combined with continue-on-error it should allow us # to have a workflow with allowed failure. # This is the "supported" build matrix, so only the "head" builds are experimental here. - rubygems: latest experimental: true runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/jruby-head.yml000066400000000000000000000042461430066224500211040ustar00rootroot00000000000000name: JRuby Head on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "false" # if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [true] gemfile: - f0 - f1 - f2 rubygems: - latest bundler: - latest ruby: - jruby-head include: # Includes a new variable experimental with a value of false # for the matrix legs matching rubygems: latest, which is all of them. # This is here for parity with the unsupported.yml # This is a hack. Combined with continue-on-error it should allow us # to have a workflow with allowed failure. # This is the "supported" build matrix, so only the "head" builds are experimental here. - rubygems: latest experimental: true runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/macos-ancient.yml000066400000000000000000000031361430066224500215700ustar00rootroot00000000000000name: Old MacOS on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "false" # if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [true] gemfile: - f0 rubygems: - "2.7.11" ruby: - "1.9" - "2.0" - "2.1" - "2.2" runs-on: macos-10.15 continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/macos.yml000066400000000000000000000032541430066224500201520ustar00rootroot00000000000000name: MacOS on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [true] gemfile: - f2 rubygems: - latest bundler: - latest ruby: - "2.7" - "3.0" - "3.1" - truffleruby - jruby runs-on: macos-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/style.yml000066400000000000000000000017211430066224500202050ustar00rootroot00000000000000name: Code Style Checks on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' jobs: rubocop: name: Rubocop if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [false] rubygems: - latest bundler: - latest ruby: - "2.7" runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run Rubocop run: bundle exec rubocop -DESP oauth2-2.0.7/.github/workflows/supported.yml000066400000000000000000000033271430066224500210760ustar00rootroot00000000000000name: Official Support on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [false] gemfile: - f0 - f1 - f2 rubygems: - latest bundler: - latest ruby: - "2.7" - "3.0" - "3.1" - truffleruby - jruby runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/unsupported.yml000066400000000000000000000036311430066224500214370ustar00rootroot00000000000000name: Unofficial Support on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [false] gemfile: - f0 - f1 - f2 rubygems: - latest bundler: - latest ruby: - "2.3" - "2.4" - "2.5" - "2.6" exclude: - ruby: "2.3" gemfile: "f1" - ruby: "2.3" gemfile: "f2" - ruby: "2.4" gemfile: "f2" - ruby: "2.5" gemfile: "f2" runs-on: ubuntu-20.04 continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/windows-jruby.yml000066400000000000000000000031231430066224500216660ustar00rootroot00000000000000name: Windows JRuby on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "false" # if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [true] gemfile: - f2 bundler: - none ruby: - jruby runs-on: windows-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.github/workflows/windows.yml000066400000000000000000000032071430066224500205400ustar00rootroot00000000000000name: Windows on: push: branches: - 'main' - 'master' - '*-maintenance' - '*-dev' - '*-stable' tags: - '!*' # Do not execute on tags pull_request: branches: - '*' # Allow manually triggering the workflow. workflow_dispatch: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: test: name: Specs - Ruby ${{ matrix.ruby }} ${{matrix.gemfile}} ${{ matrix.name_extra || '' }} env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" strategy: fail-fast: false matrix: experimental: [false] gemfile: - f2 rubygems: - latest bundler: - latest ruby: - "2.7" - "3.0" - "3.1" runs-on: windows-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Ruby & Bundle uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} bundler-cache: true - name: Run tests run: bundle exec rake test oauth2-2.0.7/.gitignore000066400000000000000000000006211430066224500147130ustar00rootroot00000000000000# Build Artifacts /pkg/ /tmp/ # rspec failure tracking .rspec_status # Bundler Gemfile.lock /.bundle/ /gemfiles/.bundle/ /gemfiles/.bundle/config /gemfiles/vendor/ /gemfiles/*.lock # Specs /coverage/ /spec/reports/ # Documentation /.yardoc /_yardoc/ /doc/ /rdoc/ # RVM .rvmrc # Editors .idea *~ # Other /measurement/ /.byebug_history .DS_Store # Version Managers .ruby-version .tool-versions oauth2-2.0.7/.jrubyrc000066400000000000000000000000251430066224500144020ustar00rootroot00000000000000debug.fullTrace=true oauth2-2.0.7/.overcommit.yml000066400000000000000000000022521430066224500157120ustar00rootroot00000000000000# Use this file to configure the Overcommit hooks you wish to use. This will # extend the default configuration defined in: # https://github.com/sds/overcommit/blob/master/config/default.yml # # At the topmost level of this YAML file is a key representing type of hook # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can # customize each hook, such as whether to only run it on certain files (via # `include`), whether to only display output if it fails (via `quiet`), etc. # # For a complete list of hooks, see: # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook # # For a complete list of options that you can use to customize hooks, see: # https://github.com/sds/overcommit#configuration # # Uncomment the following lines to make the configuration take effect. PreCommit: RuboCop: enabled: true on_warn: fail # Treat all warnings as failures TrailingWhitespace: enabled: true PostCheckout: ALL: # Special hook name that customizes all hooks of this type quiet: true # Change all post-checkout hooks to only display output on failure # # IndexTags: # enabled: true # Generate a tags file with `ctags` each time HEAD changes oauth2-2.0.7/.rspec000066400000000000000000000001041430066224500140340ustar00rootroot00000000000000--format documentation --require spec_helper --color --order random oauth2-2.0.7/.rubocop.yml000066400000000000000000000033401430066224500151760ustar00rootroot00000000000000inherit_from: - .rubocop_todo.yml - .rubocop_rspec.yml inherit_gem: rubocop-lts: rubocop-lts.yml require: - 'rubocop-md' # Can be added once we reach rubocop-ruby2_3 # - 'rubocop-packaging' - 'rubocop-performance' - 'rubocop-rake' - 'rubocop-rspec' AllCops: DisplayCopNames: true # Display the name of the failing cops Exclude: - 'gemfiles/vendor/**/*' - 'vendor/**/*' - '**/.irbrc' Metrics/BlockLength: ExcludedMethods: - context - describe - it - shared_context - shared_examples - shared_examples_for - namespace - draw Gemspec/RequiredRubyVersion: Enabled: false Metrics/BlockNesting: Max: 2 Metrics/LineLength: Enabled: false Metrics/ParameterLists: Max: 4 Layout/AccessModifierIndentation: EnforcedStyle: outdent Layout/DotPosition: EnforcedStyle: trailing Layout/SpaceInsideHashLiteralBraces: EnforcedStyle: no_space Lint/UnusedBlockArgument: Exclude: - 'spec/**/*.rb' - 'gemfiles/vendor/**/*' - 'vendor/**/*' - '**/.irbrc' RSpec/DescribeClass: Exclude: - 'spec/examples/*' RSpec/NestedGroups: Enabled: false Style/ClassVars: Enabled: false Style/CollectionMethods: PreferredMethods: map: 'collect' reduce: 'inject' find: 'detect' find_all: 'select' Style/Documentation: Enabled: false Style/DoubleNegation: Enabled: false Style/EmptyMethod: EnforcedStyle: expanded Style/Encoding: Enabled: false # Does not work with older rubies #Style/MapToHash: # Enabled: false # Does not work with older rubies #Style/RedundantBegin: # Enabled: false Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma oauth2-2.0.7/.rubocop_rspec.yml000066400000000000000000000005431430066224500163740ustar00rootroot00000000000000RSpec/FilePath: Enabled: false RSpec/MultipleExpectations: Enabled: false RSpec/NamedSubject: Enabled: false RSpec/ExampleLength: Enabled: false RSpec/VerifiedDoubles: Enabled: false RSpec/MessageSpies: Enabled: false RSpec/InstanceVariable: Enabled: false RSpec/NestedGroups: Enabled: false RSpec/ExpectInHook: Enabled: false oauth2-2.0.7/.rubocop_todo.yml000066400000000000000000000020301430066224500162160ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2022-07-13 09:52:51 +0700 using RuboCop version 0.68.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 9 Metrics/AbcSize: Max: 35 # Offense count: 6 # Configuration parameters: CountComments, ExcludedMethods. # ExcludedMethods: refine Metrics/BlockLength: Max: 35 # Offense count: 5 Metrics/CyclomaticComplexity: Max: 12 # Offense count: 10 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 34 # Offense count: 3 Metrics/PerceivedComplexity: Max: 13 # Offense count: 10 # Configuration parameters: Prefixes. # Prefixes: when, with, without RSpec/ContextWording: Exclude: - 'spec/oauth2/access_token_spec.rb' - 'spec/oauth2/authenticator_spec.rb' - 'spec/oauth2/client_spec.rb' oauth2-2.0.7/.simplecov000066400000000000000000000016551430066224500147350ustar00rootroot00000000000000# frozen_string_literal: true # To get coverage # On Local, default (HTML) output, it just works, coverage is turned on: # bundle exec rspec spec # On Local, all output formats: # COVER_ALL=true bundle exec rspec spec # # On CI, all output formats, the ENV variables CI is always set, # and COVER_ALL, and CI_CODECOV, are set in the coverage.yml workflow only, # so coverage only runs in that workflow, and outputs all formats. # if RUN_COVERAGE SimpleCov.start do enable_coverage :branch primary_coverage :branch add_filter 'spec' add_filter 'lib/oauth2/version.rb' track_files '**/*.rb' if ALL_FORMATTERS command_name "#{ENV['GITHUB_WORKFLOW']} Job #{ENV['GITHUB_RUN_ID']}:#{ENV['GITHUB_RUN_NUMBER']}" else formatter SimpleCov::Formatter::HTMLFormatter end minimum_coverage(line: 100, branch: 100) end else puts "Not running coverage on #{RUBY_ENGINE} #{RUBY_VERSION}" end oauth2-2.0.7/CHANGELOG.md000066400000000000000000000613361430066224500145460ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format (since v2) is based on [Keep a Changelog v1](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.0.0.html). ## [2.0.7] - 2022-08-22 ### Added - [#629](https://github.com/oauth-xx/oauth2/pull/629) - Allow POST of JSON to get token (@pboling, @terracatta) ### Fixed - [#626](https://github.com/oauth-xx/oauth2/pull/626) - Fixes a regression in 2.0.6. Will now prefer the key order from the lookup, not the hash keys (@rickselby) - Note: This fixes compatibility with `omniauth-oauth2` and AWS - [#625](https://github.com/oauth-xx/oauth2/pull/625) - Fixes the printed version in the post install message (@hasghari) ## [2.0.6] - 2022-07-13 ### Fixed - [#624](https://github.com/oauth-xx/oauth2/pull/624) - Fixes a [regression](https://github.com/oauth-xx/oauth2/pull/623) in v2.0.5, where an error would be raised in refresh_token flows due to (legitimate) lack of access_token (@pboling) ## [2.0.5] - 2022-07-07 ### Fixed - [#620](https://github.com/oauth-xx/oauth2/pull/620) - Documentation improvements, to help with upgrading (@swanson) - [#621](https://github.com/oauth-xx/oauth2/pull/621) - Fixed [#528](https://github.com/oauth-xx/oauth2/issues/528) and [#619](https://github.com/oauth-xx/oauth2/issues/619) (@pboling) - All data in responses is now returned, with the access token removed and set as `token` - `refresh_token` is no longer dropped - **BREAKING**: Microsoft's `id_token` is no longer left as `access_token['id_token']`, but moved to the standard `access_token.token` that all other strategies use - Remove `parse` and `snaky` from options so they don't get included in response - There is now 100% test coverage, for lines _and_ branches, and it will stay that way. ## [2.0.4] - 2022-07-01 ### Fixed - [#618](https://github.com/oauth-xx/oauth2/pull/618) - In some scenarios the `snaky` option default value was not applied (@pboling) ## [2.0.3] - 2022-06-28 ### Added - [#611](https://github.com/oauth-xx/oauth2/pull/611) - Proper deprecation warnings for `extract_access_token` argument (@pboling) - [#612](https://github.com/oauth-xx/oauth2/pull/612) - Add `snaky: false` option to skip conversion to `OAuth2::SnakyHash` (default: true) (@pboling) ### Fixed - [#608](https://github.com/oauth-xx/oauth2/pull/608) - Wrap `Faraday::TimeoutError` in `OAuth2::TimeoutError` (@nbibler) - [#615](https://github.com/oauth-xx/oauth2/pull/615) - Fix support for requests with blocks, see `Faraday::Connection#run_request` (@pboling) ## [2.0.2] - 2022-06-24 ### Fixed - [#604](https://github.com/oauth-xx/oauth2/pull/604) - Wrap `Faraday::TimeoutError` in `OAuth2::TimeoutError` (@stanhu) - [#606](https://github.com/oauth-xx/oauth2/pull/606) - Ruby 2.7 deprecation warning fix: Move `access_token_class` parameter into `Client` constructor (@stanhu) - [#607](https://github.com/oauth-xx/oauth2/pull/607) - CHANGELOG correction, reference to `OAuth2::ConnectionError` (@zavan) ## [2.0.1] - 2022-06-22 ### Added - Documentation improvements (@pboling) - Increased test coverage to 99% (@pboling) ## [2.0.0] - 2022-06-21 ### Added - [#158](https://github.com/oauth-xx/oauth2/pull/158), [#344](https://github.com/oauth-xx/oauth2/pull/344) - Optionally pass raw response to parsers (@niels) - [#190](https://github.com/oauth-xx/oauth2/pull/190), [#332](https://github.com/oauth-xx/oauth2/pull/332), [#334](https://github.com/oauth-xx/oauth2/pull/334), [#335](https://github.com/oauth-xx/oauth2/pull/335), [#360](https://github.com/oauth-xx/oauth2/pull/360), [#426](https://github.com/oauth-xx/oauth2/pull/426), [#427](https://github.com/oauth-xx/oauth2/pull/427), [#461](https://github.com/oauth-xx/oauth2/pull/461) - Documentation (@josephpage, @pboling, @meganemura, @joshRpowell, @elliotcm) - [#220](https://github.com/oauth-xx/oauth2/pull/220) - Support IETF rfc7523 JWT Bearer Tokens Draft 04+ (@jhmoore) - [#298](https://github.com/oauth-xx/oauth2/pull/298) - Set the response object on the access token on Client#get_token for debugging (@cpetschnig) - [#305](https://github.com/oauth-xx/oauth2/pull/305) - Option: `OAuth2::Client#get_token` - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token` (@styd) - [#346](https://github.com/oauth-xx/oauth2/pull/571) - Modern gem structure (@pboling) - [#351](https://github.com/oauth-xx/oauth2/pull/351) - Support Jruby 9k (@pboling) - [#362](https://github.com/oauth-xx/oauth2/pull/362) - Support SemVer release version scheme (@pboling) - [#363](https://github.com/oauth-xx/oauth2/pull/363) - New method `OAuth2::AccessToken#refresh!` same as old `refresh`, with backwards compatibility alias (@pboling) - [#364](https://github.com/oauth-xx/oauth2/pull/364) - Support `application/hal+json` format (@pboling) - [#365](https://github.com/oauth-xx/oauth2/pull/365) - Support `application/vnd.collection+json` format (@pboling) - [#376](https://github.com/oauth-xx/oauth2/pull/376) - _Documentation_: Example / Test for Google 2-legged JWT (@jhmoore) - [#381](https://github.com/oauth-xx/oauth2/pull/381) - Spec for extra header params on client credentials (@nikz) - [#394](https://github.com/oauth-xx/oauth2/pull/394) - Option: `OAuth2::AccessToken#initialize` - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency (@klippx) - [#412](https://github.com/oauth-xx/oauth2/pull/412) - Support `application/vdn.api+json` format (from jsonapi.org) (@david-christensen) - [#413](https://github.com/oauth-xx/oauth2/pull/413) - _Documentation_: License scan and report (@meganemura) - [#442](https://github.com/oauth-xx/oauth2/pull/442) - Option: `OAuth2::Client#initialize` - `:logger` (`::Logger.new($stdout)`) logger to use when OAUTH_DEBUG is enabled (for parity with `1-4-stable` branch) (@rthbound) - [#494](https://github.com/oauth-xx/oauth2/pull/494) - Support [OIDC 1.0 Private Key JWT](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication); based on the OAuth JWT assertion specification [(RFC 7523)](https://tools.ietf.org/html/rfc7523) (@SteveyblamWork) - [#549](https://github.com/oauth-xx/oauth2/pull/549) - Wrap `Faraday::ConnectionFailed` in `OAuth2::ConnectionError` (@nikkypx) - [#550](https://github.com/oauth-xx/oauth2/pull/550) - Raise error if location header not present when redirecting (@stanhu) - [#552](https://github.com/oauth-xx/oauth2/pull/552) - Add missing `version.rb` require (@ahorek) - [#553](https://github.com/oauth-xx/oauth2/pull/553) - Support `application/problem+json` format (@janz93) - [#560](https://github.com/oauth-xx/oauth2/pull/560) - Support IETF rfc6749, section 2.3.1 - don't set auth params when `nil` (@bouk) - [#571](https://github.com/oauth-xx/oauth2/pull/571) - Support Ruby 3.1 (@pboling) - [#575](https://github.com/oauth-xx/oauth2/pull/575) - Support IETF rfc7231, section 7.1.2 - relative location in redirect (@pboling) - [#581](https://github.com/oauth-xx/oauth2/pull/581) - _Documentation_: of breaking changes (@pboling) ### Changed - [#191](https://github.com/oauth-xx/oauth2/pull/191) - **BREAKING**: Token is expired if `expired_at` time is `now` (@davestevens) - [#312](https://github.com/oauth-xx/oauth2/pull/312) - **BREAKING**: Set `:basic_auth` as default for `:auth_scheme` instead of `:request_body`. This was default behavior before 1.3.0. (@tetsuya, @wy193777) - [#317](https://github.com/oauth-xx/oauth2/pull/317) - _Dependency_: Upgrade `jwt` to 2.x.x (@travisofthenorth) - [#338](https://github.com/oauth-xx/oauth2/pull/338) - _Dependency_: Switch from `Rack::Utils.escape` to `CGI.escape` (@josephpage) - [#339](https://github.com/oauth-xx/oauth2/pull/339), [#368](https://github.com/oauth-xx/oauth2/pull/368), [#424](https://github.com/oauth-xx/oauth2/pull/424), [#479](https://github.com/oauth-xx/oauth2/pull/479), [#493](https://github.com/oauth-xx/oauth2/pull/493), [#539](https://github.com/oauth-xx/oauth2/pull/539), [#542](https://github.com/oauth-xx/oauth2/pull/542), [#553](https://github.com/oauth-xx/oauth2/pull/553) - CI Updates, code coverage, linting, spelling, type fixes, New VERSION constant (@pboling, @josephpage, @ahorek) - [#410](https://github.com/oauth-xx/oauth2/pull/410) - **BREAKING**: Removed the ability to call .error from an OAuth2::Response object (@jhmoore) - [#414](https://github.com/oauth-xx/oauth2/pull/414) - Use Base64.strict_encode64 instead of custom internal logic (@meganemura) - [#489](https://github.com/oauth-xx/oauth2/pull/489) - **BREAKING**: Default value for option `OAuth2::Client` - `:authorize_url` removed leading slash to work with relative paths by default (`'oauth/authorize'`) (@ghost) - [#489](https://github.com/oauth-xx/oauth2/pull/489) - **BREAKING**: Default value for option `OAuth2::Client` - `:token_url` removed leading slash to work with relative paths by default (`'oauth/token'`) (@ghost) - [#507](https://github.com/oauth-xx/oauth2/pull/507), [#575](https://github.com/oauth-xx/oauth2/pull/575) - **BREAKING**: Transform keys to camel case, always, by default (ultimately via `rash_alt` gem) - Original keys will still work as previously, in most scenarios, thanks to `rash_alt` gem. - However, this is a _breaking_ change if you rely on `response.parsed.to_h`, as the keys in the result will be camel case. - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option. - [#576](https://github.com/oauth-xx/oauth2/pull/576) - **BREAKING**: Stop rescuing parsing errors (@pboling) - [#591](https://github.com/oauth-xx/oauth2/pull/576) - _DEPRECATION_: `OAuth2::Client` - `:extract_access_token` option is deprecated ### Fixed - [#158](https://github.com/oauth-xx/oauth2/pull/158), [#344](https://github.com/oauth-xx/oauth2/pull/344) - Handling of errors when using `omniauth-facebook` (@niels) - [#294](https://github.com/oauth-xx/oauth2/pull/294) - Fix: "Unexpected middleware set" issue with Faraday when `OAUTH_DEBUG=true` (@spectator, @gafrom) - [#300](https://github.com/oauth-xx/oauth2/pull/300) - _Documentation_: `Oauth2::Error` - Error codes are strings, not symbols (@NobodysNightmare) - [#318](https://github.com/oauth-xx/oauth2/pull/318), [#326](https://github.com/oauth-xx/oauth2/pull/326), [#343](https://github.com/oauth-xx/oauth2/pull/343), [#347](https://github.com/oauth-xx/oauth2/pull/347), [#397](https://github.com/oauth-xx/oauth2/pull/397), [#464](https://github.com/oauth-xx/oauth2/pull/464), [#561](https://github.com/oauth-xx/oauth2/pull/561), [#565](https://github.com/oauth-xx/oauth2/pull/565) - _Dependency_: Support all versions of `faraday` (see [gemfiles/README.md][gemfiles/readme] for compatibility matrix with Ruby engines & versions) (@pboling, @raimondasv, @zacharywelch, @Fudoshiki, @ryogift, @sj26, @jdelStrother) - [#322](https://github.com/oauth-xx/oauth2/pull/322), [#331](https://github.com/oauth-xx/oauth2/pull/331), [#337](https://github.com/oauth-xx/oauth2/pull/337), [#361](https://github.com/oauth-xx/oauth2/pull/361), [#371](https://github.com/oauth-xx/oauth2/pull/371), [#377](https://github.com/oauth-xx/oauth2/pull/377), [#383](https://github.com/oauth-xx/oauth2/pull/383), [#392](https://github.com/oauth-xx/oauth2/pull/392), [#395](https://github.com/oauth-xx/oauth2/pull/395), [#400](https://github.com/oauth-xx/oauth2/pull/400), [#401](https://github.com/oauth-xx/oauth2/pull/401), [#403](https://github.com/oauth-xx/oauth2/pull/403), [#415](https://github.com/oauth-xx/oauth2/pull/415), [#567](https://github.com/oauth-xx/oauth2/pull/567) - Updated Rubocop, Rubocop plugins and improved code style (@pboling, @bquorning, @lautis, @spectator) - [#328](https://github.com/oauth-xx/oauth2/pull/328) - _Documentation_: Homepage URL is SSL (@amatsuda) - [#339](https://github.com/oauth-xx/oauth2/pull/339), [#479](https://github.com/oauth-xx/oauth2/pull/479) - Update testing infrastructure for all supported Rubies (@pboling and @josephpage) - [#366](https://github.com/oauth-xx/oauth2/pull/366) - **Security**: Fix logging to `$stdout` of request and response bodies via Faraday's logger and `ENV["OAUTH_DEBUG"] == 'true'` (@pboling) - [#380](https://github.com/oauth-xx/oauth2/pull/380) - Fix: Stop attempting to encode non-encodable objects in `Oauth2::Error` (@jhmoore) - [#399](https://github.com/oauth-xx/oauth2/pull/399) - Fix: Stop duplicating `redirect_uri` in `get_token` (@markus) - [#410](https://github.com/oauth-xx/oauth2/pull/410) - Fix: `SystemStackError` caused by circular reference between Error and Response classes (@jhmoore) - [#460](https://github.com/oauth-xx/oauth2/pull/460) - Fix: Stop throwing errors when `raise_errors` is set to `false`; analog of [#524](https://github.com/oauth-xx/oauth2/pull/524) for `1-4-stable` branch (@joaolrpaulo) - [#472](https://github.com/oauth-xx/oauth2/pull/472) - **Security**: Add checks to enforce `client_secret` is *never* passed in authorize_url query params for `implicit` and `auth_code` grant types (@dfockler) - [#482](https://github.com/oauth-xx/oauth2/pull/482) - _Documentation_: Update last of `intridea` links to `oauth-xx` (@pboling) - [#536](https://github.com/oauth-xx/oauth2/pull/536) - **Security**: Compatibility with more (and recent) Ruby OpenSSL versions, Github Actions, Rubocop updated, analogous to [#535](https://github.com/oauth-xx/oauth2/pull/535) on `1-4-stable` branch (@pboling) - [#595](https://github.com/oauth-xx/oauth2/pull/595) - Graceful handling of empty responses from `Client#get_token`, respecting `:raise_errors` config (@stanhu) - [#596](https://github.com/oauth-xx/oauth2/pull/596) - Consistency between `AccessToken#refresh` and `Client#get_token` named arguments (@stanhu) - [#598](https://github.com/oauth-xx/oauth2/pull/598) - Fix unparseable data not raised as error in `Client#get_token`, respecting `:raise_errors` config (@stanhu) ### Removed - [#341](https://github.com/oauth-xx/oauth2/pull/341) - Remove Rdoc & Jeweler related files (@josephpage) - [#342](https://github.com/oauth-xx/oauth2/pull/342) - **BREAKING**: Dropped support for Ruby 1.8 (@josephpage) - [#539](https://github.com/oauth-xx/oauth2/pull/539) - Remove reliance on globally included OAuth2 in tests, analog of [#538](https://github.com/oauth-xx/oauth2/pull/538) for 1-4-stable (@anderscarling) - [#566](https://github.com/oauth-xx/oauth2/pull/566) - _Dependency_: Removed `wwtd` (@bquorning) - [#589](https://github.com/oauth-xx/oauth2/pull/589), [#593](https://github.com/oauth-xx/oauth2/pull/593) - Remove support for expired MAC token draft spec (@stanhu) - [#590](https://github.com/oauth-xx/oauth2/pull/590) - _Dependency_: Removed `multi_json` (@stanhu) ## [1.4.10] - 2022-07-01 - FIPS Compatibility [#587](https://github.com/oauth-xx/oauth2/pull/587) (@akostadinov) ## [1.4.9] - 2022-02-20 - Fixes compatibility with Faraday v2 [572](https://github.com/oauth-xx/oauth2/issues/572) - Includes supported versions of Faraday in test matrix: - Faraday ~> 2.2.0 with Ruby >= 2.6 - Faraday ~> 1.10 with Ruby >= 2.4 - Faraday ~> 0.17.3 with Ruby >= 1.9 - Add Windows and MacOS to test matrix ## [1.4.8] - 2022-02-18 - MFA is now required to push new gem versions (@pboling) - README overhaul w/ new Ruby Version and Engine compatibility policies (@pboling) - [#569](https://github.com/oauth-xx/oauth2/pull/569) Backport fixes ([#561](https://github.com/oauth-xx/oauth2/pull/561) by @ryogift), and add more fixes, to allow faraday 1.x and 2.x (@jrochkind) - Improve Code Coverage tracking (Coveralls, CodeCov, CodeClimate), and enable branch coverage (@pboling) - Add CodeQL, Security Policy, Funding info (@pboling) - Added Ruby 3.1, jruby, jruby-head, truffleruby, truffleruby-head to build matrix (@pboling) - [#543](https://github.com/oauth-xx/oauth2/pull/543) - Support for more modern Open SSL libraries (@pboling) ## [1.4.7] - 2021-03-19 - [#541](https://github.com/oauth-xx/oauth2/pull/541) - Backport fix to expires_at handling [#533](https://github.com/oauth-xx/oauth2/pull/533) to 1-4-stable branch. (@dobon) ## [1.4.6] - 2021-03-19 - [#540](https://github.com/oauth-xx/oauth2/pull/540) - Add VERSION constant (@pboling) - [#537](https://github.com/oauth-xx/oauth2/pull/537) - Fix crash in OAuth2::Client#get_token (@anderscarling) - [#538](https://github.com/oauth-xx/oauth2/pull/538) - Remove reliance on globally included OAuth2 in tests, analogous to [#539](https://github.com/oauth-xx/oauth2/pull/539) on master branch (@anderscarling) ## [1.4.5] - 2021-03-18 - [#535](https://github.com/oauth-xx/oauth2/pull/535) - Compatibility with range of supported Ruby OpenSSL versions, Rubocop updates, Github Actions, analogous to [#536](https://github.com/oauth-xx/oauth2/pull/536) on master branch (@pboling) - [#518](https://github.com/oauth-xx/oauth2/pull/518) - Add extract_access_token option to OAuth2::Client (@jonspalmer) - [#507](https://github.com/oauth-xx/oauth2/pull/507) - Fix camel case content type, response keys (@anvox) - [#500](https://github.com/oauth-xx/oauth2/pull/500) - Fix YARD documentation formatting (@olleolleolle) ## [1.4.4] - 2020-02-12 - [#408](https://github.com/oauth-xx/oauth2/pull/408) - Fixed expires_at for formatted time (@Lomey) ## [1.4.3] - 2020-01-29 - [#483](https://github.com/oauth-xx/oauth2/pull/483) - add project metadata to gemspec (@orien) - [#495](https://github.com/oauth-xx/oauth2/pull/495) - support additional types of access token requests (@SteveyblamFreeagent, @thomcorley, @dgholz) - Adds support for private_key_jwt and tls_client_auth - [#433](https://github.com/oauth-xx/oauth2/pull/433) - allow field names with square brackets and numbers in params (@asm256) ## [1.4.2] - 2019-10-01 - [#478](https://github.com/oauth-xx/oauth2/pull/478) - support latest version of faraday & fix build (@pboling) - Officially support Ruby 2.6 and truffleruby ## [1.4.1] - 2018-10-13 - [#417](https://github.com/oauth-xx/oauth2/pull/417) - update jwt dependency (@thewoolleyman) - [#419](https://github.com/oauth-xx/oauth2/pull/419) - remove rubocop dependency (temporary, added back in [#423](https://github.com/oauth-xx/oauth2/pull/423)) (@pboling) - [#418](https://github.com/oauth-xx/oauth2/pull/418) - update faraday dependency (@pboling) - [#420](https://github.com/oauth-xx/oauth2/pull/420) - update [oauth2.gemspec](https://github.com/oauth-xx/oauth2/blob/1-4-stable/oauth2.gemspec) (@pboling) - [#421](https://github.com/oauth-xx/oauth2/pull/421) - fix [CHANGELOG.md](https://github.com/oauth-xx/oauth2/blob/1-4-stable/CHANGELOG.md) for previous releases (@pboling) - [#422](https://github.com/oauth-xx/oauth2/pull/422) - update [LICENSE](https://github.com/oauth-xx/oauth2/blob/1-4-stable/LICENSE) and [README.md](https://github.com/oauth-xx/oauth2/blob/1-4-stable/README.md) (@pboling) - [#423](https://github.com/oauth-xx/oauth2/pull/423) - update [builds](https://travis-ci.org/oauth-xx/oauth2/builds), [Rakefile](https://github.com/oauth-xx/oauth2/blob/1-4-stable/Rakefile) (@pboling) - officially document supported Rubies * Ruby 1.9.3 * Ruby 2.0.0 * Ruby 2.1 * Ruby 2.2 * [JRuby 1.7][jruby-1.7] (targets MRI v1.9) * [JRuby 9.0][jruby-9.0] (targets MRI v2.0) * Ruby 2.3 * Ruby 2.4 * Ruby 2.5 * [JRuby 9.1][jruby-9.1] (targets MRI v2.3) * [JRuby 9.2][jruby-9.2] (targets MRI v2.5) [jruby-1.7]: https://www.jruby.org/2017/05/11/jruby-1-7-27.html [jruby-9.0]: https://www.jruby.org/2016/01/26/jruby-9-0-5-0.html [jruby-9.1]: https://www.jruby.org/2017/05/16/jruby-9-1-9-0.html [jruby-9.2]: https://www.jruby.org/2018/05/24/jruby-9-2-0-0.html ## [1.4.0] - 2017-06-09 - Drop Ruby 1.8.7 support (@sferik) - Fix some RuboCop offenses (@sferik) - _Dependency_: Remove Yardstick (@sferik) - _Dependency_: Upgrade Faraday to 0.12 (@sferik) ## [1.3.1] - 2017-03-03 - Add support for Ruby 2.4.0 (@pschambacher) - _Dependency_: Upgrade Faraday to Faraday 0.11 (@mcfiredrill, @rhymes, @pschambacher) ## [1.3.0] - 2016-12-28 - Add support for header-based authentication to the `Client` so it can be used across the library (@bjeanes) - Default to header-based authentication when getting a token from an authorisation code (@maletor) - **Breaking**: Allow an `auth_scheme` (`:basic_auth` or `:request_body`) to be set on the client, defaulting to `:request_body` to maintain backwards compatibility (@maletor, @bjeanes) - Handle `redirect_uri` according to the OAuth 2 spec, so it is passed on redirect and at the point of token exchange (@bjeanes) - Refactor handling of encoding of error responses (@urkle) - Avoid instantiating an `Error` if there is no error to raise (@urkle) - Add support for Faraday 0.10 (@rhymes) ## [1.2.0] - 2016-07-01 - Properly handle encoding of error responses (so we don't blow up, for example, when Google's response includes a ∞) (@Motoshi-Nishihira) - Make a copy of the options hash in `AccessToken#from_hash` to avoid accidental mutations (@Linuus) - Use `raise` rather than `fail` to throw exceptions (@sferik) ## [1.1.0] - 2016-01-30 - Various refactors (eliminating `Hash#merge!` usage in `AccessToken#refresh!`, use `yield` instead of `#call`, freezing mutable objects in constants, replacing constants with class variables) (@sferik) - Add support for Rack 2, and bump various other dependencies (@sferik) ## [1.0.0] - 2014-07-09 ### Added - Add an implementation of the MAC token spec. ### Fixed - Fix Base64.strict_encode64 incompatibility with Ruby 1.8.7. ## [0.5.0] - 2011-07-29 ### Changed - [breaking] `oauth_token` renamed to `oauth_bearer`. - [breaking] `authorize_path` Client option renamed to `authorize_url`. - [breaking] `access_token_path` Client option renamed to `token_url`. - [breaking] `access_token_method` Client option renamed to `token_method`. - [breaking] `web_server` renamed to `auth_code`. ## [0.4.1] - 2011-04-20 ## [0.4.0] - 2011-04-20 ## [0.3.0] - 2011-04-08 ## [0.2.0] - 2011-04-01 ## [0.1.1] - 2011-01-12 ## [0.1.0] - 2010-10-13 ## [0.0.13] + [0.0.12] + [0.0.11] - 2010-08-17 ## [0.0.10] - 2010-06-19 ## [0.0.9] - 2010-06-18 ## [0.0.8] + [0.0.7] - 2010-04-27 ## [0.0.6] - 2010-04-25 ## [0.0.5] - 2010-04-23 ## [0.0.4] + [0.0.3] + [0.0.2] + [0.0.1] - 2010-04-22 [0.0.1]: https://github.com/oauth-xx/oauth2/compare/311d9f4...v0.0.1 [0.0.2]: https://github.com/oauth-xx/oauth2/compare/v0.0.1...v0.0.2 [0.0.3]: https://github.com/oauth-xx/oauth2/compare/v0.0.2...v0.0.3 [0.0.4]: https://github.com/oauth-xx/oauth2/compare/v0.0.3...v0.0.4 [0.0.5]: https://github.com/oauth-xx/oauth2/compare/v0.0.4...v0.0.5 [0.0.6]: https://github.com/oauth-xx/oauth2/compare/v0.0.5...v0.0.6 [0.0.7]: https://github.com/oauth-xx/oauth2/compare/v0.0.6...v0.0.7 [0.0.8]: https://github.com/oauth-xx/oauth2/compare/v0.0.7...v0.0.8 [0.0.9]: https://github.com/oauth-xx/oauth2/compare/v0.0.8...v0.0.9 [0.0.10]: https://github.com/oauth-xx/oauth2/compare/v0.0.9...v0.0.10 [0.0.11]: https://github.com/oauth-xx/oauth2/compare/v0.0.10...v0.0.11 [0.0.12]: https://github.com/oauth-xx/oauth2/compare/v0.0.11...v0.0.12 [0.0.13]: https://github.com/oauth-xx/oauth2/compare/v0.0.12...v0.0.13 [0.1.0]: https://github.com/oauth-xx/oauth2/compare/v0.0.13...v0.1.0 [0.1.1]: https://github.com/oauth-xx/oauth2/compare/v0.1.0...v0.1.1 [0.2.0]: https://github.com/oauth-xx/oauth2/compare/v0.1.1...v0.2.0 [0.3.0]: https://github.com/oauth-xx/oauth2/compare/v0.2.0...v0.3.0 [0.4.0]: https://github.com/oauth-xx/oauth2/compare/v0.3.0...v0.4.0 [0.4.1]: https://github.com/oauth-xx/oauth2/compare/v0.4.0...v0.4.1 [0.5.0]: https://github.com/oauth-xx/oauth2/compare/v0.4.1...v0.5.0 [1.0.0]: https://github.com/oauth-xx/oauth2/compare/v0.9.4...v1.0.0 [1.1.0]: https://github.com/oauth-xx/oauth2/compare/v1.0.0...v1.1.0 [1.2.0]: https://github.com/oauth-xx/oauth2/compare/v1.1.0...v1.2.0 [1.3.0]: https://github.com/oauth-xx/oauth2/compare/v1.2.0...v1.3.0 [1.3.1]: https://github.com/oauth-xx/oauth2/compare/v1.3.0...v1.3.1 [1.4.0]: https://github.com/oauth-xx/oauth2/compare/v1.3.1...v1.4.0 [1.4.1]: https://github.com/oauth-xx/oauth2/compare/v1.4.0...v1.4.1 [1.4.2]: https://github.com/oauth-xx/oauth2/compare/v1.4.1...v1.4.2 [1.4.3]: https://github.com/oauth-xx/oauth2/compare/v1.4.2...v1.4.3 [1.4.4]: https://github.com/oauth-xx/oauth2/compare/v1.4.3...v1.4.4 [1.4.5]: https://github.com/oauth-xx/oauth2/compare/v1.4.4...v1.4.5 [1.4.6]: https://github.com/oauth-xx/oauth2/compare/v1.4.5...v1.4.6 [1.4.7]: https://github.com/oauth-xx/oauth2/compare/v1.4.6...v1.4.7 [1.4.8]: https://github.com/oauth-xx/oauth2/compare/v1.4.7...v1.4.8 [1.4.9]: https://github.com/oauth-xx/oauth2/compare/v1.4.8...v1.4.9 [1.4.10]: https://github.com/oauth-xx/oauth2/compare/v1.4.9...v1.4.10 [2.0.0]: https://github.com/oauth-xx/oauth2/compare/v1.4.10...v2.0.0 [2.0.1]: https://github.com/oauth-xx/oauth2/compare/v2.0.0...v2.0.1 [2.0.2]: https://github.com/oauth-xx/oauth2/compare/v2.0.1...v2.0.2 [2.0.3]: https://github.com/oauth-xx/oauth2/compare/v2.0.2...v2.0.3 [2.0.4]: https://github.com/oauth-xx/oauth2/compare/v2.0.3...v2.0.4 [2.0.5]: https://github.com/oauth-xx/oauth2/compare/v2.0.4...v2.0.5 [2.0.6]: https://github.com/oauth-xx/oauth2/compare/v2.0.5...v2.0.6 [Unreleased]: https://github.com/oauth-xx/oauth2/compare/v2.0.6...HEAD [gemfiles/readme]: gemfiles/README.md oauth2-2.0.7/CODE_OF_CONDUCT.md000066400000000000000000000125441430066224500155310ustar00rootroot00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge 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. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities 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. 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. ## Scope 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. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **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. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **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. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **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. ### 4. Permanent Ban **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. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations oauth2-2.0.7/CONTRIBUTING.md000066400000000000000000000033311430066224500151550ustar00rootroot00000000000000## Contributing Bug reports and pull requests are welcome on GitHub at [https://github.com/oauth-xx/oauth2][source] . This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct][conduct]. To submit a patch, please fork the project and create a patch with tests. Once you're happy with it send a pull request! ## Detailed instructions on Submitting a Pull Request 1. [Fork the repository.][fork] 2. [Create a topic branch.][branch] 3. Add specs for your unimplemented feature or bug fix. 4. Run `bundle exec rake spec`. If your specs pass, return to step 3. 5. Implement your feature or bug fix. 6. Run `bundle exec rake`. If your specs fail, return to step 5. 7. Run `open coverage/index.html`. If your changes are not completely covered by your tests, return to step 3. 8. Add documentation for your feature or bug fix. 9. Run `bundle exec rake verify_measurements`. If your changes are not 100% documented, go back to step 8. 10. Commit and push your changes. 11. [Submit a pull request.][pr] [fork]: http://help.github.com/fork-a-repo/ [branch]: http://learn.github.com/p/branching.html [pr]: http://help.github.com/send-pull-requests/ ## Contributors [![Contributors](https://contrib.rocks/image?repo=oauth-xx/oauth2)][contributors] Made with [contributors-img][contrib-rocks]. [comment]: <> (Following links are used by README, CONTRIBUTING) [conduct]: https://github.com/oauth-xx/oauth2/blob/master/CODE_OF_CONDUCT.md [contrib-rocks]: https://contrib.rocks [contributors]: https://github.com/oauth-xx/oauth2/graphs/contributors [comment]: <> (Following links are used by README, CONTRIBUTING, Homepage) [source]: https://github.com/oauth-xx/oauth2/ oauth2-2.0.7/Dangerfile000066400000000000000000000011171430066224500147070ustar00rootroot00000000000000# frozen_string_literal: true # Ideas... # 1. Check for hashtags in PR title, and disallow changes to changelog? # e.g. github.pr_title.include? "#trivial" # Make it more obvious that a PR is a work in progress and shouldn't be merged yet warn('PR is classed as Work in Progress') if github.pr_title.include? '[WIP]' # Warn when there is a big PR warn('Big PR') if git.lines_of_code > 500 # Don't let testing shortcuts get into master by accident raise('fdescribe left in tests') if `grep -r fdescribe specs/ `.length > 1 raise('fit left in tests') if `grep -r fit specs/ `.length > 1 oauth2-2.0.7/Gemfile000066400000000000000000000031631430066224500142220ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } gem 'rake', '~> 13.0' gem 'rspec', '~> 3.0' ruby_version = Gem::Version.new(RUBY_VERSION) minimum_version = ->(version, engine = 'ruby') { ruby_version >= Gem::Version.new(version) && RUBY_ENGINE == engine } linting = minimum_version.call('2.7') coverage = minimum_version.call('2.7') debug = minimum_version.call('2.5') gem 'overcommit', '~> 0.58' if linting platforms :mri do if linting # Danger is incompatible with Faraday 2 (for now) # see: https://github.com/danger/danger/issues/1349 # gem 'danger', '~> 8.4' gem 'rubocop-md', require: false # Can be added once we reach rubocop-lts >= v10 (i.e. drop Ruby 2.2) # gem 'rubocop-packaging', require: false gem 'rubocop-performance', require: false gem 'rubocop-rake', require: false gem 'rubocop-rspec', require: false gem 'rubocop-thread_safety', require: false end if coverage gem 'codecov', '~> 0.6' # For CodeCov gem 'simplecov', '~> 0.21', require: false gem 'simplecov-cobertura' # XML for Jenkins gem 'simplecov-json' # For CodeClimate gem 'simplecov-lcov', '~> 0.8', require: false end if debug # Add `byebug` to your code where you want to drop to REPL gem 'byebug' end end platforms :jruby do # Add `binding.pry` to your code where you want to drop to REPL gem 'pry-debugger-jruby' end ### deps for documentation and rdoc.info group :documentation do gem 'github-markup', platform: :mri gem 'redcarpet', platform: :mri gem 'yard', require: false end oauth2-2.0.7/LICENSE000066400000000000000000000022261430066224500137330ustar00rootroot00000000000000MIT License Copyright (c) 2011 - 2013 Michael Bleigh and Intridea, Inc. Copyright (c) 2017 - 2022 oauth-xx organization, https://github.com/oauth-xx Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. oauth2-2.0.7/README.md000066400000000000000000000720101430066224500142030ustar00rootroot00000000000000

OAuth 2.0 Logo by Chris Messina, CC BY-SA 3.0 Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5

## What OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This is a RubyGem for implementing OAuth 2.0 clients and servers in Ruby applications. See the sibling `oauth` gem for OAuth 1.0 implementations in Ruby. --- * [OAuth 2.0 Spec][oauth2-spec] * [oauth sibling gem][sibling-gem] for OAuth 1.0 implementations in Ruby. [oauth2-spec]: https://oauth.net/2/ [sibling-gem]: https://github.com/oauth-xx/oauth-ruby ## Release Documentation ### Version 2.0.x
2.0.x Readmes | Version | Release Date | Readme | |---------|--------------|----------------------------------------------------------| | 2.0.6 | 2022-07-13 | https://github.com/oauth-xx/oauth2/blob/v2.0.6/README.md | | 2.0.5 | 2022-07-07 | https://github.com/oauth-xx/oauth2/blob/v2.0.5/README.md | | 2.0.4 | 2022-07-01 | https://github.com/oauth-xx/oauth2/blob/v2.0.4/README.md | | 2.0.3 | 2022-06-28 | https://github.com/oauth-xx/oauth2/blob/v2.0.3/README.md | | 2.0.2 | 2022-06-24 | https://github.com/oauth-xx/oauth2/blob/v2.0.2/README.md | | 2.0.1 | 2022-06-22 | https://github.com/oauth-xx/oauth2/blob/v2.0.1/README.md | | 2.0.0 | 2022-06-21 | https://github.com/oauth-xx/oauth2/blob/v2.0.0/README.md |
### Older Releases
1.4.x Readmes | Version | Release Date | Readme | |---------|--------------|-----------------------------------------------------------| | 1.4.10 | Jul 1, 2022 | https://github.com/oauth-xx/oauth2/blob/v1.4.10/README.md | | 1.4.9 | Feb 20, 2022 | https://github.com/oauth-xx/oauth2/blob/v1.4.9/README.md | | 1.4.8 | Feb 18, 2022 | https://github.com/oauth-xx/oauth2/blob/v1.4.8/README.md | | 1.4.7 | Mar 19, 2021 | https://github.com/oauth-xx/oauth2/blob/v1.4.7/README.md | | 1.4.6 | Mar 19, 2021 | https://github.com/oauth-xx/oauth2/blob/v1.4.6/README.md | | 1.4.5 | Mar 18, 2021 | https://github.com/oauth-xx/oauth2/blob/v1.4.5/README.md | | 1.4.4 | Feb 12, 2020 | https://github.com/oauth-xx/oauth2/blob/v1.4.4/README.md | | 1.4.3 | Jan 29, 2020 | https://github.com/oauth-xx/oauth2/blob/v1.4.3/README.md | | 1.4.2 | Oct 1, 2019 | https://github.com/oauth-xx/oauth2/blob/v1.4.2/README.md | | 1.4.1 | Oct 13, 2018 | https://github.com/oauth-xx/oauth2/blob/v1.4.1/README.md | | 1.4.0 | Jun 9, 2017 | https://github.com/oauth-xx/oauth2/blob/v1.4.0/README.md |
1.3.x Readmes | Version | Release Date | Readme | |----------|--------------|----------------------------------------------------------| | 1.3.1 | Mar 3, 2017 | https://github.com/oauth-xx/oauth2/blob/v1.3.1/README.md | | 1.3.0 | Dec 27, 2016 | https://github.com/oauth-xx/oauth2/blob/v1.3.0/README.md |
≤= 1.2.x Readmes (2016 and before) | Version | Release Date | Readme | |----------|--------------|----------------------------------------------------------| | 1.2.0 | Jun 30, 2016 | https://github.com/oauth-xx/oauth2/blob/v1.2.0/README.md | | 1.1.0 | Jan 30, 2016 | https://github.com/oauth-xx/oauth2/blob/v1.1.0/README.md | | 1.0.0 | May 23, 2014 | https://github.com/oauth-xx/oauth2/blob/v1.0.0/README.md | | < 1.0.0 | Find here | https://github.com/oauth-xx/oauth2/tags |
## Status | | Project | bundle add oauth2 | |:----|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1️⃣ | name, license, docs | [![RubyGems.org][⛳️name-img]][⛳️gem] [![License: MIT][🖇src-license-img]][🖇src-license] [![FOSSA][🏘fossa-img]][🏘fossa] [![RubyDoc.info][🚎yard-img]][🚎yard] [![InchCI][🖐inch-ci-img]][🚎yard] | | 2️⃣ | version & activity | [![Gem Version][⛳️version-img]][⛳️gem] [![Total Downloads][🖇DL-total-img]][⛳️gem] [![Download Rank][🏘DL-rank-img]][⛳️gem] [![Source Code][🚎src-home-img]][🚎src-home] [![Open PRs][🖐prs-o-img]][🖐prs-o] [![Closed PRs][🧮prs-c-img]][🧮prs-c] [![Next Version][📗next-img♻️]][📗next♻️] | | 3️⃣ | maintanence & linting | [![Maintainability][⛳cclim-maint-img♻️]][⛳cclim-maint] [![Helpers][🖇triage-help-img]][🖇triage-help] [![Depfu][🏘depfu-img♻️]][🏘depfu♻️] [![Contributors][🚎contributors-img]][🚎contributors] [![Style][🖐style-wf-img]][🖐style-wf] [![Kloc Roll][🧮kloc-img]][🧮kloc] | | 4️⃣ | testing | [![Open Issues][⛳iss-o-img]][⛳iss-o] [![Closed Issues][🖇iss-c-img]][🖇iss-c] [![Supported][🏘sup-wf-img]][🏘sup-wf] [![Heads][🚎heads-wf-img]][🚎heads-wf] [![Unofficial Support][🖐uns-wf-img]][🖐uns-wf] [![MacOS][🧮mac-wf-img]][🧮mac-wf] [![Windows][📗win-wf-img]][📗win-wf] | | 5️⃣ | coverage & security | [![CodeClimate][⛳cclim-cov-img♻️]][⛳cclim-cov] [![CodeCov][🖇codecov-img♻️]][🖇codecov] [![Coveralls][🏘coveralls-img]][🏘coveralls] [![Security Policy][🚎sec-pol-img]][🚎sec-pol] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Code Coverage][🧮cov-wf-img]][🧮cov-wf] | | 6️⃣ | resources | [![Discussion][⛳gh-discussions-img]][⛳gh-discussions] [![Get help on Codementor][🖇codementor-img]][🖇codementor] [![Chat][🏘chat-img]][🏘chat] [![Blog][🚎blog-img]][🚎blog] [![Blog][🖐wiki-img]][🖐wiki] | | 7️⃣ | spread 💖 | [![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] [![Sponsor Me][🖇sponsor-img]][🖇sponsor] [![Tweet @ Peter][🏘tweet-img]][🏘tweet] [🌏][aboutme] [👼][angelme] [💻][coderme] | [⛳️gem]: https://rubygems.org/gems/oauth2 [⛳️name-img]: https://img.shields.io/badge/name-oauth2-brightgreen.svg?style=flat [🖇src-license]: https://opensource.org/licenses/MIT [🖇src-license-img]: https://img.shields.io/badge/License-MIT-green.svg [🏘fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_shield [🏘fossa-img]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=shield [🚎yard]: https://www.rubydoc.info/github/oauth-xx/oauth2 [🚎yard-img]: https://img.shields.io/badge/documentation-rubydoc-brightgreen.svg?style=flat [🖐inch-ci-img]: http://inch-ci.org/github/oauth-xx/oauth2.png [⛳️version-img]: http://img.shields.io/gem/v/oauth2.svg [🖇DL-total-img]: https://img.shields.io/gem/dt/oauth2.svg [🏘DL-rank-img]: https://img.shields.io/gem/rt/oauth2.svg [🚎src-home]: https://github.com/oauth-xx/oauth2 [🚎src-home-img]: https://img.shields.io/badge/source-github-brightgreen.svg?style=flat [🖐prs-o]: https://github.com/oauth-xx/oauth2/pulls [🖐prs-o-img]: https://img.shields.io/github/issues-pr/oauth-xx/oauth2 [🧮prs-c]: https://github.com/oauth-xx/oauth2/pulls?q=is%3Apr+is%3Aclosed [🧮prs-c-img]: https://img.shields.io/github/issues-pr-closed/oauth-xx/oauth2 [📗next♻️]: https://github.com/oauth-xx/oauth2/milestone/2 [📗next-img♻️]: https://img.shields.io/github/milestones/progress/oauth-xx/oauth2/2?label=Next%20Version [⛳cclim-maint]: https://codeclimate.com/github/oauth-xx/oauth2/maintainability [⛳cclim-maint-img♻️]: https://api.codeclimate.com/v1/badges/688c612528ff90a46955/maintainability [🖇triage-help]: https://www.codetriage.com/oauth-xx/oauth2 [🖇triage-help-img]: https://www.codetriage.com/oauth-xx/oauth2/badges/users.svg [🏘depfu♻️]: https://depfu.com/github/oauth-xx/oauth2?project_id=4445 [🏘depfu-img♻️]: https://badges.depfu.com/badges/6d34dc1ba682bbdf9ae2a97848241743/count.svg [🚎contributors]: https://github.com/oauth-xx/oauth2/graphs/contributors [🚎contributors-img]: https://img.shields.io/github/contributors-anon/oauth-xx/oauth2 [🖐style-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml [🖐style-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml/badge.svg [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ [🧮kloc-img]: https://img.shields.io/tokei/lines/github.com/oauth-xx/oauth2 [⛳iss-o]: https://github.com/oauth-xx/oauth2/issues [⛳iss-o-img]: https://img.shields.io/github/issues-raw/oauth-xx/oauth2 [🖇iss-c]: https://github.com/oauth-xx/oauth2/issues?q=is%3Aissue+is%3Aclosed [🖇iss-c-img]: https://img.shields.io/github/issues-closed-raw/oauth-xx/oauth2 [🏘sup-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml [🏘sup-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml/badge.svg [🚎heads-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml [🚎heads-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml/badge.svg [🖐uns-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml [🖐uns-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml/badge.svg [🧮mac-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/macos.yml [🧮mac-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/macos.yml/badge.svg [📗win-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/windows.yml [📗win-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/windows.yml/badge.svg [⛳cclim-cov]: https://codeclimate.com/github/oauth-xx/oauth2/test_coverage [⛳cclim-cov-img♻️]: https://api.codeclimate.com/v1/badges/688c612528ff90a46955/test_coverage [🖇codecov-img♻️]: https://codecov.io/gh/oauth-xx/oauth2/branch/master/graph/badge.svg?token=bNqSzNiuo2 [🖇codecov]: https://codecov.io/gh/oauth-xx/oauth2 [🏘coveralls]: https://coveralls.io/github/oauth-xx/oauth2?branch=master [🏘coveralls-img]: https://coveralls.io/repos/github/oauth-xx/oauth2/badge.svg?branch=master [🚎sec-pol]: https://github.com/oauth-xx/oauth2/blob/master/SECURITY.md [🚎sec-pol-img]: https://img.shields.io/badge/security-policy-brightgreen.svg?style=flat [🖐codeQL]: https://github.com/oauth-xx/oauth2/security/code-scanning [🖐codeQL-img]: https://github.com/oauth-xx/oauth2/actions/workflows/codeql-analysis.yml/badge.svg [🧮cov-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml [🧮cov-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml/badge.svg [⛳gh-discussions]: https://github.com/oauth-xx/oauth2/discussions [⛳gh-discussions-img]: https://img.shields.io/github/discussions/oauth-xx/oauth2 [🖇codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github [🖇codementor-img]: https://cdn.codementor.io/badges/get_help_github.svg [🏘chat]: https://gitter.im/oauth-xx/oauth2 [🏘chat-img]: https://img.shields.io/gitter/room/oauth-xx/oauth2.svg [🚎blog]: http://www.railsbling.com/tags/oauth2/ [🚎blog-img]: https://img.shields.io/badge/blog-railsbling-brightgreen.svg?style=flat [🖐wiki]: https://github.com/oauth-xx/oauth2/wiki [🖐wiki-img]: https://img.shields.io/badge/wiki-examples-brightgreen.svg?style=flat [⛳liberapay-img]: https://img.shields.io/liberapay/patrons/pboling.svg?logo=liberapay [⛳liberapay]: https://liberapay.com/pboling/donate [🖇sponsor-img]: https://img.shields.io/badge/sponsor-pboling.svg?style=social&logo=github [🖇sponsor]: https://github.com/sponsors/pboling [🏘tweet-img]: https://img.shields.io/twitter/follow/galtzo.svg?style=social&label=Follow [🏘tweet]: http://twitter.com/galtzo [railsbling]: http://www.railsbling.com [peterboling]: http://www.peterboling.com [aboutme]: https://about.me/peter.boling [angelme]: https://angel.co/peter-boling [coderme]:http://coderwall.com/pboling ## Installation Install the gem and add to the application's Gemfile by executing: $ bundle add oauth2 If bundler is not being used to manage dependencies, install the gem by executing: $ gem install oauth2 ## OAuth2 for Enterprise Available as part of the Tidelift Subscription. The maintainers of OAuth2 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] [tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=enterprise ## Security contact information To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. For more see [SECURITY.md][🚎sec-pol]. ## What is new for v2.0? - Officially support Ruby versions >= 2.7 - Unofficially support Ruby versions >= 2.5 - Incidentally support Ruby versions >= 2.2 - Drop support for the expired MAC Draft (all versions) - Support IETF rfc7523 JWT Bearer Tokens - Support IETF rfc7231 Relative Location in Redirect - Support IETF rfc6749 Don't set oauth params when nil - Support [OIDC 1.0 Private Key JWT](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication); based on the OAuth JWT assertion specification [(RFC 7523)](https://tools.ietf.org/html/rfc7523) - Support new formats, including from [jsonapi.org](http://jsonapi.org/format/): `application/vdn.api+json`, `application/vnd.collection+json`, `application/hal+json`, `application/problem+json` - Adds new option to `OAuth2::Client#get_token`: - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token` - Adds new option to `OAuth2::AccessToken#initialize`: - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency - By default, keys are transformed to camel case. - Original keys will still work as previously, in most scenarios, thanks to `rash_alt` gem. - However, this is a _breaking_ change if you rely on `response.parsed.to_h`, as the keys in the result will be camel case. - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option. - By default, the `:auth_scheme` is now `:basic_auth` (instead of `:request_body`) - Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body - [... A lot more](https://github.com/oauth-xx/oauth2/blob/master/CHANGELOG.md#2.0.0) ## Compatibility Targeted ruby compatibility is non-EOL versions of Ruby, currently 2.7, 3.0 and 3.1. Compatibility is further distinguished by supported and unsupported versions of Ruby. Ruby is limited to 2.2+ for 2.x releases. See `1-4-stable` branch for older rubies.
Ruby Engine Compatibility Policy This gem is tested against MRI, JRuby, and Truffleruby. Each of those has varying versions that target a specific version of MRI Ruby. This gem should work in the just-listed Ruby engines according to the targeted MRI compatibility in the table below. If you would like to add support for additional engines, first make sure Github Actions supports the engine, then submit a PR to the correct maintenance branch as according to the table below.
Ruby Version Compatibility Policy If something doesn't work on one of these interpreters, it's a bug. This library may inadvertently work (or seem to work) on other Ruby implementations, however support will only be provided for the versions listed above. If you would like this library to support another Ruby version, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time of a major release, support for that Ruby version may be dropped.
| | Ruby OAuth2 Version | Maintenance Branch | Supported Officially | Supported Unofficially | Supported Incidentally | |:----|---------------------|--------------------|-------------------------|------------------------|------------------------| | 1️⃣ | 2.0.x | `master` | 2.7, 3.0, 3.1 | 2.5, 2.6 | 2.2, 2.3, 2.4 | | 2️⃣ | 1.4.x | `1-4-stable` | 2.5, 2.6, 2.7, 3.0, 3.1 | 2.1, 2.2, 2.3, 2.4 | 1.9, 2.0 | | 3️⃣ | older | N/A | Best of luck to you! | Please upgrade! | | NOTE: The 1.4 series will only receive critical security updates. See [SECURITY.md][🚎sec-pol] ## Usage Examples ### `authorize_url` and `token_url` are on site root (Just Works!) ```ruby require 'oauth2' client = OAuth2::Client.new('client_id', 'client_secret', site: 'https://example.org') # => # 'Basic some_password'}) response = access.get('/api/resource', params: {'query_foo' => 'bar'}) response.class.name # => OAuth2::Response ``` ### Relative `authorize_url` and `token_url` (Not on site root, Just Works!) In above example, the default Authorization URL is `oauth/authorize` and default Access Token URL is `oauth/token`, and, as they are missing a leading `/`, both are relative. ```ruby client = OAuth2::Client.new('client_id', 'client_secret', site: 'https://example.org/nested/directory/on/your/server') # => # # OAuth2::Client ``` ### snake_case and indifferent access in Response#parsed ```ruby response = access.get('/api/resource', params: {'query_foo' => 'bar'}) # Even if the actual response is CamelCase. it will be made available as snaky: JSON.parse(response.body) # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} response.parsed # => {"access_token"=>"aaaaaaaa", "additional_data"=>"additional"} response.parsed.access_token # => "aaaaaaaa" response.parsed[:access_token] # => "aaaaaaaa" response.parsed.additional_data # => "additional" response.parsed[:additional_data] # => "additional" response.parsed.class.name # => OAuth2::SnakyHash (subclass of Hashie::Mash::Rash, from `rash_alt` gem) ``` #### What if I hate snakes and/or indifference? ```ruby response = access.get('/api/resource', params: {'query_foo' => 'bar'}, snaky: false) JSON.parse(response.body) # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} response.parsed # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} response.parsed['accessToken'] # => "aaaaaaaa" response.parsed['additionalData'] # => "additional" response.parsed.class.name # => Hash (just, regular old Hash) ```
Debugging Set an environment variable, however you would [normally do that](https://github.com/bkeepers/dotenv). ```ruby # will log both request and response, including bodies ENV['OAUTH_DEBUG'] = 'true' ``` By default, debug output will go to `$stdout`. This can be overridden when initializing your OAuth2::Client. ```ruby require 'oauth2' client = OAuth2::Client.new( 'client_id', 'client_secret', site: 'https://example.org', logger: Logger.new('example.log', 'weekly') ) ```
## OAuth2::Response The `AccessToken` methods `#get`, `#post`, `#put` and `#delete` and the generic `#request` will return an instance of the #OAuth2::Response class. This instance contains a `#parsed` method that will parse the response body and return a Hash-like [`OAuth2::SnakyHash`](https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/snaky_hash.rb) if the `Content-Type` is `application/x-www-form-urlencoded` or if the body is a JSON object. It will return an Array if the body is a JSON array. Otherwise, it will return the original body string. The original response body, headers, and status can be accessed via their respective methods. ## OAuth2::AccessToken If you have an existing Access Token for a user, you can initialize an instance using various class methods including the standard new, `from_hash` (if you have a hash of the values), or `from_kvform` (if you have an `application/x-www-form-urlencoded` encoded string of the values). ## OAuth2::Error On 400+ status code responses, an `OAuth2::Error` will be raised. If it is a standard OAuth2 error response, the body will be parsed and `#code` and `#description` will contain the values provided from the error and `error_description` parameters. The `#response` property of `OAuth2::Error` will always contain the `OAuth2::Response` instance. If you do not want an error to be raised, you may use `:raise_errors => false` option on initialization of the client. In this case the `OAuth2::Response` instance will be returned as usual and on 400+ status code responses, the Response instance will contain the `OAuth2::Error` instance. ## Authorization Grants Currently the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion authentication grant types have helper strategy classes that simplify client use. They are available via the [`#auth_code`](https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/strategy/auth_code.rb), [`#implicit`](https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/strategy/implicit.rb), [`#password`](https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/strategy/password.rb), [`#client_credentials`](https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/strategy/client_credentials.rb), and [`#assertion`](https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/strategy/assertion.rb) methods respectively. These aren't full examples, but demonstrative of the differences between usage for each strategy. ```ruby auth_url = client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth/callback') access = client.auth_code.get_token('code_value', redirect_uri: 'http://localhost:8080/oauth/callback') auth_url = client.implicit.authorize_url(redirect_uri: 'http://localhost:8080/oauth/callback') # get the token params in the callback and access = OAuth2::AccessToken.from_kvform(client, query_string) access = client.password.get_token('username', 'password') access = client.client_credentials.get_token # Client Assertion Strategy # see: https://tools.ietf.org/html/rfc7523 claimset = { iss: 'http://localhost:3001', aud: 'http://localhost:8080/oauth2/token', sub: 'me@example.com', exp: Time.now.utc.to_i + 3600, } assertion_params = [claimset, 'HS256', 'secret_key'] access = client.assertion.get_token(assertion_params) # The `access` (i.e. access token) is then used like so: access.token # actual access_token string, if you need it somewhere access.get('/api/stuff') # making api calls with access token ``` If you want to specify additional headers to be sent out with the request, add a 'headers' hash under 'params': ```ruby access = client.auth_code.get_token('code_value', redirect_uri: 'http://localhost:8080/oauth/callback', headers: {'Some' => 'Header'}) ``` You can always use the `#request` method on the `OAuth2::Client` instance to make requests for tokens for any Authentication grant type. ## Versioning This library aims to adhere to [Semantic Versioning 2.0.0][semver]. Violations of this scheme should be reported as bugs. Specifically, if a minor or patch version is released that breaks backward compatibility, a new version should be immediately released that restores compatibility. Breaking changes to the public API will only be introduced with new major versions. As a result of this policy, you can (and should) specify a dependency on this gem using the [Pessimistic Version Constraint][pvc] with two digits of precision. For example: ```ruby spec.add_dependency 'oauth2', '~> 2.0' ``` [semver]: http://semver.org/ [pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint ## License [![License: MIT][🖇src-license-img]][🖇src-license] - Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc. - Copyright (c) 2017-2022 [oauth-xx organization][oauth-xx] - See [LICENSE][license] for details. [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=large)][fossa2] [license]: https://github.com/oauth-xx/oauth2/blob/master/LICENSE [oauth-xx]: https://github.com/oauth-xx [fossa2]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_large ## Development 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. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then 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). ## Contributing See [CONTRIBUTING.md][contributing] [contributing]: https://github.com/oauth-xx/oauth2/blob/master/CONTRIBUTING.md ## Contributors [![Contributors](https://contrib.rocks/image?repo=oauth-xx/oauth2)]("https://github.com/oauth-xx/oauth2/graphs/contributors") Made with [contributors-img](https://contrib.rocks). ## Code of Conduct Everyone interacting in the OAuth2 project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/oauth-xx/oauth2/blob/master/CODE_OF_CONDUCT.md). oauth2-2.0.7/Rakefile000066400000000000000000000015731430066224500143770ustar00rootroot00000000000000# encoding: utf-8 # frozen_string_literal: true # !/usr/bin/env rake require 'bundler/gem_tasks' begin require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) rescue LoadError desc 'spec task stub' task :spec do warn 'rspec is disabled' end end desc 'alias test task to spec' task test: :spec begin require 'rubocop/rake_task' RuboCop::RakeTask.new do |task| task.options = ['-D'] # Display the name of the failing cops end rescue LoadError desc 'rubocop task stub' task :rubocop do warn 'RuboCop is disabled' end end # namespace :doc do # require 'rdoc/task' # require 'oauth2/version' # RDoc::Task.new do |rdoc| # rdoc.rdoc_dir = 'rdoc' # rdoc.title = "oauth2 #{OAuth2::Version}" # rdoc.main = 'README.md' # rdoc.rdoc_files.include('README.md', 'LICENSE.md', 'lib/**/*.rb') # end # end task default: %i[test rubocop] oauth2-2.0.7/SECURITY.md000066400000000000000000000017241430066224500145210ustar00rootroot00000000000000# Security Policy ## Supported Versions | Version | Supported | |----------|---------------------------| | 2.latest | ✅ | | 1.latest | ✅ (security updates only) | | older | ⛔️ | ## Reporting a Vulnerability To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ## OAuth2 for Enterprise Available as part of the Tidelift Subscription. The maintainers of oauth2 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.](https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) oauth2-2.0.7/bin/000077500000000000000000000000001430066224500134745ustar00rootroot00000000000000oauth2-2.0.7/bin/bundle000077500000000000000000000056301430066224500146770ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'bundle' is installed as part of a gem, and # this file is here to facilitate running it. # require 'rubygems' m = Module.new do module_function def invoked_as_script? File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__) end def env_var_version ENV['BUNDLER_VERSION'] end def cli_arg_version return unless invoked_as_script? # don't want to hijack other binstubs return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update` bundler_version = nil update_index = nil ARGV.each_with_index do |a, i| bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ bundler_version = Regexp.last_match(1) update_index = i end bundler_version end def gemfile gemfile = ENV['BUNDLE_GEMFILE'] return gemfile if gemfile && !gemfile.empty? File.expand_path('../Gemfile', __dir__) end def lockfile lockfile = case File.basename(gemfile) when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile) else "#{gemfile}.lock" end File.expand_path(lockfile) end def lockfile_version return unless File.file?(lockfile) lockfile_contents = File.read(lockfile) return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ Regexp.last_match(1) end def bundler_requirement @bundler_requirement ||= env_var_version || cli_arg_version || bundler_requirement_for(lockfile_version) end def bundler_requirement_for(version) return "#{Gem::Requirement.default}.a" unless version bundler_gem_version = Gem::Version.new(version) requirement = bundler_gem_version.approximate_recommendation return requirement unless Gem.rubygems_version < Gem::Version.new('2.7.0') requirement += '.a' if bundler_gem_version.prerelease? requirement end def load_bundler! ENV['BUNDLE_GEMFILE'] ||= gemfile activate_bundler end def activate_bundler gem_error = activation_error_handling do gem 'bundler', bundler_requirement end return if gem_error.nil? require_error = activation_error_handling do require 'bundler/version' end return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 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}'`" exit 42 end def activation_error_handling yield nil rescue StandardError, LoadError => e e end end m.load_bundler! load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script? oauth2-2.0.7/bin/console000077500000000000000000000005631430066224500150700ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'bundler/setup' require 'oauth2' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require 'irb' IRB.start(__FILE__) oauth2-2.0.7/bin/rake000077500000000000000000000013371430066224500143500ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'rake' is installed as part of a gem, and # this file is here to facilitate running it. # ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) bundle_binstub = File.expand_path('bundle', __dir__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") end end require 'rubygems' require 'bundler/setup' load Gem.bin_path('rake', 'rake') oauth2-2.0.7/bin/rspec000077500000000000000000000013471430066224500145430ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'rspec' is installed as part of a gem, and # this file is here to facilitate running it. # ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) bundle_binstub = File.expand_path('bundle', __dir__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") end end require 'rubygems' require 'bundler/setup' load Gem.bin_path('rspec-core', 'rspec') oauth2-2.0.7/bin/rubocop000077500000000000000000000013501430066224500150720ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'rubocop' is installed as part of a gem, and # this file is here to facilitate running it. # ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) bundle_binstub = File.expand_path('bundle', __dir__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") end end require 'rubygems' require 'bundler/setup' load Gem.bin_path('rubocop', 'rubocop') oauth2-2.0.7/bin/setup000077500000000000000000000002031430066224500145550ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here oauth2-2.0.7/docs/000077500000000000000000000000001430066224500136545ustar00rootroot00000000000000oauth2-2.0.7/docs/images/000077500000000000000000000000001430066224500151215ustar00rootroot00000000000000oauth2-2.0.7/docs/images/logo/000077500000000000000000000000001430066224500160615ustar00rootroot00000000000000oauth2-2.0.7/docs/images/logo/README.txt000066400000000000000000000005221430066224500175560ustar00rootroot00000000000000The OAuth 2.0 Logo - oauth2-logo-124px.png (resized) https://oauth.net/about/credits/ The OAuth logo was designed by Chris Messina. --- The Ruby Logo - ruby-logo-124px.jpeg (resized) https://www.ruby-lang.org/en/about/logo/ Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5 https://creativecommons.org/licenses/by-sa/2.5 oauth2-2.0.7/docs/images/logo/oauth2-logo-124px.png000066400000000000000000000321171430066224500216070ustar00rootroot00000000000000PNG  IHDR|{4IDATxUȀX m삽` ;(`(5=Ϝ3p{=OO|>?X~]?xw}Vww}ᮻpn}[o{o2oW;#6{ӛ޴k_/ˏ~ы^t%~WtA]-X{=яwxw߿+}٧]xnw\/mo{-oyˣov-o{r|[w퓟vЇ>t{< o}[׽u^9YzVt%<>;?Ca{CKûmݶխnm6]>ozӛMnr[lq oxC6x6̷N9唝/xg>󙕟ݻo{;ꨣ| '}[~w/?[lY׿?]|݊+|{? kvurfuoli׽u]y0._O~ON?Sĉ'}s 38!׿ߡ?]}ʕCZjUw%t^zi[zuo7M/}{ի^5ڪ+\:]izwk_{Fmtʆnx@=6y+ж ;s/~7Ժ+лg-Yd?-. !7_t{Cm lÊ̷ߎ-@jm?LT1k:ӟ-oyKwG % @k5*<E0oto]YbeGwt袋.򗿌pC߮}+_J/]t~7!]\tdWw7я~theL7-.3N |~gQ(^W<[xq}k_q{to@^8^~Z. aTs=iвC6ځ]O>oFgy&-Rfri`wQK^SO,Ϩ'D|m 80'kP?p)0`|軫X-ZH? E^-~߿YEه90>A@59aIOzR< Lt$h>YG OUoݍnt# t7;_Ztt!}GKC>hH-w1O}jW%Ty6LzUΗLJUنWkhx :GoCu[W!34A [h_\U;4`bIUɀ-RO4t3@i_j/G;xjgEIFU̡↘sb-鷪^O4ЇvU<1344@n4tUe0RjWbb КTFv~wh=4<2]㯙o&]V!m|$xǔt5՛R|jسNri;m'"hAyy6G=?TF 4{&-`&m?tk\*viykf* : |~5̺Lp4=?qx[CtIʲC_v@>G}#vΜm'nou-g_Z4k U.)^IaTq{\Wv=K lcvrĤETͥq}ih>']Piuxт>w].G/}ije~sXsaȚxUΔpZZ(Kʨ`~f]nZ)ճN!܋x@8Vvw"# _\h\G c&R/U2 lLppN saJ; t-f_WmR8ͧ>|-b" !J1|x&?(^Wg1Ik?kG∡i2>軬w`;(tY݈v"l>Tz$p:A`Plʮ9g KpTb}Ѓdh=N?Ǭ n[U>"8"\}<@?{1y RhaiN;Ĕ2<Js ̤l&r-n-\i`= I~s#[Ӓh^ 8n@&>>KRR2v99s}y@˭@o; #WBE^U0=} ' Ftaϖ0:&;&?pU<7ل\wے r eܾùT7,)tjp]y=lS|q#c|w+[*2:f S;\޶r|fv;6DJ>cDY`V4 g}A}t}s݋n}QTP{ hll"4LLD`Nuhb⡊OBOa[&\ xQ$7嗢_M=j]>{h9O&} #vWhQRk'n ЌhF?Och{4ԏb292vVsDH$p4ʧ>8hL(u ʡ@vYz@0%LVkG-[ϸ`8B96IhI>C5[`ߦ|QoQ@V#5CeKt/&\˩ `pS4&sPSν: hg܉1RgKƁY=O} (67;, D+-!ۣ|9ɯ`'0XpX`pl 6+R!X'M[eeGpst˘۬9]UͫKNѪ1`um8vv3:zG)s rN OL+w] A3$@y>ߜ-tx}xQ˗!L뵮 L:@GnsX OQr4iݾcdI6D*Mgy^t;#-]@ h_Sy\oPo<:Uj!-: GvYfg>Ms~`0kⅼv5}:ag"+nF&1 IKf}V{ED57 z VpUv(f=ԏ:6ULA{[ 2_lx@E,v@F`cD-Q4nv a\U;Mx՞1DHC@oͺ)Q@ K-jID]K\p%517Z ȴ]ɯOkI pfޥxS&FU:oR`#=\*FnD+a i?$JK0$:_^ͤ˩f%Lv R/}sQ糝#Sp׷}I'&PUUnÊqΟs pXg^4 iLzJIϡπ TQ_-4 g&9>[r-)k^8g[w;^ժ]z['V,uVC̹y!)~6y6l I~vLqIR k2!afu _$ȼSYJئe6`zءq9tEd 345t!1#`"6A! 'Jn})4͒& y❚ZC" '5)zL"gVg A @nfWt.w`30"kc0`mJqIs.@r|z1R3T;Mr{fϮ[,#F0B4{F33Z>W3k@e%лxբpLILǴpؒ@a*x}:uXA p`$03=gD UXq}ݎt}<]t{_2.+WϘk5[g>ZZ2 {$g0PGQ|ʼv /ah{IR09\'+6&WfL1&ov.' \O,}*[΋|ǽd.S7ۧi鮥:P جf!,R`uJɑ0wk1;ab^9DĊ2绥s<`ƒ113ˡ_bMڛ6zh4U2N vM sMal>P|\CrhW(FaYHHP4K@;c x?]Rq@ b 3nl}]o*0GK]GZ|>%IۛrE3 %JZ_Qr,:w?;۔D:ff6]{cH-ŷ3]i(S?rQgrT]Ш >D]4Ӷ2O.}᳁jIuM &qAܨ[уW/V;ܻHk 2ԂguJSQO8s0ev5i?x߁2]0\E cg*9~g>] x'#'Ч?rLsASHK;߱+Dh9 h`=h:' ]q,pE]S(6x PD;_*Zf|Kl*s]BIQǏbdG0IG|:iԉPqEoxaX1l10ʚ9ΨV+{ \{2ν4 + |#cw aDނI{ F&`ﶅ15鐆9/#S(>;1s[LϔV~<" ٧`Gv`Xgpq@'F^JjD~43 Ínŷ`F@Bf_LHW"jyFk"PAh:/6侢湂},ߧ3/, !`MUTׅll9圉# x' xͶX A1.6yPR(BE-Oqh^@-1 Ӏ6!`JǏ̏=t3[ĘV(bd 49, 0us $xOksE¸g/1˥"}L9ä%r5Yq򨀍 n5=r<63__ X n`[1]!(##p1d:W3N;|hRu ~3_*OK)`QBΤp.Yx6-5 (;Ϭ9*LZsXe1#LsS1c1O0I$~ejZsLPJc\^F  rΧҵRPx&Y!=w%?:/zMB򷑴.<lRؚ`HDa'aaN9o E*Psh%z~wAf6}t@ :yKZk&}X>VC iͼ`vʍtPa>6}`^^v?Kiy5"PۘEe[,s*`e2@pn +ͻߣWk |/-/b {EJיt6<ܛj xGkoo#y4'8@/j-E/HCgˣi{1JJǤ13Zlw}`W͡ ČMfᣌ{͉i>Z/2h>㏴i8 g#4=ZVogئfz%ծTț~#m&Ĭ XM1uIߒcv-40hR L6؞IɧLmvI\C"4A'H\NڟL9y}xx_7)X="#h`j FlhcR0h<?EkbO+,S 8toKT ZR=zV} ~[ .m뗾A |r)-F}\܋Kbn2]xMmͱaIEL8MBݱD 0 [=@f)*sڜC3ԺL"'`~vE)N% m_җ>oo5y2~P+6^'fj5Gh) -g21bRQ:oO_2ã|̸{a"q#O1 >)M3$٧LDή!ȋ MDrSp۳'0lG'%{a%d(tY݄@guGc>~KqL0̿׆tF)OLj{q$f'NsifQ7B#` &6"4Lo"l:>}56aro*oxD"CSmל_t2ϬG@Kafrr9uRg14C`c"a;j*MJ1Mn lob4,j홁KH]'`c>o/2Mjl5_^VD3<&8a-6oB o!}y< #&Ra4L>^C*d^wCsՀ6E0h@%Xl ǦHTDf #Vp7@ H1%p,\*~M 6*_~:+R0\j.;ɍipb_4]d>1[)@4@]NJ~4P{BLnAe*m4Ћ_K2?e=- "d&`cR@ODKSW]<;L׀O|L&4T i%%;ը* =POAyi/kL 0 1u,ՇgD_PB(;v| $@R蒹d&1- ~S^ؠ$TALtOi1.at8):lE뫆JU7Ё ~j 17_-B8~i[t@E.9;ȑIo&$+- _5 %kRj@q`Ǘ#?{NKغvlZ>V3u})  ($xk5}K')4cBB]:c!`cYg}y)lvvk0.Rjelm*[ZmNoSN~X^O<BM,oXF1J0?qA>tj7ʨL}E)<0?_撆!V2C@U)6"x$; !AU:zS CKc_cpL32Eh,"L!L-mfe!)ߍ"gwda`H`ŸWlJ2R:o*\V ۙheNJ Q%S5(jb[@'cfv;Tc26 2}9t(,Y gek؎yyYR9k?fҏVs+Uf`N7‡IWƄh@"-E9dm ,V H-/fڐAr}򼬃9ÄsjmeEfV٣Nc'B:@k3syO(`48J(0hF+ 0Jߪ.1}I! gx Ϣy"4ᑊ̍{-/ys;Z 3>1y}fv0>4g;(b#0A\;~#hg@}˫DcD,\6Voܸ3[$Z>>21M|z&`ah :+weʁKXV;]3v{ "0u<#`\=h4a 8,ߝ3jds*ާ(`ֈ#IaF5@Qi3F[BϜ4TF\CLe=[0c]w6 ï Xlʽ^R)=WtnjF)QL "1J\Lt4T0([ X֤lbdks-ke8:<йPRBϥ%1XlZ}@ 0 סypy/dDC2y0Dhx(0- gni2,_e[ʘuaw~]}ϬLIΟ}%ʼ? 3ue~'Ӏ5: 4, yȖKJCFM(G}WH""pfm[RRgR#@۵wZ웍(,X>X׭qЍjcΫ,RerMTvc߾#> PJg%C&93:5B}_g v#4>DꙂLPtH6@ZJ?(-Js ЏRAewCy4 Ҷ9*;..Y,h"h=0T!|n_{L{qA rd;lLp楾"iܐr,7g^1WI GƔeDM-voQ.&>Mqys2ˤvZmQc@ w>:ogE܈;;@>`Zj=5>+asA*|*I] v6-˧#-6RKL3XB`ٶ|r˸|&tRdG-Tj K 8G~w_aIs7HdHK -kZE3,2Gm9X_ZEە_§yXVR 08&4Nǚ$BhɃ;+7QC^l2`ݕ9* vXePBH=M~Yd> bvs(x&nkIvUK4LĚ94/U4"1 ZS,X>R^D loWH#R+,@8^-0i'p"ë-9#xe-%thj@NCuNf$Oтw6gifiPRPK7/y0m7Iо%sU.R>^Z{ &۵wP :b1;&!@M8ƂNdE~4`!ϻg1`D8"cԟ`<<RKTX/)eupi 3g f4 @޶AQNj)̞h&O 9ܴ4oo߁. \fbJXk]Z+0*O t#fz'LFvTi~۞,p64bp+;sNn5@W{3{#Ld0io,_"ecq|&r?֣OYԺ7&tbYp t='cǔg_[1*,ET00 0&LdGTh#Gy <(Q|DǍ=m} G[' //:h?= e< liLKy=h lwۙuaL ɳ~9>dY!gƬϙH&-.k/~)9rr`,m۷#/62jSrhuf14`:F3siio64&~tsCExA]1HSHm*SCKp֞G! \/9j2G2˚EG&Y"ZM >9O/3ˎ'(-m||r͹l Z-0} ._\f#fum,+(1brj4Ṙn4*اۧlg&2f >5~q=Ñփ4P~䵸@.3 5D֤|Ѻhgk?[qPQ7E^6#ZZ~Am޵ƾM~0`"o&N'Kt[?[?oش π2V.;+з̷DoQ~}Ar9%vG35,,70fQ# cߙ_)pH=1*OFo1o;ğVھҸw&Ԙp1]-',4O@'g= ^&e:(Jj>Ul1/B_Y7U)tQ`]!ś XR~B:'&=\pfX3 d\IU5N-]N`ȷ`3!BF.ϫB %D;l8og6+`(xwMs:`v8To%xWiR_N.cӥݛ (WjT_b9i`\]4}YiJێ/ְ{ |o*;'5IENDB`oauth2-2.0.7/docs/images/logo/ruby-logo-124px.jpeg000066400000000000000000000075371430066224500215370ustar00rootroot00000000000000JFIFExifMM*JR(iZ||8Photoshop 3.08BIM8BIM%ُ B~||" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222 ?( ( (c\ӴԮWXԚM*1r|WfV,P۠-+x׉>/](:yJt_'ڼs5\JsׁW%Ldcu=.CVWϢHub}T?:V.n^oҾlYֆ]ڄzf#[\&ˇrޜ=kUƹeʂɱj؃k?ِeWUZXD03'x&<]86*c*TAJҴCTpuԠw_^irVUSTc1IBƢw7>Y.e q$dmxϣ)V|o2\BpxuꧧboUϕs}򧺟pr+Nt|3 %( (7t}'H_Tae-n{ML栮Ͱyך!V3m[M?өuG^kBz(T&[^Yy$v,%IiV*,_O 5qAMW1PjP~Ft* kxK}s\#D[,!Bm$75-ag+Wmn" meH?㿱+~#G'x@1B\京⻪b4 ݻI`\[k;C2uA* ׹\%qX-*z0"z3"|z`ׯ|0[i~ͱ;1EeVd׍S"P*ʹZub$(kv.K|!GWn=j8ɤg5 m8Z:VO:KйIi^I݋31bz{V5j]5 73>;(js^N"ӷWZJZ=D-PUz43@$4VAlW# Mq-FV<=ԟOAk/3i~ ӬZ}P 0?*M>bG ɦ)̗R~@~z |4rrDev§ޭW']\xNo"he#38#c=+gEAsL㌞&~o;KþO'N7yvvߠ0ڳL4esJnNhЅ(٤o֚oֳO2ȯ~stVU2kXsTvvișRߕ5{{cb1O[c#_^hP}l#:+{s+c>~,mS˵??JacS$ϡm$Q) *'nF=\u]/!C)Sռc=:~Lq~4v,=OR<}jU_7T*RE&bl谉5{˘Sq|n.I!g.s.zk$.Mz-W6Y8z?;^YjMkK~Z%o8 SzҋNf YR;$֒X1 Ta'`&8Vڳ@W( eB( S NT#AZD OCPWG+VPT̉ZGl;_( <}k!Ҿx6OFqK'袾*9vxf~]>F HO5_hz }QP{q}wYׇڋ}V+t!V5Z*z=a,?>sNRIbGM-")?qzw,/ڄ`kPzKũ&ȵFkl>藪<@gu[| ,u ?޹#O9[1 >uMJ26}q_I'_.7iIܤUz?>|?fRլ4iO1%1N[~?ƾ`xLVL*ҋJӡUakK ݐƾo' }#kN5Y@٢޿X>*)w%z/M άGʏ{G*r97'8_q? Rw6f0"W,0^E '"|V?oauth2-2.0.7/docs/images/logo/ruby-logo-198px.svg000066400000000000000000000556321430066224500214230ustar00rootroot00000000000000 image/svg+xml oauth2-2.0.7/gemfiles/000077500000000000000000000000001430066224500145175ustar00rootroot00000000000000oauth2-2.0.7/gemfiles/README.md000066400000000000000000000132401430066224500157760ustar00rootroot00000000000000# History `faraday` v0.17.3 is the first version that stops using `&Proc.new` for block forwarding, and thus is the oldest version oauth2 is compatible with. ```ruby gem 'faraday', ['>= 0.17.3', '< 3.0'] ``` # Ruby We use the Github Action `ruby/setup-ruby@master` to install Ruby, and it has a matrix of [supported versions](https://github.com/ruby/setup-ruby/blob/master/README.md#supported-versions) (copied below). | Interpreter | Versions | |-----------------------|------------------------------------------------------------------------------------------| | `ruby` | 1.9.3, 2.0.0, 2.1.9, 2.2, all versions from 2.3.0 until 3.1.1, head, debug, mingw, mswin | | `jruby` | 9.1.17.0 - 9.3.3.0, head | | `truffleruby` | 19.3.0 - 22.0.0, head | | `truffleruby+graalvm` | 21.2.0 - 22.0.0, head | In the naming of gemfiles, we will use the below shorthand for interpreter, and version. Platforms will be represented without modification. | Interpreter | Shorthand | |-----------------------|-----------| | `ruby` | r | | `jruby` | jr | | `truffleruby` | tr | | `truffleruby+graalvm` | trg | Building onto that, we can add the MRI target spec, since that's what all Rubygems use for minimum version compatibility. | Interpreter + Version | MRI spec | Shorthand | |----------------------------|----------|------------| | ruby-1.9.3 | 1.9 | r1_9 | | ruby-2.0.0 | 2.0 | r2_0 | | ruby-2.1.9 | 2.1 | r2_1 | | ruby-2.2.x | 2.2 | r2_2 | | ruby-2.3.x | 2.3 | r2_3 | | ruby-2.4.x | 2.4 | r2_4 | | ruby-2.5.x | 2.5 | r2_5 | | ruby-2.6.x | 2.6 | r2_6 | | ruby-2.7.x | 2.7 | r2_7 | | ruby-3.0.x | 3.0 | r3_0 | | ruby-3.1.x | 3.1 | r3_1 | | ruby-head | 3.2 | rH3_2 | | ruby-mingw | (?) | rmin | | ruby-mswin | (?) | rMS | | jruby-9.1.x.x | 2.3 | jr9_1-r2_3 | | jruby-9.2.x.x | 2.5 | jr9_2-r2_5 | | jruby-9.3.x.x | 2.6 | jr9_3-r2_6 | | jruby-head | 2.7 | jrH-r2_7 | | truffleruby-19.3.x | 2.5(?) | tr19-r2_5 | | truffleruby-20.x.x | 2.6.5 | tr20-r2_6 | | truffleruby-21.x.x | 2.7.4 | tr21-r2_7 | | truffleruby-22.x.x | 3.0.2 | tr22-r3_0 | | truffleruby-head | 3.1(?) | trH-r3_1 | | truffleruby+graalvm-21.2.x | 2.7.4 | trg21-r2_7 | | truffleruby+graalvm-22.x.x | 3.0.2 | trg22-r3_0 | | truffleruby+graalvm-head | 3.1(?) | trgH-r3_1 | We will run tests on as many of these as possible, in a matrix with each supported major version of `faraday`, which means 0.17.3+ (as `f0`), 1.10.x (as `f1`), 2.2.x (as `f2`). Discrete versions of `faraday` to test against, as of 2022.02.19, with minimum version of Ruby for each: * 2.2.0, Ruby >= 2.6 * 1.10.0, Ruby >= 2.4 * 0.17.4, Ruby >= 1.9 ❌ - Incompatible ✅ - Official Support 🚧 - Unofficial Support 🤡 - Incidental Compatibility 🙈 - Unknown Compatibility | Shorthand | f0 - 0.17.3+ | f1 - 1.10.x | f2 - 2.2.x | |------------|------------------|------------------|-----------------| | r1_9 | 🤡 f0-r1_9 | ❌ | ❌ | | r2_0 | 🤡 f0-r2_0 | ❌ | ❌ | | r2_1 | 🤡 f0-r2_1 | ❌ | ❌ | | r2_2 | 🤡 f0-r2_2 | ❌ | ❌ | | r2_3 | 🚧 f0-r2_3 | ❌ | ❌ | | r2_4 | 🚧 f0-r2_4 | 🚧 f1-r2_4 | ❌ | | r2_5 | 🚧 f0-r2_5 | 🚧 f1-r2_5 | ❌ | | r2_6 | 🚧 f0-r2_6 | 🚧 f1-r2_6 | 🚧 f2-r2_6 | | r2_7 | ✅ f0-r2_7 | ✅ f1-r2_7 | ✅ f2-r2_7 | | r3_0 | ✅ f0-r3_0 | ✅ f1-r3_0 | ✅ f2-r3_0 | | r3_1 | ✅ f0-r3_1 | ✅ f1-r3_1 | ✅ f2-r3_1 | | rH3_2 | 🚧 f0-rH3_2 | 🚧 f1-rH3_2 | 🚧 f2-rH3_2 | | rmin | 🙈 f0-rmin | 🙈 f1-rmin | 🙈 f2-rmin | | rMS | 🙈 f0-rMS | 🙈 f1-rMS | 🙈 f2-rMS | | jr9_1-r2_3 | 🚧 f0-jr9_1-r2_3 | ❌ | ❌ | | jr9_2-r2_5 | 🚧 f0-jr9_2-r2_5 | 🚧 f1-jr9_2-r2_5 | ❌ | | jr9_3-r2_6 | ✅ f0-jr9_3-r2_6 | ✅ f1-jr9_3-r2_6 | ✅ f2-jr9_3-r2_6 | | jrH-r2_7 | 🚧 f0-jrH-r2_7 | 🚧 f1-jrH-r2_7 | 🚧 f2-jrH-r2_7 | | tr19-r2_5 | 🚧 f0-tr19-r2_5 | 🚧 f1-tr19-r2_5 | ❌ | | tr20-r2_6 | 🚧 f0-tr20-r2_6 | 🚧 f1-tr20-r2_6 | 🚧 f2-tr20-r2_6 | | tr21-r2_7 | ✅ f0-tr21-r2_7 | ✅ f1-tr21-r2_7 | ✅ f2-tr21-r2_7 | | tr22-r3_0 | ✅ f0-tr22-r3_0 | ✅ f1-tr22-r3_0 | ✅ f2-tr22-r3_0 | | trH-r3_1 | 🚧 f0-trH-r3_1 | 🚧 f1-trH-r3_1 | 🚧 f2-trH-r3_1 | | trg21-r2_7 | ✅ f0-trg21-r2_7 | ✅ f1-trg21-r2_7 | ✅ f2-trg21-r2_7 | | trg22-r3_0 | ✅ f0-trg22-r3_0 | ✅ f1-trg22-r3_0 | ✅ f2-trg22-r3_0 | | trgH-r3_1 | 🚧 f0-trgH-r3_1 | 🚧 f1-trgH-r3_1 | 🚧 f2-trgH-r3_1 | oauth2-2.0.7/gemfiles/f0.gemfile000066400000000000000000000004361430066224500163610ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' # See README.md in this directory # 0.17.3 is the first version that stops using &Proc.new for block forwarding, # and thus is the oldest version oauth2 is compatible with. gem 'faraday', '~> 0.17.4' gemspec path: '../' oauth2-2.0.7/gemfiles/f1.gemfile000066400000000000000000000002171430066224500163570ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' # See README.md in this directory gem 'faraday', '~> 1.10' gemspec path: '../' oauth2-2.0.7/gemfiles/f2.gemfile000066400000000000000000000002161430066224500163570ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' # See README.md in this directory gem 'faraday', '~> 2.2' gemspec path: '../' oauth2-2.0.7/gemfiles/jruby_9.1.gemfile000066400000000000000000000001221430066224500175660ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: '../' oauth2-2.0.7/gemfiles/jruby_9.2.gemfile000066400000000000000000000001221430066224500175670ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: '../' oauth2-2.0.7/gemfiles/jruby_head.gemfile000066400000000000000000000001221430066224500201600ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: '../' oauth2-2.0.7/gemfiles/ruby_head.gemfile000066400000000000000000000001221430066224500200060ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: '../' oauth2-2.0.7/gemfiles/truffleruby.gemfile000066400000000000000000000001221430066224500204150ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec path: '../' oauth2-2.0.7/lib/000077500000000000000000000000001430066224500134725ustar00rootroot00000000000000oauth2-2.0.7/lib/oauth2.rb000066400000000000000000000012601430066224500152200ustar00rootroot00000000000000# frozen_string_literal: true # includes modules from stdlib require 'cgi' require 'time' # third party gems require 'rash' require 'version_gem' # includes gem files require 'oauth2/version' require 'oauth2/error' require 'oauth2/snaky_hash' require 'oauth2/authenticator' require 'oauth2/client' require 'oauth2/strategy/base' require 'oauth2/strategy/auth_code' require 'oauth2/strategy/implicit' require 'oauth2/strategy/password' require 'oauth2/strategy/client_credentials' require 'oauth2/strategy/assertion' require 'oauth2/access_token' require 'oauth2/response' # The namespace of this library module OAuth2 end OAuth2::Version.class_eval do extend VersionGem::Basic end oauth2-2.0.7/lib/oauth2/000077500000000000000000000000001430066224500146745ustar00rootroot00000000000000oauth2-2.0.7/lib/oauth2/access_token.rb000066400000000000000000000167551430066224500177000ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 class AccessToken # rubocop:disable Metrics/ClassLength TOKEN_KEYS_STR = %w[access_token id_token token accessToken idToken].freeze TOKEN_KEYS_SYM = %i[access_token id_token token accessToken idToken].freeze TOKEN_KEY_LOOKUP = TOKEN_KEYS_STR + TOKEN_KEYS_SYM attr_reader :client, :token, :expires_in, :expires_at, :expires_latency, :params attr_accessor :options, :refresh_token, :response class << self # Initializes an AccessToken from a Hash # # @param [Client] client the OAuth2::Client instance # @param [Hash] hash a hash of AccessToken property values # @option hash [String] 'access_token', 'id_token', 'token', :access_token, :id_token, or :token the access token # @return [AccessToken] the initialized AccessToken def from_hash(client, hash) fresh = hash.dup supported_keys = TOKEN_KEY_LOOKUP & fresh.keys key = supported_keys[0] # Having too many is sus, and may lead to bugs. Having none is fine (e.g. refresh flow doesn't need a token). warn("OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (#{supported_keys}); using #{key.inspect}.") if supported_keys.length > 1 token = fresh.delete(key) new(client, token, fresh) end # Initializes an AccessToken from a key/value application/x-www-form-urlencoded string # # @param [Client] client the OAuth2::Client instance # @param [String] kvform the application/x-www-form-urlencoded string # @return [AccessToken] the initialized AccessToken def from_kvform(client, kvform) from_hash(client, Rack::Utils.parse_query(kvform)) end end # Initialize an AccessToken # # @param [Client] client the OAuth2::Client instance # @param [String] token the Access Token value (optional, may not be used in refresh flows) # @param [Hash] opts the options to create the Access Token with # @option opts [String] :refresh_token (nil) the refresh_token value # @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire # @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire # @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+ # @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value # one of :header, :body or :query # @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header # @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the # Access Token value in :body or :query transmission mode def initialize(client, token, opts = {}) @client = client @token = token.to_s opts = opts.dup %i[refresh_token expires_in expires_at expires_latency].each do |arg| instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s)) end no_tokens = (@token.nil? || @token.empty?) && (@refresh_token.nil? || @refresh_token.empty?) if no_tokens if @client.options[:raise_errors] error = Error.new(opts) raise(error) else warn('OAuth2::AccessToken has no token') end end @expires_in ||= opts.delete('expires') @expires_in &&= @expires_in.to_i @expires_at &&= convert_expires_at(@expires_at) @expires_latency &&= @expires_latency.to_i @expires_at ||= Time.now.to_i + @expires_in if @expires_in @expires_at -= @expires_latency if @expires_latency @options = {mode: opts.delete(:mode) || :header, header_format: opts.delete(:header_format) || 'Bearer %s', param_name: opts.delete(:param_name) || 'access_token'} @params = opts end # Indexer to additional params present in token response # # @param [String] key entry key to Hash def [](key) @params[key] end # Whether or not the token expires # # @return [Boolean] def expires? !!@expires_at end # Whether or not the token is expired # # @return [Boolean] def expired? expires? && (expires_at <= Time.now.to_i) end # Refreshes the current Access Token # # @return [AccessToken] a new AccessToken # @note options should be carried over to the new AccessToken def refresh(params = {}, access_token_opts = {}) raise('A refresh_token is not available') unless refresh_token params[:grant_type] = 'refresh_token' params[:refresh_token] = refresh_token new_token = @client.get_token(params, access_token_opts) new_token.options = options if new_token.refresh_token # Keep it, if there is one else new_token.refresh_token = refresh_token end new_token end # A compatibility alias # @note does not modify the receiver, so bang is not the default method alias refresh! refresh # Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash # # @return [Hash] a hash of AccessToken property values def to_hash params.merge(access_token: token, refresh_token: refresh_token, expires_at: expires_at) end # Make a request with the Access Token # # @param [Symbol] verb the HTTP request method # @param [String] path the HTTP URL path of the request # @param [Hash] opts the options to make the request with # @see Client#request def request(verb, path, opts = {}, &block) configure_authentication!(opts) @client.request(verb, path, opts, &block) end # Make a GET request with the Access Token # # @see AccessToken#request def get(path, opts = {}, &block) request(:get, path, opts, &block) end # Make a POST request with the Access Token # # @see AccessToken#request def post(path, opts = {}, &block) request(:post, path, opts, &block) end # Make a PUT request with the Access Token # # @see AccessToken#request def put(path, opts = {}, &block) request(:put, path, opts, &block) end # Make a PATCH request with the Access Token # # @see AccessToken#request def patch(path, opts = {}, &block) request(:patch, path, opts, &block) end # Make a DELETE request with the Access Token # # @see AccessToken#request def delete(path, opts = {}, &block) request(:delete, path, opts, &block) end # Get the headers hash (includes Authorization token) def headers {'Authorization' => options[:header_format] % token} end private def configure_authentication!(opts) case options[:mode] when :header opts[:headers] ||= {} opts[:headers].merge!(headers) when :query opts[:params] ||= {} opts[:params][options[:param_name]] = token when :body opts[:body] ||= {} if opts[:body].is_a?(Hash) opts[:body][options[:param_name]] = token else opts[:body] += "&#{options[:param_name]}=#{token}" end # @todo support for multi-part (file uploads) else raise("invalid :mode option of #{options[:mode]}") end end def convert_expires_at(expires_at) Time.iso8601(expires_at.to_s).to_i rescue ArgumentError expires_at.to_i end end end oauth2-2.0.7/lib/oauth2/authenticator.rb000066400000000000000000000040771430066224500201030ustar00rootroot00000000000000# frozen_string_literal: true require 'base64' module OAuth2 class Authenticator attr_reader :mode, :id, :secret def initialize(id, secret, mode) @id = id @secret = secret @mode = mode end # Apply the request credentials used to authenticate to the Authorization Server # # Depending on configuration, this might be as request params or as an # Authorization header. # # User-provided params and header take precedence. # # @param [Hash] params a Hash of params for the token endpoint # @return [Hash] params amended with appropriate authentication details def apply(params) case mode.to_sym when :basic_auth apply_basic_auth(params) when :request_body apply_params_auth(params) when :tls_client_auth apply_client_id(params) when :private_key_jwt params else raise NotImplementedError end end def self.encode_basic_auth(user, password) "Basic #{Base64.strict_encode64("#{user}:#{password}")}" end private # Adds client_id and client_secret request parameters if they are not # already set. def apply_params_auth(params) result = {} result['client_id'] = id unless id.nil? result['client_secret'] = secret unless secret.nil? result.merge(params) end # When using schemes that don't require the client_secret to be passed i.e TLS Client Auth, # we don't want to send the secret def apply_client_id(params) result = {} result['client_id'] = id unless id.nil? result.merge(params) end # Adds an `Authorization` header with Basic Auth credentials if and only if # it is not already set in the params. def apply_basic_auth(params) headers = params.fetch(:headers, {}) headers = basic_auth_header.merge(headers) params.merge(headers: headers) end # @see https://datatracker.ietf.org/doc/html/rfc2617#section-2 def basic_auth_header {'Authorization' => self.class.encode_basic_auth(id, secret)} end end end oauth2-2.0.7/lib/oauth2/client.rb000066400000000000000000000332421430066224500165030ustar00rootroot00000000000000# frozen_string_literal: true require 'faraday' require 'logger' module OAuth2 ConnectionError = Class.new(Faraday::ConnectionFailed) TimeoutError = Class.new(Faraday::TimeoutError) # The OAuth2::Client class class Client # rubocop:disable Metrics/ClassLength RESERVED_PARAM_KEYS = %w[body headers params parse snaky].freeze attr_reader :id, :secret, :site attr_accessor :options attr_writer :connection # Instantiate a new OAuth 2.0 client using the # Client ID and Client Secret registered to your # application. # # @param [String] client_id the client_id value # @param [String] client_secret the client_secret value # @param [Hash] options the options to create the client with # @option options [String] :site the OAuth2 provider site host # @option options [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange # @option options [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint # @option options [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint # @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get, :post, :post_with_query_string) # @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body) # @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with # @option options [FixNum] :max_redirects (5) maximum number of redirects to follow # @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes # @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled # @option options [Proc] :extract_access_token proc that takes the client and the response Hash and extracts the access token from the response (DEPRECATED) # @option options [Class] :access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken, @version 2.0+ # @yield [builder] The Faraday connection builder def initialize(client_id, client_secret, options = {}, &block) opts = options.dup @id = client_id @secret = client_secret @site = opts.delete(:site) ssl = opts.delete(:ssl) warn('OAuth2::Client#initialize argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class`.') if opts[:extract_access_token] @options = { authorize_url: 'oauth/authorize', token_url: 'oauth/token', token_method: :post, auth_scheme: :basic_auth, connection_opts: {}, connection_build: block, max_redirects: 5, raise_errors: true, logger: ::Logger.new($stdout), access_token_class: AccessToken, }.merge(opts) @options[:connection_opts][:ssl] = ssl if ssl end # Set the site host # # @param value [String] the OAuth2 provider site host def site=(value) @connection = nil @site = value end # The Faraday connection object def connection @connection ||= Faraday.new(site, options[:connection_opts]) do |builder| oauth_debug_logging(builder) if options[:connection_build] options[:connection_build].call(builder) else builder.request :url_encoded # form-encode POST params builder.adapter Faraday.default_adapter # make requests with Net::HTTP end end end # The authorize endpoint URL of the OAuth2 provider # # @param [Hash] params additional query parameters def authorize_url(params = {}) params = (params || {}).merge(redirection_params) connection.build_url(options[:authorize_url], params).to_s end # The token endpoint URL of the OAuth2 provider # # @param [Hash] params additional query parameters def token_url(params = nil) connection.build_url(options[:token_url], params).to_s end # Makes a request relative to the specified site root. # Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616), # allowing the use of relative URLs in Location headers. # @see https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2 # # @param [Symbol] verb one of :get, :post, :put, :delete # @param [String] url URL path of request # @param [Hash] opts the options to make the request with # @option opts [Hash] :params additional query parameters for the URL of the request # @option opts [Hash, String] :body the body of the request # @option opts [Hash] :headers http request headers # @option opts [Boolean] :raise_errors whether or not to raise an OAuth2::Error on 400+ status # code response for this request. Will default to client option # @option opts [Symbol] :parse @see Response::initialize # @option opts [true, false] :snaky (true) @see Response::initialize # @yield [req] @see Faraday::Connection#run_request def request(verb, url, opts = {}, &block) response = execute_request(verb, url, opts, &block) case response.status when 301, 302, 303, 307 opts[:redirect_count] ||= 0 opts[:redirect_count] += 1 return response if opts[:redirect_count] > options[:max_redirects] if response.status == 303 verb = :get opts.delete(:body) end location = response.headers['location'] if location full_location = response.response.env.url.merge(location) request(verb, full_location, opts) else error = Error.new(response) raise(error, "Got #{response.status} status code, but no Location header was present") end when 200..299, 300..399 # on non-redirecting 3xx statuses, just return the response response when 400..599 error = Error.new(response) raise(error) if opts.fetch(:raise_errors, options[:raise_errors]) response else error = Error.new(response) raise(error, "Unhandled status code value of #{response.status}") end end # Initializes an AccessToken by making a request to the token endpoint # # @param params [Hash] a Hash of params for the token endpoint, except: # @option params [Symbol] :parse @see Response#initialize # @option params [true, false] :snaky (true) @see Response#initialize # @param access_token_opts [Hash] access token options, to pass to the AccessToken object # @param extract_access_token [Proc] proc that extracts the access token from the response (DEPRECATED) # @yield [req] @see Faraday::Connection#run_request # @return [AccessToken] the initialized AccessToken def get_token(params, access_token_opts = {}, extract_access_token = nil, &block) warn('OAuth2::Client#get_token argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class` on #initialize.') if extract_access_token extract_access_token ||= options[:extract_access_token] parse, snaky, params, headers = parse_snaky_params_headers(params) request_opts = { raise_errors: options[:raise_errors], parse: parse, snaky: snaky, } if options[:token_method] == :post # NOTE: If proliferation of request types continues we should implement a parser solution for Request, # just like we have with Response. request_opts[:body] = if headers['Content-Type'] == 'application/json' params.to_json else params end request_opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'} else request_opts[:params] = params request_opts[:headers] = {} end request_opts[:headers].merge!(headers) response = request(http_method, token_url, request_opts, &block) # In v1.4.x, the deprecated extract_access_token option retrieves the token from the response. # We preserve this behavior here, but a custom access_token_class that implements #from_hash # should be used instead. if extract_access_token parse_response_legacy(response, access_token_opts, extract_access_token) else parse_response(response, access_token_opts) end end # The HTTP Method of the request # @return [Symbol] HTTP verb, one of :get, :post, :put, :delete def http_method http_meth = options[:token_method].to_sym return :post if http_meth == :post_with_query_string http_meth end # The Authorization Code strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1 def auth_code @auth_code ||= OAuth2::Strategy::AuthCode.new(self) end # The Implicit strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2 def implicit @implicit ||= OAuth2::Strategy::Implicit.new(self) end # The Resource Owner Password Credentials strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3 def password @password ||= OAuth2::Strategy::Password.new(self) end # The Client Credentials strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4 def client_credentials @client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self) end def assertion @assertion ||= OAuth2::Strategy::Assertion.new(self) end # The redirect_uri parameters, if configured # # The redirect_uri query parameter is OPTIONAL (though encouraged) when # requesting authorization. If it is provided at authorization time it MUST # also be provided with the token exchange request. # # Providing the :redirect_uri to the OAuth2::Client instantiation will take # care of managing this. # # @api semipublic # # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1 # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3 # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1 # @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6 # @return [Hash] the params to add to a request or URL def redirection_params if options[:redirect_uri] {'redirect_uri' => options[:redirect_uri]} else {} end end private def parse_snaky_params_headers(params) params = params.map do |key, value| if RESERVED_PARAM_KEYS.include?(key) [key.to_sym, value] else [key, value] end end.to_h parse = params.key?(:parse) ? params.delete(:parse) : Response::DEFAULT_OPTIONS[:parse] snaky = params.key?(:snaky) ? params.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky] params = authenticator.apply(params) # authenticator may add :headers, and we remove them here headers = params.delete(:headers) || {} [parse, snaky, params, headers] end def execute_request(verb, url, opts = {}) url = connection.build_url(url).to_s begin response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req| req.params.update(opts[:params]) if opts[:params] yield(req) if block_given? end rescue Faraday::ConnectionFailed => e raise ConnectionError, e rescue Faraday::TimeoutError => e raise TimeoutError, e end parse = opts.key?(:parse) ? opts.delete(:parse) : Response::DEFAULT_OPTIONS[:parse] snaky = opts.key?(:snaky) ? opts.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky] Response.new(response, parse: parse, snaky: snaky) end # Returns the authenticator object # # @return [Authenticator] the initialized Authenticator def authenticator Authenticator.new(id, secret, options[:auth_scheme]) end def parse_response_legacy(response, access_token_opts, extract_access_token) access_token = build_access_token_legacy(response, access_token_opts, extract_access_token) return access_token if access_token if options[:raise_errors] error = Error.new(response) raise(error) end nil end def parse_response(response, access_token_opts) access_token_class = options[:access_token_class] data = response.parsed unless data.is_a?(Hash) && !data.empty? return unless options[:raise_errors] error = Error.new(response) raise(error) end build_access_token(response, access_token_opts, access_token_class) end # Builds the access token from the response of the HTTP call # # @return [AccessToken] the initialized AccessToken def build_access_token(response, access_token_opts, access_token_class) access_token_class.from_hash(self, response.parsed.merge(access_token_opts)).tap do |access_token| access_token.response = response if access_token.respond_to?(:response=) end end # Builds the access token from the response of the HTTP call with legacy extract_access_token # # @return [AccessToken] the initialized AccessToken def build_access_token_legacy(response, access_token_opts, extract_access_token) extract_access_token.call(self, response.parsed.merge(access_token_opts)) rescue StandardError nil end def oauth_debug_logging(builder) builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true' end end end oauth2-2.0.7/lib/oauth2/error.rb000066400000000000000000000035111430066224500163520ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 class Error < StandardError attr_reader :response, :body, :code, :description # standard error codes include: # 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope' # response might be a Response object, or the response.parsed hash def initialize(response) @response = response if response.respond_to?(:parsed) if response.parsed.is_a?(Hash) @code = response.parsed['error'] @description = response.parsed['error_description'] end elsif response.is_a?(Hash) @code = response['error'] @description = response['error_description'] end @body = if response.respond_to?(:body) response.body else @response end message_opts = parse_error_description(@code, @description) super(error_message(@body, message_opts)) end private def error_message(response_body, opts = {}) lines = [] lines << opts[:error_description] if opts[:error_description] error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding) script_encoding = opts[:error_description].encoding response_body.encode(script_encoding, invalid: :replace, undef: :replace) else response_body end lines << error_string lines.join("\n") end def parse_error_description(code, description) return {} unless code || description error_description = '' error_description += "#{code}: " if code error_description += description if description {error_description: error_description} end end end oauth2-2.0.7/lib/oauth2/response.rb000066400000000000000000000111651430066224500170630ustar00rootroot00000000000000# frozen_string_literal: true require 'json' require 'multi_xml' require 'rack' module OAuth2 # OAuth2::Response class class Response DEFAULT_OPTIONS = { parse: :automatic, snaky: true, }.freeze attr_reader :response attr_accessor :options # Procs that, when called, will parse a response body according # to the specified format. @@parsers = { query: ->(body) { Rack::Utils.parse_query(body) }, text: ->(body) { body }, } # Content type assignments for various potential HTTP content types. @@content_types = { 'application/x-www-form-urlencoded' => :query, 'text/plain' => :text, } # Adds a new content type parser. # # @param [Symbol] key A descriptive symbol key such as :json or :query. # @param [Array] mime_types One or more mime types to which this parser applies. # @yield [String] A block returning parsed content. def self.register_parser(key, mime_types, &block) key = key.to_sym @@parsers[key] = block Array(mime_types).each do |mime_type| @@content_types[mime_type] = key end end # Initializes a Response instance # # @param [Faraday::Response] response The Faraday response instance # @param [Symbol] parse (:automatic) how to parse the response body. one of :query (for x-www-form-urlencoded), # :json, or :automatic (determined by Content-Type response header) # @param [true, false] snaky (true) Convert @parsed to a snake-case, # indifferent-access OAuth2::SnakyHash, which is a subclass of Hashie::Mash::Rash (from rash_alt gem)? # @param [Hash] options all other options for initializing the instance def initialize(response, parse: :automatic, snaky: true, **options) @response = response @options = { parse: parse, snaky: snaky, }.merge(options) end # The HTTP response headers def headers response.headers end # The HTTP response status code def status response.status end # The HTTP response body def body response.body || '' end # The {#response} {#body} as parsed by {#parser}. # # @return [Object] As returned by {#parser} if it is #call-able. # @return [nil] If the {#parser} is not #call-able. def parsed return @parsed if defined?(@parsed) @parsed = if parser.respond_to?(:call) case parser.arity when 0 parser.call when 1 parser.call(body) else parser.call(body, response) end end @parsed = OAuth2::SnakyHash.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash) @parsed end # Attempts to determine the content type of the response. def content_type return nil unless response.headers ((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip.downcase end # Determines the parser (a Proc or other Object which responds to #call) # that will be passed the {#body} (and optional {#response}) to supply # {#parsed}. # # The parser can be supplied as the +:parse+ option in the form of a Proc # (or other Object responding to #call) or a Symbol. In the latter case, # the actual parser will be looked up in {@@parsers} by the supplied Symbol. # # If no +:parse+ option is supplied, the lookup Symbol will be determined # by looking up {#content_type} in {@@content_types}. # # If {#parser} is a Proc, it will be called with no arguments, just # {#body}, or {#body} and {#response}, depending on the Proc's arity. # # @return [Proc, #call] If a parser was found. # @return [nil] If no parser was found. def parser return @parser if defined?(@parser) @parser = if options[:parse].respond_to?(:call) options[:parse] elsif options[:parse] @@parsers[options[:parse].to_sym] end @parser ||= @@parsers[@@content_types[content_type]] end end end OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml', 'application/xml']) do |body| next body unless body.respond_to?(:to_str) MultiXml.parse(body) end OAuth2::Response.register_parser(:json, ['application/json', 'text/javascript', 'application/hal+json', 'application/vnd.collection+json', 'application/vnd.api+json', 'application/problem+json']) do |body| next body unless body.respond_to?(:to_str) body = body.dup.force_encoding(::Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding) ::JSON.parse(body) end oauth2-2.0.7/lib/oauth2/snaky_hash.rb000066400000000000000000000003001430066224500173420ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 # Hash which allow assign string key in camel case # and query on both camel and snake case class SnakyHash < ::Hashie::Mash::Rash end end oauth2-2.0.7/lib/oauth2/strategy/000077500000000000000000000000001430066224500165365ustar00rootroot00000000000000oauth2-2.0.7/lib/oauth2/strategy/assertion.rb000066400000000000000000000105521430066224500210750ustar00rootroot00000000000000# frozen_string_literal: true require 'jwt' module OAuth2 module Strategy # The Client Assertion Strategy # # @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-10#section-4.1.3 # # Sample usage: # client = OAuth2::Client.new(client_id, client_secret, # :site => 'http://localhost:8080', # :auth_scheme => :request_body) # # claim_set = { # :iss => "http://localhost:3001", # :aud => "http://localhost:8080/oauth2/token", # :sub => "me@example.com", # :exp => Time.now.utc.to_i + 3600, # } # # encoding = { # :algorithm => 'HS256', # :key => 'secret_key', # } # # access = client.assertion.get_token(claim_set, encoding) # access.token # actual access_token string # access.get("/api/stuff") # making api calls with access token in header # class Assertion < Base # Not used for this strategy # # @raise [NotImplementedError] def authorize_url raise(NotImplementedError, 'The authorization endpoint is not used in this strategy') end # Retrieve an access token given the specified client. # # @param [Hash] claims the hash representation of the claims that should be encoded as a JWT (JSON Web Token) # # For reading on JWT and claim keys: # @see https://github.com/jwt/ruby-jwt # @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 # @see https://datatracker.ietf.org/doc/html/rfc7523#section-3 # @see https://www.iana.org/assignments/jwt/jwt.xhtml # # There are many possible claim keys, and applications may ask for their own custom keys. # Some typically required ones: # :iss (issuer) # :aud (audience) # :sub (subject) -- formerly :prn https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token-06#appendix-F # :exp, (expiration time) -- in seconds, e.g. Time.now.utc.to_i + 3600 # # Note that this method does *not* validate presence of those four claim keys indicated as required by RFC 7523. # There are endpoints that may not conform with this RFC, and this gem should still work for those use cases. # # @param [Hash] encoding_opts a hash containing instructions on how the JWT should be encoded # @option algorithm [String] the algorithm with which you would like the JWT to be encoded # @option key [Object] the key with which you would like to encode the JWT # # These two options are passed directly to `JWT.encode`. For supported encoding arguments: # @see https://github.com/jwt/ruby-jwt#algorithms-and-usage # @see https://datatracker.ietf.org/doc/html/rfc7518#section-3.1 # # The object type of `:key` may depend on the value of `:algorithm`. Sample arguments: # get_token(claim_set, {:algorithm => 'HS256', :key => 'secret_key'}) # get_token(claim_set, {:algorithm => 'RS256', :key => OpenSSL::PKCS12.new(File.read('my_key.p12'), 'not_secret')}) # # @param [Hash] request_opts options that will be used to assemble the request # @option request_opts [String] :scope the url parameter `scope` that may be required by some endpoints # @see https://datatracker.ietf.org/doc/html/rfc7521#section-4.1 # # @param [Hash] response_opts this will be merged with the token response to create the AccessToken object # @see the access_token_opts argument to Client#get_token def get_token(claims, encoding_opts, request_opts = {}, response_opts = {}) assertion = build_assertion(claims, encoding_opts) params = build_request(assertion, request_opts) @client.get_token(params, response_opts) end private def build_request(assertion, request_opts = {}) { grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: assertion, }.merge(request_opts) end def build_assertion(claims, encoding_opts) raise ArgumentError.new(message: 'Please provide an encoding_opts hash with :algorithm and :key') if !encoding_opts.is_a?(Hash) || (%i[algorithm key] - encoding_opts.keys).any? JWT.encode(claims, encoding_opts[:key], encoding_opts[:algorithm]) end end end end oauth2-2.0.7/lib/oauth2/strategy/auth_code.rb000066400000000000000000000032401430066224500210150ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 module Strategy # The Authorization Code Strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1 class AuthCode < Base # The required query parameters for the authorize URL # # @param [Hash] params additional query parameters def authorize_params(params = {}) params.merge('response_type' => 'code', 'client_id' => @client.id) end # The authorization URL endpoint of the provider # # @param [Hash] params additional query parameters for the URL def authorize_url(params = {}) assert_valid_params(params) @client.authorize_url(authorize_params.merge(params)) end # Retrieve an access token given the specified validation code. # # @param [String] code The Authorization Code value # @param [Hash] params additional params # @param [Hash] opts access_token_opts, @see Client#get_token # @note that you must also provide a :redirect_uri with most OAuth 2.0 providers def get_token(code, params = {}, opts = {}) params = {'grant_type' => 'authorization_code', 'code' => code}.merge(@client.redirection_params).merge(params) params_dup = params.dup params.each_key do |key| params_dup[key.to_s] = params_dup.delete(key) if key.is_a?(Symbol) end @client.get_token(params_dup, opts) end private def assert_valid_params(params) raise(ArgumentError, 'client_secret is not allowed in authorize URL query params') if params.key?(:client_secret) || params.key?('client_secret') end end end end oauth2-2.0.7/lib/oauth2/strategy/base.rb000066400000000000000000000002401430066224500177710ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 module Strategy class Base def initialize(client) @client = client end end end end oauth2-2.0.7/lib/oauth2/strategy/client_credentials.rb000066400000000000000000000013751430066224500227240ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 module Strategy # The Client Credentials Strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4 class ClientCredentials < Base # Not used for this strategy # # @raise [NotImplementedError] def authorize_url raise(NotImplementedError, 'The authorization endpoint is not used in this strategy') end # Retrieve an access token given the specified client. # # @param [Hash] params additional params # @param [Hash] opts options def get_token(params = {}, opts = {}) params = params.merge('grant_type' => 'client_credentials') @client.get_token(params, opts) end end end end oauth2-2.0.7/lib/oauth2/strategy/implicit.rb000066400000000000000000000022241430066224500206750ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 module Strategy # The Implicit Strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2 class Implicit < Base # The required query parameters for the authorize URL # # @param [Hash] params additional query parameters def authorize_params(params = {}) params.merge('response_type' => 'token', 'client_id' => @client.id) end # The authorization URL endpoint of the provider # # @param [Hash] params additional query parameters for the URL def authorize_url(params = {}) assert_valid_params(params) @client.authorize_url(authorize_params.merge(params)) end # Not used for this strategy # # @raise [NotImplementedError] def get_token(*) raise(NotImplementedError, 'The token is accessed differently in this strategy') end private def assert_valid_params(params) raise(ArgumentError, 'client_secret is not allowed in authorize URL query params') if params.key?(:client_secret) || params.key?('client_secret') end end end end oauth2-2.0.7/lib/oauth2/strategy/password.rb000066400000000000000000000017261430066224500207330ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 module Strategy # The Resource Owner Password Credentials Authorization Strategy # # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3 class Password < Base # Not used for this strategy # # @raise [NotImplementedError] def authorize_url raise(NotImplementedError, 'The authorization endpoint is not used in this strategy') end # Retrieve an access token given the specified End User username and password. # # @param [String] username the End User username # @param [String] password the End User password # @param [Hash] params additional params def get_token(username, password, params = {}, opts = {}) params = {'grant_type' => 'password', 'username' => username, 'password' => password}.merge(params) @client.get_token(params, opts) end end end end oauth2-2.0.7/lib/oauth2/version.rb000066400000000000000000000001451430066224500167060ustar00rootroot00000000000000# frozen_string_literal: true module OAuth2 module Version VERSION = '2.0.7'.freeze end end oauth2-2.0.7/oauth2.gemspec000066400000000000000000000052751430066224500155040ustar00rootroot00000000000000# encoding: utf-8 # frozen_string_literal: true require_relative 'lib/oauth2/version' Gem::Specification.new do |spec| spec.add_dependency 'faraday', ['>= 0.17.3', '< 3.0'] spec.add_dependency 'jwt', ['>= 1.0', '< 3.0'] spec.add_dependency 'multi_xml', '~> 0.5' spec.add_dependency 'rack', ['>= 1.2', '< 3'] spec.add_dependency 'rash_alt', ['>= 0.4', '< 1'] spec.add_dependency 'version_gem', '~> 1.1' spec.authors = ['Peter Boling', 'Erik Michaels-Ober', 'Michael Bleigh'] spec.description = 'A Ruby wrapper for the OAuth 2.0 protocol built with a similar style to the original OAuth spec.' spec.email = ['peter.boling@gmail.com'] spec.homepage = 'https://github.com/oauth-xx/oauth2' spec.licenses = %w[MIT] spec.name = 'oauth2' spec.required_ruby_version = '>= 2.2.0' spec.summary = 'A Ruby wrapper for the OAuth 2.0 protocol.' spec.version = OAuth2::Version::VERSION spec.post_install_message = " You have installed oauth2 version #{OAuth2::Version::VERSION}, congratulations! There are BREAKING changes, but most will not encounter them, and updating your code should be easy! Please see: • https://github.com/oauth-xx/oauth2#what-is-new-for-v20 • https://github.com/oauth-xx/oauth2/blob/master/CHANGELOG.md Please report issues, and support the project! Thanks, |7eter l-|. l3oling " spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = "#{spec.homepage}/tree/v#{spec.version}" spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md" spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues" spec.metadata['documentation_uri'] = "https://www.rubydoc.info/gems/#{spec.name}/#{spec.version}" spec.metadata['wiki_uri'] = "#{spec.homepage}/wiki" spec.metadata['rubygems_mfa_required'] = 'true' spec.require_paths = %w[lib] spec.bindir = 'exe' spec.files = Dir[ 'lib/**/*', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'CONTRIBUTING.md', 'LICENSE', 'README.md', 'SECURITY.md', ] spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.add_development_dependency 'addressable', '>= 2' spec.add_development_dependency 'backports', '>= 3' spec.add_development_dependency 'bundler', '>= 2' spec.add_development_dependency 'rake', '>= 12' spec.add_development_dependency 'rexml', '>= 3' spec.add_development_dependency 'rspec', '>= 3' spec.add_development_dependency 'rspec-block_is_expected' spec.add_development_dependency 'rspec-pending_for' spec.add_development_dependency 'rspec-stubbed_env' spec.add_development_dependency 'rubocop-lts', '~> 8.0' spec.add_development_dependency 'silent_stream' end oauth2-2.0.7/spec/000077500000000000000000000000001430066224500136565ustar00rootroot00000000000000oauth2-2.0.7/spec/config/000077500000000000000000000000001430066224500151235ustar00rootroot00000000000000oauth2-2.0.7/spec/config/faraday.rb000066400000000000000000000000771430066224500170630ustar00rootroot00000000000000# frozen_string_literal: true Faraday.default_adapter = :test oauth2-2.0.7/spec/config/multi_xml.rb000066400000000000000000000000701430066224500174570ustar00rootroot00000000000000# frozen_string_literal: true MultiXml.parser = :rexml oauth2-2.0.7/spec/config/rspec/000077500000000000000000000000001430066224500162375ustar00rootroot00000000000000oauth2-2.0.7/spec/config/rspec/rspec_core.rb000066400000000000000000000005371430066224500207150ustar00rootroot00000000000000# frozen_string_literal: true RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = '.rspec_status' # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.expect_with :rspec do |c| c.syntax = :expect end end oauth2-2.0.7/spec/config/rspec/silent_stream.rb000066400000000000000000000001351430066224500214340ustar00rootroot00000000000000# frozen_string_literal: true RSpec.configure do |config| config.include SilentStream end oauth2-2.0.7/spec/examples/000077500000000000000000000000001430066224500154745ustar00rootroot00000000000000oauth2-2.0.7/spec/examples/google_spec.rb000066400000000000000000000124001430066224500203040ustar00rootroot00000000000000# frozen_string_literal: true require 'jwt' RSpec.describe 'using OAuth2 with Google' do # This describes authenticating to a Google API via a service account. # See their docs: https://developers.google.com/identity/protocols/OAuth2ServiceAccount describe 'via 2-legged JWT assertion' do let(:client) do OAuth2::Client.new( '', '', site: 'https://accounts.google.com', authorize_url: '/o/oauth2/auth', token_url: '/o/oauth2/token', auth_scheme: :request_body ) end # These are taken directly from Google's documentation example: let(:required_claims) do { 'iss' => '761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com', # The email address of the service account. 'scope' => 'https://www.googleapis.com/auth/devstorage.readonly https://www.googleapis.com/auth/prediction', # A space-delimited list of the permissions that the application requests. 'aud' => 'https://www.googleapis.com/oauth2/v4/token', # A descriptor of the intended target of the assertion. When making an access token request this value # is always https://www.googleapis.com/oauth2/v4/token. 'exp' => Time.now.to_i + 3600, # The expiration time of the assertion, specified as seconds since 00:00:00 UTC, January 1, 1970. This value # has a maximum of 1 hour after the issued time. 'iat' => Time.now.to_i, # The time the assertion was issued, specified as seconds since 00:00:00 UTC, January 1, 1970. } end let(:optional_claims) do { 'sub' => 'some.user@example.com', # The email address of the user for which the application is requesting delegated access. } end let(:algorithm) { 'RS256' } # Per Google: "Service accounts rely on the RSA SHA-256 algorithm" let(:key) do begin OpenSSL::PKCS12.new(File.read('spec/fixtures/google_service_account_key.p12'), 'notasecret').key # This simulates the .p12 file that Google gives you to download and keep somewhere. This is meant to # illustrate extracting the key and using it to generate the JWT. rescue OpenSSL::PKCS12::PKCS12Error # JRuby CI builds are blowing up trying to extract a sample key for some reason. This simulates the end result # of actually figuring out the problem. OpenSSL::PKey::RSA.new(1024) end end # Per Google: # "Take note of the service account's email address and store the service account's P12 private key file in a # location accessible to your application. Your application needs them to make authorized API calls." let(:encoding_options) { {key: key, algorithm: algorithm} } before do client.connection = Faraday.new(client.site, client.options[:connection_opts]) do |builder| builder.request :url_encoded builder.adapter :test do |stub| stub.post('https://accounts.google.com/o/oauth2/token') do |token_request| @request_body = Rack::Utils.parse_nested_query(token_request.body).transform_keys(&:to_sym) [ 200, { 'Content-Type' => 'application/json', }, { 'access_token' => '1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M', 'token_type' => 'Bearer', 'expires_in' => 3600, }.to_json, ] end end end end context 'when passing the required claims' do let(:claims) { required_claims } it 'sends a JWT with the 5 keys' do client.assertion.get_token(claims, encoding_options) expect(@request_body).not_to be_nil, 'No access token request was made!' expect(@request_body[:grant_type]).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer') expect(@request_body[:assertion]).to be_a(String) payload, header = JWT.decode(@request_body[:assertion], key, true, algorithm: algorithm) expect(header['alg']).to eq('RS256') expect(payload.keys).to match_array(%w[iss scope aud exp iat]) # Note that these specifically do _not_ include the 'sub' claim, which is indicated as being 'required' # by the OAuth2 JWT RFC: https://tools.ietf.org/html/rfc7523#section-3 # This may indicate that this is a nonstandard use case by Google. payload.each do |key, value| expect(value).to eq(claims[key]) end end end context 'when including the optional `sub` claim' do let(:claims) { required_claims.merge(optional_claims) } it 'sends a JWT with the 6 keys' do client.assertion.get_token(claims, encoding_options) expect(@request_body).not_to be_nil, 'No access token request was made!' expect(@request_body[:grant_type]).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer') expect(@request_body[:assertion]).to be_a(String) payload, header = JWT.decode(@request_body[:assertion], key, true, algorithm: algorithm) expect(header['alg']).to eq('RS256') expect(payload.keys).to match_array(%w[iss scope aud exp iat sub]) payload.each do |key, value| expect(value).to eq(claims[key]) end end end end end oauth2-2.0.7/spec/ext/000077500000000000000000000000001430066224500144565ustar00rootroot00000000000000oauth2-2.0.7/spec/ext/backports.rb000066400000000000000000000001151430066224500167700ustar00rootroot00000000000000# frozen_string_literal: true require 'backports/2.5.0/hash/transform_keys' oauth2-2.0.7/spec/fixtures/000077500000000000000000000000001430066224500155275ustar00rootroot00000000000000oauth2-2.0.7/spec/fixtures/README.md000066400000000000000000000004001430066224500170000ustar00rootroot00000000000000# RS256 ## How keys were made ```shell # No passphrase # Generates the public and private keys: ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key # Converts the key to PEM format openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub ``` oauth2-2.0.7/spec/fixtures/RS256/000077500000000000000000000000001430066224500163105ustar00rootroot00000000000000oauth2-2.0.7/spec/fixtures/RS256/jwtRS256.key000066400000000000000000000062571430066224500203420ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEA5hdXV/4YSymY1T9VNvK2bWRfulwIty1RnAPNINQmfh3aRRkV +PNrbC2Crji9G0AHmQwgW1bZ3kgkkpIm6RVn44fHvBvuXkZ9ABgXw0d2cLIHmwOF xSKmWAm/EW//GszUTLLLsMZUe2udtFJW0jxXB2GRY0WVYuo6Oo58RCeP719lw3Ag s0YF9/IobxKkGd4BautUPw6ZszAa3o+j0zR74x7ouPxybZAOuPsMxqanyeYJeH4o sJjLMYV9qem9uG2sj7GENJ8UszcpmGbqxBhexPEB7mgDeONIF0XJF23zdOf8ANE5 mAU2h2v7M6moAfkdUzJ+j48+VT2omHAzAL5yNcmrl2xiWdyoxOw1Y1UmfEmJYV5V gGYyZ12JZRKY+szPT+vR+MDuYxbquF40O7kvkFNBfL1yCpzfSQCLnEs4rX8qRzZX ciLeyq4Ht5FLuRFgxjA//XI8LAmp0u7gk+Q7FUH1UgW3kmJDTG0XaxQxYTBSIO7m cmyjDyBgKVuQmt5E1ycFeteOVdPD/CG/fPYhthvc4UytEFwsMdNy3iD6/wuUH68t AKam28UZaOb0qK+00cQQD8fulY9rKtSL10LvJFWUOa/SJyLvk9vUmfvFn182il1n X6GpyxyMmE/FCnH4CT/DjrSZf08mOO8eL5ofYHMK/oiXr1eODqx+pOwClNsCAwEA AQKCAgEAy34vMFI4WBk04rx9d/hWoQ7Znu8QgjihaZLvEy6t0HJEfUH/bcqS4fyq C72Aeh452gCgiUeZrf4t4jdCFHhrBg8q9dHaEiTTHocwVPPZ6zd4hH8sCrpnVYth IWHkw2YOCLtEbFYrl3AI7Na5lHvrGEsREzQSN4Yh83Has0guAy1iyeNb+FFgq/XO DtX0ri/rHw1717zo8FIGIXn2EK/lNWw7tIcICKAUdUMK/JGd6XD6RUeGYxDu/CAs kF55/Sd6Kyd7XjKnUwzhS7kRvlYzUog4BgqVr4+LTZHZlFAYtfcJqAtinXFW1ZQJ eZp9TSlt5wvMZNjx7t92QUNRyEGmrQAU+8COHnT0/drFf0MCiyHSUN0E7/5fswhc uMSU9XiJA9G0wYvJl4zIuOuIYWZWhIqvjYSkvdlP70t9XO2gk/ZcCWsMW8i+xbwC w1+MMjsKsNedXxI99TIPPHcCNMxqlt1E1kHH3SAwCuEH/ez7PRMyEQQ0EyAk22x/ piYIWXkX5835cLbLRIYafXgOiugWZjCwIqfRIcIpscmcijZwCF2DyevveYdx3krR FGA2PFydFyxCNG7XwvKb9kHb7WBERUPV/H3eCqu2SZ/RvF+I94LUYP4bu6CmFdO9 wCJcGJoL1P7tVhS9lA5Oj0QWczrjnejCoI9XMMduWk032rR1VYECggEBAPZDnTBY H2uiVmGdMfWTAmX86kiHVpkL03OG6rgvDMsMOYKnik9Lb3gNeUIuPeAWFNrXCoD1 qp0loxPhKSojNOOM8Yiz/GwQ/QI9dzgtxs7E7rFFyTuJcY48Do8uOFyUHbAbeOBF b9UL/uBfWZGVV1YY753xyqYlCpxTVQGms1jsbVFdZE1iVpOwAkFVuoLYaHLut4zB 01ORyBSoWan173P+IQH6F1uNXE2Kk/FIMDN6bgP1pXkdkrTx4WjAmRnP/Sc4r38/ F1xN+gxnWGPUKDVRPYBpVzDR036w65ODgg2FROK2vIxlStiAC/rc0JLsvaWfb1Rn dsWdJJ1V6mZ6a5sCggEBAO8wC1jcIoiBz3xoA8E5BSt8qLJ7ZuSFaaidvWX2/xj6 lSWJxCGQfhR7P6ozvH6UDo1WbJT6nNyXPkiDkAzcmAdsYVjULW3K2LI9oPajaJxY L7KJpylgh9JhMvbMz3VVjTgYRt+kjX+3uFMZNx1YfiBP+S6xx5sjK9CKDz3H99kC q9bX95YFqZ7yFE3aBCR6CENo2tXpMN96CLQGpwa0bwt3xNzC4MhZMXbGR3DdBYbD tS9lJfQvAVUYxbSE/2FBgjpO6ArMyU2ZUEDFx9J6IhfhVbQV4VeITMyRNo0XwBiQ /+XpLXgHkw7LiNMIoc7d+M7yLA1Vz7+r8XxWHHZCL8ECggEBAPK8VrYORno7e1Wg MlxS2WxZzTxMWmlkpLoc5END7SI/HHjSV5wtSORWs40uM0MrwMasa+gNPmzDamjv 6Tllln4ssO8EKe0DGcAZgefYBzxMFNKbbOzIXyvJurga4Ocv/8tUaOL2znJ67nGO yqSbRYjR724JpKv7mufXo9SK0gD2mhI3MeSs55WPScnIjJzoXpva/QU7D+gxq7vg 7PCAP9RfS329W0Sco7yyuXx8oTY8mTBB8ybcpXzBZmNwY/hzcJ42W5XbRFVxbuTH APL1beSP/UUTkCPIzuTz0mCGoaxeDjZB1Lu2I/4eyLAu80+/FneoHX5etU23xR1o UDFOvb0CggEBALTTc6CoPAtLaBs7X6tSelAYHEli9bTKD8kEB83wX4b42ozYjEh7 vnWpf8Yi+twO/rlnnws6NCCoztNvcxXmJ6FlFGtdbULV2eFWqjwL6ehY2yZ03sVv Tv+DsE3ZJPYlyW+hGuO0uazWrilUpNAwuJmhHFdq2+azPkqYNVGVvhB37oWsHGd0 vHmHtkXtDris8VZVDSwu8V3iGnZPmTJ+cn0O/OuRAPM2SyjqWdQ/pA/wIShFpd3n M3CsG7uP2KokJloCkXaov39E6uEtJRZAc0nudyaAbC4Kw1Tca4tba0SnSm78S/20 bD8BLN2uZvXH5nQ9rYQfXcIgMZ64UygsfYECggEBAIw0fQaIVmafa0Hz3ipD4PJI 5QNkh2t9hvOCSKm1xYTNATl0q/VIkZoy1WoxY6SSchcObLxQKbJ9ORi4XNr+IJK5 3C1Qz/3iv/S3/ktgmqGhQiqybkkHZcbqTXB2wxrx+aaLS7PEfYiuYCrPbX93160k MVns8PjvYU8KCNMbL2e+AiKEt1KkKAZIpNQdeeJOEhV9wuLYFosd400aYssuSOVW IkJhGI0lT/7FDJaw0LV98DhQtauANPSUQKN5iw6vciwtsaF1kXMfGlMXj58ntiMq NizQPR6/Ar1ewLPMh1exDoAfLnCIMk8nbSraW+cebLAZctPugUpfpu3j2LM98aE= -----END RSA PRIVATE KEY----- oauth2-2.0.7/spec/fixtures/RS256/jwtRS256.key.pub000066400000000000000000000014401430066224500211140ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5hdXV/4YSymY1T9VNvK2 bWRfulwIty1RnAPNINQmfh3aRRkV+PNrbC2Crji9G0AHmQwgW1bZ3kgkkpIm6RVn 44fHvBvuXkZ9ABgXw0d2cLIHmwOFxSKmWAm/EW//GszUTLLLsMZUe2udtFJW0jxX B2GRY0WVYuo6Oo58RCeP719lw3Ags0YF9/IobxKkGd4BautUPw6ZszAa3o+j0zR7 4x7ouPxybZAOuPsMxqanyeYJeH4osJjLMYV9qem9uG2sj7GENJ8UszcpmGbqxBhe xPEB7mgDeONIF0XJF23zdOf8ANE5mAU2h2v7M6moAfkdUzJ+j48+VT2omHAzAL5y Ncmrl2xiWdyoxOw1Y1UmfEmJYV5VgGYyZ12JZRKY+szPT+vR+MDuYxbquF40O7kv kFNBfL1yCpzfSQCLnEs4rX8qRzZXciLeyq4Ht5FLuRFgxjA//XI8LAmp0u7gk+Q7 FUH1UgW3kmJDTG0XaxQxYTBSIO7mcmyjDyBgKVuQmt5E1ycFeteOVdPD/CG/fPYh thvc4UytEFwsMdNy3iD6/wuUH68tAKam28UZaOb0qK+00cQQD8fulY9rKtSL10Lv JFWUOa/SJyLvk9vUmfvFn182il1nX6GpyxyMmE/FCnH4CT/DjrSZf08mOO8eL5of YHMK/oiXr1eODqx+pOwClNsCAwEAAQ== -----END PUBLIC KEY----- oauth2-2.0.7/spec/fixtures/google_service_account_key.p12000066400000000000000000000056621430066224500234440ustar00rootroot000000000000000 0 - *H   0 0M *H >:0602 *H  00 *H  00b *H  0U@2 ݨ]|}U0^WSeX:SO$Ui0$Ybp"}F 0 *H 0 `He2S;)'0 $UP G/Tg̜ dg=qwOHVRki;:)yN'6ehZ?䎕(~%U$,uEѥ2yb9^v1Þ,eH˅Cjdtǻ42*f+HG j#Ҷ`[.Tjը,pw$ThJ \s+M-yN_߽Tq曐\h]V>˷~)2%.?pjo C/ZjޝPG]lIPf )/?-5,Lctt5ji8 ڼ4{WGJz$ig+AѬx^h|'W'|qU5ߠ@n_xٍ}.]G᳴7 ^y")![rOV+m~3 d 4!%wJ^iǿluE"$T_\*/> H,L~ЧQOc4iE8d1e`ܜx_BPbMpOihIM2a2$N0}i!Abd^ ;d)*NNY2}9qty@$׵NmT<PKCS8ShroudedKeyBag from PKIjs0 *H  1t07 +71*(http://www.pkijs.org0 *H 00 *H 0 *H  00b *H  0U@IID= { t̰h!@vr H.f¥?"s0 *H 0 `HeFUJ,Ta 60&Z*mM4y*x?3^WX)0Eױ321J0snHp6924rQ8+ P{)S~7П*dHQN C!3RMCif*j|'O{鋃ǎ*W힁Ŀ,ts0gDkv)Tl<2x3ӵbϺ .DH8VY7jı(ŔZUOFTt!ЗnXHD@:ob8Q_a KkqpkCZ *mj3>:\ G=Lf%(@Px@K#L4ʃBj$0ts)oTQ ÷l_yvs`R).E /@V -dxǑf(Kiߒ6Ieo2TWdŋbqbYx\R-ul#i -Gl|:bhlsi[[@ 0!L.Āf9%„&nx y>s6-ɇ_cFpϞ".Q3e&( b܇!9o Zb+]'/F^x:*EnQ'Ѓ\*PXt.l?Pض%d,/n,͞H2W O>6bo.9% ڿj5H/YMTK?x}9<'; rGi YimeRLڵ7ߺǡ5zW8'lхbw|kϬ"CG鼯x9 psYPdj(浶$slv ןxj;_3[2UnAM3K GD.e7%y+}}{x<V,8߇>(ܧC 3|NhQlڣb:i)oUa0x0/0  `He u^ͷwHʄb\W 0VD=Ŏ?@8 Ic67&b@/.;/A48_+ 29b̓Wp%n72$C,L@Kc/oauth2-2.0.7/spec/oauth2/000077500000000000000000000000001430066224500150605ustar00rootroot00000000000000oauth2-2.0.7/spec/oauth2/access_token_spec.rb000066400000000000000000000541131430066224500210640ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::AccessToken do subject { described_class.new(client, token) } let(:base_options) { {site: 'https://api.example.com'} } let(:options) { {} } let(:token) { 'monkey' } let(:refresh_body) { JSON.dump(access_token: 'refreshed_foo', expires_in: 600, refresh_token: 'refresh_bar') } let(:client) do OAuth2::Client.new('abc', 'def', options.merge(base_options)) do |builder| builder.request :url_encoded builder.adapter :test do |stub| VERBS.each do |verb| stub.send(verb, '/token/header') { |env| [200, {}, env[:request_headers]['Authorization']] } stub.send(verb, "/token/query?access_token=#{token}") { |env| [200, {}, Addressable::URI.parse(env[:url]).query_values['access_token']] } stub.send(verb, '/token/query_string') { |env| [200, {}, CGI.unescape(Addressable::URI.parse(env[:url]).query)] } stub.send(verb, '/token/body') { |env| [200, {}, env[:body]] } end stub.post('/oauth/token') { |_env| [200, {'Content-Type' => 'application/json'}, refresh_body] } end end end describe '.from_hash' do subject(:target) { described_class.from_hash(client, hash) } let(:hash) do { :access_token => token, :id_token => 'confusing bug here', :refresh_token => 'foobar', :expires_at => Time.now.to_i + 200, 'foo' => 'bar', } end it 'return a hash equals to the hash used to initialize access token' do expect(target.to_hash).to eq(hash) end context 'with warning for too many tokens' do subject(:printed) do capture(:stderr) do target end end it 'warns on STDERR' do msg = <<-MSG.lstrip OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key ([:access_token, :id_token]); using :access_token. MSG expect(printed).to eq(msg) end end context 'with keys in a different order to the lookup' do subject(:printed) do capture(:stderr) do target end end let(:hash) do { id_token: 'confusing bug here', access_token: token, } end it 'warns on STDERR and selects the correct key' do msg = <<-MSG.lstrip OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key ([:access_token, :id_token]); using :access_token. MSG expect(printed).to eq(msg) end end end describe '#initialize' do it 'assigns client and token' do expect(subject.client).to eq(client) expect(subject.token).to eq(token) end it 'assigns extra params' do target = described_class.new(client, token, 'foo' => 'bar') expect(target.params).to include('foo') expect(target.params['foo']).to eq('bar') end def assert_initialized_token(target) expect(target.token).to eq(token) expect(target).to be_expires expect(target.params.keys).to include('foo') expect(target.params['foo']).to eq('bar') end it 'initializes with a Hash' do hash = {:access_token => token, :expires_at => Time.now.to_i + 200, 'foo' => 'bar'} target = described_class.from_hash(client, hash) assert_initialized_token(target) end it 'from_hash does not modify opts hash' do hash = {access_token: token, expires_at: Time.now.to_i} hash_before = hash.dup described_class.from_hash(client, hash) expect(hash).to eq(hash_before) end it 'initializes with a form-urlencoded key/value string' do kvform = "access_token=#{token}&expires_at=#{Time.now.to_i + 200}&foo=bar" target = described_class.from_kvform(client, kvform) assert_initialized_token(target) end context 'with options' do subject(:target) { described_class.new(client, token, **options) } context 'with body mode' do let(:mode) { :body } let(:options) { {param_name: 'foo', header_format: 'Bearer %', mode: mode} } it 'sets options' do expect(target.options[:param_name]).to eq('foo') expect(target.options[:header_format]).to eq('Bearer %') expect(target.options[:mode]).to eq(mode) end end context 'with header mode' do let(:mode) { :header } let(:options) { {headers: {}, mode: mode} } it 'sets options' do expect(target.options[:headers]).to be_nil expect(target.options[:mode]).to eq(mode) end end context 'with query mode' do let(:mode) { :query } let(:options) { {params: {}, param_name: 'foo', mode: mode} } it 'sets options' do expect(target.options[:param_name]).to eq('foo') expect(target.options[:params]).to be_nil expect(target.options[:mode]).to eq(mode) end end context 'with invalid mode' do let(:mode) { :this_is_bad } let(:options) { {mode: mode} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end context 'with request' do subject(:request) { target.post('/token/header') } it 'raises' do block_is_expected.to raise_error("invalid :mode option of #{mode}") end end context 'with client.options[:raise_errors] = true' do let(:mode) { :this_is_bad } let(:options) { {mode: mode, raise_errors: true} } before do expect(client.options[:raise_errors]).to be(true) end context 'when there is a token' do it 'does not raise on initialize' do block_is_expected.not_to raise_error end context 'with request' do subject(:request) { target.post('/token/header') } it 'raises' do block_is_expected.to raise_error("invalid :mode option of #{mode}") end end end context 'when there is empty token' do let(:token) { '' } it 'raises on initialize' do block_is_expected.to raise_error(OAuth2::Error, '{:mode=>:this_is_bad, :raise_errors=>true}') end end context 'when there is nil token' do let(:token) { nil } it 'raises on initialize' do block_is_expected.to raise_error(OAuth2::Error, '{:mode=>:this_is_bad, :raise_errors=>true}') end end end end context 'with client.options[:raise_errors] = false' do let(:options) { {raise_errors: false} } before do expect(client.options[:raise_errors]).to be(false) end context 'when there is a token' do let(:token) { 'hurdygurdy' } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has token' do expect(target.token).to eq(token) end it 'has no refresh_token' do expect(target.refresh_token).to be_nil end context 'when there is refresh_token' do let(:options) { {raise_errors: false, refresh_token: 'zxcv'} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has token' do expect(target.token).to eq(token) end it 'has refresh_token' do expect(target.refresh_token).to eq('zxcv') end end end context 'when there is empty token' do let(:token) { '' } context 'when there is no refresh_token' do it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has no token' do expect(target.token).to eq('') end it 'has no refresh_token' do expect(target.refresh_token).to be_nil end context 'with warning for no token' do subject(:printed) do capture(:stderr) do target end end it 'warns on STDERR' do msg = <<-MSG.lstrip OAuth2::AccessToken has no token MSG expect(printed).to eq(msg) end end end context 'when there is refresh_token' do let(:options) { {raise_errors: false, refresh_token: 'qwer'} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has no token' do expect(target.token).to eq('') end it 'has refresh_token' do expect(target.refresh_token).to eq('qwer') end end end context 'when there is nil token' do let(:token) { nil } context 'when there is no refresh_token' do it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has no token' do expect(target.token).to eq('') end it 'has no refresh_token' do expect(target.refresh_token).to be_nil end context 'with warning for no token' do subject(:printed) do capture(:stderr) do target end end it 'warns on STDERR' do msg = <<-MSG.lstrip OAuth2::AccessToken has no token MSG expect(printed).to eq(msg) end end end context 'when there is refresh_token' do let(:options) { {raise_errors: false, refresh_token: 'asdf'} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has no token' do expect(target.token).to eq('') end it 'has refresh_token' do expect(target.refresh_token).to eq('asdf') end end end end context 'with client.options[:raise_errors] = true' do let(:options) { {raise_errors: true} } before do expect(client.options[:raise_errors]).to be(true) end context 'when there is a token' do let(:token) { 'hurdygurdy' } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has token' do expect(target.token).to eq(token) end it 'has no refresh_token' do expect(target.refresh_token).to be_nil end context 'when there is refresh_token' do let(:options) { {raise_errors: true, refresh_token: 'zxcv'} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has token' do expect(target.token).to eq(token) end it 'has refresh_token' do expect(target.refresh_token).to eq('zxcv') end end end context 'when there is empty token' do let(:token) { '' } context 'when there is no refresh_token' do it 'raises on initialize' do block_is_expected.to raise_error(OAuth2::Error, '{:raise_errors=>true}') end end context 'when there is refresh_token' do let(:options) { {raise_errors: true, refresh_token: 'qwer'} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has no token' do expect(target.token).to eq('') end it 'has refresh_token' do expect(target.refresh_token).to eq('qwer') end end end context 'when there is nil token' do let(:token) { nil } context 'when there is no refresh_token' do it 'raises on initialize' do block_is_expected.to raise_error(OAuth2::Error, '{:raise_errors=>true}') end end context 'when there is refresh_token' do let(:options) { {raise_errors: true, refresh_token: 'asdf'} } it 'does not raise on initialize' do block_is_expected.not_to raise_error end it 'has no token' do expect(target.token).to eq('') end it 'has refresh_token' do expect(target.refresh_token).to eq('asdf') end end end end end it 'does not modify opts hash' do opts = {param_name: 'foo', header_format: 'Bearer %', mode: :body} opts_before = opts.dup described_class.new(client, token, opts) expect(opts).to eq(opts_before) end describe 'expires_at' do let(:expires_at) { 1_361_396_829 } let(:hash) do { :access_token => token, :expires_at => expires_at.to_s, 'foo' => 'bar', } end it 'initializes with an integer timestamp expires_at' do target = described_class.from_hash(client, hash.merge(expires_at: expires_at)) assert_initialized_token(target) expect(target.expires_at).to eql(expires_at) end it 'initializes with a string timestamp expires_at' do target = described_class.from_hash(client, hash) assert_initialized_token(target) expect(target.expires_at).to eql(expires_at) end it 'initializes with a string time expires_at' do target = described_class.from_hash(client, hash.merge(expires_at: Time.at(expires_at).iso8601)) assert_initialized_token(target) expect(target.expires_at).to eql(expires_at) end end describe 'expires_latency' do let(:expires_at) { 1_530_000_000 } let(:expires_in) { 100 } let(:expires_latency) { 10 } let(:hash) do { access_token: token, expires_latency: expires_latency, expires_in: expires_in, } end it 'sets it via options' do target = described_class.from_hash(client, hash.merge(expires_latency: expires_latency.to_s)) expect(target.expires_latency).to eq expires_latency end it 'sets it nil by default' do hash.delete(:expires_latency) target = described_class.from_hash(client, hash) expect(target.expires_latency).to be_nil end it 'reduces expires_at by the given amount' do allow(Time).to receive(:now).and_return(expires_at) target = described_class.from_hash(client, hash) expect(target.expires_at).to eq(expires_at + expires_in - expires_latency) end it 'reduces expires_at by the given amount if expires_at is provided as option' do target = described_class.from_hash(client, hash.merge(expires_at: expires_at)) expect(target.expires_at).to eq(expires_at - expires_latency) end end end describe '#request' do context 'with :mode => :header' do before do subject.options[:mode] = :header end VERBS.each do |verb| it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do expect(subject.post('/token/header').body).to include(token) end end end context 'with :mode => :query' do before do subject.options[:mode] = :query end VERBS.each do |verb| it "sends the token in the body for a #{verb.to_s.upcase} request" do expect(subject.post('/token/query').body).to eq(token) end it "sends a #{verb.to_s.upcase} request and options[:param_name] include [number]." do subject.options[:param_name] = 'auth[1]' expect(subject.__send__(verb, '/token/query_string').body).to include("auth[1]=#{token}") end end end context 'with :mode => :body' do before do subject.options[:mode] = :body end VERBS.each do |verb| it "sends the token in the body for a #{verb.to_s.upcase} request" do expect(subject.post('/token/body').body.split('=').last).to eq(token) end context 'when options[:param_name] include [number]' do it "sends a #{verb.to_s.upcase} request when body is a hash" do subject.options[:param_name] = 'auth[1]' expect(subject.__send__(verb, '/token/body', body: {hi: 'there'}).body).to include("auth%5B1%5D=#{token}") end it "sends a #{verb.to_s.upcase} request when body is overridden as string" do subject.options[:param_name] = 'snoo[1]' expect(subject.__send__(verb, '/token/body', body: 'hi_there').body).to include("hi_there&snoo[1]=#{token}") end end end end context 'params include [number]' do VERBS.each do |verb| it "sends #{verb.to_s.upcase} correct query" do expect(subject.__send__(verb, '/token/query_string', params: {'foo[bar][1]' => 'val'}).body).to include('foo[bar][1]=val') end end end end describe '#expires?' do it 'is false if there is no expires_at' do expect(described_class.new(client, token)).not_to be_expires end it 'is true if there is an expires_in' do expect(described_class.new(client, token, refresh_token: 'abaca', expires_in: 600)).to be_expires end it 'is true if there is an expires_at' do expect(described_class.new(client, token, refresh_token: 'abaca', expires_in: Time.now.getutc.to_i + 600)).to be_expires end end describe '#expired?' do it 'is false if there is no expires_in or expires_at' do expect(described_class.new(client, token)).not_to be_expired end it 'is false if expires_in is in the future' do expect(described_class.new(client, token, refresh_token: 'abaca', expires_in: 10_800)).not_to be_expired end it 'is true if expires_at is in the past' do access = described_class.new(client, token, refresh_token: 'abaca', expires_in: 600) @now = Time.now + 10_800 allow(Time).to receive(:now).and_return(@now) expect(access).to be_expired end it 'is true if expires_at is now' do @now = Time.now access = described_class.new(client, token, refresh_token: 'abaca', expires_at: @now.to_i) allow(Time).to receive(:now).and_return(@now) expect(access).to be_expired end end describe '#refresh' do let(:options) { {access_token_class: access_token_class} } let(:access_token_class) { NewAccessToken } let(:access) do described_class.new(client, token, refresh_token: 'abaca', expires_in: 600, param_name: 'o_param', access_token_class: access_token_class) end let(:new_access) do NewAccessToken.new(client, token, refresh_token: 'abaca') end before do custom_class = Class.new(described_class) do def self.from_hash(client, hash) new(client, hash.delete('access_token'), hash) end def self.contains_token?(hash) hash.key?('refresh_token') end end stub_const('NewAccessToken', custom_class) end context 'without refresh_token' do subject(:no_refresh) { no_access.refresh } let(:no_access) do described_class.new(client, token, refresh_token: nil, expires_in: 600, param_name: 'o_param', access_token_class: access_token_class) end it 'raises when no refresh_token' do block_is_expected.to raise_error('A refresh_token is not available') end end it 'returns a refresh token with appropriate values carried over' do refreshed = access.refresh expect(access.client).to eq(refreshed.client) expect(access.options[:param_name]).to eq(refreshed.options[:param_name]) end it 'returns a refresh token of the same access token class' do refreshed = new_access.refresh! expect(new_access.class).to eq(refreshed.class) end context 'with a nil refresh_token in the response' do let(:refresh_body) { JSON.dump(access_token: 'refreshed_foo', expires_in: 600, refresh_token: nil) } it 'copies the refresh_token from the original token' do refreshed = access.refresh expect(refreshed.refresh_token).to eq(access.refresh_token) end end context 'with a not-nil refresh_token in the response' do let(:refresh_body) { JSON.dump(access_token: 'refreshed_foo', expires_in: 600, refresh_token: 'qerwer') } it 'copies the refresh_token from the original token' do refreshed = access.refresh expect(refreshed.token).to eq('refreshed_foo') expect(refreshed.refresh_token).to eq('qerwer') end end context 'with a not-nil, not camel case, refresh_token in the response' do let(:refresh_body) { JSON.dump(accessToken: 'refreshed_foo', expires_in: 600, refreshToken: 'qerwer') } it 'copies the refresh_token from the original token' do refreshed = access.refresh expect(refreshed.token).to eq('refreshed_foo') expect(refreshed.refresh_token).to eq('qerwer') end end context 'with a custom access_token_class' do let(:access_token_class) { NewAccessToken } it 'returns a refresh token of NewAccessToken' do refreshed = access.refresh! expect(new_access.class).to eq(refreshed.class) end end end describe '#to_hash' do it 'return a hash equals to the hash used to initialize access token' do hash = {:access_token => token, :refresh_token => 'foobar', :expires_at => Time.now.to_i + 200, 'foo' => 'bar'} access_token = described_class.from_hash(client, hash.clone) expect(access_token.to_hash).to eq(hash) end end end oauth2-2.0.7/spec/oauth2/authenticator_spec.rb000066400000000000000000000070661430066224500213020ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Authenticator do subject do described_class.new(client_id, client_secret, mode) end let(:client_id) { 'foo' } let(:client_secret) { 'bar' } let(:mode) { :undefined } it 'raises NotImplementedError for unknown authentication mode' do expect { subject.apply({}) }.to raise_error(NotImplementedError) end describe '#apply' do context 'with parameter-based authentication' do let(:mode) { :request_body } it 'adds client_id and client_secret to params' do output = subject.apply({}) expect(output).to eq('client_id' => 'foo', 'client_secret' => 'bar') end context 'when client_id nil' do let(:client_id) { nil } it 'ignores client_id, but adds client_secret to params' do output = subject.apply({}) expect(output).to eq('client_secret' => 'bar') end end it 'does not overwrite existing credentials' do input = {'client_secret' => 's3cr3t'} output = subject.apply(input) expect(output).to eq('client_id' => 'foo', 'client_secret' => 's3cr3t') end it 'preserves other parameters' do input = {'state' => '42', :headers => {'A' => 'b'}} output = subject.apply(input) expect(output).to eq( 'client_id' => 'foo', 'client_secret' => 'bar', 'state' => '42', :headers => {'A' => 'b'} ) end context 'passing nil secret' do let(:client_secret) { nil } it 'does not set nil client_secret' do output = subject.apply({}) expect(output).to eq('client_id' => 'foo') end end context 'using tls client authentication' do let(:mode) { :tls_client_auth } it 'does not add client_secret' do output = subject.apply({}) expect(output).to eq('client_id' => 'foo') end end context 'using private key jwt authentication' do let(:mode) { :private_key_jwt } it 'does not include client_id or client_secret' do output = subject.apply({}) expect(output).to eq({}) end end end context 'using tls_client_auth' do let(:mode) { :tls_client_auth } context 'when client_id present' do let(:client_id) { 'foobar' } it 'adds client_id to params' do output = subject.apply({}) expect(output).to eq('client_id' => 'foobar') end end context 'when client_id nil' do let(:client_id) { nil } it 'ignores client_id for params' do output = subject.apply({}) expect(output).to eq({}) end end end context 'with Basic authentication' do let(:mode) { :basic_auth } let(:header) { "Basic #{Base64.strict_encode64("#{client_id}:#{client_secret}")}" } it 'encodes credentials in headers' do output = subject.apply({}) expect(output).to eq(headers: {'Authorization' => header}) end it 'does not overwrite existing credentials' do input = {headers: {'Authorization' => 'Bearer abc123'}} output = subject.apply(input) expect(output).to eq(headers: {'Authorization' => 'Bearer abc123'}) end it 'does not overwrite existing params or headers' do input = {'state' => '42', :headers => {'A' => 'b'}} output = subject.apply(input) expect(output).to eq( 'state' => '42', :headers => {'A' => 'b', 'Authorization' => header} ) end end end end oauth2-2.0.7/spec/oauth2/client_spec.rb000066400000000000000000001025671430066224500177100ustar00rootroot00000000000000# coding: utf-8 # frozen_string_literal: true require 'nkf' RSpec.describe OAuth2::Client do subject(:instance) do described_class.new('abc', 'def', {site: 'https://api.example.com'}.merge(options)) do |builder| builder.adapter :test do |stub| stub.get('/success') { |_env| [200, {'Content-Type' => 'text/awesome'}, 'yay'] } stub.get('/reflect') { |env| [200, {}, env[:body]] } stub.post('/reflect') { |env| [200, {}, env[:body]] } stub.get('/unauthorized') { |_env| [401, {'Content-Type' => 'application/json'}, JSON.dump(error: error_value, error_description: error_description_value)] } stub.get('/conflict') { |_env| [409, {'Content-Type' => 'text/plain'}, 'not authorized'] } stub.get('/redirect') { |_env| [302, {'Content-Type' => 'text/plain', 'location' => '/success'}, ''] } stub.get('/redirect_no_loc') { |_env| [302, {'Content-Type' => 'text/plain'}, ''] } stub.post('/redirect') { |_env| [303, {'Content-Type' => 'text/plain', 'location' => '/reflect'}, ''] } stub.get('/error') { |_env| [500, {'Content-Type' => 'text/plain'}, 'unknown error'] } stub.get('/empty_get') { |_env| [204, {}, nil] } stub.get('/different_encoding') { |_env| [500, {'Content-Type' => 'application/json'}, NKF.nkf('-We', JSON.dump(error: error_value, error_description: '∞'))] } stub.get('/ascii_8bit_encoding') { |_env| [500, {'Content-Type' => 'application/json'}, JSON.dump(error: 'invalid_request', error_description: 'é').force_encoding('ASCII-8BIT')] } stub.get('/unhandled_status') { |_env| [600, {}, nil] } end end end let!(:error_value) { 'invalid_token' } let!(:error_description_value) { 'bad bad token' } let(:options) { {} } describe '#initialize' do it 'assigns id and secret' do expect(subject.id).to eq('abc') expect(subject.secret).to eq('def') end it 'assigns site from the options hash' do expect(subject.site).to eq('https://api.example.com') end it 'assigns Faraday::Connection#host' do expect(subject.connection.host).to eq('api.example.com') end it 'leaves Faraday::Connection#ssl unset' do expect(subject.connection.ssl).to be_empty end it 'is able to pass a block to configure the connection' do builder = double('builder') allow(Faraday).to receive(:new).and_yield(builder) allow(builder).to receive(:response) expect(builder).to receive(:adapter).with(:test) described_class.new('abc', 'def') do |client| client.adapter :test end.connection end it 'defaults raise_errors to true' do expect(subject.options[:raise_errors]).to be true end it 'allows true/false for raise_errors option' do client = described_class.new('abc', 'def', site: 'https://api.example.com', raise_errors: false) expect(client.options[:raise_errors]).to be false client = described_class.new('abc', 'def', site: 'https://api.example.com', raise_errors: true) expect(client.options[:raise_errors]).to be true end it 'allows override of raise_errors option' do client = described_class.new('abc', 'def', site: 'https://api.example.com', raise_errors: true) do |builder| builder.adapter :test do |stub| stub.get('/notfound') { |_env| [404, {}, nil] } end end expect(client.options[:raise_errors]).to be true expect { client.request(:get, '/notfound') }.to raise_error(OAuth2::Error) response = client.request(:get, '/notfound', raise_errors: false) expect(response.status).to eq(404) end it 'allows get/post for access_token_method option' do client = described_class.new('abc', 'def', site: 'https://api.example.com', access_token_method: :get) expect(client.options[:access_token_method]).to eq(:get) client = described_class.new('abc', 'def', site: 'https://api.example.com', access_token_method: :post) expect(client.options[:access_token_method]).to eq(:post) end it 'does not mutate the opts hash argument' do opts = {site: 'http://example.com/'} opts2 = opts.dup described_class.new 'abc', 'def', opts expect(opts).to eq(opts2) end end describe '#site=(val)' do subject(:site) { instance.site = new_site } let(:options) do {site: 'https://example.com/blog'} end let(:new_site) { 'https://example.com/sharpie' } it 'sets site' do block_is_expected.to change(instance, :site).from('https://example.com/blog').to('https://example.com/sharpie') end context 'with connection' do before do instance.connection end it 'allows connection to reset with new url prefix' do block_is_expected.to change { instance.connection.url_prefix }.from(URI('https://example.com/blog')).to(URI('https://example.com/sharpie')) end end end %w[authorize token].each do |url_type| describe ":#{url_type}_url option" do it "defaults to a path of /oauth/#{url_type}" do expect(subject.send("#{url_type}_url")).to eq("https://api.example.com/oauth/#{url_type}") end it "is settable via the :#{url_type}_url option" do subject.options[:"#{url_type}_url"] = '/oauth/custom' expect(subject.send("#{url_type}_url")).to eq('https://api.example.com/oauth/custom') end it 'allows a different host than the site' do subject.options[:"#{url_type}_url"] = 'https://api.foo.com/oauth/custom' expect(subject.send("#{url_type}_url")).to eq('https://api.foo.com/oauth/custom') end context 'when a URL with path is used in the site' do let(:options) do {site: 'https://example.com/blog'} end it 'generates an authorization URL relative to the site' do expect(subject.send("#{url_type}_url")).to eq("https://example.com/blog/oauth/#{url_type}") end end end end describe ':redirect_uri option' do let(:auth_code_params) do { 'client_id' => 'abc', 'client_secret' => 'def', 'code' => 'code', 'grant_type' => 'authorization_code', } end context 'when blank' do it 'there is no redirect_uri param added to authorization URL' do expect(subject.authorize_url('a' => 'b')).to eq('https://api.example.com/oauth/authorize?a=b') end it 'does not add the redirect_uri param to the auth_code token exchange request' do client = described_class.new('abc', 'def', site: 'https://api.example.com', auth_scheme: :request_body) do |builder| builder.adapter :test do |stub| stub.post('/oauth/token', auth_code_params) do [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}'] end end end client.auth_code.get_token('code') end end context 'when set' do before { subject.options[:redirect_uri] = 'https://site.com/oauth/callback' } it 'adds the redirect_uri param to authorization URL' do expect(subject.authorize_url('a' => 'b')).to eq('https://api.example.com/oauth/authorize?a=b&redirect_uri=https%3A%2F%2Fsite.com%2Foauth%2Fcallback') end it 'adds the redirect_uri param to the auth_code token exchange request' do client = described_class.new('abc', 'def', redirect_uri: 'https://site.com/oauth/callback', site: 'https://api.example.com', auth_scheme: :request_body) do |builder| builder.adapter :test do |stub| stub.post('/oauth/token', auth_code_params.merge('redirect_uri' => 'https://site.com/oauth/callback')) do [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}'] end end end client.auth_code.get_token('code') end end describe 'custom headers' do context 'string key headers' do it 'adds the custom headers to request' do client = described_class.new('abc', 'def', site: 'https://api.example.com', auth_scheme: :request_body) do |builder| builder.adapter :test do |stub| stub.post('/oauth/token') do |env| expect(env.request_headers).to include('CustomHeader' => 'CustomHeader') [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}'] end end end header_params = {'headers' => {'CustomHeader' => 'CustomHeader'}} client.auth_code.get_token('code', header_params) end end context 'symbol key headers' do it 'adds the custom headers to request' do client = described_class.new('abc', 'def', site: 'https://api.example.com', auth_scheme: :request_body) do |builder| builder.adapter :test do |stub| stub.post('/oauth/token') do |env| expect(env.request_headers).to include('CustomHeader' => 'CustomHeader') [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}'] end end end header_params = {headers: {'CustomHeader' => 'CustomHeader'}} client.auth_code.get_token('code', header_params) end end context 'string key custom headers with basic auth' do it 'adds the custom headers to request' do client = described_class.new('abc', 'def', site: 'https://api.example.com') do |builder| builder.adapter :test do |stub| stub.post('/oauth/token') do |env| expect(env.request_headers).to include('CustomHeader' => 'CustomHeader') [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}'] end end end header_params = {'headers' => {'CustomHeader' => 'CustomHeader'}} client.auth_code.get_token('code', header_params) end end context 'symbol key custom headers with basic auth' do it 'adds the custom headers to request' do client = described_class.new('abc', 'def', site: 'https://api.example.com') do |builder| builder.adapter :test do |stub| stub.post('/oauth/token') do |env| expect(env.request_headers).to include('CustomHeader' => 'CustomHeader') [200, {'Content-Type' => 'application/json'}, '{"access_token":"token"}'] end end end header_params = {headers: {'CustomHeader' => 'CustomHeader'}} client.auth_code.get_token('code', header_params) end end end end describe '#connection' do context 'when debugging' do include_context 'with stubbed env' before do stub_env('OAUTH_DEBUG' => debug_value) end context 'when OAUTH_DEBUG=true' do let(:debug_value) { 'true' } it 'smoothly handles successive requests' do silence_all do # first request (always goes smoothly) subject.request(:get, '/success') end expect do # second request (used to throw Faraday::RackBuilder::StackLocked) subject.request(:get, '/success') end.not_to raise_error end it 'prints both request and response bodies to STDOUT' do printed = capture(:stdout) do subject.request(:get, '/success') subject.request(:get, '/reflect', body: 'this is magical') end expect(printed).to match 'request: GET https://api.example.com/success' expect(printed).to match 'response: Content-Type:' expect(printed).to match 'response: yay' expect(printed).to match 'request: this is magical' expect(printed).to match 'response: this is magical' end end context 'when OAUTH_DEBUG=false' do let(:debug_value) { 'false' } it 'smoothly handles successive requests' do silence_all do # first request (always goes smoothly) subject.request(:get, '/success') end expect do # second request (used to throw Faraday::RackBuilder::StackLocked) subject.request(:get, '/success') end.not_to raise_error end it 'prints nothing to STDOUT' do printed = capture(:stdout) do subject.request(:get, '/success') subject.request(:get, '/reflect', body: 'this is magical') end expect(printed).to eq '' end end end end describe '#request' do it 'works with a null response body' do expect(subject.request(:get, 'empty_get').body).to eq('') end it 'returns on a successful response' do response = subject.request(:get, '/success') expect(response.body).to eq('yay') expect(response.status).to eq(200) expect(response.headers).to eq('Content-Type' => 'text/awesome') end context 'with ENV' do include_context 'with stubbed env' context 'when OAUTH_DEBUG=true' do before do stub_env('OAUTH_DEBUG' => 'true') end it 'outputs to $stdout when OAUTH_DEBUG=true' do output = capture(:stdout) do subject.request(:get, '/success') end logs = [ 'request: GET https://api.example.com/success', 'response: Status 200', 'response: Content-Type: "text/awesome"', ] expect(output).to include(*logs) end end end it 'posts a body' do response = subject.request(:post, '/reflect', body: 'foo=bar') expect(response.body).to eq('foo=bar') end it 'follows redirects properly' do response = subject.request(:get, '/redirect') expect(response.body).to eq('yay') expect(response.status).to eq(200) expect(response.headers).to eq('Content-Type' => 'text/awesome') expect(response.response.env.url.to_s).to eq('https://api.example.com/success') end it 'redirects using GET on a 303' do response = subject.request(:post, '/redirect', body: 'foo=bar') expect(response.body).to be_empty expect(response.status).to eq(200) expect(response.response.env.url.to_s).to eq('https://api.example.com/reflect') end it 'raises an error if a redirect has no Location header' do expect { subject.request(:get, '/redirect_no_loc') }.to raise_error(OAuth2::Error, 'Got 302 status code, but no Location header was present') end it 'obeys the :max_redirects option' do max_redirects = subject.options[:max_redirects] subject.options[:max_redirects] = 0 response = subject.request(:get, '/redirect') expect(response.status).to eq(302) subject.options[:max_redirects] = max_redirects end it 'returns if raise_errors is false' do subject.options[:raise_errors] = false response = subject.request(:get, '/unauthorized') expect(response.status).to eq(401) expect(response.headers).to eq('Content-Type' => 'application/json') end %w[/unauthorized /conflict /error /different_encoding /ascii_8bit_encoding].each do |error_path| it "raises OAuth2::Error on error response to path #{error_path}" do pending_for(engine: 'jruby', reason: 'https://github.com/jruby/jruby/issues/4921') if error_path == '/different_encoding' expect { subject.request(:get, error_path) }.to raise_error(OAuth2::Error) end end it 're-encodes response body in the error message' do expect { subject.request(:get, '/ascii_8bit_encoding') }.to raise_error do |ex| expect(ex.message).to eq("invalid_request: é\n{\"error\":\"invalid_request\",\"error_description\":\"��\"}") expect(ex.message.encoding.name).to eq('UTF-8') end end it 'parses OAuth2 standard error response' do expect { subject.request(:get, '/unauthorized') }.to raise_error do |ex| expect(ex.code).to eq(error_value) expect(ex.description).to eq(error_description_value) expect(ex.to_s).to match(/#{error_value}/) expect(ex.to_s).to match(/#{error_description_value}/) end end it 'provides the response in the Exception' do expect { subject.request(:get, '/error') }.to raise_error do |ex| expect(ex.response).not_to be_nil expect(ex.to_s).to match(/unknown error/) end end it 'informs about unhandled status code' do expect { subject.request(:get, '/unhandled_status') }.to raise_error do |ex| expect(ex.response).not_to be_nil expect(ex.to_s).to match(/Unhandled status code value of 600/) end end context 'when errors are raised by Faraday' do let(:connection) { instance_double(Faraday::Connection, build_url: double) } before do allow(connection).to( receive(:run_request).and_raise(faraday_exception) ) allow(subject).to receive(:connection).and_return(connection) # rubocop:disable RSpec/SubjectStub end shared_examples 'failed connection handler' do it 'rescues the exception' do expect { subject.request(:get, '/redirect') }.to raise_error do |e| expect(e.class).to eq(expected_exception) expect(e.message).to eq(faraday_exception.message) end end end context 'with Faraday::ConnectionFailed' do let(:faraday_exception) { Faraday::ConnectionFailed.new('fail') } let(:expected_exception) { OAuth2::ConnectionError } it_behaves_like 'failed connection handler' end context 'with Faraday::TimeoutError' do let(:faraday_exception) { Faraday::TimeoutError.new('timeout') } let(:expected_exception) { OAuth2::TimeoutError } it_behaves_like 'failed connection handler' end end end describe '#get_token' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end token = client.get_token({}) expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') end context 'when parse: :automatic' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end token = client.get_token(parse: :automatic) expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') end end context 'when parse: :xml but response is JSON' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end expect { client.get_token(parse: :xml) }.to raise_error( MultiXml::ParseError, 'The document "{\"access_token\":\"the-token\"}" does not have a valid root' ) end end context 'when parse is fuzzed' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end token = client.get_token(parse: 'random') expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') end end context 'when parse is correct' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end token = client.get_token(parse: :json) expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') end end context 'when snaky is falsy, but response is snaky' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end token = client.get_token(snaky: false) expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') expect(token.response.parsed.to_h).to eq('access_token' => 'the-token') end end context 'when snaky is falsy, but response is not snaky' do it 'returns a configured AccessToken' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('accessToken' => 'the-token')] end end token = client.get_token({snaky: false}, {param_name: 'accessToken'}) expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') expect(token.response.parsed.to_h).to eq('accessToken' => 'the-token') end end it 'authenticates with request parameters' do client = stubbed_client(auth_scheme: :request_body) do |stub| stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def') do |_env| [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end client.get_token({}) end it 'authenticates with Basic auth' do client = stubbed_client(auth_scheme: :basic_auth) do |stub| stub.post('/oauth/token') do |env| raise Faraday::Adapter::Test::Stubs::NotFound unless env[:request_headers]['Authorization'] == OAuth2::Authenticator.encode_basic_auth('abc', 'def') [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end client.get_token({}) end it 'authenticates with JSON' do client = stubbed_client(auth_scheme: :basic_auth) do |stub| stub.post('/oauth/token') do |env| [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end client.get_token(headers: {'Content-Type' => 'application/json'}) end it 'sets the response object on the access token' do client = stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end token = client.get_token({}) expect(token.response).to be_a OAuth2::Response expect(token.response.parsed).to eq('access_token' => 'the-token') end context 'when the :raise_errors flag is set to false' do let(:body) { nil } let(:status_code) { 500 } let(:content_type) { 'application/json' } let(:client) do stubbed_client(raise_errors: false) do |stub| stub.post('/oauth/token') do [status_code, {'Content-Type' => content_type}, body] end end end context 'when the request body is nil' do subject(:get_token) { client.get_token({}) } it 'raises error JSON::ParserError' do block_is_expected { get_token }.to raise_error(JSON::ParserError) end context 'when extract_access_token raises an exception' do let(:status_code) { 200 } let(:extract_proc) { proc { |client, hash| raise ArgumentError } } it 'returns a nil :access_token' do expect(client.get_token({}, {}, extract_proc)).to eq(nil) end end end context 'when status code is 200' do let(:status_code) { 200 } context 'when the request body is not nil' do let(:body) { JSON.dump('access_token' => 'the-token') } it 'returns the parsed :access_token from body' do token = client.get_token({}) expect(token.response).to be_a OAuth2::Response expect(token.response.parsed).to eq('access_token' => 'the-token') end end context 'when Content-Type is not JSON' do let(:content_type) { 'text/plain' } let(:body) { 'hello world' } it 'returns the parsed :access_token from body' do expect(client.get_token({})).to be_nil end end end end describe 'with custom access_token_class option' do let(:options) { {access_token_class: CustomAccessToken} } let(:payload) { {'custom_token' => 'the-token'} } let(:content_type) { 'application/json' } let(:client) do stubbed_client(options) do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => content_type}, JSON.dump(payload)] end end end before do custom_class = Class.new(OAuth2::AccessToken) do attr_accessor :response def self.from_hash(client, hash) new(client, hash.delete('custom_token')) end def self.contains_token?(hash) hash.key?('custom_token') end end stub_const('CustomAccessToken', custom_class) end it 'returns the parsed :custom_token from body' do client.get_token({}) end context 'when the :raise_errors flag is set to true' do let(:options) { {access_token_class: CustomAccessToken, raise_errors: true} } let(:payload) { {} } it 'raises an error' do expect { client.get_token({}) }.to raise_error(OAuth2::Error) end context 'when the legacy extract_access_token' do let(:extract_access_token) do proc do |client, hash| token = hash['data']['access_token'] OAuth2::AccessToken.new(client, token, hash) end end let(:options) { {raise_errors: true} } let(:payload) { {} } it 'raises an error' do expect { client.get_token({}, {}, extract_access_token) }.to raise_error(OAuth2::Error) end end end context 'when status code is 200' do let(:status_code) { 200 } context 'when the request body is blank' do let(:payload) { {} } it 'raises an error' do expect { client.get_token({}) }.to raise_error(OAuth2::Error) end end context 'when Content-Type is not JSON' do let(:content_type) { 'text/plain' } let(:body) { 'hello world' } it 'raises an error' do expect { client.get_token({}) }.to raise_error(OAuth2::Error) end end end context 'when access token instance responds to response=' do let(:options) { {access_token_class: CustomAccessToken, raise_errors: false} } it 'sets response' do expect(client.get_token({}).response).to be_a(OAuth2::Response) end end context 'when request has a block' do subject(:request) do client.get_token({}) do |req| raise 'Block is executing' end end let(:options) { {access_token_class: CustomAccessToken, raise_errors: false} } it 'sets response' do block_is_expected.to raise_error('Block is executing') end end end describe 'abnormal custom access_token_class option' do let(:payload) { {'custom_token' => 'the-token'} } let(:content_type) { 'application/json' } let(:client) do stubbed_client(options) do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => content_type}, JSON.dump(payload)] end end end before do custom_class = Class.new do def initialize(client, hash) end def self.from_hash(client, hash) new(client, hash.delete('custom_token')) end def self.contains_token?(hash) hash.key?('custom_token') end end stub_const('StrangeAccessToken', custom_class) end context 'when the :raise_errors flag is set to true' do let(:options) { {access_token_class: StrangeAccessToken, raise_errors: true} } let(:payload) { {} } it 'raises an error' do expect { client.get_token({}) }.to raise_error(OAuth2::Error) end end context 'when access token instance does not responds to response=' do let(:options) { {access_token_class: StrangeAccessToken} } let(:payload) { {'custom_token' => 'the-token'} } it 'sets response' do token_access = client.get_token({}) expect(token_access).to be_a(StrangeAccessToken) expect(token_access).not_to respond_to(:response=) expect(token_access).not_to respond_to(:response) end end context 'when request has a block' do subject(:request) do client.get_token({}) do |req| raise 'Block is executing' end end let(:options) { {access_token_class: StrangeAccessToken} } it 'sets response' do block_is_expected.to raise_error('Block is executing') end end end describe 'with extract_access_token option' do let(:client) do stubbed_client(extract_access_token: extract_access_token) do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('data' => {'access_token' => 'the-token'})] end end end let(:extract_access_token) do proc do |client, hash| token = hash['data']['access_token'] OAuth2::AccessToken.new(client, token, hash) end end it 'returns a configured AccessToken' do token = client.get_token({}) expect(token).to be_a OAuth2::AccessToken expect(token.token).to eq('the-token') end context 'with deprecation' do subject(:printed) do capture(:stderr) do client.get_token({}) end end it 'warns on STDERR' do msg = <<-MSG.lstrip OAuth2::Client#initialize argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class`. MSG expect(printed).to eq(msg) end context 'on request' do subject(:printed) do capture(:stderr) do client.get_token({}, {}, extract_access_token) end end let(:client) do stubbed_client do |stub| stub.post('/oauth/token') do [200, {'Content-Type' => 'application/json'}, JSON.dump('data' => {'access_token' => 'the-token'})] end end end it 'warns on STDERR' do msg = <<-MSG.lstrip OAuth2::Client#get_token argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class` on #initialize. MSG expect(printed).to eq(msg) end end end end it 'forwards given token parameters' do client = stubbed_client(auth_scheme: :request_body) do |stub| stub.post('/oauth/token', 'arbitrary' => 'parameter', 'client_id' => 'abc', 'client_secret' => 'def') do |_env| [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end client.get_token({'arbitrary' => 'parameter'}) # rubocop:disable Style/BracesAroundHashParameters end context 'when token_method is set to post_with_query_string' do it 'uses the http post method and passes parameters in the query string' do client = stubbed_client(token_method: :post_with_query_string) do |stub| stub.post('/oauth/token?state=abc123') do |_env| [200, {'Content-Type' => 'application/json'}, JSON.dump('access_token' => 'the-token')] end end client.get_token({'state' => 'abc123'}) # rubocop:disable Style/BracesAroundHashParameters end end def stubbed_client(params = {}, &stubs) params = {site: 'https://api.example.com'}.merge(params) OAuth2::Client.new('abc', 'def', params) do |builder| builder.adapter :test, &stubs end end end it 'instantiates an HTTP Method with this client' do expect(subject.http_method).to be_kind_of(Symbol) end it 'instantiates an AuthCode strategy with this client' do expect(subject.auth_code).to be_kind_of(OAuth2::Strategy::AuthCode) end it 'instantiates an Implicit strategy with this client' do expect(subject.implicit).to be_kind_of(OAuth2::Strategy::Implicit) end context 'with SSL options' do subject do cli = described_class.new('abc', 'def', site: 'https://api.example.com', ssl: {ca_file: 'foo.pem'}) cli.connection = Faraday.new(cli.site, cli.options[:connection_opts]) do |b| b.adapter :test end cli end it 'passes the SSL options along to Faraday::Connection#ssl' do expect(subject.connection.ssl.fetch(:ca_file)).to eq('foo.pem') end end context 'without a connection-configuration block' do subject do described_class.new('abc', 'def', site: 'https://api.example.com') end it 'applies default faraday middleware to the connection' do expect(subject.connection.builder.handlers).to include(Faraday::Request::UrlEncoded) end end end oauth2-2.0.7/spec/oauth2/error_spec.rb000066400000000000000000000472701430066224500175620ustar00rootroot00000000000000# encoding: UTF-8 # frozen_string_literal: true class StirredHash < Hash def to_str '{"hello":"� Cool � StirredHash"}' end end class XmledString < String XML = ' � Cool � XmledString '.freeze def to_str XML end end RSpec.describe OAuth2::Error do subject { described_class.new(response) } let(:response) do raw_response = Faraday::Response.new( status: 418, response_headers: response_headers, body: response_body ) OAuth2::Response.new(raw_response) end let(:response_headers) { {'Content-Type' => 'application/json'} } let(:response_body) { {text: 'Coffee brewing failed'}.to_json } it 'sets the response object to #response on self' do error = described_class.new(response) expect(error.response).to equal(response) end describe 'attr_readers' do it 'has code' do expect(subject).to respond_to(:code) end it 'has description' do expect(subject).to respond_to(:description) end it 'has response' do expect(subject).to respond_to(:response) end end context 'when the response is parsed' do let(:response_body) { response_hash.to_json } let(:response_hash) { {text: 'Coffee brewing failed'} } context 'when the response has an error and error_description' do before do response_hash['error_description'] = 'Short and stout' response_hash['error'] = 'i_am_a_teapot' end it 'sets the code attribute' do expect(subject.code).to eq('i_am_a_teapot') end it 'sets the description attribute' do expect(subject.description).to eq('Short and stout') end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "i_am_a_teapot: Short and stout\n", '{"text":"Coffee brewing failed","error_description":"Short and stout","error":"i_am_a_teapot"}', ] ) end context 'when the response needs to be encoded' do let(:response_body) { JSON.dump(response_hash).force_encoding('ASCII-8BIT') } context 'with invalid characters present' do before do response_body.gsub!('stout', "\255 invalid \255") end it 'replaces them' do # The skip can be removed once support for < 2.1 is dropped. encoding = {reason: 'encode/scrub only works as of Ruby 2.1'} skip_for(encoding.merge(engine: 'jruby')) # See https://bibwild.wordpress.com/2013/03/12/removing-illegal-bytes-for-encoding-in-ruby-1-9-strings/ raise 'Invalid characters not replaced' unless subject.message.include?('� invalid �') # This will fail if {:invalid => replace} is not passed into `encode` end end context 'with undefined characters present' do before do response_hash['error_description'] += ": 'A magical voyage of tea 🍵'" end it 'replaces them' do raise 'Undefined characters not replaced' unless subject.message.include?('tea �') # This will fail if {:undef => replace} is not passed into `encode` end end end context 'when the response is not an encodable thing' do let(:response_headers) { {'Content-Type' => 'who knows'} } let(:response_body) { {text: 'Coffee brewing failed'} } before do expect(response_body).not_to respond_to(:encode) # i.e. a Ruby hash end it 'does not try to encode the message string' do expect(subject.message).to eq(response_body.to_s) end end context 'when using :json parser with non-encodable data' do let(:response_headers) { {'Content-Type' => 'application/hal+json'} } let(:response_body) do StirredHash.new( "_links": { "self": {"href": '/orders/523'}, "warehouse": {"href": '/warehouse/56'}, "invoice": {"href": '/invoices/873'}, }, "currency": 'USD', "status": 'shipped', "total": 10.20 ) end before do expect(response_body).not_to respond_to(:force_encoding) expect(response_body).to respond_to(:to_str) end it 'does not force encode the message' do expect(subject.message).to eq('{"hello":"� Cool � StirredHash"}') end end context 'when using :xml parser' do let(:response_headers) { {'Content-Type' => 'text/xml'} } let(:response_body) do XmledString.new(XmledString::XML) end before do expect(response_body).to respond_to(:to_str) end it 'parses the XML' do expect(subject.message).to eq(XmledString::XML) end end context 'when using :xml parser with non-String-like thing' do let(:response_headers) { {'Content-Type' => 'text/xml'} } let(:response_body) { {hello: :world} } before do expect(response_body).not_to respond_to(:to_str) end it 'just returns the thing if it can' do expect(subject.message).to eq('{:hello=>:world}') end end end it 'sets the code attribute to nil' do expect(subject.code).to be_nil end it 'sets the description attribute' do expect(subject.description).to be_nil end context 'when there is no error description' do before do expect(response_hash).not_to have_key('error') expect(response_hash).not_to have_key('error_description') end it 'does not prepend anything to the message' do expect(subject.message.lines.count).to eq(1) expect(subject.message).to eq '{"text":"Coffee brewing failed"}' end it 'does not set code' do expect(subject.code).to be_nil end it 'does not set description' do expect(subject.description).to be_nil end end context 'when there is code (error)' do before do response_hash['error_description'] = 'Short and stout' response_hash['error'] = 'i_am_a_teapot' response_hash['status'] = '418' end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "i_am_a_teapot: Short and stout\n", { "text": 'Coffee brewing failed', "error_description": 'Short and stout', "error": 'i_am_a_teapot', "status": '418', }.to_json, ] ) end context 'when the response needs to be encoded' do let(:response_body) { JSON.dump(response_hash).force_encoding('ASCII-8BIT') } context 'with invalid characters present' do before do response_body.gsub!('stout', "\255 invalid \255") end it 'replaces them' do # The skip can be removed once support for < 2.1 is dropped. encoding = {reason: 'encode/scrub only works as of Ruby 2.1'} skip_for(encoding.merge(engine: 'jruby')) # See https://bibwild.wordpress.com/2013/03/12/removing-illegal-bytes-for-encoding-in-ruby-1-9-strings/ raise 'Invalid characters not replaced' unless subject.message.include?('� invalid �') # This will fail if {:invalid => replace} is not passed into `encode` end end context 'with undefined characters present' do before do response_hash['error_description'] += ": 'A magical voyage of tea 🍵'" end it 'replaces them' do raise 'Undefined characters not replaced' unless subject.message.include?('tea �') # This will fail if {:undef => replace} is not passed into `encode` end end end context 'when the response is not an encodable thing' do let(:response_headers) { {'Content-Type' => 'who knows'} } let(:response_body) { {text: 'Coffee brewing failed'} } before do expect(response_body).not_to respond_to(:encode) # i.e. a Ruby hash end it 'does not try to encode the message string' do expect(subject.message).to eq(response_body.to_s) end end it 'sets the code attribute' do expect(subject.code).to eq('i_am_a_teapot') end it 'sets the description attribute' do expect(subject.description).to eq('Short and stout') end end context 'when there is code (error) but no error_description' do before do response_hash.delete('error_description') response_hash['error'] = 'i_am_a_teapot' response_hash['status'] = '418' end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "i_am_a_teapot: \n", { "text": 'Coffee brewing failed', "error": 'i_am_a_teapot', "status": '418', }.to_json, ] ) end context 'when the response needs to be encoded' do let(:response_body) { JSON.dump(response_hash).force_encoding('ASCII-8BIT') } context 'with invalid characters present' do before do response_body.gsub!('brewing', "\255 invalid \255") end it 'replaces them' do # The skip can be removed once support for < 2.1 is dropped. encoding = {reason: 'encode/scrub only works as of Ruby 2.1'} skip_for(encoding.merge(engine: 'jruby')) # See https://bibwild.wordpress.com/2013/03/12/removing-illegal-bytes-for-encoding-in-ruby-1-9-strings/ raise 'Invalid characters not replaced' unless subject.message.include?('� invalid �') # This will fail if {:invalid => replace} is not passed into `encode` end end end context 'when the response is not an encodable thing' do let(:response_headers) { {'Content-Type' => 'who knows'} } let(:response_body) { {text: 'Coffee brewing failed'} } before do expect(response_body).not_to respond_to(:encode) # i.e. a Ruby hash end it 'does not try to encode the message string' do expect(subject.message).to eq(response_body.to_s) end end it 'sets the code attribute from error' do expect(subject.code).to eq('i_am_a_teapot') end it 'does not set the description attribute' do expect(subject.description).to be_nil end end context 'when there is error_description but no code (error)' do before do response_hash['error_description'] = 'Short and stout' response_hash.delete('error') end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "Short and stout\n", { "text": 'Coffee brewing failed', "error_description": 'Short and stout', }.to_json, ] ) end context 'when the response needs to be encoded' do let(:response_body) { JSON.dump(response_hash).force_encoding('ASCII-8BIT') } context 'with invalid characters present' do before do response_body.gsub!('stout', "\255 invalid \255") end it 'replaces them' do # The skip can be removed once support for < 2.1 is dropped. encoding = {reason: 'encode/scrub only works as of Ruby 2.1'} skip_for(encoding.merge(engine: 'jruby')) # See https://bibwild.wordpress.com/2013/03/12/removing-illegal-bytes-for-encoding-in-ruby-1-9-strings/ raise 'Invalid characters not replaced' unless subject.message.include?('� invalid �') # This will fail if {:invalid => replace} is not passed into `encode` end end context 'with undefined characters present' do before do response_hash['error_description'] += ": 'A magical voyage of tea 🍵'" end it 'replaces them' do raise 'Undefined characters not replaced' unless subject.message.include?('tea �') # This will fail if {:undef => replace} is not passed into `encode` end end end context 'when the response is not an encodable thing' do let(:response_headers) { {'Content-Type' => 'who knows'} } let(:response_body) { {text: 'Coffee brewing failed'} } before do expect(response_body).not_to respond_to(:encode) # i.e. a Ruby hash end it 'does not try to encode the message string' do expect(subject.message).to eq(response_body.to_s) end end it 'sets the code attribute' do expect(subject.code).to be_nil end it 'sets the description attribute' do expect(subject.description).to eq('Short and stout') end end end context 'when the response is simple hash, not parsed' do subject { described_class.new(response_hash) } let(:response_hash) { {text: 'Coffee brewing failed'} } it 'sets the code attribute to nil' do expect(subject.code).to be_nil end it 'sets the description attribute' do expect(subject.description).to be_nil end context 'when the response has an error and error_description' do before do response_hash['error_description'] = 'Short and stout' response_hash['error'] = 'i_am_a_teapot' end it 'sets the code attribute' do expect(subject.code).to eq('i_am_a_teapot') end it 'sets the description attribute' do expect(subject.description).to eq('Short and stout') end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "i_am_a_teapot: Short and stout\n", '{:text=>"Coffee brewing failed", "error_description"=>"Short and stout", "error"=>"i_am_a_teapot"}', ] ) end context 'when using :xml parser with non-String-like thing' do let(:response_headers) { {'Content-Type' => 'text/xml'} } let(:response_hash) { {hello: :world} } before do expect(response_hash).not_to respond_to(:to_str) end it 'just returns whatever it can' do expect(subject.message).to eq("i_am_a_teapot: Short and stout\n{:hello=>:world, \"error_description\"=>\"Short and stout\", \"error\"=>\"i_am_a_teapot\"}") end end end context 'when using :xml parser with non-String-like thing' do let(:response_headers) { {'Content-Type' => 'text/xml'} } let(:response_hash) { {hello: :world} } before do expect(response_hash).not_to respond_to(:to_str) end it 'just returns the thing if it can' do expect(subject.message).to eq('{:hello=>:world}') end end context 'when there is no error description' do before do expect(response_hash).not_to have_key('error') expect(response_hash).not_to have_key('error_description') end it 'does not prepend anything to the message' do expect(subject.message.lines.count).to eq(1) expect(subject.message).to eq '{:text=>"Coffee brewing failed"}' end it 'does not set code' do expect(subject.code).to be_nil end it 'does not set description' do expect(subject.description).to be_nil end end context 'when there is code (error)' do before do response_hash['error_description'] = 'Short and stout' response_hash['error'] = 'i_am_a_teapot' response_hash['status'] = '418' end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "i_am_a_teapot: Short and stout\n", '{:text=>"Coffee brewing failed", "error_description"=>"Short and stout", "error"=>"i_am_a_teapot", "status"=>"418"}', ] ) end it 'sets the code attribute' do expect(subject.code).to eq('i_am_a_teapot') end it 'sets the description attribute' do expect(subject.description).to eq('Short and stout') end end context 'when there is code (error) but no error_description' do before do response_hash.delete('error_description') response_hash['error'] = 'i_am_a_teapot' response_hash['status'] = '418' end it 'sets the code attribute from error' do expect(subject.code).to eq('i_am_a_teapot') end it 'does not set the description attribute' do expect(subject.description).to be_nil end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "i_am_a_teapot: \n", '{:text=>"Coffee brewing failed", "error"=>"i_am_a_teapot", "status"=>"418"}', ] ) end end context 'when there is error_description but no code (error)' do before do response_hash['error_description'] = 'Short and stout' response_hash.delete('error') end it 'prepends to the error message with a return character' do expect(subject.message.each_line.to_a).to eq( [ "Short and stout\n", '{:text=>"Coffee brewing failed", "error_description"=>"Short and stout"}', ] ) end context 'when the response is not an encodable thing' do let(:response_headers) { {'Content-Type' => 'who knows'} } let(:response_hash) { {text: 'Coffee brewing failed'} } before do expect(response_hash).not_to respond_to(:encode) # i.e. a Ruby hash end it 'does not try to encode the message string' do expect(subject.message).to eq("Short and stout\n{:text=>\"Coffee brewing failed\", \"error_description\"=>\"Short and stout\"}") end end it 'sets the code attribute' do expect(subject.code).to be_nil end it 'sets the description attribute' do expect(subject.description).to eq('Short and stout') end end end context 'when the response is not a hash, not parsed' do subject { described_class.new(response_thing) } let(:response_thing) { [200, 'Success'] } it 'sets the code attribute to nil' do expect(subject.code).to be_nil end it 'sets the description attribute' do expect(subject.description).to be_nil end it 'sets the body attribute' do expect(subject.body).to eq(response_thing) end it 'sets the response attribute' do expect(subject.response).to eq(response_thing) end end context 'when the response does not parse to a hash' do let(:response_headers) { {'Content-Type' => 'text/html'} } let(:response_body) { 'Hello, I am a teapot' } before do expect(response.parsed).not_to be_a(Hash) end it 'does not do anything to the message' do expect(subject.message.lines.count).to eq(1) expect(subject.message).to eq(response_body) end it 'does not set code' do expect(subject.code).to be_nil end it 'does not set description' do expect(subject.description).to be_nil end end describe 'parsing json' do it 'does not blow up' do expect { subject.to_json }.not_to raise_error expect { subject.response.to_json }.not_to raise_error end end end oauth2-2.0.7/spec/oauth2/response_spec.rb000066400000000000000000000265231430066224500202650ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Response do subject { described_class.new(raw_response) } let(:raw_response) { Faraday::Response.new(status: status, response_headers: headers, body: body) } let(:status) { 200 } let(:headers) { {'foo' => 'bar'} } let(:body) { 'foo' } describe '#initialize' do it 'returns the status, headers and body' do expect(subject.headers).to eq(headers) expect(subject.status).to eq(status) expect(subject.body).to eq(body) end end describe '.register_parser' do let(:response) do double('response', headers: {'Content-Type' => 'application/foo-bar'}, status: 200, body: 'baz') end before do described_class.register_parser(:foobar, ['application/foo-bar']) do |body| "foobar #{body}" end end it 'adds to the content types and parsers' do expect(described_class.send(:class_variable_get, :@@parsers).keys).to include(:foobar) expect(described_class.send(:class_variable_get, :@@content_types).keys).to include('application/foo-bar') end it 'is able to parse that content type automatically' do expect(described_class.new(response).parsed).to eq('foobar baz') end end describe '#content_type' do context 'when headers are blank' do let(:headers) { nil } it 'returns nil' do expect(subject.content_type).to be_nil end end context 'when content-type is not present' do let(:headers) { {'a fuzzy' => 'fuzzer'} } it 'returns empty string' do expect(subject.content_type).to eq('') end end context 'when content-type is present' do let(:headers) { {'Content-Type' => 'application/x-www-form-urlencoded'} } it 'returns the content type header contents' do expect(subject.content_type).to eq('application/x-www-form-urlencoded') end end end describe '#parsed' do subject(:parsed) do headers = {'Content-Type' => content_type} response = double('response', headers: headers, body: body) instance = described_class.new(response) instance.parsed end shared_examples_for 'parsing JSON-like' do it 'has num keys' do expect(parsed.keys.size).to eq(6) end it 'parses string' do expect(parsed['foo']).to eq('bar') expect(parsed.key('bar')).to eq('foo') end it 'parses non-zero number' do expect(parsed['answer']).to eq(42) expect(parsed.key(42)).to eq('answer') end it 'parses nil as NilClass' do expect(parsed['krill']).to be_nil expect(parsed.key(nil)).to eq('krill') end it 'parses zero as number' do expect(parsed['zero']).to eq(0) expect(parsed.key(0)).to eq('zero') end it 'parses false as FalseClass' do expect(parsed['malign']).to be(false) expect(parsed.key(false)).to eq('malign') end it 'parses false as TrueClass' do expect(parsed['shine']).to be(true) expect(parsed.key(true)).to eq('shine') end end context 'when application/json' do let(:content_type) { 'application/json' } let(:body) { JSON.dump(foo: 'bar', answer: 42, krill: nil, zero: 0, malign: false, shine: true) } it_behaves_like 'parsing JSON-like' end context 'when application/Json' do let(:content_type) { 'application/Json' } let(:body) { JSON.dump(foo: 'bar', answer: 42, krill: nil, zero: 0, malign: false, shine: true) } it_behaves_like 'parsing JSON-like' end context 'when application/hal+json' do let(:content_type) { 'application/hal+json' } let(:body) { JSON.dump(foo: 'bar', answer: 42, krill: nil, zero: 0, malign: false, shine: true) } it_behaves_like 'parsing JSON-like' end context 'when application/x-www-form-urlencoded' do let(:content_type) { 'application/x-www-form-urlencoded' } let(:body) { 'foo=bar&answer=42&krill=&zero=0&malign=false&shine=true' } it 'has num keys' do expect(parsed.keys.size).to eq(6) end it 'parses string' do expect(parsed['foo']).to eq('bar') expect(parsed.key('bar')).to eq('foo') end it 'parses non-zero number as string' do expect(parsed['answer']).to eq('42') expect(parsed.key('42')).to eq('answer') end it 'parses nil as empty string' do expect(parsed['krill']).to eq('') expect(parsed.key('')).to eq('krill') end it 'parses zero as string' do expect(parsed['zero']).to eq('0') expect(parsed.key('0')).to eq('zero') end it 'parses false as string' do expect(parsed['malign']).to eq('false') expect(parsed.key('false')).to eq('malign') end it 'parses true as string' do expect(parsed['shine']).to eq('true') expect(parsed.key('true')).to eq('shine') end end it 'parses application/vnd.collection+json body' do headers = {'Content-Type' => 'application/vnd.collection+json'} body = JSON.dump(collection: {}) response = double('response', headers: headers, body: body) subject = described_class.new(response) expect(subject.parsed.keys.size).to eq(1) end it 'parses application/vnd.api+json body' do headers = {'Content-Type' => 'application/vnd.api+json'} body = JSON.dump(collection: {}) response = double('response', headers: headers, body: body) subject = described_class.new(response) expect(subject.parsed.keys.size).to eq(1) end it 'parses application/problem+json body' do headers = {'Content-Type' => 'application/problem+json'} body = JSON.dump(type: 'https://tools.ietf.org/html/rfc7231#section-6.5.4', title: 'Not Found') response = double('response', headers: headers, body: body) subject = described_class.new(response) expect(subject.parsed.keys.size).to eq(2) expect(subject.parsed['type']).to eq('https://tools.ietf.org/html/rfc7231#section-6.5.4') expect(subject.parsed['title']).to eq('Not Found') end it "doesn't try to parse other content-types" do headers = {'Content-Type' => 'text/html'} body = '' response = double('response', headers: headers, body: body) expect(JSON).not_to receive(:parse) expect(Rack::Utils).not_to receive(:parse_query) subject = described_class.new(response) expect(subject.parsed).to be_nil end it "doesn't parse bodies which have previously been parsed" do headers = {'Content-Type' => 'application/json'} body = {foo: 'bar', answer: 42, krill: nil, zero: 0, malign: false, shine: true} response = double('response', headers: headers, body: body) expect(JSON).not_to receive(:parse) expect(Rack::Utils).not_to receive(:parse_query) subject = described_class.new(response) expect(subject.parsed.keys.size).to eq(6) end it 'snakecases json keys when parsing' do headers = {'Content-Type' => 'application/json'} body = JSON.dump('accessToken' => 'bar', 'MiGever' => 'Ani') response = double('response', headers: headers, body: body) subject = described_class.new(response) expect(subject.parsed.keys.size).to eq(2) expect(subject.parsed['access_token']).to eq('bar') expect(subject.parsed['mi_gever']).to eq('Ani') end context 'when not snaky' do it 'does not snakecase json keys when parsing' do headers = {'Content-Type' => 'application/json'} body = JSON.dump('accessToken' => 'bar', 'MiGever' => 'Ani') response = double('response', headers: headers, body: body) subject = described_class.new(response, snaky: false) expect(subject.parsed.keys.size).to eq(2) expect(subject.parsed['accessToken']).to eq('bar') expect(subject.parsed['MiGever']).to eq('Ani') expect(subject.parsed['access_token']).to be_nil expect(subject.parsed['mi_gever']).to be_nil end end it 'supports registered parsers with arity == 0; passing nothing' do described_class.register_parser(:arity_0, []) do 'a-ok' end headers = {'Content-Type' => 'text/html'} body = '' response = double('response', headers: headers, body: body) subject = described_class.new(response, parse: :arity_0) expect(subject.parsed).to eq('a-ok') end it 'supports registered parsers with arity == 2; passing body and response' do headers = {'Content-Type' => 'text/html'} body = '' response = double('response', headers: headers, body: body) described_class.register_parser(:arity_2, []) do |passed_body, passed_response| expect(passed_body).to eq(body) expect(passed_response).to eq(response) 'a-ok' end subject = described_class.new(response, parse: :arity_2) expect(subject.parsed).to eq('a-ok') end it 'supports registered parsers with arity > 2; passing body and response' do headers = {'Content-Type' => 'text/html'} body = '' response = double('response', headers: headers, body: body) described_class.register_parser(:arity_3, []) do |passed_body, passed_response, *args| expect(passed_body).to eq(body) expect(passed_response).to eq(response) expect(args).to eq([]) 'a-ok' end subject = described_class.new(response, parse: :arity_3) expect(subject.parsed).to eq('a-ok') end it 'supports directly passed parsers' do headers = {'Content-Type' => 'text/html'} body = '' response = double('response', headers: headers, body: body) subject = described_class.new(response, parse: -> { 'a-ok' }) expect(subject.parsed).to eq('a-ok') end it 'supports no parsing' do headers = {'Content-Type' => 'text/html'} body = '' response = double('response', headers: headers, body: body) subject = described_class.new(response, parse: false) expect(subject.parsed).to eq(nil) end end context 'with xml parser registration' do it 'tries to load multi_xml.rb and use it' do expect(described_class.send(:class_variable_get, :@@parsers)[:xml]).not_to be_nil end it 'is able to parse xml' do headers = {'Content-Type' => 'text/xml'} body = 'baz' response = double('response', headers: headers, body: body) expect(described_class.new(response).parsed).to eq('foo' => {'bar' => 'baz'}) end it 'is able to parse application/xml' do headers = {'Content-Type' => 'application/xml'} body = 'baz' response = double('response', headers: headers, body: body) expect(described_class.new(response).parsed).to eq('foo' => {'bar' => 'baz'}) end end describe 'converting to json' do it 'does not blow up' do expect { subject.to_json }.not_to raise_error end end end oauth2-2.0.7/spec/oauth2/snaky_hash_spec.rb000066400000000000000000000103321430066224500205460ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::SnakyHash do subject(:instance) { described_class.new } describe '.build' do context 'when build from hash' do subject { described_class.new('AccessToken' => '1') } it 'create correct snake hash' do expect(subject).to be_a(described_class) expect(subject['AccessToken']).to eq('1') expect(subject['access_token']).to eq('1') end end context 'when build from snake_hash' do subject do h = described_class.new h['AccessToken'] = '1' described_class.new(h) end it 'create correct snake hash' do expect(subject).to be_a(described_class) expect(subject['AccessToken']).to eq('1') expect(subject['access_token']).to eq('1') end end end describe 'assign and query' do it 'returns assigned value with camel key' do subject['AccessToken'] = '1' expect(subject['AccessToken']).to eq('1') expect(subject['access_token']).to eq('1') end it 'returns assigned value with snake key' do subject['access_token'] = '1' expect(subject['AccessToken']).to eq('1') expect(subject['access_token']).to eq('1') end it 'overwrite by alternate key' do subject['AccessToken'] = '1' expect(subject['AccessToken']).to eq('1') expect(subject['access_token']).to eq('1') subject['access_token'] = '2' expect(subject['AccessToken']).to eq('2') expect(subject['access_token']).to eq('2') end end describe '#to_h' do context 'when nil' do it 'can be converted to empty hash' do expect(instance.to_h).to eq({}) end end context 'when empty' do subject(:instance) { described_class.new(original) } let(:original) { {} } it 'can be converted to empty hash' do expect(instance.to_h).to eq({}) end end context 'when not empty' do subject(:instance) { described_class.new(original) } let(:original) { {'a' => 'b', 'bTo' => 'aDo', 'v_rt' => 1, yy_yy: 'yy_yy', :LuLu => :CRays} } it 'converts to snake hash' do expect(instance.to_h).to eq('a' => 'b', 'b_to' => 'aDo', 'lu_lu' => :CRays, 'v_rt' => 1, 'yy_yy' => 'yy_yy') end end end describe '#fetch' do context 'when Camel case key' do subject { described_class.new('AccessToken' => '1') } it 'return correct token' do expect(subject.fetch('access_token')).to eq('1') end end context 'when Camel case key with down-cased first letter' do subject { described_class.new('accessToken' => '1') } it 'return correct token' do expect(subject.fetch('access_token')).to eq('1') end end context 'when snake case key' do subject { described_class.new('access_token' => '1') } it 'return correct token' do expect(subject.fetch('access_token')).to eq('1') end end context 'when missing any key' do subject { described_class.new } it 'raise KeyError with key' do pending_for(engine: 'jruby', versions: '3.1.0', reason: 'https://github.com/jruby/jruby/issues/7112') expect do subject.fetch('access_token') end.to raise_error(KeyError, /access_token/) end it 'return default value' do expect(subject.fetch('access_token', 'default')).to eq('default') end end end describe '#key?' do context 'when Camel case key' do subject { described_class.new('AccessToken' => '1') } it 'return true' do expect(subject.key?('access_token')).to be(true) end end context 'when Camel case key with down-cased first letter' do subject { described_class.new('accessToken' => '1') } it 'return true' do expect(subject.key?('access_token')).to be(true) end end context 'when snake case key' do subject { described_class.new('access_token' => '1') } it 'return true' do expect(subject.key?('access_token')).to be(true) end end context 'when missing any key' do subject { described_class.new } it 'return false' do expect(subject.key?('access_token')).to be(false) end end end end oauth2-2.0.7/spec/oauth2/strategy/000077500000000000000000000000001430066224500167225ustar00rootroot00000000000000oauth2-2.0.7/spec/oauth2/strategy/assertion_spec.rb000066400000000000000000000221211430066224500222660ustar00rootroot00000000000000# frozen_string_literal: true require 'openssl' require 'jwt' RSpec.describe OAuth2::Strategy::Assertion do let(:client_assertion) { client.assertion } let(:client) do cli = OAuth2::Client.new('abc', 'def', site: 'http://api.example.com', auth_scheme: auth_scheme) cli.connection = Faraday.new(cli.site, cli.options[:connection_opts]) do |b| b.request :url_encoded b.adapter :test do |stub| stub.post('/oauth/token') do |token_request| @request_body = Rack::Utils.parse_nested_query(token_request.body).transform_keys(&:to_sym) case @response_format when 'formencoded' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout'] when 'json' [200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}'] else raise 'Please define @response_format to choose a response content type!' end end end end cli end let(:auth_scheme) { :request_body } describe '#authorize_url' do it 'raises NotImplementedError' do expect { client_assertion.authorize_url }.to raise_error(NotImplementedError) end end describe '#get_token' do let(:algorithm) { 'HS256' } let(:key) { 'arowana' } let(:timestamp) { Time.now.to_i } let(:claims) do { iss: 'carp@example.com', scope: 'https://oauth.example.com/auth/flounder', aud: 'https://sturgeon.example.com/oauth2/token', exp: timestamp + 3600, iat: timestamp, sub: '12345', custom_claim: 'ling cod', } end before do @response_format = 'json' end describe 'assembling a JWT assertion' do let(:jwt) do payload, header = JWT.decode(@request_body[:assertion], key, true, algorithm: algorithm) {payload: payload, header: header} end let(:payload) { jwt[:payload] } let(:header) { jwt[:header] } shared_examples_for 'encodes the JWT' do it 'indicates algorithm in the header' do expect(header).not_to be_nil expect(header['alg']).to eq(algorithm) end it 'has claims' do expect(payload).not_to be_nil expect(payload.keys).to match_array(%w[iss scope aud exp iat sub custom_claim]) payload.each do |key, claim| expect(claims[key.to_sym]).to eq(claim) end end end context 'when encoding as HS256' do let(:algorithm) { 'HS256' } let(:key) { 'super_secret!' } before do client_assertion.get_token(claims, algorithm: algorithm, key: key) raise 'No request made!' if @request_body.nil? end it_behaves_like 'encodes the JWT' context 'with real key' do let(:key) { '1883be842495c3b58f68ca71fbf1397fbb9ed2fdf8990f8404a25d0a1b995943' } it_behaves_like 'encodes the JWT' end end context 'when encoding as RS256' do let(:algorithm) { 'RS256' } let(:key) { OpenSSL::PKey::RSA.new(1024) } before do client_assertion.get_token(claims, algorithm: algorithm, key: key) raise 'No request made!' if @request_body.nil? end it_behaves_like 'encodes the JWT' context 'with private key' do let(:private_key_file) { 'spec/fixtures/RS256/jwtRS256.key' } let(:password) { '' } let(:key) { OpenSSL::PKey::RSA.new(File.read(private_key_file), password) } it_behaves_like 'encodes the JWT' end end context 'with bad encoding params' do let(:encoding_opts) { {algorithm: algorithm, key: key} } describe 'non-supported algorithms' do let(:algorithm) { 'the blockchain' } let(:key) { 'machine learning' } it 'raises NotImplementedError' do # this behavior is handled by the JWT gem, but this should make sure it is consistent expect { client_assertion.get_token(claims, encoding_opts) }.to raise_error(NotImplementedError) end end describe 'of a wrong object type' do let(:encoding_opts) { 'the cloud' } it 'raises ArgumentError' do expect { client_assertion.get_token(claims, encoding_opts) }.to raise_error(ArgumentError, /encoding_opts/) end end describe 'missing encoding_opts[:algorithm]' do before do encoding_opts.delete(:algorithm) end it 'raises ArgumentError' do expect { client_assertion.get_token(claims, encoding_opts) }.to raise_error(ArgumentError, /encoding_opts/) end end describe 'missing encoding_opts[:key]' do before do encoding_opts.delete(:key) end it 'raises ArgumentError' do expect { client_assertion.get_token(claims, encoding_opts) }.to raise_error(ArgumentError, /encoding_opts/) end end end end describe 'POST request parameters' do context 'when using :auth_scheme => :request_body' do let(:auth_scheme) { :request_body } it 'includes assertion and grant_type, along with the client parameters' do client_assertion.get_token(claims, algorithm: algorithm, key: key) expect(@request_body).not_to be_nil expect(@request_body.keys).to match_array(%i[assertion grant_type client_id client_secret]) expect(@request_body[:grant_type]).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer') expect(@request_body[:assertion]).to be_a(String) expect(@request_body[:client_id]).to eq('abc') expect(@request_body[:client_secret]).to eq('def') end it 'includes other params via request_options' do client_assertion.get_token(claims, {algorithm: algorithm, key: key}, {scope: 'dover sole'}) expect(@request_body).not_to be_nil expect(@request_body.keys).to match_array(%i[assertion grant_type scope client_id client_secret]) expect(@request_body[:grant_type]).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer') expect(@request_body[:assertion]).to be_a(String) expect(@request_body[:scope]).to eq('dover sole') expect(@request_body[:client_id]).to eq('abc') expect(@request_body[:client_secret]).to eq('def') end end context 'when using :auth_scheme => :basic_auth' do let(:auth_scheme) { :basic_auth } it 'includes assertion and grant_type by default' do client_assertion.get_token(claims, algorithm: algorithm, key: key) expect(@request_body).not_to be_nil expect(@request_body.keys).to match_array(%i[assertion grant_type]) expect(@request_body[:grant_type]).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer') expect(@request_body[:assertion]).to be_a(String) end it 'includes other params via request_options' do client_assertion.get_token(claims, {algorithm: algorithm, key: key}, {scope: 'dover sole'}) expect(@request_body).not_to be_nil expect(@request_body.keys).to match_array(%i[assertion grant_type scope]) expect(@request_body[:grant_type]).to eq('urn:ietf:params:oauth:grant-type:jwt-bearer') expect(@request_body[:assertion]).to be_a(String) expect(@request_body[:scope]).to eq('dover sole') end end end describe 'returning the response' do let(:access_token) { client_assertion.get_token(claims, {algorithm: algorithm, key: key}, {}, response_opts) } let(:response_opts) { {} } %w[json formencoded].each do |mode| context "when the content type is #{mode}" do before do @response_format = mode end it 'returns an AccessToken' do expect(access_token).to be_an(OAuth2::AccessToken) end it 'returns AccessToken with same Client' do expect(access_token.client).to eq(client) end it 'returns AccessToken with #token' do expect(access_token.token).to eq('salmon') end it 'returns AccessToken with #expires_in' do expect(access_token.expires_in).to eq(600) end it 'returns AccessToken with #expires_at' do expect(access_token.expires_at).not_to be_nil end it 'sets AccessToken#refresh_token to nil' do expect(access_token.refresh_token).to eq('trout') end context 'with custom response_opts' do let(:response_opts) { {'custom_token_option' => 'mackerel'} } it 'passes them into the token params' do expect(access_token.params).to eq(response_opts) end end context 'when no custom opts are passed in' do let(:response_opts) { {} } it 'does not set any params by default' do expect(access_token.params).to eq({}) end end end end end end end oauth2-2.0.7/spec/oauth2/strategy/auth_code_spec.rb000066400000000000000000000126711430066224500222230ustar00rootroot00000000000000# encoding: utf-8 # frozen_string_literal: true RSpec.describe OAuth2::Strategy::AuthCode do subject { client.auth_code } let(:code) { 'sushi' } let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout&extra_param=steve' } let(:facebook_token) { kvform_token.gsub('_in', '') } let(:json_token) { JSON.dump(expires_in: 600, access_token: 'salmon', refresh_token: 'trout', extra_param: 'steve') } let(:redirect_uri) { 'http://example.com/redirect_uri' } let(:microsoft_token) { 'id_token=i_am_MSFT' } let(:client) do OAuth2::Client.new('abc', 'def', site: 'http://api.example.com') do |builder| builder.adapter :test do |stub| stub.get("/oauth/token?client_id=abc&code=#{code}&grant_type=authorization_code") do |_env| case @mode when 'formencoded' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token] when 'json' [200, {'Content-Type' => 'application/json'}, json_token] when 'from_facebook' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token] when 'from_microsoft' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, microsoft_token] else raise ArgumentError, "Bad @mode: #{@mode}" end end stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def', 'code' => 'sushi', 'grant_type' => 'authorization_code') do |_env| case @mode when 'formencoded' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token] when 'json' [200, {'Content-Type' => 'application/json'}, json_token] when 'from_facebook' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token] else raise ArgumentError, "Bad @mode: #{@mode}" end end stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def', 'code' => 'sushi', 'grant_type' => 'authorization_code', 'redirect_uri' => redirect_uri) do |_env| [200, {'Content-Type' => 'application/json'}, json_token] end end end end describe '#authorize_url' do it 'includes the client_id' do expect(subject.authorize_url).to include('client_id=abc') end it 'includes the type' do expect(subject.authorize_url).to include('response_type=code') end it 'does not include the client_secret' do expect(subject.authorize_url).not_to include('client_secret=def') end it 'raises an error if the client_secret is passed in' do expect { subject.authorize_url(client_secret: 'def') }.to raise_error(ArgumentError) end it 'raises an error if the client_secret is passed in with string keys' do expect { subject.authorize_url('client_secret' => 'def') }.to raise_error(ArgumentError) end it 'includes passed in options' do cb = 'http://myserver.local/oauth/callback' expect(subject.authorize_url(redirect_uri: cb)).to include("redirect_uri=#{CGI.escape(cb)}") end end describe '#get_token (with dynamic redirect_uri)' do before do @mode = 'json' client.options[:token_method] = :post client.options[:auth_scheme] = :request_body client.options[:redirect_uri] = redirect_uri end it 'includes redirect_uri once in the request parameters' do expect { subject.get_token(code, redirect_uri: redirect_uri) }.not_to raise_error end end describe '#get_token (handling utf-8 data)' do let(:json_token) { JSON.dump(expires_in: 600, access_token: 'salmon', refresh_token: 'trout', extra_param: 'André') } before do @mode = 'json' client.options[:token_method] = :post client.options[:auth_scheme] = :request_body end it 'does not raise an error' do expect { subject.get_token(code) }.not_to raise_error end it 'does not create an error instance' do expect(OAuth2::Error).not_to receive(:new) subject.get_token(code) end end describe '#get_token' do it "doesn't treat an OpenID Connect token with only an id_token (like from Microsoft) as invalid" do @mode = 'from_microsoft' client.options[:token_method] = :get client.options[:auth_scheme] = :request_body @access = subject.get_token(code) expect(@access.token).to eq('i_am_MSFT') end end %w[json formencoded from_facebook].each do |mode| %i[get post].each do |verb| describe "#get_token (#{mode}, access_token_method=#{verb}" do before do @mode = mode client.options[:token_method] = verb client.options[:auth_scheme] = :request_body @access = subject.get_token(code) end it 'returns AccessToken with same Client' do expect(@access.client).to eq(client) end it 'returns AccessToken with #token' do expect(@access.token).to eq('salmon') end it 'returns AccessToken with #refresh_token' do expect(@access.refresh_token).to eq('trout') end it 'returns AccessToken with #expires_in' do expect(@access.expires_in).to eq(600) end it 'returns AccessToken with #expires_at' do expect(@access.expires_at).to be_kind_of(Integer) end it 'returns AccessToken with params accessible via []' do expect(@access['extra_param']).to eq('steve') end end end end end oauth2-2.0.7/spec/oauth2/strategy/base_spec.rb000066400000000000000000000003161430066224500211730ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Strategy::Base do it 'initializes with a Client' do expect { described_class.new(OAuth2::Client.new('abc', 'def')) }.not_to raise_error end end oauth2-2.0.7/spec/oauth2/strategy/client_credentials_spec.rb000066400000000000000000000062611430066224500241210ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Strategy::ClientCredentials do subject { client.client_credentials } let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout' } let(:json_token) { '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}' } let(:client) do OAuth2::Client.new('abc', 'def', site: 'http://api.example.com') do |builder| builder.adapter :test do |stub| stub.post('/oauth/token', 'grant_type' => 'client_credentials') do |env| client_id, client_secret = Base64.decode64(env[:request_headers]['Authorization'].split(' ', 2)[1]).split(':', 2) (client_id == 'abc' && client_secret == 'def') || raise(Faraday::Adapter::Test::Stubs::NotFound) @last_headers = env[:request_headers] case @mode when 'formencoded' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token] when 'json' [200, {'Content-Type' => 'application/json'}, json_token] else raise ArgumentError, "Bad @mode: #{@mode}" end end stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def', 'grant_type' => 'client_credentials') do |_env| case @mode when 'formencoded' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token] when 'json' [200, {'Content-Type' => 'application/json'}, json_token] else raise ArgumentError, "Bad @mode: #{@mode}" end end end end end describe '#authorize_url' do it 'raises NotImplementedError' do expect { subject.authorize_url }.to raise_error(NotImplementedError) end end %w[json formencoded].each do |mode| %i[basic_auth request_body].each do |auth_scheme| describe "#get_token (#{mode}) (#{auth_scheme})" do before do @mode = mode client.options[:auth_scheme] = auth_scheme @access = subject.get_token end it 'returns AccessToken with same Client' do expect(@access.client).to eq(client) end it 'returns AccessToken with #token' do expect(@access.token).to eq('salmon') end it 'returns AccessToken without #refresh_token' do expect(@access.refresh_token).to eq('trout') end it 'returns AccessToken with #expires_in' do expect(@access.expires_in).to eq(600) end it 'returns AccessToken with #expires_at' do expect(@access.expires_at).not_to be_nil end end end end describe '#get_token (with extra header parameters)' do before do @mode = 'json' @access = subject.get_token(headers: {'X-Extra-Header' => 'wow'}) end it 'sends the header correctly.' do expect(@last_headers['X-Extra-Header']).to eq('wow') end end describe '#get_token (with option overriding response)' do before do @mode = 'json' @access = subject.get_token({}, {'refresh_token' => 'guppy'}) end it 'override is applied' do expect(@access.token).to eq('salmon') expect(@access.refresh_token).to eq('guppy') end end end oauth2-2.0.7/spec/oauth2/strategy/implicit_spec.rb000066400000000000000000000023671430066224500221030ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Strategy::Implicit do subject { client.implicit } let(:client) { OAuth2::Client.new('abc', 'def', site: 'http://api.example.com') } describe '#authorize_url' do it 'includes the client_id' do expect(subject.authorize_url).to include('client_id=abc') end it 'includes the type' do expect(subject.authorize_url).to include('response_type=token') end it 'does not include the client_secret' do expect(subject.authorize_url).not_to include('client_secret=def') end it 'raises an error if the client_secret is passed in' do expect { subject.authorize_url(client_secret: 'def') }.to raise_error(ArgumentError) end it 'raises an error if the client_secret is passed in with string keys' do expect { subject.authorize_url('client_secret' => 'def') }.to raise_error(ArgumentError) end it 'includes passed in options' do cb = 'http://myserver.local/oauth/callback' expect(subject.authorize_url(redirect_uri: cb)).to include("redirect_uri=#{CGI.escape(cb)}") end end describe '#get_token' do it 'raises NotImplementedError' do expect { subject.get_token }.to raise_error(NotImplementedError) end end end oauth2-2.0.7/spec/oauth2/strategy/password_spec.rb000066400000000000000000000033211430066224500221220ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Strategy::Password do subject { client.password } let(:client) do cli = OAuth2::Client.new('abc', 'def', site: 'http://api.example.com') cli.connection = Faraday.new(cli.site, cli.options[:connection_opts]) do |b| b.request :url_encoded b.adapter :test do |stub| stub.post('/oauth/token') do |_env| case @mode when 'formencoded' [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout'] when 'json' [200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}'] else raise ArgumentError, "Bad @mode: #{@mode}" end end end end cli end describe '#authorize_url' do it 'raises NotImplementedError' do expect { subject.authorize_url }.to raise_error(NotImplementedError) end end %w[json formencoded].each do |mode| describe "#get_token (#{mode})" do before do @mode = mode @access = subject.get_token('username', 'password') end it 'returns AccessToken with same Client' do expect(@access.client).to eq(client) end it 'returns AccessToken with #token' do expect(@access.token).to eq('salmon') end it 'returns AccessToken with #refresh_token' do expect(@access.refresh_token).to eq('trout') end it 'returns AccessToken with #expires_in' do expect(@access.expires_in).to eq(600) end it 'returns AccessToken with #expires_at' do expect(@access.expires_at).not_to be_nil end end end end oauth2-2.0.7/spec/oauth2/version_spec.rb000066400000000000000000000016421430066224500201070ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe OAuth2::Version do it 'has a version number' do expect(described_class).not_to be_nil end it 'can be a string' do expect(described_class.to_s).to be_a(String) end it 'allows Constant access' do expect(described_class::VERSION).to be_a(String) end it 'is greater than 0.1.0' do expect(Gem::Version.new(described_class) > Gem::Version.new('0.1.0')).to be(true) end it 'major version is an integer' do expect(described_class.major).to be_a(Integer) end it 'minor version is an integer' do expect(described_class.minor).to be_a(Integer) end it 'patch version is an integer' do expect(described_class.patch).to be_a(Integer) end it 'returns a Hash' do expect(described_class.to_h.keys).to match_array(%i[major minor patch pre]) end it 'returns an Array' do expect(described_class.to_a).to be_a(Array) end end oauth2-2.0.7/spec/spec_helper.rb000066400000000000000000000037601430066224500165020ustar00rootroot00000000000000# frozen_string_literal: true # ensure test env ENV['RACK_ENV'] = 'test' # Third Party Libraries require 'rspec' require 'rspec/stubbed_env' require 'silent_stream' require 'addressable/uri' require 'rspec/pending_for' require 'rspec/block_is_expected' # Extensions require 'ext/backports' DEBUG = ENV['DEBUG'] == 'true' ruby_version = Gem::Version.new(RUBY_VERSION) minimum_version = ->(version, engine = 'ruby') { ruby_version >= Gem::Version.new(version) && RUBY_ENGINE == engine } actual_version = lambda do |major, minor| actual = Gem::Version.new(ruby_version) major == actual.segments[0] && minor == actual.segments[1] && RUBY_ENGINE == 'ruby' end debugging = minimum_version.call('2.7') && DEBUG RUN_COVERAGE = minimum_version.call('2.6') && (ENV['COVER_ALL'] || ENV['CI_CODECOV'] || ENV['CI'].nil?) ALL_FORMATTERS = actual_version.call(2, 7) && (ENV['COVER_ALL'] || ENV['CI_CODECOV'] || ENV['CI']) if DEBUG if debugging require 'byebug' elsif minimum_version.call('2.7', 'jruby') require 'pry-debugger-jruby' end end if RUN_COVERAGE require 'simplecov' # Config file `.simplecov` is run immediately when simplecov loads require 'codecov' require 'simplecov-json' require 'simplecov-lcov' require 'simplecov-cobertura' # This will override the formatter set in .simplecov if ALL_FORMATTERS SimpleCov::Formatter::LcovFormatter.config do |c| c.report_with_single_file = true c.single_report_path = 'coverage/lcov.info' end SimpleCov.formatters = [ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::CoberturaFormatter, # XML for Jenkins SimpleCov::Formatter::LcovFormatter, SimpleCov::Formatter::JSONFormatter, # For CodeClimate SimpleCov::Formatter::Codecov, # For CodeCov ] end end # This gem require 'oauth2' # Library Configs require 'config/multi_xml' require 'config/faraday' # RSpec Configs require 'config/rspec/rspec_core' require 'config/rspec/silent_stream' VERBS = %i[get post put delete patch].freeze