diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..db2e5864 --- /dev/null +++ b/.envrc @@ -0,0 +1,38 @@ +# Run any command in this library's bin/ without the bin/ prefix! +PATH_add bin + +# Only add things to this file that should be shared with the team. + +# **dotenv** (See end of file for .env.local integration) +# .env would override anything in this file, if enabled. +# .env is a DOCKER standard, and if we use it, it would be in deployed, or DOCKER, environments. +# Override and customize anything below in your own .env.local +# If you are using dotenv and not direnv, +# copy the following `export` statements to your own .env file. + +### General Ruby ### +# Turn off Ruby Warnings about deprecated code +# export RUBYOPT="-W0" + +### External Testing Controls +export K_SOUP_COV_DO=true # Means you want code coverage +# Available formats are html, xml, rcov, lcov, json, tty +export K_SOUP_COV_COMMAND_NAME="RSpec Coverage" +export K_SOUP_COV_FORMATTERS="html,tty" +export K_SOUP_COV_MIN_BRANCH=99 # Means you want to enforce X% branch coverage +export K_SOUP_COV_MIN_LINE=100 # Means you want to enforce X% line coverage +export K_SOUP_COV_MIN_HARD=true # Means you want the build to fail if the coverage thresholds are not met +export K_SOUP_COV_MULTI_FORMATTERS=true +export K_SOUP_COV_OPEN_BIN= # Means don't try to open coverage results in browser +export MAX_ROWS=1 # Setting for simplecov-console gem for tty output, limits to the worst N rows of bad coverage + +# Internal Debugging Controls +export DEBUG=false # do not allow byebug statements (override in .env.local) + +# .env would override anything in this file, if `dotenv` is uncommented below. +# .env is a DOCKER standard, and if we use it, it would be in deployed, or DOCKER, environments, +# and that is why we generally want to leave it commented out. +# dotenv + +# .env.local will override anything in this file. +dotenv_if_exists .env.local diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f6d2e5aa..e6c57875 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -9,5 +9,5 @@ liberapay: pboling # Replace with a single Liberapay username open_collective: # Replace with a single Open Collective username patreon: galtzo # Replace with a single Patreon username polar: pboling -thanks_dev: gh/pboling +thanks_dev: u/gh/pboling tidelift: rubygems/oauth2 # Replace with a single Tidelift platform-name/package-name e.g., npm/babel diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 89c4a1c3..dc043b45 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,7 @@ updates: open-pull-requests-limit: 10 ignore: - dependency-name: "rubocop-lts" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/heads.yml b/.github/workflows/heads.yml index 9bd8f0ae..3292009a 100644 --- a/.github/workflows/heads.yml +++ b/.github/workflows/heads.yml @@ -36,9 +36,9 @@ jobs: - f1 - f2 rubygems: - - latest + - default bundler: - - latest + - default ruby: - truffleruby+graalvm-head - truffleruby-head diff --git a/.github/workflows/jruby-head.yml b/.github/workflows/jruby-head.yml index 8c3960d4..9c0498d9 100644 --- a/.github/workflows/jruby-head.yml +++ b/.github/workflows/jruby-head.yml @@ -37,9 +37,9 @@ jobs: - f1 - f2 rubygems: - - latest + - default bundler: - - latest + - default ruby: - jruby-head include: diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index d27761a7..edb198fd 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -26,7 +26,7 @@ jobs: bundler: - latest ruby: - - "2.7" + - ruby runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.gitignore b/.gitignore index 38ac0429..aa5a7c14 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ # Version Managers .ruby-version .tool-versions + +# Local config +.env.local \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 20aa732c..de040959 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,19 @@ default: image: ruby:3.2 +variables: + BUNDLE_INSTALL_FLAGS: "--quiet --jobs=$(nproc) --retry=3" + BUNDLE_FROZEN: "false" # No lockfile! + K_SOUP_COV_DEBUG: true + K_SOUP_COV_DO: true + K_SOUP_COV_HARD: true + K_SOUP_COV_MIN_BRANCH: 98 + K_SOUP_COV_MIN_LINE: 98 + K_SOUP_COV_VERBOSE: true + K_SOUP_COV_FORMATTERS: "html,xml,rcov,lcov,json,tty" + K_SOUP_COV_MULTI_FORMATTERS: true + K_SOUP_COV_COMMAND_NAME: "RSpec Coverage" + workflow: rules: # For merge requests, create a pipeline. @@ -16,8 +29,8 @@ workflow: script: - gem update --system > /dev/null 2>&1 - bundle config --local path vendor - - bundle install --quiet --jobs 4 --retry 3 - - bundle exec rake test + - bundle install + - bundle exec rake cache: key: ${CI_JOB_IMAGE} paths: @@ -28,11 +41,11 @@ workflow: stage: test script: # Because we support EOL Ruby still... - - gem install rubygems-update -v 3.4.22 > /dev/null 2>&1 + - gem install rubygems-update -v ${RUBYGEMS_VERSION} # Actually updates both RubyGems and Bundler! - - update_rubygems > /dev/null 2>&1 + - update_rubygems - bundle config --local path vendor - - bundle install --quiet --jobs 4 --retry 3 + - bundle install - bundle exec rake test cache: key: ${CI_JOB_IMAGE} @@ -40,12 +53,39 @@ workflow: - vendor/ruby ruby-current: + variables: + BUNDLE_GEMFILE: gemfiles/omnibus.gemfile + K_SOUP_COV_DO: true <<: *test_definition-current parallel: matrix: - - RUBY_VERSION: ["3.0", "3.1", "3.2"] + - RUBY_VERSION: ["3.2", "3.3", "3.4"] + +ruby-ruby3_1: + variables: + RUBYGEMS_VERSION: "3.6.9" + BUNDLE_GEMFILE: gemfiles/vanilla.gemfile + K_SOUP_COV_DO: false + <<: *test_definition-legacy + parallel: + matrix: + - RUBY_VERSION: ["3.1"] + +ruby-ruby3_0: + variables: + RUBYGEMS_VERSION: "3.5.23" + BUNDLE_GEMFILE: gemfiles/vanilla.gemfile + K_SOUP_COV_DO: false + <<: *test_definition-legacy + parallel: + matrix: + - RUBY_VERSION: ["3.0"] -ruby-legacy: +ruby-ruby2_7: + variables: + RUBYGEMS_VERSION: "3.4.22" + BUNDLE_GEMFILE: gemfiles/vanilla.gemfile + K_SOUP_COV_DO: false <<: *test_definition-legacy parallel: matrix: diff --git a/.rspec b/.rspec index 2db90875..6c6110e5 100644 --- a/.rspec +++ b/.rspec @@ -1,4 +1,4 @@ --format documentation ---require spec_helper --color +--require spec_helper --order random diff --git a/.rubocop.yml b/.rubocop.yml index 879d964a..32a249f3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,18 +1,8 @@ -inherit_from: - - .rubocop_todo.yml - - .rubocop_rspec.yml - inherit_gem: rubocop-lts: rubocop-lts.yml -require: - # Try adding back once we reach rubocop-ruby2_3+ - # - 'rubocop-md' - # Can be added once we reach rubocop-ruby2_3+ - # - 'rubocop-packaging' - - 'rubocop-performance' - - 'rubocop-rake' - - 'rubocop-rspec' +inherit_from: + - .rubocop_rspec.yml AllCops: DisplayCopNames: true # Display the name of the failing cops @@ -22,15 +12,7 @@ AllCops: - '**/.irbrc' Metrics/BlockLength: - ExcludedMethods: - - context - - describe - - it - - shared_context - - shared_examples - - shared_examples_for - - namespace - - draw + Enabled: false Gemspec/RequiredRubyVersion: Enabled: false @@ -38,7 +20,7 @@ Gemspec/RequiredRubyVersion: Metrics/BlockNesting: Max: 2 -Metrics/LineLength: +Layout/LineLength: Enabled: false Metrics/ParameterLists: @@ -60,12 +42,6 @@ Lint/UnusedBlockArgument: - 'vendor/**/*' - '**/.irbrc' -RSpec/DescribeClass: - Exclude: - - 'spec/examples/*' - -RSpec/NestedGroups: - Enabled: false Style/ClassVars: Enabled: false diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock new file mode 100644 index 00000000..469a85d4 --- /dev/null +++ b/.rubocop_gradual.lock @@ -0,0 +1,180 @@ +{ + "README.md:620392337": [ + [305, 3, 1, "Layout/ClosingParenthesisIndentation: Indent `)` to column 0 (not 2)", 177548] + ], + "bin/bundle:3976421676": [ + [66, 5, 20, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2485198147], + [78, 5, 74, "Style/InvertibleUnlessCondition: Prefer `if Gem.rubygems_version >= Gem::Version.new(\"2.7.0\")` over `unless Gem.rubygems_version < Gem::Version.new(\"2.7.0\")`.", 2453573257] + ], + "lib/oauth2.rb:3930909031": [ + [31, 5, 21, "ThreadSafety/ClassAndModuleAttributes: Avoid mutating class and module attributes.", 622027168], + [34, 11, 7, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 651502127] + ], + "lib/oauth2/authenticator.rb:3711266135": [ + [42, 5, 113, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 734523108] + ], + "lib/oauth2/filtered_attributes.rb:1202323815": [ + [3, 5, 63, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2901108034], + [9, 9, 25, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2012823020], + [13, 9, 25, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2012823020] + ], + "lib/oauth2/response.rb:877496664": [ + [35, 5, 204, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 996912427] + ], + "oauth2.gemspec:1672982387": [ + [5, 6, 12, "Gemspec/RubyVersionGlobalsUsage: Do not use `RUBY_VERSION` in gemspec file.", 31296028], + [117, 3, 54, "Gemspec/DependencyVersion: Dependency version specification is required.", 3677216839], + [118, 3, 47, "Gemspec/DependencyVersion: Dependency version specification is required.", 2440116108], + [120, 3, 46, "Gemspec/DependencyVersion: Dependency version specification is required.", 1075698341], + [130, 3, 58, "Gemspec/DependencyVersion: Dependency version specification is required.", 2795510341], + [131, 3, 52, "Gemspec/DependencyVersion: Dependency version specification is required.", 804182931], + [132, 3, 52, "Gemspec/DependencyVersion: Dependency version specification is required.", 3163430777], + [134, 3, 48, "Gemspec/DependencyVersion: Dependency version specification is required.", 425065368] + ], + "spec/examples/google_spec.rb:1491180421": [ + [9, 3, 5115, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1014001606], + [97, 5, 1016, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3156315524], + [121, 5, 783, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 1916865261] + ], + "spec/oauth2/access_token_spec.rb:1576666213": [ + [3, 1, 34, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/access_token*_spec.rb`.", 1972107547], + [25, 3, 1935, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1152039306], + [42, 5, 915, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1914441490], + [56, 7, 507, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3775341637], + [81, 5, 564, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 935902373], + [145, 7, 371, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 81675473], + [156, 7, 269, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 2703574041], + [166, 7, 343, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 571450510], + [177, 7, 1671, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 2358061917], + [185, 9, 218, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 2937949503], + [193, 9, 1213, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3948450440], + [201, 11, 416, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3896472588], + [206, 13, 238, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 669428729], + [215, 11, 250, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 962614116], + [223, 11, 249, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1923581233], + [471, 5, 968, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 908014549], + [500, 5, 1224, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [9/5]", 2179768666], + [590, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088], + [641, 3, 3135, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 2805647353], + [660, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639], + [664, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967], + [672, 5, 472, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [9/5]", 1289485551], + [702, 5, 346, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 2554883613], + [712, 5, 398, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 2789987624], + [723, 5, 413, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 1645012911], + [734, 5, 263, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 4224752268], + [753, 3, 385, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 293530329] + ], + "spec/oauth2/authenticator_spec.rb:853320290": [ + [3, 1, 36, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/authenticator*_spec.rb`.", 819808017], + [51, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 482779785], + [60, 15, 33, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 297534737], + [69, 15, 38, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1480816240], + [79, 13, 23, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2314399065] + ], + "spec/oauth2/client_spec.rb:4220405778": [ + [6, 1, 29, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/client*_spec.rb`.", 439549885], + [174, 7, 492, "RSpec/NoExpectationExample: No expectation found in this example.", 1272021224], + [193, 7, 592, "RSpec/NoExpectationExample: No expectation found in this example.", 3428877205], + [206, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2320605227], + [221, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1276531672], + [236, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1383956904], + [251, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3376202107], + [472, 7, 241, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1113144453], + [479, 7, 233, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 2616254065], + [585, 5, 360, "RSpec/NoExpectationExample: No expectation found in this example.", 536201463], + [594, 5, 461, "RSpec/NoExpectationExample: No expectation found in this example.", 3392600621], + [605, 5, 340, "RSpec/NoExpectationExample: No expectation found in this example.", 244592251], + [626, 5, 1711, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 821658737], + [638, 7, 564, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3188010848], + [645, 9, 314, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 2323166106], + [650, 63, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785], + [655, 7, 745, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 2242274228], + [658, 9, 379, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3157074309], + [668, 9, 266, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 165934392], + [679, 5, 2992, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3212702825], + [695, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886], + [699, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529], + [707, 7, 89, "RSpec/NoExpectationExample: No expectation found in this example.", 4609419], + [711, 7, 812, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3531056573], + [719, 9, 505, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 2126944993], + [735, 7, 571, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 2450549440], + [738, 9, 209, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 1769133328], + [746, 9, 262, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [8/5]", 165934392], + [756, 7, 275, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 4192619324], + [764, 7, 377, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1634937780], + [779, 5, 1920, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 3715188517], + [795, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886], + [799, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529], + [807, 7, 298, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 2420524519], + [816, 7, 474, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 2129407861], + [828, 7, 357, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [6/5]", 1696484657], + [879, 17, 12, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 664794325], + [904, 5, 459, "RSpec/NoExpectationExample: No expectation found in this example.", 2216851076], + [914, 7, 450, "RSpec/NoExpectationExample: No expectation found in this example.", 2619808549] + ], + "spec/oauth2/error_spec.rb:1209122273": [ + [23, 1, 28, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/error*_spec.rb`.", 3385870076], + [93, 11, 534, "RSpec/NoExpectationExample: No expectation found in this example.", 3347340910], + [109, 11, 210, "RSpec/NoExpectationExample: No expectation found in this example.", 3948582233], + [241, 11, 534, "RSpec/NoExpectationExample: No expectation found in this example.", 3347340910], + [257, 11, 210, "RSpec/NoExpectationExample: No expectation found in this example.", 3948582233], + [315, 11, 534, "RSpec/NoExpectationExample: No expectation found in this example.", 3347340910], + [376, 11, 534, "RSpec/NoExpectationExample: No expectation found in this example.", 3347340910], + [392, 11, 210, "RSpec/NoExpectationExample: No expectation found in this example.", 3948582233] + ], + "spec/oauth2/response_spec.rb:3742350944": [ + [3, 1, 31, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/response*_spec.rb`.", 3190869319], + [317, 33, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785] + ], + "spec/oauth2/strategy/assertion_spec.rb:1649395638": [ + [6, 1, 42, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/strategy/assertion*_spec.rb`.", 3665690869], + [39, 3, 8028, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3790653154], + [59, 5, 3399, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [10/5]", 1213098407], + [68, 7, 475, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [10/5]", 3673049530], + [83, 7, 511, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [10/5]", 1482428850], + [94, 9, 174, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [10/5]", 509043384], + [101, 7, 626, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [10/5]", 1073364157], + [112, 9, 276, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [12/5]", 3402508104], + [121, 7, 1463, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [11/5]", 631415582], + [124, 9, 431, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [11/5]", 1333000403], + [134, 9, 268, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [11/5]", 4208916299], + [142, 9, 312, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [11/5]", 4006695562], + [152, 9, 300, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [11/5]", 504386954], + [164, 5, 2485, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3985973933], + [165, 7, 1368, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 975431363], + [190, 7, 1057, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 2712213015], + [212, 5, 1639, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [9/5]", 325089515], + [217, 9, 1383, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [9/5]", 2493875547], + [246, 11, 260, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [9/5]", 3397767518], + [254, 11, 223, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [9/5]", 242220550] + ], + "spec/oauth2/strategy/auth_code_spec.rb:142083698": [ + [4, 1, 41, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/strategy/auth_code*_spec.rb`.", 1553708922], + [4, 1, 5753, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 833437399], + [48, 3, 919, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3083983110], + [75, 3, 522, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 1383502446], + [94, 3, 672, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3159970527], + [119, 3, 372, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 3139680688], + [131, 7, 986, "RSpec/MultipleMemoizedHelpers: Example group has too many memoized helpers [7/5]", 2685471594] + ], + "spec/oauth2/strategy/base_spec.rb:2524881749": [ + [3, 1, 37, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/strategy/base*_spec.rb`.", 1951594922] + ], + "spec/oauth2/strategy/client_credentials_spec.rb:2609739899": [ + [3, 1, 50, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/strategy/client_credentials*_spec.rb`.", 690311422] + ], + "spec/oauth2/strategy/implicit_spec.rb:1595799281": [ + [3, 1, 41, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/strategy/implicit*_spec.rb`.", 3731171632] + ], + "spec/oauth2/strategy/password_spec.rb:331601826": [ + [3, 1, 41, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/strategy/password*_spec.rb`.", 3463323840] + ], + "spec/oauth2/version_spec.rb:2895330438": [ + [3, 1, 30, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/version*_spec.rb`.", 1099517182] + ], + "spec/oauth2_spec.rb:1511642301": [ + [3, 1, 21, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2*_spec.rb`.", 3359091140], + [5, 68, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785] + ] +} diff --git a/.rubocop_rspec.yml b/.rubocop_rspec.yml index 347777dc..461083ca 100644 --- a/.rubocop_rspec.yml +++ b/.rubocop_rspec.yml @@ -1,6 +1,3 @@ -RSpec/FilePath: - Enabled: false - RSpec/MultipleExpectations: Enabled: false @@ -24,3 +21,8 @@ RSpec/NestedGroups: RSpec/ExpectInHook: Enabled: false + +RSpec/DescribeClass: + Exclude: + - 'spec/examples/*' + diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index ef809e2c..00000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,46 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2022-09-01 09:04:26 +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: 4 -Metrics/CyclomaticComplexity: - Max: 12 - -# Offense count: 11 -# Configuration parameters: CountComments, ExcludedMethods. -Metrics/MethodLength: - Max: 28 - -# 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' - -# Offense count: 1 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: inline, group -Style/AccessModifierDeclarations: - Exclude: - - 'lib/oauth2.rb' diff --git a/.simplecov b/.simplecov index 423aa578..bfe90c08 100644 --- a/.simplecov +++ b/.simplecov @@ -1,32 +1,3 @@ -# frozen_string_literal: true +require "kettle/soup/cover/config" -# 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 +SimpleCov.start diff --git a/CHANGELOG.md b/CHANGELOG.md index 265d6adc..cf322133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [2.0.10] - 2025-05-12 ([tag][2.0.10t]) ### Added -- [#635](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/635) - `.gitlab-ci.yml` file (@jessieay) +[!635](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/635) - `.gitlab-ci.yml` file (@jessieay) - 20 year certificate for signing gem releases, expires 2045-04-29 (@pboling) - Gemspec metadata (@pboling) - funding_uri @@ -21,13 +21,14 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. - SHA256 and SHA512 Checksums for release (@pboling) ### Changed - Gem releases are now cryptographically signed, with a 20-year cert (@pboling) + - Allow linux distros to build release without signing, as their package managers sign independently ### Fixed -- [#633](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/633) - Spaces will now be encoded as `%20` instead of `+` (@nov.matake) -- [#634](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/634) - `CHANGELOG.md` documentation fix (@skuwa229) -- [#638](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/638) - fix `expired?` when `expires_in` is `0` (@disep) -- [#639](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/639) - Only instantiate `OAuth2::Error` if `raise_errors` option is `true` (@glytch2) -- [#640](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/640) - `README.md` documentation fix (@martinezcoder) -- [#641](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/641) - Do not include sensitive information in the `inspect` (@manuelvanrijn) +[!633](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/633) - Spaces will now be encoded as `%20` instead of `+` (@nov.matake) +[!634](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/634) - `CHANGELOG.md` documentation fix (@skuwa229) +[!638](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/638) - fix `expired?` when `expires_in` is `0` (@disep) +[!639](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/639) - Only instantiate `OAuth2::Error` if `raise_errors` option is `true` (@glytch2) +[!640](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/640) - `README.md` documentation fix (@martinezcoder) +[!641](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/641) - Do not include sensitive information in the `inspect` (@manuelvanrijn) ## [2.0.9] - 2022-09-16 ([tag][2.0.9t]) ### Added @@ -44,20 +45,20 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [2.0.7] - 2022-08-22 ([tag][2.0.7t]) ### Added -- [#629](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/629) - Allow POST of JSON to get token (@pboling, @terracatta) +[!629](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/629) - Allow POST of JSON to get token (@pboling, @terracatta) ### Fixed -- [#626](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/626) - Fixes a regression in 2.0.6. Will now prefer the key order from the lookup, not the hash keys (@rickselby) +[!626](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/625) - Fixes the printed version in the post install message (@hasghari) +[!625](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/625) - Fixes the printed version in the post install message (@hasghari) ## [2.0.6] - 2022-07-13 ([tag][2.0.6t]) ### Fixed -- [#624](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/624) - Fixes a [regression](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/623) in v2.0.5, where an error would be raised in refresh_token flows due to (legitimate) lack of access_token (@pboling) +[!624](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/624) - Fixes a [regression](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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 ([tag][2.0.5t]) ### Fixed -- [#620](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/620) - Documentation improvements, to help with upgrading (@swanson) -- [#621](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/621) - Fixed [#528](https://gitlab.com/oauth-xx/oauth2/-/issues/528) and [#619](https://gitlab.com/oauth-xx/oauth2/-/issues/619) (@pboling) +[!620](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/620) - Documentation improvements, to help with upgrading (@swanson) +[!621](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/621) - Fixed [#528](https://gitlab.com/oauth-xx/oauth2/-/issues/528) and [#619](https://gitlab.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 @@ -66,21 +67,21 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [2.0.4] - 2022-07-01 ([tag][2.0.4t]) ### Fixed -- [#618](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/618) - In some scenarios the `snaky` option default value was not applied (@pboling) +[!618](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/618) - In some scenarios the `snaky` option default value was not applied (@pboling) ## [2.0.3] - 2022-06-28 ([tag][2.0.3t]) ### Added -- [#611](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/611) - Proper deprecation warnings for `extract_access_token` argument (@pboling) -- [#612](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/612) - Add `snaky: false` option to skip conversion to `OAuth2::SnakyHash` (default: true) (@pboling) +[!611](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/611) - Proper deprecation warnings for `extract_access_token` argument (@pboling) +[!612](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/612) - Add `snaky: false` option to skip conversion to `OAuth2::SnakyHash` (default: true) (@pboling) ### Fixed -- [#608](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/608) - Wrap `Faraday::TimeoutError` in `OAuth2::TimeoutError` (@nbibler) -- [#615](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/615) - Fix support for requests with blocks, see `Faraday::Connection#run_request` (@pboling) +[!608](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/608) - Wrap `Faraday::TimeoutError` in `OAuth2::TimeoutError` (@nbibler) +[!615](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/615) - Fix support for requests with blocks, see `Faraday::Connection#run_request` (@pboling) ## [2.0.2] - 2022-06-24 ([tag][2.0.2t]) ### Fixed -- [#604](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/604) - Wrap `Faraday::TimeoutError` in `OAuth2::TimeoutError` (@stanhu) -- [#606](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/606) - Ruby 2.7 deprecation warning fix: Move `access_token_class` parameter into `Client` constructor (@stanhu) -- [#607](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/607) - CHANGELOG correction, reference to `OAuth2::ConnectionError` (@zavan) +[!604](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/604) - Wrap `Faraday::TimeoutError` in `OAuth2::TimeoutError` (@stanhu) +[!606](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/606) - Ruby 2.7 deprecation warning fix: Move `access_token_class` parameter into `Client` constructor (@stanhu) +[!607](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/607) - CHANGELOG correction, reference to `OAuth2::ConnectionError` (@zavan) ## [2.0.1] - 2022-06-22 ([tag][2.0.1t]) ### Added @@ -89,81 +90,81 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [2.0.0] - 2022-06-21 ([tag][2.0.0t]) ### Added -- [#158](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/158), [#344](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/344) - Optionally pass raw response to parsers (@niels) -- [#190](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/190), [#332](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/332), [#334](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/334), [#335](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/335), [#360](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/360), [#426](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/426), [#427](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/427), [#461](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/461) - Documentation (@josephpage, @pboling, @meganemura, @joshRpowell, @elliotcm) -- [#220](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/220) - Support IETF rfc7523 JWT Bearer Tokens Draft 04+ (@jhmoore) -- [#298](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/298) - Set the response object on the access token on Client#get_token for debugging (@cpetschnig) -- [#305](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/305) - Option: `OAuth2::Client#get_token` - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token` (@styd) -- [#346](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/571) - Modern gem structure (@pboling) -- [#351](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/351) - Support Jruby 9k (@pboling) -- [#362](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/362) - Support SemVer release version scheme (@pboling) -- [#363](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/363) - New method `OAuth2::AccessToken#refresh!` same as old `refresh`, with backwards compatibility alias (@pboling) -- [#364](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/364) - Support `application/hal+json` format (@pboling) -- [#365](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/365) - Support `application/vnd.collection+json` format (@pboling) -- [#376](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/376) - _Documentation_: Example / Test for Google 2-legged JWT (@jhmoore) -- [#381](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/381) - Spec for extra header params on client credentials (@nikz) -- [#394](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/394) - Option: `OAuth2::AccessToken#initialize` - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency (@klippx) -- [#412](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/412) - Support `application/vdn.api+json` format (from jsonapi.org) (@david-christensen) -- [#413](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/413) - _Documentation_: License scan and report (@meganemura) -- [#442](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/549) - Wrap `Faraday::ConnectionFailed` in `OAuth2::ConnectionError` (@nikkypx) -- [#550](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/550) - Raise error if location header not present when redirecting (@stanhu) -- [#552](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/552) - Add missing `version.rb` require (@ahorek) -- [#553](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/553) - Support `application/problem+json` format (@janz93) -- [#560](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/560) - Support IETF rfc6749, section 2.3.1 - don't set auth params when `nil` (@bouk) -- [#571](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/571) - Support Ruby 3.1 (@pboling) -- [#575](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/575) - Support IETF rfc7231, section 7.1.2 - relative location in redirect (@pboling) -- [#581](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/581) - _Documentation_: of breaking changes (@pboling) +[!158](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/158), [!344](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/344) - Optionally pass raw response to parsers (@niels) +[!190](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/190), [!332](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/332), [!334](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/334), [!335](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/335), [!360](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/360), [!426](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/426), [!427](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/427), [!461](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/461) - Documentation (@josephpage, @pboling, @meganemura, @joshRpowell, @elliotcm) +[!220](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/220) - Support IETF rfc7523 JWT Bearer Tokens Draft 04+ (@jhmoore) +[!298](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/298) - Set the response object on the access token on Client#get_token for debugging (@cpetschnig) +[!305](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/305) - Option: `OAuth2::Client#get_token` - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token` (@styd) +[!346](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/571) - Modern gem structure (@pboling) +[!351](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/351) - Support Jruby 9k (@pboling) +[!362](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/362) - Support SemVer release version scheme (@pboling) +[!363](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/363) - New method `OAuth2::AccessToken#refresh!` same as old `refresh`, with backwards compatibility alias (@pboling) +[!364](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/364) - Support `application/hal+json` format (@pboling) +[!365](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/365) - Support `application/vnd.collection+json` format (@pboling) +[!376](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/376) - _Documentation_: Example / Test for Google 2-legged JWT (@jhmoore) +[!381](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/381) - Spec for extra header params on client credentials (@nikz) +[!394](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/394) - Option: `OAuth2::AccessToken#initialize` - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency (@klippx) +[!412](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/412) - Support `application/vdn.api+json` format (from jsonapi.org) (@david-christensen) +[!413](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/413) - _Documentation_: License scan and report (@meganemura) +[!442](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/549) - Wrap `Faraday::ConnectionFailed` in `OAuth2::ConnectionError` (@nikkypx) +[!550](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/550) - Raise error if location header not present when redirecting (@stanhu) +[!552](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/552) - Add missing `version.rb` require (@ahorek) +[!553](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/553) - Support `application/problem+json` format (@janz93) +[!560](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/560) - Support IETF rfc6749, section 2.3.1 - don't set auth params when `nil` (@bouk) +[!571](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/571) - Support Ruby 3.1 (@pboling) +[!575](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/575) - Support IETF rfc7231, section 7.1.2 - relative location in redirect (@pboling) +[!581](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/581) - _Documentation_: of breaking changes (@pboling) ### Changed -- [#191](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/191) - **BREAKING**: Token is expired if `expired_at` time is `now` (@davestevens) -- [#312](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/317) - _Dependency_: Upgrade `jwt` to 2.x.x (@travisofthenorth) -- [#338](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/338) - _Dependency_: Switch from `Rack::Utils.escape` to `CGI.escape` (@josephpage) -- [#339](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/339), [#368](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/368), [#424](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/424), [#479](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/479), [#493](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/493), [#539](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/539), [#542](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/542), [#553](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/553) - CI Updates, code coverage, linting, spelling, type fixes, New VERSION constant (@pboling, @josephpage, @ahorek) -- [#410](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/410) - **BREAKING**: Removed the ability to call .error from an OAuth2::Response object (@jhmoore) -- [#414](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/414) - Use Base64.strict_encode64 instead of custom internal logic (@meganemura) -- [#469](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/469) - **BREAKING**: Default value for option `OAuth2::Client` - `:authorize_url` removed leading slash to work with relative paths by default (`'oauth/authorize'`) (@ghost) -- [#469](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/469) - **BREAKING**: Default value for option `OAuth2::Client` - `:token_url` removed leading slash to work with relative paths by default (`'oauth/token'`) (@ghost) -- [#507](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/507), [#575](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/575) - **BREAKING**: Transform keys to snake case, always, by default (ultimately via `rash_alt` gem) +[!191](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/191) - **BREAKING**: Token is expired if `expired_at` time is `now` (@davestevens) +[!312](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/317) - _Dependency_: Upgrade `jwt` to 2.x.x (@travisofthenorth) +[!338](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/338) - _Dependency_: Switch from `Rack::Utils.escape` to `CGI.escape` (@josephpage) +[!339](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/339), [!368](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/368), [!424](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/424), [!479](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/479), [!493](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/493), [!539](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/539), [!542](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/542), [!553](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/553) - CI Updates, code coverage, linting, spelling, type fixes, New VERSION constant (@pboling, @josephpage, @ahorek) +[!410](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/410) - **BREAKING**: Removed the ability to call .error from an OAuth2::Response object (@jhmoore) +[!414](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/414) - Use Base64.strict_encode64 instead of custom internal logic (@meganemura) +[!469](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/469) - **BREAKING**: Default value for option `OAuth2::Client` - `:authorize_url` removed leading slash to work with relative paths by default (`'oauth/authorize'`) (@ghost) +[!469](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/469) - **BREAKING**: Default value for option `OAuth2::Client` - `:token_url` removed leading slash to work with relative paths by default (`'oauth/token'`) (@ghost) +[!507](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/507), [!575](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/575) - **BREAKING**: Transform keys to snake 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 snake case. - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option. -- [#576](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/576) - **BREAKING**: Stop rescuing parsing errors (@pboling) -- [#591](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/576) - _DEPRECATION_: `OAuth2::Client` - `:extract_access_token` option is deprecated +[!576](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/576) - **BREAKING**: Stop rescuing parsing errors (@pboling) +[!591](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/576) - _DEPRECATION_: `OAuth2::Client` - `:extract_access_token` option is deprecated ### Fixed -- [#158](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/158), [#344](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/344) - Handling of errors when using `omniauth-facebook` (@niels) -- [#294](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/294) - Fix: "Unexpected middleware set" issue with Faraday when `OAUTH_DEBUG=true` (@spectator, @gafrom) -- [#300](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/300) - _Documentation_: `Oauth2::Error` - Error codes are strings, not symbols (@NobodysNightmare) -- [#318](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/318), [#326](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/326), [#343](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/343), [#347](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/347), [#397](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/397), [#464](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/464), [#561](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/561), [#565](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/322), [#331](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/331), [#337](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/337), [#361](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/361), [#371](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/371), [#377](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/377), [#383](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/383), [#392](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/392), [#395](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/395), [#400](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/400), [#401](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/401), [#403](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/403), [#415](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/415), [#567](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/567) - Updated Rubocop, Rubocop plugins and improved code style (@pboling, @bquorning, @lautis, @spectator) -- [#328](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/328) - _Documentation_: Homepage URL is SSL (@amatsuda) -- [#339](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/339), [#479](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/479) - Update testing infrastructure for all supported Rubies (@pboling and @josephpage) -- [#366](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/366) - **Security**: Fix logging to `$stdout` of request and response bodies via Faraday's logger and `ENV["OAUTH_DEBUG"] == 'true'` (@pboling) -- [#380](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/380) - Fix: Stop attempting to encode non-encodable objects in `Oauth2::Error` (@jhmoore) -- [#399](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/399) - Fix: Stop duplicating `redirect_uri` in `get_token` (@markus) -- [#410](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/410) - Fix: `SystemStackError` caused by circular reference between Error and Response classes (@jhmoore) -- [#460](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/460) - Fix: Stop throwing errors when `raise_errors` is set to `false`; analog of [#524](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/524) for `1-4-stable` branch (@joaolrpaulo) -- [#472](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/482) - _Documentation_: Update last of `intridea` links to `oauth-xx` (@pboling) -- [#536](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/536) - **Security**: Compatibility with more (and recent) Ruby OpenSSL versions, Github Actions, Rubocop updated, analogous to [#535](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/535) on `1-4-stable` branch (@pboling) -- [#595](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/595) - Graceful handling of empty responses from `Client#get_token`, respecting `:raise_errors` config (@stanhu) -- [#596](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/596) - Consistency between `AccessToken#refresh` and `Client#get_token` named arguments (@stanhu) -- [#598](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/598) - Fix unparseable data not raised as error in `Client#get_token`, respecting `:raise_errors` config (@stanhu) +[!158](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/158), [!344](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/344) - Handling of errors when using `omniauth-facebook` (@niels) +[!294](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/294) - Fix: "Unexpected middleware set" issue with Faraday when `OAUTH_DEBUG=true` (@spectator, @gafrom) +[!300](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/300) - _Documentation_: `Oauth2::Error` - Error codes are strings, not symbols (@NobodysNightmare) +[!318](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/318), [!326](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/326), [!343](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/343), [!347](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/347), [!397](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/397), [!464](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/464), [!561](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/561), [!565](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/322), [!331](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/331), [!337](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/337), [!361](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/361), [!371](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/371), [!377](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/377), [!383](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/383), [!392](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/392), [!395](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/395), [!400](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/400), [!401](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/401), [!403](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/403), [!415](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/415), [!567](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/567) - Updated Rubocop, Rubocop plugins and improved code style (@pboling, @bquorning, @lautis, @spectator) +[!328](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/328) - _Documentation_: Homepage URL is SSL (@amatsuda) +[!339](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/339), [!479](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/479) - Update testing infrastructure for all supported Rubies (@pboling and @josephpage) +[!366](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/366) - **Security**: Fix logging to `$stdout` of request and response bodies via Faraday's logger and `ENV["OAUTH_DEBUG"] == 'true'` (@pboling) +[!380](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/380) - Fix: Stop attempting to encode non-encodable objects in `Oauth2::Error` (@jhmoore) +[!399](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/399) - Fix: Stop duplicating `redirect_uri` in `get_token` (@markus) +[!410](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/410) - Fix: `SystemStackError` caused by circular reference between Error and Response classes (@jhmoore) +[!460](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/460) - Fix: Stop throwing errors when `raise_errors` is set to `false`; analog of [!524](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/524) for `1-4-stable` branch (@joaolrpaulo) +[!472](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/482) - _Documentation_: Update last of `intridea` links to `oauth-xx` (@pboling) +[!536](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/536) - **Security**: Compatibility with more (and recent) Ruby OpenSSL versions, Github Actions, Rubocop updated, analogous to [!535](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/535) on `1-4-stable` branch (@pboling) +[!595](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/595) - Graceful handling of empty responses from `Client#get_token`, respecting `:raise_errors` config (@stanhu) +[!596](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/596) - Consistency between `AccessToken#refresh` and `Client#get_token` named arguments (@stanhu) +[!598](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/598) - Fix unparseable data not raised as error in `Client#get_token`, respecting `:raise_errors` config (@stanhu) ### Removed -- [#341](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/341) - Remove Rdoc & Jeweler related files (@josephpage) -- [#342](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/342) - **BREAKING**: Dropped support for Ruby 1.8 (@josephpage) -- [#539](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/539) - Remove reliance on globally included OAuth2 in tests, analog of [#538](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/538) for 1-4-stable (@anderscarling) -- [#566](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/566) - _Dependency_: Removed `wwtd` (@bquorning) -- [#589](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/589), [#593](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/593) - Remove support for expired MAC token draft spec (@stanhu) -- [#590](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/590) - _Dependency_: Removed `multi_json` (@stanhu) +[!341](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/341) - Remove Rdoc & Jeweler related files (@josephpage) +[!342](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/342) - **BREAKING**: Dropped support for Ruby 1.8 (@josephpage) +[!539](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/539) - Remove reliance on globally included OAuth2 in tests, analog of [!538](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/538) for 1-4-stable (@anderscarling) +[!566](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/566) - _Dependency_: Removed `wwtd` (@bquorning) +[!589](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/589), [!593](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/593) - Remove support for expired MAC token draft spec (@stanhu) +[!590](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/590) - _Dependency_: Removed `multi_json` (@stanhu) ## [1.4.11] - 2022-09-16 ([tag][1.4.11t]) - Complete migration to main branch as default (@pboling) - Complete migration to Gitlab, updating all links, and references in VCS-managed files (@pboling) ## [1.4.10] - 2022-07-01 ([tag][1.4.10t]) -- FIPS Compatibility [#587](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/587) (@akostadinov) +- FIPS Compatibility [!587](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/587) (@akostadinov) ## [1.4.9] - 2022-02-20 ([tag][1.4.9t]) - Fixes compatibility with Faraday v2 [572](https://gitlab.com/oauth-xx/oauth2/-/issues/572) @@ -176,47 +177,47 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [1.4.8] - 2022-02-18 ([tag][1.4.8t]) - MFA is now required to push new gem versions (@pboling) - README overhaul w/ new Ruby Version and Engine compatibility policies (@pboling) -- [#569](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/569) Backport fixes ([#561](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/561) by @ryogift), and add more fixes, to allow faraday 1.x and 2.x (@jrochkind) +[!569](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/569) Backport fixes ([!561](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/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://gitlab.com/oauth-xx/oauth2/-/merge_requests/543) - Support for more modern Open SSL libraries (@pboling) +[!543](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/543) - Support for more modern Open SSL libraries (@pboling) ## [1.4.7] - 2021-03-19 ([tag][1.4.7t]) -- [#541](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/541) - Backport fix to expires_at handling [#533](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/533) to 1-4-stable branch. (@dobon) +[!541](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/541) - Backport fix to expires_at handling [!533](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/533) to 1-4-stable branch. (@dobon) ## [1.4.6] - 2021-03-19 ([tag][1.4.6t]) -- [#540](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/540) - Add VERSION constant (@pboling) -- [#537](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/537) - Fix crash in OAuth2::Client#get_token (@anderscarling) -- [#538](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/538) - Remove reliance on globally included OAuth2 in tests, analogous to [#539](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/539) on main branch (@anderscarling) +[!540](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/540) - Add VERSION constant (@pboling) +[!537](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/537) - Fix crash in OAuth2::Client#get_token (@anderscarling) +[!538](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/538) - Remove reliance on globally included OAuth2 in tests, analogous to [!539](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/539) on main branch (@anderscarling) ## [1.4.5] - 2021-03-18 ([tag][1.4.5t]) -- [#535](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/535) - Compatibility with range of supported Ruby OpenSSL versions, Rubocop updates, Github Actions, analogous to [#536](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/536) on main branch (@pboling) -- [#518](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/518) - Add extract_access_token option to OAuth2::Client (@jonspalmer) -- [#507](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/507) - Fix camel case content type, response keys (@anvox) -- [#500](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/500) - Fix YARD documentation formatting (@olleolleolle) +[!535](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/535) - Compatibility with range of supported Ruby OpenSSL versions, Rubocop updates, Github Actions, analogous to [!536](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/536) on main branch (@pboling) +[!518](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/518) - Add extract_access_token option to OAuth2::Client (@jonspalmer) +[!507](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/507) - Fix camel case content type, response keys (@anvox) +[!500](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/500) - Fix YARD documentation formatting (@olleolleolle) ## [1.4.4] - 2020-02-12 ([tag][1.4.4t]) -- [#408](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/408) - Fixed expires_at for formatted time (@Lomey) +[!408](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/408) - Fixed expires_at for formatted time (@Lomey) ## [1.4.3] - 2020-01-29 ([tag][1.4.3t]) -- [#483](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/483) - add project metadata to gemspec (@orien) -- [#495](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/495) - support additional types of access token requests (@SteveyblamFreeagent, @thomcorley, @dgholz) +[!483](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/483) - add project metadata to gemspec (@orien) +[!495](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/495) - support additional types of access token requests (@SteveyblamFreeagent, @thomcorley, @dgholz) - Adds support for private_key_jwt and tls_client_auth -- [#433](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/433) - allow field names with square brackets and numbers in params (@asm256) +[!433](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/433) - allow field names with square brackets and numbers in params (@asm256) ## [1.4.2] - 2019-10-01 ([tag][1.4.2t]) -- [#478](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/478) - support latest version of faraday & fix build (@pboling) +[!478](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/478) - support latest version of faraday & fix build (@pboling) - Officially support Ruby 2.6 and truffleruby ## [1.4.1] - 2018-10-13 ([tag][1.4.1t]) -- [#417](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/417) - update jwt dependency (@thewoolleyman) -- [#419](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/419) - remove rubocop dependency (temporary, added back in [#423](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/423)) (@pboling) -- [#418](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/418) - update faraday dependency (@pboling) -- [#420](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/420) - update [oauth2.gemspec](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/oauth2.gemspec) (@pboling) -- [#421](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/421) - fix [CHANGELOG.md](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/CHANGELOG.md) for previous releases (@pboling) -- [#422](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/422) - update [LICENSE](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/LICENSE) and [README.md](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/README.md) (@pboling) -- [#423](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/423) - update [builds](https://travis-ci.org/oauth-xx/oauth2/builds), [Rakefile](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/Rakefile) (@pboling) +[!417](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/417) - update jwt dependency (@thewoolleyman) +[!419](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/419) - remove rubocop dependency (temporary, added back in [!423](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/423)) (@pboling) +[!418](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/418) - update faraday dependency (@pboling) +[!420](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/420) - update [oauth2.gemspec](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/oauth2.gemspec) (@pboling) +[!421](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/421) - fix [CHANGELOG.md](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/CHANGELOG.md) for previous releases (@pboling) +[!422](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/422) - update [LICENSE](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/LICENSE) and [README.md](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/README.md) (@pboling) +[!423](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/423) - update [builds](https://travis-ci.org/oauth-xx/oauth2/builds), [Rakefile](https://gitlab.com/oauth-xx/oauth2/-/blob/1-4-stable/Rakefile) (@pboling) - officially document supported Rubies * Ruby 1.9.3 * Ruby 2.0.0 @@ -308,7 +309,7 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2. ## [0.0.5] - 2010-04-23 ([tag][0.0.5t]) ## [0.0.4] - 2010-04-22 ([tag][0.0.4t]) - + ## [0.0.3] - 2010-04-22 ([tag][0.0.3t]) ## [0.0.2] - 2010-04-22 ([tag][0.0.2t]) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b81c846..b4401666 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,38 +2,138 @@ Bug reports and pull requests are welcome on GitLab at [https://gitlab.com/oauth-xx/oauth2][🚎src-main] . 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 and post a message to the -[google group][mailinglist] or on the [gitter chat][🏘chat]. - -## 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/ +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. + +We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it. + +## You can help! + +Take a look at the `reek` list which is the file called `REEK` and find something to improve. + +Simply follow these instructions: + +1. Fork the repository +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Make some fixes. +4. Commit your changes (`git commit -am 'Added some feature'`) +5. Push to the branch (`git push origin my-new-feature`) +6. Make sure to add tests for it. This is important, so it doesn't break in a future release. +7. Create new Pull Request. + +## Appraisals + +From time to time the appraisal gemfiles in `gemfiles/` will need to be updated. +They are created and updated with the commands: + +NOTE: We run on a [fork][🚎appraisal-fork] of Appraisal. + +Please upvote the PR for `eval_gemfile` [support][🚎appraisal-eval-gemfile-pr] + +```shell +BUNDLE_GEMFILE=Appraisal.root.gemfile bundle +BUNDLE_GEMFILE=Appraisal.root.gemfile bundle exec appraisal update +bundle exec rake rubocop_gradual:autocorrect +``` + +When adding an appraisal to CI check the [runner tool cache][🏃‍♂️runner-tool-cache] to see which runner to use. + +## The Reek List + +Take a look at the `reek` list which is the file called `REEK` and find something to improve. + +To refresh the `reek` list: + +```bash +bundle exec reek > REEK +``` + +## Run Tests + +To run all tests + +```bash +bundle exec rake test +``` + +## Lint It + +Run all the default tasks, which includes running the gradually autocorrecting linter, `rubocop-gradual`. + +```bash +bundle exec rake +``` + +Or just run the linter. + +```bash +bundle exec rake rubocop_gradual:autocorrect +``` ## Contributors -See: [https://gitlab.com/oauth-xx/oauth2/-/graphs/main][🚎contributors] +Your picture could be here! + +[![Contributors][🖐contributors-img]][🖐contributors] + +Made with [contributors-img][🖐contrib-rocks]. + +Also see GitLab Contributors: [https://gitlab.com/oauth-xx/oauth2/-/graphs/main][🚎contributors-gl] + +## For Maintainers + +### One-time, Per-maintainer, Setup + +**IMPORTANT**: If you want to sign the build you create, +your public key for signing gems will need to be picked up by the line in the +`gemspec` defining the `spec.cert_chain` (check the relevant ENV variables there). +All releases to RubyGems.org will be signed. +See: [RubyGems Security Guide][🔒️rubygems-security-guide] + +NOTE: To build without signing the gem you must set `SKIP_GEM_SIGNING` to some value in your environment. + +### To release a new version: + +1. Run `bin/setup && bin/rake` as a tests, coverage, & linting sanity check +2. Update the version number in `version.rb`, and ensure `CHANGELOG.md` reflects changes +3. Run `bin/setup && bin/rake` again as a secondary check, and to update `Gemfile.lock` +4. Run `git commit -am "🔖 Prepare release v"` to commit the changes +5. Run `git push` to trigger the final CI pipeline before release, & merge PRs + - NOTE: Remember to [check the build][🧪build]! +6. Run `export GIT_TRUNK_BRANCH_NAME="$(git remote show origin | grep 'HEAD branch' | cut -d ' ' -f5)" && echo $GIT_TRUNK_BRANCH_NAME` +7. Run `git checkout $GIT_TRUNK_BRANCH_NAME` +8. Run `git pull origin $GIT_TRUNK_BRANCH_NAME` to ensure you will release the latest trunk code +9. Set `SOURCE_DATE_EPOCH` so `rake build` and `rake release` use same timestamp, and generate same checksums + - Run `export SOURCE_DATE_EPOCH=$EPOCHSECONDS && echo $SOURCE_DATE_EPOCH` + - If the echo above has no output, then it didn't work. + - Note that you'll need the `zsh/datetime` module, if running `zsh`. + - In older versions of `bash` you can use `date +%s` instead, i.e. `export SOURCE_DATE_EPOCH=$(date +%s) && echo $SOURCE_DATE_EPOCH` +10. Run `bundle exec rake build` +11. Run `bin/gem_checksums` (more context [1][🔒️rubygems-checksums-pr], [2][🔒️rubygems-guides-pr]) + to create SHA-256 and SHA-512 checksums. This functionality is provided by the `stone_checksums` + [gem][💎stone_checksums]. + - Checksums will be committed automatically by the script, but not pushed +12. 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][💎rubygems] -[comment]: <> (Following links are used by README, CONTRIBUTING, Homepage) -[conduct]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CODE_OF_CONDUCT.md -[🚎contributors]: https://gitlab.com/oauth-xx/oauth2/-/graphs/main -[mailinglist]: http://groups.google.com/group/oauth-ruby -[🚎src-main]: https://gitlab.com/oauth-xx/oauth2/-/tree/main -[🏘chat]: https://gitter.im/oauth-xx/oauth2 \ No newline at end of file +[🚎src-main]: https://gitlab.com/oauth-xx/oauth2 +[🧪build]: https://github.com/oauth-xx/oauth2/actions +[🤝conduct]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CODE_OF_CONDUCT.md +[🖐contrib-rocks]: https://contrib.rocks +[🖐contributors]: https://github.com/oauth-xx/oauth2/graphs/contributors +[🚎contributors-gl]: https://gitlab.com/oauth-xx/oauth2/-/graphs/main +[🖐contributors-img]: https://contrib.rocks/image?repo=oauth-xx/oauth2 +[💎rubygems]: https://rubygems.org +[🔒️rubygems-security-guide]: https://guides.rubygems.org/security/#building-gems +[🔒️rubygems-checksums-pr]: https://github.com/rubygems/rubygems/pull/6022 +[🔒️rubygems-guides-pr]: https://github.com/rubygems/guides/pull/325 +[💎stone_checksums]: https://github.com/pboling/stone_checksums +[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ +[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat +[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 +[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html +[🚎appraisal-eval-gemfile-pr]: https://github.com/thoughtbot/appraisal/pull/248 +[🚎appraisal-fork]: https://github.com/pboling/appraisal/tree/galtzo +[🏃‍♂️runner-tool-cache]: https://github.com/ruby/ruby-builder/releases/tag/toolcache diff --git a/Dangerfile b/Dangerfile index 2f8600bb..a01a246d 100644 --- a/Dangerfile +++ b/Dangerfile @@ -5,11 +5,11 @@ # 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("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 +warn("Big PR") if git.lines_of_code > 500 # Don't let testing shortcuts get into main 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 +raise("fdescribe left in tests") if %x(grep -r fdescribe specs/).length > 1 +raise("fit left in tests") if %x(grep -r fit specs/).length > 1 diff --git a/Gemfile b/Gemfile index 96cff377..b031a325 100644 --- a/Gemfile +++ b/Gemfile @@ -1,58 +1,31 @@ # frozen_string_literal: true -source 'https://rubygems.org' - -gemspec +source "https://rubygems.org" git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } git_source(:gitlab) { |repo_name| "https://gitlab.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' - # Commented out rubocop-md because of the <--rubocop/md--> bug - # 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 +#### IMPORTANT ####################################################### +# Gemfile is for local development ONLY; Gemfile is NOT loaded in CI # +####################################################### IMPORTANT #### + +# Include dependencies from .gemspec +gemspec -### deps for documentation and rdoc.info -group :documentation do - gem 'github-markup', platform: :mri - gem 'redcarpet', platform: :mri - gem 'yard', require: false +platform :mri do + # Use binding.break, binding.b, or debugger in code + gem "debug", ">= 1.0.0" # ruby >= 2.7 + gem "gem_bench", "~> 2.0", ">= 2.0.5" end + +# Security Audit +eval_gemfile "gemfiles/modular/audit.gemfile" + +# Code Coverage +eval_gemfile "gemfiles/modular/coverage.gemfile" + +# Linting +eval_gemfile "gemfiles/modular/style.gemfile" + +# Documentation +eval_gemfile "gemfiles/modular/documentation.gemfile" diff --git a/Gemfile.lock b/Gemfile.lock index 14452141..6ca4debd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,15 @@ +GIT + remote: https://github.com/pboling/yard-junk + revision: 54ccebabbfa9a9cd44d0b991687ebbfd22c32b55 + branch: next + specs: + yard-junk (0.0.10) + backports (>= 3.18) + benchmark + ostruct + rainbow + yard + PATH remote: . specs: @@ -7,144 +19,280 @@ PATH multi_xml (~> 0.5) rack (>= 1.2, < 4) snaky_hash (~> 2.0) - version_gem (~> 1.1) + version_gem (>= 1.1.8, < 3) GEM remote: https://rubygems.org/ specs: - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - ast (2.4.2) - backports (3.23.0) - byebug (11.1.3) - childprocess (4.1.0) - codecov (0.6.0) - simplecov (>= 0.15, < 0.22) - diff-lcs (1.5.0) - docile (1.4.0) - faraday (2.5.2) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.0) - github-markup (4.0.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + backports (3.25.1) + base64 (0.2.0) + benchmark (0.4.0) + bigdecimal (3.1.9) + bundler-audit (0.9.2) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) + concurrent-ruby (1.3.5) + date (3.4.1) + debug (1.10.0) + irb (~> 1.10) + reline (>= 0.3.8) + diff-lcs (1.6.2) + diffy (3.4.3) + docile (1.4.1) + dry-configurable (1.3.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-core (1.1.0) + concurrent-ruby (~> 1.0) + logger + zeitwerk (~> 2.6) + dry-inflector (1.2.0) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal + concurrent-ruby (~> 1.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-schema (1.14.1) + concurrent-ruby (~> 1.0) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) + zeitwerk (~> 2.6) + dry-types (1.8.2) + bigdecimal (~> 3.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + faraday (2.13.1) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.0) + net-http (>= 0.5.0) + gem_bench (2.0.5) + bundler (>= 1.14) + version_gem (~> 1.1, >= 1.1.4) hashie (5.0.0) - iniparse (1.5.0) - jaro_winkler (1.5.4) - json (2.6.2) - jwt (2.5.0) - multi_xml (0.6.0) - overcommit (0.59.1) - childprocess (>= 0.6.3, < 5) - iniparse (~> 1.4) - rexml (~> 3.2) - parallel (1.22.1) - parser (3.1.2.1) + io-console (0.8.0) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.12.0) + jwt (2.10.1) + base64 + kettle-soup-cover (1.0.6) + simplecov (~> 0.22) + simplecov-cobertura (~> 2.1) + simplecov-console (~> 0.9, >= 0.9.1) + simplecov-html (~> 0.12) + simplecov-lcov (~> 0.8) + simplecov-rcov (~> 0.3, >= 0.3.3) + simplecov_json_formatter (~> 0.1, >= 0.1.4) + version_gem (~> 1.1, >= 1.1.7) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + multi_xml (0.7.2) + bigdecimal (~> 3.1) + net-http (0.6.0) + uri + nkf (0.2.0) + ostruct (0.6.1) + parallel (1.27.0) + parser (3.3.8.0) ast (~> 2.4.1) - public_suffix (5.0.0) - rack (3.0.0) - rainbow (2.2.2) - rake - rake (13.0.6) - redcarpet (3.5.1) - rexml (3.2.5) - rspec (3.11.0) - rspec-core (~> 3.11.0) - rspec-expectations (~> 3.11.0) - rspec-mocks (~> 3.11.0) - rspec-block_is_expected (1.0.2) - rspec-core - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + racc (1.8.1) + rack (3.1.14) + rainbow (3.1.1) + rake (13.2.1) + rdoc (6.13.1) + psych (>= 4.0.0) + reek (6.5.0) + dry-schema (~> 1.13) + logger (~> 1.6) + parser (~> 3.3.0) + rainbow (>= 2.0, < 4.0) + rexml (~> 3.1) + regexp_parser (2.10.0) + reline (0.6.1) + io-console (~> 0.5) + rexml (3.4.1) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-block_is_expected (1.0.6) + rspec-core (3.13.3) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.4) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.4) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-pending_for (0.1.16) - rspec-core - ruby_engine (>= 1, < 3) + rspec-support (~> 3.13.0) + rspec-pending_for (0.1.17) + rake (>= 10) + rspec-core (~> 3.0) + ruby_engine (~> 2.0) ruby_version (~> 1.0) - rspec-stubbed_env (1.0.0) - rspec (>= 3.0) - rspec-support (3.11.1) - rubocop (0.68.1) - jaro_winkler (~> 1.5.1) + rspec-stubbed_env (1.0.2) + rspec-support (3.13.3) + rubocop (1.75.5) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.6) - rubocop-lts (8.0.2) - rubocop-ruby2_2 (~> 1.0.4) - rubocop-performance (1.3.0) - rubocop (>= 0.68.0) - rubocop-rake (0.5.1) - rubocop - rubocop-rspec (1.41.0) - rubocop (>= 0.68.1) - rubocop-ruby2_2 (1.0.4) - rubocop (= 0.68.1) - rubocop-thread_safety (0.4.4) - rubocop (>= 0.53.0) - ruby-progressbar (1.11.0) - ruby2_keywords (0.0.5) - ruby_engine (2.0.0) - ruby_version (1.0.2) - silent_stream (1.0.6) - simplecov (0.21.2) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.44.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-gradual (0.3.6) + diff-lcs (>= 1.2.0, < 2.0) + diffy (~> 3.0) + parallel (~> 1.10) + rainbow (>= 2.2.2, < 4.0) + rubocop (~> 1.0) + rubocop-lts (8.1.1) + rubocop-ruby2_2 (>= 2.0.3, < 3) + standard-rubocop-lts (>= 1.0.3, < 3) + version_gem (>= 1.1.2, < 3) + rubocop-md (1.2.4) + rubocop (>= 1.45) + rubocop-packaging (0.6.0) + lint_roller (~> 1.1.0) + rubocop (>= 1.72.1, < 2.0) + rubocop-performance (1.25.0) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rake (0.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + rubocop-rspec (3.6.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-ruby2_2 (2.0.5) + rubocop-gradual (~> 0.3, >= 0.3.1) + rubocop-md (~> 1.2) + rubocop-rake (~> 0.6) + rubocop-shopify (~> 2.14) + rubocop-thread_safety (~> 0.5, >= 0.5.1) + standard-rubocop-lts (~> 1.0, >= 1.0.7) + version_gem (>= 1.1.3, < 3) + rubocop-shopify (2.17.0) + rubocop (~> 1.62) + rubocop-thread_safety (0.7.2) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + ruby-progressbar (1.13.0) + ruby_engine (2.0.3) + ruby_version (1.0.3) + silent_stream (1.0.10) + logger (>= 1.4.4) + version_gem (~> 1.1, >= 1.1.7) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-cobertura (2.1.0) rexml simplecov (~> 0.19) - simplecov-html (0.12.3) - simplecov-json (0.2.3) - json + simplecov-console (0.9.3) + ansi simplecov + terminal-table + simplecov-html (0.13.1) simplecov-lcov (0.8.0) + simplecov-rcov (0.3.7) + simplecov (>= 0.4.1) simplecov_json_formatter (0.1.4) snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) - unicode-display_width (1.5.0) - version_gem (1.1.1) - webrick (1.7.0) - yard (0.9.28) - webrick (~> 1.7.0) + standard (1.50.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.75.5) + standard-custom (~> 1.0.0) + standard-performance (~> 1.8) + standard-custom (1.0.2) + lint_roller (~> 1.0) + rubocop (~> 1.50) + standard-performance (1.8.0) + lint_roller (~> 1.1) + rubocop-performance (~> 1.25.0) + standard-rubocop-lts (1.0.10) + rspec-block_is_expected (~> 1.0, >= 1.0.5) + standard (>= 1.35.1, < 2) + standard-custom (>= 1.0.2, < 2) + standard-performance (>= 1.3.1, < 2) + version_gem (>= 1.1.4, < 3) + stringio (3.1.7) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + thor (1.3.2) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.3) + version_gem (1.1.8) + yard (0.9.37) + zeitwerk (2.7.2) PLATFORMS x86_64-darwin-21 + x86_64-linux DEPENDENCIES addressable (>= 2) backports (>= 3) - bundler (>= 2) - byebug - codecov (~> 0.6) - github-markup + benchmark (~> 0.4) + bundler-audit (~> 0.9.2) + debug (>= 1.0.0) + gem_bench (~> 2.0, >= 2.0.5) + kettle-soup-cover (~> 1.0, >= 1.0.6) + nkf (~> 0.2) oauth2! - overcommit (~> 0.58) - pry-debugger-jruby - rake (~> 13.0) - redcarpet + rake (>= 12) + rdoc (~> 6.11) + reek (~> 6.4) rexml (>= 3) - rspec (~> 3.0) + rspec (>= 3) rspec-block_is_expected rspec-pending_for rspec-stubbed_env + rubocop (~> 1.73, >= 1.73.2) rubocop-lts (~> 8.0) - rubocop-performance - rubocop-rake - rubocop-rspec - rubocop-thread_safety + rubocop-packaging (~> 0.5, >= 0.5.2) + rubocop-rspec (~> 3.2) silent_stream - simplecov (~> 0.21) - simplecov-cobertura - simplecov-json - simplecov-lcov (~> 0.8) - yard + standard (~> 1.47) + yard (~> 0.9, >= 0.9.37) + yard-junk (~> 0.0, >= 0.0.10)! BUNDLED WITH - 2.3.22 + 2.6.8 diff --git a/README.md b/README.md index 473cdc47..831cd55f 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,43 @@

-## What +## OAuth2 + +[![Version][👽versioni]][👽version] +[![License: MIT][📄license-img]][📄license-ref] +[![Downloads Rank][👽dl-ranki]][👽dl-rank] +[![Open Source Helpers][👽oss-helpi]][👽oss-help] +[![Depfu][🔑depfui♻️]][🔑depfu] +[![CodeCov Test Coverage][🔑codecovi♻️]][🔑codecov] +[![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] +[![CodeClimate Test Coverage][🔑cc-covi♻️]][🔑cc-cov] +[![Maintainability][🔑cc-mnti♻️]][🔑cc-mnt] +[![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] +[![CI Current][🚎11-c-wfi]][🚎11-c-wf] +[![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] +[![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] +[![CI Supported][🚎6-s-wfi]][🚎6-s-wf] +[![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] +[![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] +[![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] +[![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] +[![CI Style][🚎5-st-wfi]][🚎5-st-wf] + +--- + +[![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] +[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] +[![Buy me a coffee][🖇buyme-small-img]][🖇buyme] +[![Donate on Polar][🖇polar-img]][🖇polar] +[![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] +[![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon] 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 (not servers) in Ruby applications. ---- +## 💡 Info you can shake a stick at * [OAuth 2.0 Spec][oauth2-spec] * [doorkeeper gem][doorkeeper-gem] for OAuth 2.0 server/provider implementation. @@ -24,28 +53,21 @@ This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby appli [sibling-gem]: https://gitlab.com/oauth-xx/oauth [doorkeeper-gem]: https://github.com/doorkeeper-gem/doorkeeper -If this library has helped you, or your organization, -please support my efforts by making a donation, becoming a sponsor, or giving me a shout on Mastodon. - -[![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] -[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] - - -Buy me coffee donation button - - -Patreon donate button - - - - - -[⛳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_Me!-pboling.svg?style=social&logo=github -[🖇sponsor]: https://github.com/sponsors/pboling - -## Release Documentation +| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] | +|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Works with JRuby | [![JRuby 9.1 Compat][💎jruby-9.1i]][🚎10-j-wf] [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎10-j-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎10-j-wf] [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] | +| Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎9-t-wf] [![Truffle Ruby 23.0 Compat][💎truby-23.0i]][🚎9-t-wf] [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] [![Truffle Ruby HEAD Compat][💎truby-headi]][🚎3-hd-wf] | +| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] | +| Works with MRI Ruby 2 | [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] | +| Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] | +| Documentation | [![Discussion][⛳gg-discussions-img]][⛳gg-discussions] [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![HEAD on RubyDoc.info][📜docs-head-rd-img]][🚎yard-head] [![BDFL Blog][🚂bdfl-blog-img]][🚂bdfl-blog] [![Wiki][📜wiki-img]][📜wiki] | +| Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Enforced Code Style][💎rlts-img]][💎rlts] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![FOSSA][🏘fossa-img]][🏘fossa] | +| Expert 1:1 Support | [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] `or` [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] | +| Enterprise Support | [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
💡Subscribe for support guarantees covering _all_ FLOSS dependencies!
💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]!
💡Tidelift pays maintainers to maintain the software you depend on!
📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers! | +| Comrade BDFL 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact BDFL][🚂bdfl-contact-img]][🚂bdfl-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] | +| `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] | + +## 🚀 Release Documentation ### Version 2.0.x @@ -108,136 +130,7 @@ please support my efforts by making a donation, becoming a sponsor, or giving me | < 1.0.0 | Find here | https://gitlab.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] [![SemVer 2.0.0][🧮semver-img]][🧮semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] | -| 2️⃣ | version & activity | [![Gem Version][⛳️version-img]][⛳️gem] [![Total Downloads][🖇DL-total-img]][⛳️gem] [![Download Rank][🏘DL-rank-img]][⛳️gem] [![Source Code][🚎src-main-img]][🚎src-main] | -| 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] | -| 4️⃣ | testing | [![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][⛳gg-discussions-img]][⛳gg-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/gems/oauth2 -[🚎yard-img]: https://img.shields.io/badge/documentation-rubydoc-brightgreen.svg?style=flat -[🧮semver]: http://semver.org/ -[🧮semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat -[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ -[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat - - -[⛳️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-main]: https://gitlab.com/oauth-xx/oauth2/-/tree/main -[🚎src-main-img]: https://img.shields.io/badge/source-gitlab-blue.svg?style=flat - - -[⛳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://gitlab.com/oauth-xx/oauth2/-/graphs/main -[🚎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 - - -[🏘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/main/graph/badge.svg?token=bNqSzNiuo2 -[🖇codecov]: https://codecov.io/gh/oauth-xx/oauth2 -[🏘coveralls]: https://coveralls.io/github/oauth-xx/oauth2?branch=main -[🏘coveralls-img]: https://coveralls.io/repos/github/oauth-xx/oauth2/badge.svg?branch=main -[🚎sec-pol]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/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 - - -[⛳gg-discussions]: https://groups.google.com/g/oauth-ruby -[⛳gg-discussions-img]: https://img.shields.io/badge/google-group-purple.svg?style=flat -[🖇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://gitlab.com/oauth-xx/oauth2/-/wikis/home -[🖐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 +## ✨ Installation Install the gem and add to the application's Gemfile by executing: @@ -247,6 +140,36 @@ If bundler is not being used to manage dependencies, install the gem by executin $ gem install oauth2 +### 🔒 Secure Installation + +`oauth2` is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by +[stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with +by following the instructions below. + +Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate: + +```shell +gem cert --add <(curl -Ls https://raw.github.com/kettle-rb/oauth2/main/certs/pboling.pem) +``` + +You only need to do that once. Then proceed to install with: + +```shell +gem install oauth2 -P MediumSecurity +``` + +The `MediumSecurity` trust profile will verify signed gems, but allow the installation of unsigned dependencies. + +This is necessary because not all of `oauth2`’s dependencies are signed, so we cannot use `HighSecurity`. + +If you want to up your security game full-time: + +```shell +bundle config set --global trust-policy MediumSecurity +``` + +NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine. + ## OAuth2 for Enterprise Available as part of the Tidelift Subscription. @@ -260,7 +183,7 @@ The maintainers of OAuth2 and thousands of other packages are working with Tidel 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]. +For more see [SECURITY.md][🔐security]. ## What is new for v2.0? @@ -345,14 +268,14 @@ end ### `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') +require "oauth2" +client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org") # => # "https://example.org/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code" -access = client.auth_code.get_token('authorization_code_value', redirect_uri: 'http://localhost:8080/oauth2/callback', headers: {'Authorization' => 'Basic some_password'}) -response = access.get('/api/resource', params: {'query_foo' => 'bar'}) +access = client.auth_code.get_token("authorization_code_value", redirect_uri: "http://localhost:8080/oauth2/callback", headers: {"Authorization" => "Basic some_password"}) +response = access.get("/api/resource", params: {"query_foo" => "bar"}) response.class.name # => OAuth2::Response ``` @@ -362,9 +285,9 @@ response.class.name 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') +client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/nested/directory/on/your/server") # => # "https://example.org/nested/directory/on/your/server/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code" ``` @@ -373,12 +296,15 @@ client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth2/callb You can specify custom URLs for authorization and access token, and when using a leading `/` they will _not be relative_, as shown below: ```ruby -client = OAuth2::Client.new('client_id', 'client_secret', - site: 'https://example.org/nested/directory/on/your/server', - authorize_url: '/jaunty/authorize/', - token_url: '/stirrups/access_token') +client = OAuth2::Client.new( + "client_id", + "client_secret", + site: "https://example.org/nested/directory/on/your/server", + authorize_url: "/jaunty/authorize/", + token_url: "/stirrups/access_token", + ) # => # "https://example.org/jaunty/authorize/?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code" client.class.name # => OAuth2::Client @@ -387,7 +313,7 @@ client.class.name ### snake_case and indifferent access in Response#parsed ```ruby -response = access.get('/api/resource', params: {'query_foo' => 'bar'}) +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"} @@ -401,11 +327,11 @@ response.parsed.class.name # => OAuth2::SnakyHash (subclass of Hashie::Ma #### What if I hate snakes and/or indifference? ```ruby -response = access.get('/api/resource', params: {'query_foo' => 'bar'}, snaky: false) +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["accessToken"] # => "aaaaaaaa" +response.parsed["additionalData"] # => "additional" response.parsed.class.name # => Hash (just, regular old Hash) ``` @@ -416,19 +342,19 @@ Set an environment variable, however you would [normally do that](https://github ```ruby # will log both request and response, including bodies -ENV['OAUTH_DEBUG'] = 'true' +ENV["OAUTH_DEBUG"] = "true" ``` By default, debug output will go to `$stdout`. This can be overridden when initializing your OAuth2::Client. ```ruby -require 'oauth2' +require "oauth2" client = OAuth2::Client.new( - 'client_id', - 'client_secret', - site: 'https://example.org', - logger: Logger.new('example.log', 'weekly') + "client_id", + "client_secret", + site: "https://example.org", + logger: Logger.new("example.log", "weekly"), ) ``` @@ -477,96 +403,336 @@ use. They are available via the [`#auth_code`](https://gitlab.com/oauth-xx/oauth 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.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') +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.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', + 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'] +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 +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'}) +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 +### 🚀 Release Instructions -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. +See [CONTRIBUTING.md][🤝contributing]. -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. +## 🔐 Security -For example: +See [SECURITY.md][🔐security]. -```ruby -spec.add_dependency 'oauth2', '~> 2.0' -``` +## 🤝 Contributing -[semver]: http://semver.org/ -[pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint +If you need some ideas of where to help, you could work on adding more code coverage, +or if it is already 💯 (see [below](#code-coverage)) check TODOs (see [below](#todos)), +or check [issues][🤝issues], or [PRs][🤝pulls], +or use the gem and think about how it could be better. -## License +We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it. -[![License: MIT][🖇src-license-img]][🖇src-license] +See [CONTRIBUTING.md][🤝contributing] for more detailed instructions. -- Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc. -- Copyright (c) 2017-2022 [oauth-xx organization][oauth-xx] -- See [LICENSE][license] for details. +### Code Coverage -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=large)][fossa2] +[![Coverage Graph][🔑codecov-g♻️]][🔑codecov] -[license]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/LICENSE -[oauth-xx]: https://gitlab.com/oauth-xx -[fossa2]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_large +### 🪇 Code of Conduct + +Everyone interacting in this project's codebases, issue trackers, +chat rooms and mailing lists is expected to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct]. + +## 🌈 Contributors + +[![Contributors][🖐contributors-img]][🖐contributors] + +Made with [contributors-img][🖐contrib-rocks]. + +Also see GitLab Contributors: [https://gitlab.com/oauth-xx/oauth2/-/graphs/main][🚎contributors-gl] + +## ⭐️ Star History + + + + + + Star History Chart + + + +## 📌 Versioning + +This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌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. + +### 📌 Is "Platform Support" part of the public API? + +Yes. But I'm obligated to include notes... + +SemVer should, but doesn't explicitly, say that dropping support for specific Platforms +is a *breaking change* to an API. +It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless. + +> dropping support for a platform is both obviously and objectively a breaking change + +- Jordan Harband (@ljharb, maintainer of SemVer) [in SemVer issue 716][📌semver-breaking] + +To get a better understanding of how SemVer is intended to work over a project's lifetime, +read this article from the creator of SemVer: -## Development +- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred] -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. +As a result of this policy, and the interpretive lens used by the maintainer, +you can (and should) specify a dependency on these libraries using +the [Pessimistic Version Constraint][📌pvc] with two digits of precision. -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). +For example: + +```ruby +spec.add_dependency("oauth2", "~> 1.0") +``` + +See [CHANGELOG.md][📌changelog] for list of releases. -## Contributing +## 📄 License -See [CONTRIBUTING.md][contributing] +The gem is available as open source under the terms of +the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref]. +See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer]. -[contributing]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CONTRIBUTING.md -## Contributors +[![FOSSA Status][fossa2-img])][fossa2] -[![Contributors](https://contrib.rocks/image?repo=oauth-xx/oauth2)]("https://gitlab.com/oauth-xx/oauth2/-/graphs/main") +[fossa2]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_large +[fossa2-img]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=large + +### © Copyright + +
    +
  • + 2017 - 2025 Peter H. Boling, of + + RailsBling.com + + Rails Bling + + , and oauth2 contributors +
  • +
  • + Copyright (c) 2011 - 2013 Michael Bleigh and Intridea, Inc. +
  • +
+ +## 🤑 One more thing + +You made it to the bottom of the page, +so perhaps you'll indulge me for another 20 seconds. +I maintain many dozens of gems, including this one, +because I want Ruby to be a great place for people to solve problems, big and small. +Please consider supporting my efforts via the giant yellow link below, +or one of the others at the head of this README. + +[![Buy me a latte][🖇buyme-img]][🖇buyme] -Made with [contributors-img](https://contrib.rocks). +[⛳gg-discussions]: https://groups.google.com/g/oauth-ruby +[⛳gg-discussions-img]: https://img.shields.io/badge/google-group-purple.svg?style=flat + +[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780 +[⛳️gem-namespace]: https://github.com/oauth-xx/oauth2 +[⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-brightgreen.svg?style=flat&logo=ruby&logoColor=white +[⛳️gem-name]: https://rubygems.org/gems/oauth2 +[⛳️name-img]: https://img.shields.io/badge/name-oauth2-brightgreen.svg?style=flat&logo=rubygems&logoColor=red +[🚂bdfl-blog]: http://www.railsbling.com/tags/oauth2 +[🚂bdfl-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange +[🚂bdfl-contact]: http://www.railsbling.com/contact +[🚂bdfl-contact-img]: https://img.shields.io/badge/Contact-BDFL-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red +[💖🖇linkedin]: http://www.linkedin.com/in/peterboling +[💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling +[💖✌️wellfound]: https://angel.co/u/peter-boling +[💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound +[💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling +[💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase +[💖🐘ruby-mast]: https://ruby.social/@galtzo +[💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=flat&logo=mastodon&label=Ruby%20%40galtzo +[💖🦋bluesky]: https://bsky.app/profile/galtzo.com +[💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white +[💖🌳linktree]: https://linktr.ee/galtzo +[💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree +[💖💁🏼‍♂️devto]: https://dev.to/galtzo +[💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white +[💖💁🏼‍♂️aboutme]: https://about.me/peter.boling +[💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white +[💖🧊berg]: https://codeberg.org/pboling +[💖🐙hub]: https://github.org/pboling +[💖🛖hut]: https://sr.ht/~galtzo/ +[💖🧪lab]: https://gitlab.com/pboling +[👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share +[👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white +[👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github +[👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white +[🏙️entsup-tidelift]: https://tidelift.com/subscription +[🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white +[🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar +[💁🏼‍♂️peterboling]: http://www.peterboling.com +[🚂railsbling]: http://www.railsbling.com +[📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange +[📜src-gl]: https://gitlab.com/oauth-xx/oauth2/ +[📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue +[📜src-cb]: https://codeberg.org/oauth-xx/oauth2 +[📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green +[📜src-gh]: https://github.com/oauth-xx/oauth2 +[📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white +[📜docs-head-rd-img]: https://img.shields.io/badge/RubyDoc-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white +[📜wiki]: https://gitlab.com/oauth-xx/oauth2/-/wikis/home +[📜wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=Wiki&logoColor=white +[👽dl-rank]: https://rubygems.org/gems/oauth2 +[👽dl-ranki]: https://img.shields.io/gem/rd/oauth2.svg +[👽oss-help]: https://www.codetriage.com/oauth-xx/oauth2 +[👽oss-helpi]: https://www.codetriage.com/oauth-xx/oauth2/badges/users.svg +[👽version]: https://rubygems.org/gems/oauth2 +[👽versioni]: https://img.shields.io/gem/v/oauth2.svg +[🔑cc-mnt]: https://qlty.sh/gh/oauth-xx/projects/oauth2 +[🔑cc-mnti♻️]: https://qlty.sh/badges/d3370c2c-8791-4202-9759-76f527f76005/maintainability.svg +[🔑cc-cov]: https://qlty.sh/gh/oauth-xx/projects/oauth2 +[🔑cc-covi♻️]: https://qlty.sh/badges/d3370c2c-8791-4202-9759-76f527f76005/test_coverage.svg +[🔑codecov]: https://codecov.io/gh/oauth-xx/oauth2 +[🔑codecovi♻️]: https://codecov.io/gh/oauth-xx/oauth2/branch/main/graph/badge.svg?token=bNqSzNiuo2 +[🔑coveralls]: https://coveralls.io/github/oauth-xx/oauth2?branch=main +[🔑coveralls-img]: https://coveralls.io/repos/github/oauth-xx/oauth2/badge.svg?branch=main +[🔑depfu]: https://depfu.com/github/oauth-xx/oauth2?project_id=5884 +[🔑depfui♻️]: https://badges.depfu.com/badges/6d34dc1ba682bbdf9ae2a97848241743/count.svg +[🖐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 +[🚎1-an-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/ancient.yml +[🚎1-an-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/ancient.yml/badge.svg +[🚎2-cov-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml +[🚎2-cov-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml/badge.svg +[🚎3-hd-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml +[🚎3-hd-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml/badge.svg +[🚎4-lg-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/legacy.yml +[🚎4-lg-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/legacy.yml/badge.svg +[🚎5-st-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml +[🚎5-st-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml/badge.svg +[🚎6-s-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml +[🚎6-s-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml/badge.svg +[🚎7-us-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml +[🚎7-us-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml/badge.svg +[🚎8-ho-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/hoary.yml +[🚎8-ho-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/hoary.yml/badge.svg +[🚎9-t-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/truffle.yml +[🚎9-t-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/truffle.yml/badge.svg +[🚎10-j-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/jruby.yml +[🚎10-j-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/jruby.yml/badge.svg +[🚎11-c-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/current.yml +[🚎11-c-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/current.yml/badge.svg +[⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay +[⛳liberapay]: https://liberapay.com/pboling/donate +[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github +[🖇sponsor]: https://github.com/sponsors/pboling +[🖇polar-img]: https://img.shields.io/badge/polar-donate-yellow.svg +[🖇polar]: https://polar.sh/pboling +[🖇kofi-img]: https://img.shields.io/badge/a_more_different_coffee-✓-yellow.svg +[🖇kofi]: https://ko-fi.com/O5O86SNP4 +[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg +[🖇patreon]: https://patreon.com/galtzo +[🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff +[🖇buyme]: https://www.buymeacoffee.com/pboling +[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-✓-yellow.svg?style=flat +[💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white +[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green +[💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue +[💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green +[💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue +[💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1-FBE742?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red +[💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green +[💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue +[🤝issues]: https://github.com/oauth-xx/oauth2/issues +[🤝pulls]: https://github.com/oauth-xx/oauth2/pulls +[🤝contributing]: CONTRIBUTING.md +[🔑codecov-g♻️]: https://codecov.io/gh/oauth-xx/oauth2/graphs/tree.svg?token=bNqSzNiuo2 +[🖐contrib-rocks]: https://contrib.rocks +[🖐contributors]: https://github.com/oauth-xx/oauth2/graphs/contributors +[🖐contributors-img]: https://contrib.rocks/image?repo=oauth-xx/oauth2 +[🚎contributors-gl]: https://gitlab.com/oauth-xx/oauth2/-/graphs/main +[🪇conduct]: CODE_OF_CONDUCT.md +[🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-4baaaa.svg +[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint +[📌semver]: https://semver.org/spec/v2.0.0.html +[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat +[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 +[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html +[📌changelog]: CHANGELOG.md +[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ +[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat +[📌gitmoji]:https://gitmoji.dev +[📌gitmoji-img]:https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square +[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ +[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.073-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue +[🔐security]: SECURITY.md +[🔐security-img]: https://img.shields.io/badge/security-policy-brightgreen.svg?style=flat +[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year +[📄license]: LICENSE.txt +[📄license-ref]: https://opensource.org/licenses/MIT +[📄license-img]: https://img.shields.io/badge/License-MIT-green.svg +[📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm +[📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-brightgreen.svg?style=flat +[🚎yard-current]: http://rubydoc.info/gems/oauth2 +[🚎yard-head]: https://rubydoc.info/github/oauth-xx/oauth2/main +[💎stone_checksums]: https://github.com/pboling/stone_checksums +[💎SHA_checksums]: https://gitlab.com/oauth-xx/oauth2/-/tree/main/checksums +[💎rlts]: https://github.com/rubocop-lts/rubocop-lts +[💎rlts-img]: https://img.shields.io/badge/code_style-rubocop--lts-brightgreen.svg?plastic&logo=ruby&logoColor=white +[🏘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 -## Code of Conduct +
+ + rel="me" Social Proofs + -Everyone interacting in the OAuth2 project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.com/oauth-xx/oauth2/-/blob/main/CODE_OF_CONDUCT.md). + + +
diff --git a/Rakefile b/Rakefile index 9449ebe8..cdd18938 100644 --- a/Rakefile +++ b/Rakefile @@ -1,43 +1,88 @@ -# encoding: utf-8 -# frozen_string_literal: true +require "bundler/gem_tasks" -# !/usr/bin/env rake +defaults = [] -require 'bundler/gem_tasks' +# See: https://docs.gitlab.com/ci/variables/predefined_variables/ +is_gitlab = ENV.fetch("GITLAB_CI", "false").casecmp("true") == 0 +# Setup Bundle Audit begin - require 'rspec/core/rake_task' + require "bundler/audit/task" + + Bundler::Audit::Task.new + defaults.push("bundle:audit:update", "bundle:audit") +rescue LoadError + desc("(stub) bundle:audit is unavailable") + task("bundle:audit") do + warn("NOTE: bundler-audit isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") + end + desc("(stub) bundle:audit:update is unavailable") + task("bundle:audit:update") do + warn("NOTE: bundler-audit isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") + end +end + +begin + require "rspec/core/rake_task" + RSpec::Core::RakeTask.new(:spec) + defaults << "spec" rescue LoadError - desc 'spec task stub' - task :spec do - warn 'rspec is disabled' + desc("spec task stub") + task(:spec) do + warn("NOTE: rspec isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") end end -desc 'alias test task to spec' + +desc "run spec task with test task" task test: :spec +# Setup RuboCop-LTS begin - require 'rubocop/rake_task' - RuboCop::RakeTask.new do |task| - task.options = ['-D'] # Display the name of the failing cops + require "rubocop/lts" + + Rubocop::Lts.install_tasks + defaults << "rubocop_gradual" +rescue LoadError + desc("(stub) rubocop_gradual is unavailable") + task(:rubocop_gradual) do + warn("NOTE: rubocop-lts isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") + end +end + +# Setup Yard +begin + require "yard" + + YARD::Rake::YardocTask.new(:yard) do |t| + t.files = [ + # Splats (alphabetical) + "lib/**/*.rb", + ] + end + defaults << "yard" +rescue LoadError + desc("(stub) yard is unavailable") + task(:yard) do + warn("NOTE: yard isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") + end +end + +# Setup Reek +begin + require "reek/rake/task" + + Reek::Rake::Task.new do |t| + t.fail_on_error = true + t.verbose = false + t.source_files = "{lib,spec}/**/*.rb" end + defaults << "reek" unless is_gitlab rescue LoadError - desc 'rubocop task stub' - task :rubocop do - warn 'RuboCop is disabled' + desc("(stub) reek is unavailable") + task(:reek) do + warn("NOTE: reek isn't installed, or is disabled for #{RUBY_VERSION} in the current environment") 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.txt', 'lib/**/*.rb') -# end -# end - -task default: %i[test rubocop] +task default: defaults diff --git a/SECURITY.md b/SECURITY.md index 274337d2..f41dda1f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,8 @@ | Version | Supported | EOL | Post-EOL / Enterprise | |----------|-----------|---------|---------------------------------------| -| 2.latest | ✅ | 04/2024 | [Tidelift Subscription][tidelift-ref] | -| 1.latest | ✅ | 04/2023 | [Tidelift Subscription][tidelift-ref] | +| 2.latest | ✅ | 04/2026 | [Tidelift Subscription][tidelift-ref] | +| 1.latest | ✅ | 10/2025 | [Tidelift Subscription][tidelift-ref] | | <= 1 | ⛔ | ⛔ | ⛔ | ### EOL Policy diff --git a/bin/bundle b/bin/bundle index fece50fe..4a95618e 100755 --- a/bin/bundle +++ b/bin/bundle @@ -8,7 +8,7 @@ # this file is here to facilitate running it. # -require 'rubygems' +require "rubygems" m = Module.new do module_function @@ -18,18 +18,18 @@ module_function end def env_var_version - ENV['BUNDLER_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` + 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/ + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/o bundler_version = Regexp.last_match(1) update_index = i @@ -38,16 +38,16 @@ module_function end def gemfile - gemfile = ENV['BUNDLE_GEMFILE'] + gemfile = ENV["BUNDLE_GEMFILE"] return gemfile if gemfile && !gemfile.empty? - File.expand_path('../Gemfile', __dir__) + File.expand_path("../Gemfile", __dir__) end def lockfile lockfile = case File.basename(gemfile) - when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) else "#{gemfile}.lock" end File.expand_path(lockfile) @@ -57,7 +57,7 @@ module_function 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/ + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/o Regexp.last_match(1) end @@ -75,32 +75,32 @@ module_function requirement = bundler_gem_version.approximate_recommendation - return requirement unless Gem.rubygems_version < Gem::Version.new('2.7.0') + return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") - requirement += '.a' if bundler_gem_version.prerelease? + requirement += ".a" if bundler_gem_version.prerelease? requirement end def load_bundler! - ENV['BUNDLE_GEMFILE'] ||= gemfile + ENV["BUNDLE_GEMFILE"] ||= gemfile activate_bundler end def activate_bundler gem_error = activation_error_handling do - gem 'bundler', bundler_requirement + gem("bundler", bundler_requirement) end return if gem_error.nil? require_error = activation_error_handling do - require 'bundler/version' + 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 + 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 @@ -113,4 +113,4 @@ end m.load_bundler! -load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script? +load Gem.bin_path("bundler", "bundle") if m.invoked_as_script? diff --git a/bin/bundle-audit b/bin/bundle-audit new file mode 100644 index 00000000..a0e7ba0e --- /dev/null +++ b/bin/bundle-audit @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle-audit' 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).include?("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("bundler-audit", "bundle-audit") diff --git a/bin/bundler-audit b/bin/bundler-audit new file mode 100644 index 00000000..334a7378 --- /dev/null +++ b/bin/bundler-audit @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundler-audit' 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).include?("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("bundler-audit", "bundler-audit") diff --git a/bin/code_climate_reek b/bin/code_climate_reek new file mode 100644 index 00000000..afe0d79f --- /dev/null +++ b/bin/code_climate_reek @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'code_climate_reek' 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).include?("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("reek", "code_climate_reek") diff --git a/bin/coderay b/bin/coderay new file mode 100644 index 00000000..b13b22e9 --- /dev/null +++ b/bin/coderay @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'coderay' 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).include?("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("coderay", "coderay") diff --git a/bin/console b/bin/console index d8fb16d0..53fc8fd9 100755 --- a/bin/console +++ b/bin/console @@ -1,16 +1,15 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require 'bundler/setup' -require 'oauth2' +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. -require 'byebug' if ENV['DEBUG'] == 'true' # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start -require 'irb' +require "irb" IRB.start(__FILE__) diff --git a/bin/github-markup b/bin/github-markup new file mode 100644 index 00000000..5cb47930 --- /dev/null +++ b/bin/github-markup @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'github-markup' 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).include?("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("github-markup", "github-markup") diff --git a/bin/htmldiff b/bin/htmldiff new file mode 100644 index 00000000..0aeaec87 --- /dev/null +++ b/bin/htmldiff @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'htmldiff' 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).include?("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("diff-lcs", "htmldiff") diff --git a/bin/irb b/bin/irb new file mode 100644 index 00000000..e7de6d6c --- /dev/null +++ b/bin/irb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'irb' 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).include?("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("irb", "irb") diff --git a/bin/ldiff b/bin/ldiff new file mode 100644 index 00000000..8173edec --- /dev/null +++ b/bin/ldiff @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ldiff' 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).include?("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("diff-lcs", "ldiff") diff --git a/bin/racc b/bin/racc new file mode 100644 index 00000000..81900158 --- /dev/null +++ b/bin/racc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'racc' 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).include?("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("racc", "racc") diff --git a/bin/rake b/bin/rake index 5f615c2a..51e10c4a 100755 --- a/bin/rake +++ b/bin/rake @@ -8,9 +8,9 @@ # this file is here to facilitate running it. # -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -bundle_binstub = File.expand_path('bundle', __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/ @@ -21,7 +21,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end end -require 'rubygems' -require 'bundler/setup' +require "rubygems" +require "bundler/setup" -load Gem.bin_path('rake', 'rake') +load Gem.bin_path("rake", "rake") diff --git a/bin/rdbg b/bin/rdbg new file mode 100644 index 00000000..5e3b279f --- /dev/null +++ b/bin/rdbg @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rdbg' 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).include?("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("debug", "rdbg") diff --git a/bin/rdoc b/bin/rdoc new file mode 100644 index 00000000..d2b6bcf8 --- /dev/null +++ b/bin/rdoc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rdoc' 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).include?("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("rdoc", "rdoc") diff --git a/bin/redcarpet b/bin/redcarpet new file mode 100644 index 00000000..76a1cb80 --- /dev/null +++ b/bin/redcarpet @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'redcarpet' 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).include?("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("redcarpet", "redcarpet") diff --git a/bin/reek b/bin/reek new file mode 100644 index 00000000..2ec45920 --- /dev/null +++ b/bin/reek @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'reek' 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).include?("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("reek", "reek") diff --git a/bin/ri b/bin/ri new file mode 100644 index 00000000..72e25813 --- /dev/null +++ b/bin/ri @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ri' 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).include?("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("rdoc", "ri") diff --git a/bin/rspec b/bin/rspec index d3f4959a..757e79b3 100755 --- a/bin/rspec +++ b/bin/rspec @@ -8,9 +8,9 @@ # this file is here to facilitate running it. # -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -bundle_binstub = File.expand_path('bundle', __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/ @@ -21,7 +21,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end end -require 'rubygems' -require 'bundler/setup' +require "rubygems" +require "bundler/setup" -load Gem.bin_path('rspec-core', 'rspec') +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/rubocop b/bin/rubocop index cc105e8d..2b1fa1f7 100755 --- a/bin/rubocop +++ b/bin/rubocop @@ -8,9 +8,9 @@ # this file is here to facilitate running it. # -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -bundle_binstub = File.expand_path('bundle', __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/ @@ -21,7 +21,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this end end -require 'rubygems' -require 'bundler/setup' +require "rubygems" +require "bundler/setup" -load Gem.bin_path('rubocop', 'rubocop') +load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/rubocop-gradual b/bin/rubocop-gradual new file mode 100644 index 00000000..07520055 --- /dev/null +++ b/bin/rubocop-gradual @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rubocop-gradual' 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).include?("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-gradual", "rubocop-gradual") diff --git a/bin/ruby-parse b/bin/ruby-parse new file mode 100644 index 00000000..d8ebc68d --- /dev/null +++ b/bin/ruby-parse @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ruby-parse' 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).include?("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("parser", "ruby-parse") diff --git a/bin/ruby-rewrite b/bin/ruby-rewrite new file mode 100644 index 00000000..b4574aba --- /dev/null +++ b/bin/ruby-rewrite @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'ruby-rewrite' 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).include?("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("parser", "ruby-rewrite") diff --git a/bin/standardrb b/bin/standardrb new file mode 100644 index 00000000..b329561c --- /dev/null +++ b/bin/standardrb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'standardrb' 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).include?("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("standard", "standardrb") diff --git a/bin/thor b/bin/thor new file mode 100644 index 00000000..ec401151 --- /dev/null +++ b/bin/thor @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'thor' 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).include?("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("thor", "thor") diff --git a/bin/yard b/bin/yard new file mode 100644 index 00000000..ea9daf5f --- /dev/null +++ b/bin/yard @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yard' 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).include?("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("yard", "yard") diff --git a/bin/yard-junk b/bin/yard-junk new file mode 100644 index 00000000..be420a5c --- /dev/null +++ b/bin/yard-junk @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yard-junk' 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).include?("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("yard-junk", "yard-junk") diff --git a/bin/yardoc b/bin/yardoc new file mode 100644 index 00000000..e1324dc1 --- /dev/null +++ b/bin/yardoc @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yardoc' 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).include?("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("yard", "yardoc") diff --git a/bin/yri b/bin/yri new file mode 100644 index 00000000..f968fde1 --- /dev/null +++ b/bin/yri @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'yri' 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).include?("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("yard", "yri") diff --git a/certs/pboling.pem b/certs/pboling.pem index f11daea5..d5c7e8bb 100644 --- a/certs/pboling.pem +++ b/certs/pboling.pem @@ -1,27 +1,27 @@ -----BEGIN CERTIFICATE----- MIIEgDCCAuigAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMRUwEwYDVQQDDAxwZXRl ci5ib2xpbmcxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkW -A2NvbTAeFw0yMjA5MTgyMzEyMzBaFw0yMzA5MTgyMzEyMzBaMEMxFTATBgNVBAMM +A2NvbTAeFw0yNTA1MDQxNTMzMDlaFw00NTA0MjkxNTMzMDlaMEMxFTATBgNVBAMM DHBldGVyLmJvbGluZzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy -LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2Dn1GM3W -8K2/rvN1zz+06bQMcxD16ZKTihVwi7Pb1v3T98rM4Omnxohm3s+CwpDWGeiB9pj6 -0I/CTce0e4e3s8GKJSOrg93veImPSoH2PfsMsRsuB8wtqyiOCjLbF5o6S29x87r0 -LA5EawH+Lh4xqrkkPjdffsmLk7TaCig/vlmNvnzxXKBdey/X/aEJZXzzBiWRfVdh -O1fmMbVKyieGv9HK7+pLotIoT08bjDv8NP6V7zZslwQRqW27bQc6cqC2LGIbTYO3 -3jt1kQxfMWmhOictS6SzG9VtKSrXf0L4Neq0Gh7CLBZBvJFWJYZPfb92YNITDbd8 -emPOAQlXXNMN4mMXsEqtEhCPZRMnmwO+fOk/cC4AyglKi9lnQugCQoFV1XDMZST/ -CYbzdQyadOdPDInTntG6V+Uw51d2QGXZ6PDDfrx9+toc/3sl5h68rCUGgE6Q3jPz -srinqmBsxv2vTpmd4FjmiAtEnwH5/ooLpQYL8UdAjEoeysxS3AwIh+5dAgMBAAGj -fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQWU6D156a2cle+ -lb5RBfvVXlxTwjAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG +LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAruUoo0WA +uoNuq6puKWYeRYiZekz/nsDeK5x/0IEirzcCEvaHr3Bmz7rjo1I6On3gGKmiZs61 +LRmQ3oxy77ydmkGTXBjruJB+pQEn7UfLSgQ0xa1/X3kdBZt6RmabFlBxnHkoaGY5 +mZuZ5+Z7walmv6sFD9ajhzj+oIgwWfnEHkXYTR8I6VLN7MRRKGMPoZ/yvOmxb2DN +coEEHWKO9CvgYpW7asIihl/9GMpKiRkcYPm9dGQzZc6uTwom1COfW0+ZOFrDVBuV +FMQRPswZcY4Wlq0uEBLPU7hxnCL9nKK6Y9IhdDcz1mY6HZ91WImNslOSI0S8hRpj +yGOWxQIhBT3fqCBlRIqFQBudrnD9jSNpSGsFvbEijd5ns7Z9ZMehXkXDycpGAUj1 +to/5cuTWWw1JqUWrKJYoifnVhtE1o1DZ+LkPtWxHtz5kjDG/zR3MG0Ula0UOavlD +qbnbcXPBnwXtTFeZ3C+yrWpE4pGnl3yGkZj9SMTlo9qnTMiPmuWKQDatAgMBAAGj +fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQE8uWvNbPVNRXZ +HlgPbc2PCzC4bjAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG A1UdEgQaMBiBFnBldGVyLmJvbGluZ0BnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD -ggGBAJ4SqhPlgUiLYIrphGXIaxXScHyvx4kixuvdrwhI4VoQV2qXvO7R6ZjOXVwX -f/z84BWPiTZ8lzThPbt1UV/BGwkvLw9I4RjOdzvUz3J42j9Ly6q63isall07bo3F -QWe/OBvIMBF1IbjC3q5vKPg4rq8+TkNRJNoE86U2gfR+PkW3jYYs9uiy0GloHDCP -k5xgaj0vSL0Uy5mTOPdk3K6a/sUGZyYniWK05zdhIi956ynhfGaFO988FFdVw5Jq -LHtXfIpAU8F7ES04syZSslxOluw7VlcSKyRdVIr737J92ZTduppB4PRGSKRgBsWV -hXTahRE72Kyw53Q7FAuzF3v102WxAAQ7BuMjW+MyCUT75fwPm3W4ELPL8HYkNGE7 -2oA5CPghFitRnvYS3GNrDG+9bNiRMEskeaBYwZ9UgReBQIwGYVj7LZk3UhiAsn44 -gwGrEXGQGDZ0NIgBcmvMOqlXjkGQwQvugKycJ024z89+fz2332vdZIKTrSxJrXGk -4/bR9A== +ggGBAJbnUwfJQFPkBgH9cL7hoBfRtmWiCvdqdjeTmi04u8zVNCUox0A4gT982DE9 +wmuN12LpdajxZONqbXuzZvc+nb0StFwmFYZG6iDwaf4BPywm2e/Vmq0YG45vZXGR +L8yMDSK1cQXjmA+ZBKOHKWavxP6Vp7lWvjAhz8RFwqF9GuNIdhv9NpnCAWcMZtpm +GUPyIWw/Cw/2wZp74QzZj6Npx+LdXoLTF1HMSJXZ7/pkxLCsB8m4EFVdb/IrW/0k +kNSfjtAfBHO8nLGuqQZVH9IBD1i9K6aSs7pT6TW8itXUIlkIUI2tg5YzW6OFfPzq +QekSkX3lZfY+HTSp/o+YvKkqWLUV7PQ7xh1ZYDtocpaHwgxe/j3bBqHE+CUPH2vA +0V/FwdTRWcwsjVoOJTrYcff8pBZ8r2MvtAc54xfnnhGFzeRHfcltobgFxkAXdE6p +DVjBtqT23eugOqQ73umLcYDZkc36vnqGxUBSsXrzY9pzV5gGr2I8YUxMqf6ATrZt +L9nRqA== -----END CERTIFICATE----- diff --git a/checksums/oauth2-2.0.10.gem.sha256 b/checksums/oauth2-2.0.10.gem.sha256 deleted file mode 100644 index e0e65592..00000000 --- a/checksums/oauth2-2.0.10.gem.sha256 +++ /dev/null @@ -1 +0,0 @@ -e0bbe33434c32cdd01bc970f265c64db42b44f42d2036896710b1cf3e682e704 \ No newline at end of file diff --git a/checksums/oauth2-2.0.10.gem.sha512 b/checksums/oauth2-2.0.10.gem.sha512 deleted file mode 100644 index 02ad7661..00000000 --- a/checksums/oauth2-2.0.10.gem.sha512 +++ /dev/null @@ -1 +0,0 @@ -a73cc4c6502893f219e4b1cc5d2df480184bbf11069b43e08b781f4d278ce7219097e8904710948b644d22de95b98cdad575de1d1864d815d0e3064c2601a270 \ No newline at end of file diff --git a/gemfiles/README.md b/gemfiles/README.md index 1ac3a713..b217d4cf 100644 --- a/gemfiles/README.md +++ b/gemfiles/README.md @@ -4,7 +4,7 @@ and thus is the oldest version oauth2 is compatible with. ```ruby -gem 'faraday', ['>= 0.17.3', '< 3.0'] +gem "faraday", [">= 0.17.3", "< 3.0"] ``` # Ruby diff --git a/gemfiles/f0.gemfile b/gemfiles/f0.gemfile index 4cb7f887..dc8d3f31 100644 --- a/gemfiles/f0.gemfile +++ b/gemfiles/f0.gemfile @@ -1,11 +1,11 @@ # frozen_string_literal: true -source 'https://rubygems.org' +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' +gem "faraday", "~> 0.17.4" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/f1.gemfile b/gemfiles/f1.gemfile index 94cba5c6..40043bca 100644 --- a/gemfiles/f1.gemfile +++ b/gemfiles/f1.gemfile @@ -1,9 +1,9 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" # See README.md in this directory -gem 'faraday', '~> 1.10' +gem "faraday", "~> 1.10" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/f2.gemfile b/gemfiles/f2.gemfile index 7c3868df..44081f52 100644 --- a/gemfiles/f2.gemfile +++ b/gemfiles/f2.gemfile @@ -1,9 +1,9 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" # See README.md in this directory -gem 'faraday', '~> 2.2' +gem "faraday", "~> 2.2" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/jruby_9.1.gemfile b/gemfiles/jruby_9.1.gemfile index fb2b9158..7573a1b5 100644 --- a/gemfiles/jruby_9.1.gemfile +++ b/gemfiles/jruby_9.1.gemfile @@ -1,5 +1,5 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/jruby_9.2.gemfile b/gemfiles/jruby_9.2.gemfile index fb2b9158..7573a1b5 100644 --- a/gemfiles/jruby_9.2.gemfile +++ b/gemfiles/jruby_9.2.gemfile @@ -1,5 +1,5 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/jruby_head.gemfile b/gemfiles/jruby_head.gemfile index fb2b9158..7573a1b5 100644 --- a/gemfiles/jruby_head.gemfile +++ b/gemfiles/jruby_head.gemfile @@ -1,5 +1,5 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/modular/audit.gemfile b/gemfiles/modular/audit.gemfile new file mode 100644 index 00000000..e5cc9199 --- /dev/null +++ b/gemfiles/modular/audit.gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Many gems are dropping support for Ruby < 3, +# so we only want to run our security audit in CI on Ruby 3+ +gem "bundler-audit", "~> 0.9.2" diff --git a/gemfiles/modular/coverage.gemfile b/gemfiles/modular/coverage.gemfile new file mode 100644 index 00000000..5ef0c45a --- /dev/null +++ b/gemfiles/modular/coverage.gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# We run code coverage on the latest version of Ruby only. + +# Coverage +gem "kettle-soup-cover", "~> 1.0", ">= 1.0.6", require: false diff --git a/gemfiles/modular/documentation.gemfile b/gemfiles/modular/documentation.gemfile new file mode 100644 index 00000000..5cccab2e --- /dev/null +++ b/gemfiles/modular/documentation.gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Documentation +gem "yard", "~> 0.9", ">= 0.9.37", require: false +gem "yard-junk", "~> 0.0", ">= 0.0.10", github: "pboling/yard-junk", branch: "next" + +# Std Lib extractions +gem "rdoc", "~> 6.11" diff --git a/gemfiles/modular/style.gemfile b/gemfiles/modular/style.gemfile new file mode 100644 index 00000000..8966ca93 --- /dev/null +++ b/gemfiles/modular/style.gemfile @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# We run rubocop on the latest version of Ruby, +# but in support of the oldest supported version of Ruby + +gem "reek", "~> 6.4" +gem "rubocop", "~> 1.73", ">= 1.73.2" +# gem "rubocop-lts", "~> 0.1", ">= 0.1.1" # Linting for Ruby >= 1.8 +gem "rubocop-packaging", "~> 0.5", ">= 0.5.2" +gem "rubocop-rspec", "~> 3.2" +gem "standard", "~> 1.47" + +# Std Lib extractions +gem "benchmark", "~> 0.4" # Removed from Std Lib in Ruby 3.5 + +# gem "rubocop-lts", :path => "/home/pboling/src/rubocop-lts/rubocop-lts" +# gem "rubocop-lts-rspec", :path => "/home/pboling/src/rubocop-lts/rubocop-lts-rspec" +# gem "rubocop-ruby1_8", :path => "/home/pboling/src/rubocop-lts/rubocop-ruby1_8" +# gem "standard-rubocop-lts", :path => "/home/pboling/src/rubocop-lts/standard-rubocop-lts" diff --git a/gemfiles/omnibus.gemfile b/gemfiles/omnibus.gemfile new file mode 100644 index 00000000..553053f5 --- /dev/null +++ b/gemfiles/omnibus.gemfile @@ -0,0 +1,18 @@ +# This gemfile is used for GitLab CI, current ruby pipeline. +# This gemfile includes all dependencies necessary to run the naked `rake default` set of tasks + +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } +git_source(:gitlab) { |repo_name| "https://gitlab.com/#{repo_name}" } + +eval_gemfile "modular/audit.gemfile" +eval_gemfile "modular/coverage.gemfile" +eval_gemfile "modular/documentation.gemfile" +eval_gemfile "modular/style.gemfile" + +# Root Gemfile is only for local development. +# On CI, we only need the gemspec dependencies (including development dependencies). +# Exceptions, if any, will be found in gemfiles/*.gemfile + +gemspec path: "../" diff --git a/gemfiles/ruby_head.gemfile b/gemfiles/ruby_head.gemfile index fb2b9158..7573a1b5 100644 --- a/gemfiles/ruby_head.gemfile +++ b/gemfiles/ruby_head.gemfile @@ -1,5 +1,5 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/truffleruby.gemfile b/gemfiles/truffleruby.gemfile index fb2b9158..7573a1b5 100644 --- a/gemfiles/truffleruby.gemfile +++ b/gemfiles/truffleruby.gemfile @@ -1,5 +1,5 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" -gemspec path: '../' +gemspec path: "../" diff --git a/gemfiles/vanilla.gemfile b/gemfiles/vanilla.gemfile new file mode 100644 index 00000000..78c20166 --- /dev/null +++ b/gemfiles/vanilla.gemfile @@ -0,0 +1,11 @@ +# This gemfile is used for GitLab CI, current ruby pipeline. +# This gemfile includes all dependencies necessary to run the naked `rake default` set of tasks + +source "https://rubygems.org" + +# Root Gemfile is only for local development. +# On CI, we only need the gemspec dependencies (including development dependencies). +# Exceptions, if any, will be found in gemfiles/*.gemfile + +# The vanilla gemfile is intended to what we can with *only* gemspec dependencies. +gemspec path: "../" diff --git a/lib/oauth2.rb b/lib/oauth2.rb index 00f51b08..2f950419 100644 --- a/lib/oauth2.rb +++ b/lib/oauth2.rb @@ -1,27 +1,27 @@ # frozen_string_literal: true # includes modules from stdlib -require 'cgi' -require 'time' +require "cgi" +require "time" # third party gems -require 'snaky_hash' -require 'version_gem' +require "snaky_hash" +require "version_gem" # includes gem files -require 'oauth2/version' -require 'oauth2/filtered_attributes' -require 'oauth2/error' -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' +require "oauth2/version" +require "oauth2/filtered_attributes" +require "oauth2/error" +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 diff --git a/lib/oauth2/access_token.rb b/lib/oauth2/access_token.rb index 45682629..04a2049d 100644 --- a/lib/oauth2/access_token.rb +++ b/lib/oauth2/access_token.rb @@ -76,19 +76,21 @@ def initialize(client, token, opts = {}) error = Error.new(opts) raise(error) else - warn('OAuth2::AccessToken has no token') + warn("OAuth2::AccessToken has no token") end end # @option opts [Fixnum, String] :expires is deprecated - @expires_in ||= opts.delete('expires') + @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_in.zero? @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'} + @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 @@ -118,9 +120,9 @@ def expired? # @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 + raise("A refresh_token is not available") unless refresh_token - params[:grant_type] = '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 @@ -133,7 +135,7 @@ def refresh(params = {}, access_token_opts = {}) end # A compatibility alias # @note does not modify the receiver, so bang is not the default method - alias refresh! refresh + alias_method :refresh!, :refresh # Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash # @@ -190,7 +192,7 @@ def delete(path, opts = {}, &block) # Get the headers hash (includes Authorization token) def headers - {'Authorization' => options[:header_format] % token} + {"Authorization" => options[:header_format] % token} end private diff --git a/lib/oauth2/authenticator.rb b/lib/oauth2/authenticator.rb index f3e2888a..512d1cd7 100644 --- a/lib/oauth2/authenticator.rb +++ b/lib/oauth2/authenticator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'base64' +require "base64" module OAuth2 class Authenticator @@ -49,8 +49,8 @@ def self.encode_basic_auth(user, password) # already set. def apply_params_auth(params) result = {} - result['client_id'] = id unless id.nil? - result['client_secret'] = secret unless secret.nil? + result["client_id"] = id unless id.nil? + result["client_secret"] = secret unless secret.nil? result.merge(params) end @@ -58,7 +58,7 @@ def apply_params_auth(params) # we don't want to send the secret def apply_client_id(params) result = {} - result['client_id'] = id unless id.nil? + result["client_id"] = id unless id.nil? result.merge(params) end @@ -72,7 +72,7 @@ def apply_basic_auth(params) # @see https://datatracker.ietf.org/doc/html/rfc2617#section-2 def basic_auth_header - {'Authorization' => self.class.encode_basic_auth(id, secret)} + {"Authorization" => self.class.encode_basic_auth(id, secret)} end end end diff --git a/lib/oauth2/client.rb b/lib/oauth2/client.rb index e87a5cd0..4176cc25 100644 --- a/lib/oauth2/client.rb +++ b/lib/oauth2/client.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'faraday' -require 'logger' +require "faraday" +require "logger" if Faraday::Utils.respond_to?(:default_space_encoding) # This setting doesn't exist in faraday 0.x - Faraday::Utils.default_space_encoding = '%20' + Faraday::Utils.default_space_encoding = "%20" end module OAuth2 @@ -49,10 +49,10 @@ def initialize(client_id, client_secret, options = {}, &block) @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] + 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', + authorize_url: "oauth/authorize", + token_url: "oauth/token", token_method: :post, auth_scheme: :basic_auth, connection_opts: {}, @@ -81,8 +81,8 @@ def connection 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 + builder.request(:url_encoded) # form-encode POST params + builder.adapter(Faraday.default_adapter) # make requests with Net::HTTP end end end @@ -131,7 +131,7 @@ def request(verb, url, opts = {}, &block) verb = :get opts.delete(:body) end - location = response.headers['location'] + location = response.headers["location"] if location full_location = response.response.env.url.merge(location) request(verb, full_location, opts) @@ -165,7 +165,7 @@ def request(verb, url, opts = {}, &block) # @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 + 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) @@ -178,13 +178,13 @@ def get_token(params, access_token_opts = {}, extract_access_token = nil, &block # 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[:body] = if headers["Content-Type"] == "application/json" + params.to_json + else + params + end - request_opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'} + request_opts[:headers] = {"Content-Type" => "application/x-www-form-urlencoded"} else request_opts[:params] = params request_opts[:headers] = {} @@ -261,7 +261,7 @@ def assertion # @return [Hash] the params to add to a request or URL def redirection_params if options[:redirect_uri] - {'redirect_uri' => options[:redirect_uri]} + {"redirect_uri" => options[:redirect_uri]} else {} end @@ -358,7 +358,7 @@ def build_access_token_legacy(response, access_token_opts, extract_access_token) end def oauth_debug_logging(builder) - builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true' + builder.response(:logger, options[:logger], bodies: true) if ENV["OAUTH_DEBUG"] == "true" end end end diff --git a/lib/oauth2/error.rb b/lib/oauth2/error.rb index cd99ff86..076abbe0 100644 --- a/lib/oauth2/error.rb +++ b/lib/oauth2/error.rb @@ -11,18 +11,18 @@ 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'] + @code = response.parsed["error"] + @description = response.parsed["error_description"] end elsif response.is_a?(Hash) - @code = response['error'] - @description = response['error_description'] + @code = response["error"] + @description = response["error_description"] end @body = if response.respond_to?(:body) - response.body - else - @response - end + response.body + else + @response + end message_opts = parse_error_description(@code, @description) super(error_message(@body, message_opts)) end @@ -35,11 +35,11 @@ def error_message(response_body, opts = {}) 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 + script_encoding = opts[:error_description].encoding + response_body.encode(script_encoding, invalid: :replace, undef: :replace) + else + response_body + end lines << error_string @@ -49,7 +49,7 @@ def error_message(response_body, opts = {}) def parse_error_description(code, description) return {} unless code || description - error_description = '' + error_description = "" error_description += "#{code}: " if code error_description += description if description diff --git a/lib/oauth2/filtered_attributes.rb b/lib/oauth2/filtered_attributes.rb index 299d2d92..2794b94b 100644 --- a/lib/oauth2/filtered_attributes.rb +++ b/lib/oauth2/filtered_attributes.rb @@ -25,7 +25,7 @@ def inspect "#{var}=#{instance_variable_get(var).inspect}" end end - "#<#{self.class}:#{object_id} #{inspected_vars.join(', ')}>" + "#<#{self.class}:#{object_id} #{inspected_vars.join(", ")}>" end end end diff --git a/lib/oauth2/response.rb b/lib/oauth2/response.rb index c5bbb3ba..7003bf20 100644 --- a/lib/oauth2/response.rb +++ b/lib/oauth2/response.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'json' -require 'multi_xml' -require 'rack' +require "json" +require "multi_xml" +require "rack" module OAuth2 # OAuth2::Response class @@ -23,8 +23,8 @@ class Response # Content type assignments for various potential HTTP content types. @@content_types = { - 'application/x-www-form-urlencoded' => :query, - 'text/plain' => :text, + "application/x-www-form-urlencoded" => :query, + "text/plain" => :text, } # Adds a new content type parser. @@ -68,7 +68,7 @@ def status # The HTTP response body def body - response.body || '' + response.body || "" end # The {#response} {#body} as parsed by {#parser}. @@ -97,9 +97,9 @@ def parsed # Attempts to determine the content type of the response. def content_type - return nil unless response.headers + return unless response.headers - ((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip.downcase + ((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) @@ -133,16 +133,16 @@ def parser end end -OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml', 'application/xml']) do |body| +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| +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) + body = body.dup.force_encoding(Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding) - ::JSON.parse(body) + JSON.parse(body) end diff --git a/lib/oauth2/strategy/assertion.rb b/lib/oauth2/strategy/assertion.rb index 5d921fbc..800a4a78 100644 --- a/lib/oauth2/strategy/assertion.rb +++ b/lib/oauth2/strategy/assertion.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'jwt' +require "jwt" module OAuth2 module Strategy @@ -34,7 +34,7 @@ class Assertion < Base # # @raise [NotImplementedError] def authorize_url - raise(NotImplementedError, 'The authorization endpoint is not used in this strategy') + raise(NotImplementedError, "The authorization endpoint is not used in this strategy") end # Retrieve an access token given the specified client. @@ -87,13 +87,13 @@ def get_token(claims, encoding_opts, request_opts = {}, response_opts = {}) def build_request(assertion, request_opts = {}) { - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 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? + 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 diff --git a/lib/oauth2/strategy/auth_code.rb b/lib/oauth2/strategy/auth_code.rb index f3aaad0a..96eedf5d 100644 --- a/lib/oauth2/strategy/auth_code.rb +++ b/lib/oauth2/strategy/auth_code.rb @@ -10,7 +10,7 @@ class AuthCode < Base # # @param [Hash] params additional query parameters def authorize_params(params = {}) - params.merge('response_type' => 'code', 'client_id' => @client.id) + params.merge("response_type" => "code", "client_id" => @client.id) end # The authorization URL endpoint of the provider @@ -28,7 +28,7 @@ def authorize_url(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 = {"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) @@ -40,7 +40,7 @@ def get_token(code, params = {}, opts = {}) 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') + raise(ArgumentError, "client_secret is not allowed in authorize URL query params") if params.key?(:client_secret) || params.key?("client_secret") end end end diff --git a/lib/oauth2/strategy/client_credentials.rb b/lib/oauth2/strategy/client_credentials.rb index 2fba0e86..00a3ed80 100644 --- a/lib/oauth2/strategy/client_credentials.rb +++ b/lib/oauth2/strategy/client_credentials.rb @@ -10,7 +10,7 @@ class ClientCredentials < Base # # @raise [NotImplementedError] def authorize_url - raise(NotImplementedError, 'The authorization endpoint is not used in this strategy') + raise(NotImplementedError, "The authorization endpoint is not used in this strategy") end # Retrieve an access token given the specified client. @@ -18,7 +18,7 @@ def authorize_url # @param [Hash] params additional params # @param [Hash] opts options def get_token(params = {}, opts = {}) - params = params.merge('grant_type' => 'client_credentials') + params = params.merge("grant_type" => "client_credentials") @client.get_token(params, opts) end end diff --git a/lib/oauth2/strategy/implicit.rb b/lib/oauth2/strategy/implicit.rb index 5e61d1d6..e9efe5c2 100644 --- a/lib/oauth2/strategy/implicit.rb +++ b/lib/oauth2/strategy/implicit.rb @@ -10,7 +10,7 @@ class Implicit < Base # # @param [Hash] params additional query parameters def authorize_params(params = {}) - params.merge('response_type' => 'token', 'client_id' => @client.id) + params.merge("response_type" => "token", "client_id" => @client.id) end # The authorization URL endpoint of the provider @@ -25,13 +25,13 @@ def authorize_url(params = {}) # # @raise [NotImplementedError] def get_token(*) - raise(NotImplementedError, 'The token is accessed differently in this strategy') + 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') + raise(ArgumentError, "client_secret is not allowed in authorize URL query params") if params.key?(:client_secret) || params.key?("client_secret") end end end diff --git a/lib/oauth2/strategy/password.rb b/lib/oauth2/strategy/password.rb index d41ca07a..79acf654 100644 --- a/lib/oauth2/strategy/password.rb +++ b/lib/oauth2/strategy/password.rb @@ -10,7 +10,7 @@ class Password < Base # # @raise [NotImplementedError] def authorize_url - raise(NotImplementedError, 'The authorization endpoint is not used in this strategy') + raise(NotImplementedError, "The authorization endpoint is not used in this strategy") end # Retrieve an access token given the specified End User username and password. @@ -19,9 +19,11 @@ def authorize_url # @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) + params = { + "grant_type" => "password", + "username" => username, + "password" => password, + }.merge(params) @client.get_token(params, opts) end end diff --git a/lib/oauth2/version.rb b/lib/oauth2/version.rb index c4c3e3bd..42e2e99c 100644 --- a/lib/oauth2/version.rb +++ b/lib/oauth2/version.rb @@ -2,6 +2,6 @@ module OAuth2 module Version - VERSION = '2.0.10'.freeze + VERSION = "2.0.10" end end diff --git a/oauth2.gemspec b/oauth2.gemspec index 9e08984e..9c77ef34 100644 --- a/oauth2.gemspec +++ b/oauth2.gemspec @@ -1,30 +1,46 @@ # encoding: utf-8 # frozen_string_literal: true -require_relative 'lib/oauth2/version' +gem_version = + if RUBY_VERSION >= "3.1" + # Loading version into an anonymous module allows version.rb to get code coverage from SimpleCov! + # See: https://github.com/simplecov-ruby/simplecov/issues/557#issuecomment-2630782358 + Module.new.tap { |mod| Kernel.load("lib/oauth2/version.rb", mod) }::OAuth2::Version::VERSION + else + lib = File.expand_path("lib", __dir__) + $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + require "oauth2/version" + OAuth2::Version::VERSION + end 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', '< 4'] - spec.add_dependency 'snaky_hash', '~> 2.0' - spec.add_dependency 'version_gem', '~> 1.1' + # Linux distros may package ruby gems differently, + # and securely certify them independently via alternate package management systems. + # Ref: https://gitlab.com/oauth-xx/version_gem/-/issues/3 + # Hence, only enable signing if the cert_file is present. + # See CONTRIBUTING.md + default_user_cert = "certs/#{ENV.fetch("GEM_CERT_USER", ENV["USER"])}.pem" + default_user_cert_path = File.join(__dir__, default_user_cert) + cert_file_path = ENV.fetch("GEM_CERT_PATH", default_user_cert_path) + cert_chain = cert_file_path.split(",") + if cert_file_path && cert_chain.map { |fp| File.exist?(fp) } + spec.cert_chain = cert_chain + if $PROGRAM_NAME.end_with?("gem", "rake") && ARGV[0] == "build" + spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") + end + end - spec.cert_chain = ['certs/pboling.pem'] - spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem') if $PROGRAM_NAME.end_with?('gem') - - spec.authors = ['Peter Boling', 'Erik Michaels-Ober', 'Michael Bleigh'] - spec.summary = 'OAuth 2.0 Core Ruby implementation' - 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', 'oauth-ruby@googlegroups.com'] - spec.homepage = 'https://gitlab.com/oauth-xx/oauth2' - spec.licenses = 'MIT' - spec.name = 'oauth2' - spec.required_ruby_version = '>= 2.2.0' - spec.version = OAuth2::Version::VERSION - spec.post_install_message = " -You have installed oauth2 version #{OAuth2::Version::VERSION}, congratulations! + spec.authors = ["Peter Boling", "Erik Michaels-Ober", "Michael Bleigh"] + spec.summary = "OAuth 2.0 Core Ruby implementation" + 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", "oauth-ruby@googlegroups.com"] + spec.homepage = "https://gitlab.com/oauth-xx/oauth2" + spec.licenses = "MIT" + spec.name = "oauth2" + spec.required_ruby_version = ">= 2.2.0" + spec.version = gem_version + spec.post_install_message = %{ +You have installed oauth2 version #{gem_version}, congratulations! There are BREAKING changes if you are upgrading from < v2, but most will not encounter them, and updating your code should be easy! Please see: @@ -32,64 +48,88 @@ Please see: • #{spec.homepage}/-/blob/v#{spec.version}/CHANGELOG.md#200-2022-06-21-tag • Summary of most important breaking changes: #{spec.homepage}#what-is-new-for-v20 -Major updates: -1. master branch renamed to main -• Update your local: git checkout master; git branch -m master main; git branch --unset-upstream; git branch -u origin/main -2. Github has been replaced with Gitlab; I wrote about some of the reasons here: -• https://dev.to/galtzo/im-leaving-github-50ba -• Update your local: git remote set-url origin git@gitlab.com:oauth-xx/oauth2.git -3. Google Group is active (again)! +There are BUGFIXES in v2.0.10, which depending on how you relied on them instead of reporting and fixing them, may be BREAKING for you. +For more information please see: +https://railsbling.com/tags/oauth2 + +Important News: +1. Google Group is "active" (again)! • https://groups.google.com/g/oauth-ruby/c/QA_dtrXWXaE -4. Gitter Chat is active (still)! -• https://gitter.im/oauth-xx/ -5. Non-commercial support for the 2.x series will end by April, 2024. Please make a plan to upgrade to the next version prior to that date. -Support will be dropped for Ruby 2.2, 2.3, 2.4, 2.5, 2.6, 2.7 and any other Ruby versions which will also have reached EOL by then. -6. Gem releases are now cryptographically signed for security. +2. Non-commercial support for the 2.x series will end by April, 2026. Please make a plan to upgrade to the next version prior to that date. +Support will be dropped for Ruby 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1 and any other Ruby versions which will also have reached EOL by then. +3. Gem releases are now cryptographically signed with a 20-year cert, with checksums by stone_checksums. +4. I need your support. -If you are a human, please consider a donation as I move toward supporting myself with Open Source work: +If you are sentient, please consider a donation as I move toward supporting myself with Open Source work: • https://liberapay.com/pboling • https://ko-fi.com/pboling -• https://patreon.com/galtzo +• https://www.buymeacoffee.com/pboling +• https://github.com/sponsors/pboling If you are a corporation, please consider supporting this project, and open source work generally, with a TideLift subscription. • https://tidelift.com/funding/github/rubygems/oauth • Or hire me. I am looking for a job! -Please report issues, and support the project! +Please report issues, and star 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['mailing_list_uri'] = 'https://groups.google.com/g/oauth-ruby' - spec.metadata['funding_uri'] = 'https://liberapay.com/pboling' - spec.metadata['rubygems_mfa_required'] = 'true' + 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["mailing_list_uri"] = "https://groups.google.com/g/oauth-ruby" + spec.metadata["news_uri"] = "https://www.railsbling.com/tags/#{spec.name}" + spec.metadata["funding_uri"] = "https://liberapay.com/pboling" + spec.metadata["rubygems_mfa_required"] = "true" - spec.require_paths = %w[lib] + # Specify which files should be added to the gem when it is released. spec.files = Dir[ - 'lib/**/*', - 'CHANGELOG.md', - 'CODE_OF_CONDUCT.md', - 'CONTRIBUTING.md', - 'LICENSE.txt', - 'README.md', - 'SECURITY.md', + # Splats (alphabetical) + "lib/**/*", + ] + # Automatically included with gem package, no need to list again in files. + spec.extra_rdoc_files = Dir[ + # Files (alphabetical) + "CHANGELOG.md", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "LICENSE.txt", + "README.md", + "SECURITY.md", ] + spec.rdoc_options += [ + "--title", + "#{spec.name} - #{spec.summary}", + "--main", + "README.md", + "--line-numbers", + "--inline-source", + "--quiet", + ] + spec.require_paths = ["lib"] + spec.bindir = "exe" + spec.executables = [] + + 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", "< 4"]) + spec.add_dependency("snaky_hash", "~> 2.0") + spec.add_dependency("version_gem", ">= 1.1.8", "< 3") # Ruby >= 2.2.0 - 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' + spec.add_development_dependency("addressable", ">= 2") + spec.add_development_dependency("backports", ">= 3") + spec.add_development_dependency("nkf", "~> 0.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 diff --git a/spec/config/constants.rb b/spec/config/constants.rb new file mode 100644 index 00000000..0c1b205b --- /dev/null +++ b/spec/config/constants.rb @@ -0,0 +1 @@ +VERBS = %i[get post put delete patch].freeze diff --git a/spec/config/debug.rb b/spec/config/debug.rb new file mode 100644 index 00000000..140b10e3 --- /dev/null +++ b/spec/config/debug.rb @@ -0,0 +1,5 @@ +load_debugger = ENV.fetch("DEBUG", "false").casecmp("true") == 0 + +puts "LOADING DEBUGGER: #{load_debugger}" if load_debugger + +require "debug" if load_debugger diff --git a/spec/config/faraday.rb b/spec/config/faraday.rb index 2051ebb1..e9158898 100644 --- a/spec/config/faraday.rb +++ b/spec/config/faraday.rb @@ -1,3 +1,5 @@ # frozen_string_literal: true +require "faraday" + Faraday.default_adapter = :test diff --git a/spec/config/multi_xml.rb b/spec/config/multi_xml.rb index 2d788eb5..8d579c28 100644 --- a/spec/config/multi_xml.rb +++ b/spec/config/multi_xml.rb @@ -1,3 +1,5 @@ # frozen_string_literal: true +require "multi_xml" + MultiXml.parser = :rexml diff --git a/spec/config/rspec/rspec_core.rb b/spec/config/rspec/rspec_core.rb index 7ee40059..b960d8c0 100644 --- a/spec/config/rspec/rspec_core.rb +++ b/spec/config/rspec/rspec_core.rb @@ -2,7 +2,7 @@ RSpec.configure do |config| # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' + config.example_status_persistence_file_path = ".rspec_status" # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! diff --git a/spec/examples/google_spec.rb b/spec/examples/google_spec.rb index cf1aa7ee..425abb07 100644 --- a/spec/examples/google_spec.rb +++ b/spec/examples/google_spec.rb @@ -1,20 +1,20 @@ # frozen_string_literal: true -require 'jwt' +require "jwt" -RSpec.describe 'using OAuth2 with Google' do +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 + 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 + "", + "", + site: "https://accounts.google.com", + authorize_url: "/o/oauth2/auth", + token_url: "/o/oauth2/token", + auth_scheme: :request_body, ) end @@ -22,38 +22,38 @@ let(:required_claims) do { - 'iss' => '761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com', + "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', + "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', + "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, + "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, + "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', + "sub" => "some.user@example.com", # The email address of the user for which the application is requesting delegated access. } end - let(:algorithm) { 'RS256' } + 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 + 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 @@ -73,20 +73,20 @@ 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| + 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', + "Content-Type" => "application/json", }, { - 'access_token' => '1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M', - 'token_type' => 'Bearer', - 'expires_in' => 3600, + "access_token" => "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M", + "token_type" => "Bearer", + "expires_in" => 3600, }.to_json, ] end @@ -94,18 +94,18 @@ end end - context 'when passing the required claims' do + context "when passing the required claims" do let(:claims) { required_claims } - it 'sends a JWT with the 5 keys' do + 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).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(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' @@ -118,18 +118,18 @@ end end - context 'when including the optional `sub` claim' do + context "when including the optional `sub` claim" do let(:claims) { required_claims.merge(optional_claims) } - it 'sends a JWT with the 6 keys' do + 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).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(header["alg"]).to eq("RS256") expect(payload.keys).to match_array(%w[iss scope aud exp iat sub]) payload.each do |key, value| diff --git a/spec/ext/backports.rb b/spec/ext/backports.rb index 5811858b..21f76e1c 100644 --- a/spec/ext/backports.rb +++ b/spec/ext/backports.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -require 'backports/2.5.0/hash/transform_keys' +require "backports/2.5.0/hash/transform_keys" diff --git a/spec/oauth2/access_token_spec.rb b/spec/oauth2/access_token_spec.rb index ae828fa2..8c1ef0a5 100644 --- a/spec/oauth2/access_token_spec.rb +++ b/spec/oauth2/access_token_spec.rb @@ -3,57 +3,57 @@ RSpec.describe OAuth2::AccessToken do subject { described_class.new(client, token) } - let(:base_options) { {site: 'https://api.example.com'} } + 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(: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| + 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]] } + 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] } + stub.post("/oauth/token") { |_env| [200, {"Content-Type" => "application/json"}, refresh_body] } end end end - describe '.from_hash' do + 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', + :id_token => "confusing bug here", + :refresh_token => "foobar", :expires_at => Time.now.to_i + 200, - 'foo' => 'bar', + "foo" => "bar", } end - it 'return a hash equals to the hash used to initialize access token' do + 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 + context "with warning for too many tokens" do subject(:printed) do capture(:stderr) do target end end - it 'warns on STDERR' do + 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 - context 'when silenced' do + context "when silenced" do subject(:printed) do capture(:stderr) do target @@ -72,13 +72,13 @@ end end - it 'does not warn on STDERR' do - expect(printed).to eq('') + it "does not warn on STDERR" do + expect(printed).to eq("") end end end - context 'with keys in a different order to the lookup' do + context "with keys in a different order to the lookup" do subject(:printed) do capture(:stderr) do target @@ -87,12 +87,12 @@ let(:hash) do { - id_token: 'confusing bug here', + id_token: "confusing bug here", access_token: token, } end - it 'warns on STDERR and selects the correct key' do + 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 @@ -101,96 +101,96 @@ end end - describe '#initialize' do - it 'assigns client and token' do + 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') + 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') + 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'} + 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 + 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 + 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 + context "with options" do subject(:target) { described_class.new(client, token, **options) } - context 'with body mode' do + context "with body mode" do let(:mode) { :body } - let(:options) { {param_name: 'foo', header_format: 'Bearer %', mode: mode} } + 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 %') + 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 + context "with header mode" do let(:mode) { :header } let(:options) { {headers: {}, mode: mode} } - it 'sets options' do + 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 + context "with query mode" do let(:mode) { :query } - let(:options) { {params: {}, param_name: 'foo', mode: mode} } + let(:options) { {params: {}, param_name: "foo", mode: mode} } - it 'sets options' do - expect(target.options[:param_name]).to eq('foo') + 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 + context "with invalid mode" do let(:mode) { :this_is_bad } let(:options) { {mode: mode} } - it 'does not raise on initialize' 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') } + context "with request" do + subject(:request) { target.post("/token/header") } - it 'raises' do + it "raises" do block_is_expected.to raise_error("invalid :mode option of #{mode}") end end - context 'with client.options[:raise_errors] = true' do + context "with client.options[:raise_errors] = true" do let(:mode) { :this_is_bad } let(:options) { {mode: mode, raise_errors: true} } @@ -198,101 +198,101 @@ def assert_initialized_token(target) expect(client.options[:raise_errors]).to be(true) end - context 'when there is a token' do - it 'does not raise on initialize' do + 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') } + context "with request" do + subject(:request) { target.post("/token/header") } - it 'raises' do + 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) { '' } + 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}') + it "raises on initialize" do + block_is_expected.to raise_error(OAuth2::Error, {mode: :this_is_bad, raise_errors: true}.to_s) end end - context 'when there is nil token' do + 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}') + it "raises on initialize" do + block_is_expected.to raise_error(OAuth2::Error, {mode: :this_is_bad, raise_errors: true}.to_s) end end end end - context 'with client.options[:raise_errors] = false' do + 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' } + context "when there is a token" do + let(:token) { "hurdygurdy" } - it 'does not raise on initialize' do + it "does not raise on initialize" do block_is_expected.not_to raise_error end - it 'has token' do + it "has token" do expect(target.token).to eq(token) end - it 'has no refresh_token' do + 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'} } + context "when there is refresh_token" do + let(:options) { {raise_errors: false, refresh_token: "zxcv"} } - it 'does not raise on initialize' do + it "does not raise on initialize" do block_is_expected.not_to raise_error end - it 'has token' do + it "has token" do expect(target.token).to eq(token) end - it 'has refresh_token' do - expect(target.refresh_token).to eq('zxcv') + 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 empty token" do + let(:token) { "" } - context 'when there is no refresh_token' do - it 'does not raise on initialize' do + 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('') + it "has no token" do + expect(target.token).to eq("") end - it 'has no refresh_token' do + it "has no refresh_token" do expect(target.refresh_token).to be_nil end - context 'with warning for no token' do + context "with warning for no token" do subject(:printed) do capture(:stderr) do target end end - it 'warns on STDERR' do + it "warns on STDERR" do msg = <<-MSG.lstrip OAuth2::AccessToken has no token MSG @@ -301,47 +301,47 @@ def assert_initialized_token(target) end end - context 'when there is refresh_token' do - let(:options) { {raise_errors: false, refresh_token: 'qwer'} } + context "when there is refresh_token" do + let(:options) { {raise_errors: false, refresh_token: "qwer"} } - it 'does not raise on initialize' 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('') + it "has no token" do + expect(target.token).to eq("") end - it 'has refresh_token' do - expect(target.refresh_token).to eq('qwer') + it "has refresh_token" do + expect(target.refresh_token).to eq("qwer") end end end - context 'when there is nil token' do + 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 + 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('') + it "has no token" do + expect(target.token).to eq("") end - it 'has no refresh_token' do + it "has no refresh_token" do expect(target.refresh_token).to be_nil end - context 'with warning for no token' do + context "with warning for no token" do subject(:printed) do capture(:stderr) do target end end - it 'warns on STDERR' do + it "warns on STDERR" do msg = <<-MSG.lstrip OAuth2::AccessToken has no token MSG @@ -350,154 +350,154 @@ def assert_initialized_token(target) end end - context 'when there is refresh_token' do - let(:options) { {raise_errors: false, refresh_token: 'asdf'} } + context "when there is refresh_token" do + let(:options) { {raise_errors: false, refresh_token: "asdf"} } - it 'does not raise on initialize' 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('') + it "has no token" do + expect(target.token).to eq("") end - it 'has refresh_token' do - expect(target.refresh_token).to eq('asdf') + it "has refresh_token" do + expect(target.refresh_token).to eq("asdf") end end end end - context 'with client.options[:raise_errors] = true' do + 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' } + context "when there is a token" do + let(:token) { "hurdygurdy" } - it 'does not raise on initialize' do + it "does not raise on initialize" do block_is_expected.not_to raise_error end - it 'has token' do + it "has token" do expect(target.token).to eq(token) end - it 'has no refresh_token' do + 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'} } + context "when there is refresh_token" do + let(:options) { {raise_errors: true, refresh_token: "zxcv"} } - it 'does not raise on initialize' do + it "does not raise on initialize" do block_is_expected.not_to raise_error end - it 'has token' do + it "has token" do expect(target.token).to eq(token) end - it 'has refresh_token' do - expect(target.refresh_token).to eq('zxcv') + 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 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}') + context "when there is no refresh_token" do + it "raises on initialize" do + block_is_expected.to raise_error(OAuth2::Error, {raise_errors: true}.to_s) end end - context 'when there is refresh_token' do - let(:options) { {raise_errors: true, refresh_token: 'qwer'} } + context "when there is refresh_token" do + let(:options) { {raise_errors: true, refresh_token: "qwer"} } - it 'does not raise on initialize' 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('') + it "has no token" do + expect(target.token).to eq("") end - it 'has refresh_token' do - expect(target.refresh_token).to eq('qwer') + it "has refresh_token" do + expect(target.refresh_token).to eq("qwer") end end end - context 'when there is nil token' do + 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}') + context "when there is no refresh_token" do + it "raises on initialize" do + block_is_expected.to raise_error(OAuth2::Error, {raise_errors: true}.to_s) end end - context 'when there is refresh_token' do - let(:options) { {raise_errors: true, refresh_token: 'asdf'} } + context "when there is refresh_token" do + let(:options) { {raise_errors: true, refresh_token: "asdf"} } - it 'does not raise on initialize' 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('') + it "has no token" do + expect(target.token).to eq("") end - it 'has refresh_token' do - expect(target.refresh_token).to eq('asdf') + 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} + 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 + 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', + "foo" => "bar", } end - it 'initializes with an integer timestamp expires_at' do + 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 + 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 + 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 + describe "expires_latency" do let(:expires_at) { 1_530_000_000 } let(:expires_in) { 100 } let(:expires_latency) { 10 } @@ -509,224 +509,232 @@ def assert_initialized_token(target) } end - it 'sets it via options' do + 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 + 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 + 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 + 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 + 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) + expect(subject.post("/token/header").body).to include(token) end end end - context 'with :mode => :query' do + 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) + 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}") + 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 + 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) + expect(subject.post("/token/body").body.split("=").last).to eq(token) end - context 'when options[:param_name] include [number]' do + 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}") + 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}") + 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 + 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') + 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 + 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 + 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 + 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 + 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 0 (token is permanent)' do - expect(described_class.new(client, token, refresh_token: 'abaca', expires_in: 0)).not_to be_expired + it "is false if expires_in is 0 (token is permanent)" do + expect(described_class.new(client, token, refresh_token: "abaca", expires_in: 0)).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 + 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) + 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 + 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) + 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 + 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) + 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') + 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) + new(client, hash.delete("access_token"), hash) end def self.contains_token?(hash) - hash.key?('refresh_token') + hash.key?("refresh_token") end end - stub_const('NewAccessToken', custom_class) + stub_const("NewAccessToken", custom_class) end - context 'without refresh_token' do + 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) + 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') + 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 + 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 + 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) } + 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 + 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') } + 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 + 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') + 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') } + 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 + 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') + expect(refreshed.token).to eq("refreshed_foo") + expect(refreshed.refresh_token).to eq("qerwer") end end - context 'with a custom access_token_class' do + context "with a custom access_token_class" do let(:access_token_class) { NewAccessToken } - it 'returns a refresh token of NewAccessToken' do + it "returns a refresh token of NewAccessToken" do refreshed = access.refresh! expect(new_access.class).to eq(refreshed.class) @@ -734,23 +742,23 @@ def self.contains_token?(hash) end end - describe '#to_hash' do - it 'return a hash equal 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'} + describe "#to_hash" do + it "return a hash equal 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 - describe '#inspect' do - let(:inspect_result) { described_class.new(nil, 'secret-token', { refresh_token: 'secret-refresh-token' }).inspect } + describe "#inspect" do + let(:inspect_result) { described_class.new(nil, "secret-token", {refresh_token: "secret-refresh-token"}).inspect } - it 'filters out the @token value' do - expect(inspect_result).to include('@token=[FILTERED]') + it "filters out the @token value" do + expect(inspect_result).to include("@token=[FILTERED]") end - it 'filters out the @refresh_token value' do - expect(inspect_result).to include('@refresh_token=[FILTERED]') + it "filters out the @refresh_token value" do + expect(inspect_result).to include("@refresh_token=[FILTERED]") end end end diff --git a/spec/oauth2/authenticator_spec.rb b/spec/oauth2/authenticator_spec.rb index 158bc593..f7396c5e 100644 --- a/spec/oauth2/authenticator_spec.rb +++ b/spec/oauth2/authenticator_spec.rb @@ -5,128 +5,170 @@ described_class.new(client_id, client_secret, mode) end - let(:client_id) { 'foo' } - let(:client_secret) { 'bar' } + let(:client_id) { "foo" } + let(:client_secret) { "bar" } let(:mode) { :undefined } - it 'raises NotImplementedError for unknown authentication mode' do + 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 + describe "#apply" do + context "with parameter-based authentication" do let(:mode) { :request_body } - it 'adds client_id and client_secret to params' do + it "adds client_id and client_secret to params" do output = subject.apply({}) - expect(output).to eq('client_id' => 'foo', 'client_secret' => 'bar') + expect(output).to eq("client_id" => "foo", "client_secret" => "bar") end - context 'when client_id nil' do + context "when client_id nil" do let(:client_id) { nil } - it 'ignores client_id, but adds client_secret to params' do + it "ignores client_id, but adds client_secret to params" do output = subject.apply({}) - expect(output).to eq('client_secret' => 'bar') + expect(output).to eq("client_secret" => "bar") end end - it 'does not overwrite existing credentials' do - input = {'client_secret' => 's3cr3t'} + 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') + expect(output).to eq("client_id" => "foo", "client_secret" => "s3cr3t") end - it 'preserves other parameters' do - input = {'state' => '42', :headers => {'A' => 'b'}} + 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'} + "client_id" => "foo", + "client_secret" => "bar", + "state" => "42", + :headers => {"A" => "b"}, ) end - context 'passing nil secret' do + context "passing nil secret" do let(:client_secret) { nil } - it 'does not set nil client_secret' do + it "does not set nil client_secret" do output = subject.apply({}) - expect(output).to eq('client_id' => 'foo') + expect(output).to eq("client_id" => "foo") end end - context 'using tls client authentication' do + context "using tls client authentication" do let(:mode) { :tls_client_auth } - it 'does not add client_secret' do + it "does not add client_secret" do output = subject.apply({}) - expect(output).to eq('client_id' => 'foo') + expect(output).to eq("client_id" => "foo") end end - context 'using private key jwt authentication' do + context "using private key jwt authentication" do let(:mode) { :private_key_jwt } - it 'does not include client_id or client_secret' do + 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 + context "using tls_client_auth" do let(:mode) { :tls_client_auth } - context 'when client_id present' do - let(:client_id) { 'foobar' } + context "when client_id present" do + let(:client_id) { "foobar" } - it 'adds client_id to params' do + it "adds client_id to params" do output = subject.apply({}) - expect(output).to eq('client_id' => 'foobar') + expect(output).to eq("client_id" => "foobar") end end - context 'when client_id nil' do + context "when client_id nil" do let(:client_id) { nil } - it 'ignores client_id for params' do + it "ignores client_id for params" do output = subject.apply({}) expect(output).to eq({}) end end end - context 'with Basic authentication' do + 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 + it "encodes credentials in headers" do output = subject.apply({}) - expect(output).to eq(headers: {'Authorization' => header}) + expect(output).to eq(headers: {"Authorization" => header}) end - it 'does not overwrite existing credentials' do - input = {headers: {'Authorization' => 'Bearer abc123'}} + it "does not overwrite existing credentials" do + input = {headers: {"Authorization" => "Bearer abc123"}} output = subject.apply(input) - expect(output).to eq(headers: {'Authorization' => 'Bearer abc123'}) + expect(output).to eq(headers: {"Authorization" => "Bearer abc123"}) end - it 'does not overwrite existing params or headers' do - input = {'state' => '42', :headers => {'A' => 'b'}} + 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} + "state" => "42", + :headers => {"A" => "b", "Authorization" => header}, ) end end end - describe '#inspect' do - it 'filters out the @secret value' do - expect(subject.inspect).to include('@secret=[FILTERED]') + describe "#inspect" do + it "filters secret by default" do + expect(described_class.filtered_attribute_names).to include(:secret) + end + + it "filters out the @secret value" do + expect(subject.inspect).to include("@secret=[FILTERED]") + end + + context "when filter is changed" do + before do + @original_filter = described_class.filtered_attribute_names + described_class.filtered_attributes :vanilla + end + + after do + described_class.filtered_attributes(*@original_filter) + end + + it "changes the filter" do + expect(described_class.filtered_attribute_names).to eq([:vanilla]) + end + + it "does not filter out the @secret value" do + expect(subject.inspect).to include("@secret=\"bar\"") + end + end + + context "when filter is empty" do + before do + @original_filter = described_class.filtered_attribute_names + described_class.filtered_attributes + end + + after do + described_class.filtered_attributes(*@original_filter) + end + + it "changes the filter" do + expect(described_class.filtered_attribute_names).to eq([]) + end + + it "does not filter out the @secret value" do + expect(subject.inspect).to include("@secret=\"bar\"") + end end end end diff --git a/spec/oauth2/client_spec.rb b/spec/oauth2/client_spec.rb index d87d61ec..8855827f 100644 --- a/spec/oauth2/client_spec.rb +++ b/spec/oauth2/client_spec.rb @@ -1,129 +1,129 @@ # coding: utf-8 # frozen_string_literal: true -require 'nkf' +require "nkf" RSpec.describe OAuth2::Client do subject(:instance) do - described_class.new('abc', 'def', {site: 'https://api.example.com'}.merge(options)) do |builder| + 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('/unparsable_error') { |_env| [500, {'Content-Type' => 'application/json'}, '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] } + 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("/unparsable_error") { |_env| [500, {"Content-Type" => "application/json"}, "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!(: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') + 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') + 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') + it "assigns Faraday::Connection#host" do + expect(subject.connection.host).to eq("api.example.com") end - it 'leaves Faraday::Connection#ssl unset' do + 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') + 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| + described_class.new("abc", "def") do |client| client.adapter :test end.connection end - it 'defaults raise_errors to true' do + 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) + 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) + 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| + 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] } + 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 { 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) + 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) + 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/'} + it "does not mutate the opts hash argument" do + opts = {site: "http://example.com/"} opts2 = opts.dup - described_class.new 'abc', 'def', opts + described_class.new "abc", "def", opts expect(opts).to eq(opts2) end - it 'raises exception if JSON is expected, but server returns invalid JSON' do + it "raises exception if JSON is expected, but server returns invalid JSON" do client = instance - expect { client.request(:get, '/unparsable_error') }.to raise_error(JSON::ParserError) - response = client.request(:get, '/unparsable_error', raise_errors: false) + expect { client.request(:get, "/unparsable_error") }.to raise_error(JSON::ParserError) + response = client.request(:get, "/unparsable_error", raise_errors: false) expect(response.status).to eq(500) end end - describe '#site=(val)' do + describe "#site=(val)" do subject(:site) { instance.site = new_site } let(:options) do - {site: 'https://example.com/blog'} + {site: "https://example.com/blog"} end - let(:new_site) { 'https://example.com/sharpie' } + 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') + 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 + 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')) + 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 @@ -135,237 +135,237 @@ 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') + 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') + 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 + context "when a URL with path is used in the site" do let(:options) do - {site: 'https://example.com/blog'} + {site: "https://example.com/blog"} end - it 'generates an authorization URL relative to the site' do + 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 + describe ":redirect_uri option" do let(:auth_code_params) do { - 'client_id' => 'abc', - 'client_secret' => 'def', - 'code' => 'code', - 'grant_type' => 'authorization_code', + "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') + 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| + 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"}'] + 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') + client.auth_code.get_token("code") end end - context 'when set' do - before { subject.options[:redirect_uri] = 'https://site.com/oauth/callback' } + 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') + 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| + 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"}'] + 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') + 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| + 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"}'] + 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) + 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| + 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"}'] + 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) + 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| + 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"}'] + 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) + 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| + 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"}'] + 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) + 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' + describe "#connection" do + context "when debugging" do + include_context "with stubbed env" before do - stub_env('OAUTH_DEBUG' => debug_value) + stub_env("OAUTH_DEBUG" => debug_value) end - context 'when OAUTH_DEBUG=true' do - let(:debug_value) { 'true' } + context "when OAUTH_DEBUG=true" do + let(:debug_value) { "true" } - it 'smoothly handles successive requests' do + it "smoothly handles successive requests" do silence_all do # first request (always goes smoothly) - subject.request(:get, '/success') + subject.request(:get, "/success") end expect do # second request (used to throw Faraday::RackBuilder::StackLocked) - subject.request(:get, '/success') + subject.request(:get, "/success") end.not_to raise_error end - it 'prints both request and response bodies to STDOUT' do + 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') + 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' + 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' } + context "when OAUTH_DEBUG=false" do + let(:debug_value) { "false" } - it 'smoothly handles successive requests' do + it "smoothly handles successive requests" do silence_all do # first request (always goes smoothly) - subject.request(:get, '/success') + subject.request(:get, "/success") end expect do # second request (used to throw Faraday::RackBuilder::StackLocked) - subject.request(:get, '/success') + subject.request(:get, "/success") end.not_to raise_error end - it 'prints nothing to STDOUT' do + it "prints nothing to STDOUT" do printed = capture(:stdout) do - subject.request(:get, '/success') - subject.request(:get, '/reflect', body: 'this is magical') + subject.request(:get, "/success") + subject.request(:get, "/reflect", body: "this is magical") end - expect(printed).to eq '' + expect(printed).to eq "" end end end end - describe '#authorize_url' do + describe "#authorize_url" do subject { instance.authorize_url(params) } - context 'when space included' do + context "when space included" do let(:params) do - {scope: 'email profile'} + {scope: "email profile"} end - it 'encoded as %20' do - expect(subject).to include 'email%20profile' + it "encoded as %20" do + expect(subject).to include "email%20profile" end end end - describe '#request' do - it 'works with a null response body' do - expect(subject.request(:get, 'empty_get').body).to eq('') + 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') + 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') + 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 + context "with ENV" do + include_context "with stubbed env" + context "when OAUTH_DEBUG=true" do before do - stub_env('OAUTH_DEBUG' => 'true') + stub_env("OAUTH_DEBUG" => "true") end - it 'outputs to $stdout when OAUTH_DEBUG=true' do + it "outputs to $stdout when OAUTH_DEBUG=true" do output = capture(:stdout) do - subject.request(:get, '/success') + subject.request(:get, "/success") end logs = [ - 'request: GET https://api.example.com/success', - 'response: Status 200', + "request: GET https://api.example.com/success", + "response: Status 200", 'response: Content-Type: "text/awesome"', ] expect(output).to include(*logs) @@ -373,62 +373,62 @@ end end - it 'posts a body' do - response = subject.request(:post, '/reflect', body: 'foo=bar') - expect(response.body).to eq('foo=bar') + 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') + 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') + 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') + 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') + 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') + 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 + it "obeys the :max_redirects option" do max_redirects = subject.options[:max_redirects] subject.options[:max_redirects] = 0 - response = subject.request(:get, '/redirect') + 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 + it "returns if raise_errors is false" do subject.options[:raise_errors] = false - response = subject.request(:get, '/unauthorized') + response = subject.request(:get, "/unauthorized") expect(response.status).to eq(401) - expect(response.headers).to eq('Content-Type' => 'application/json') + 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' + 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| + 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') + 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| + 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}/) @@ -436,257 +436,254 @@ end end - it 'provides the response in the Exception' do - expect { subject.request(:get, '/error') }.to raise_error do |ex| + 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| + 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 + 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) + 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| + 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') } + context "with Faraday::ConnectionFailed" do + let(:faraday_exception) { Faraday::ConnectionFailed.new("fail") } let(:expected_exception) { OAuth2::ConnectionError } - it_behaves_like 'failed connection handler' + it_behaves_like "failed connection handler" end - context 'with Faraday::TimeoutError' do - let(:faraday_exception) { Faraday::TimeoutError.new('timeout') } + context "with Faraday::TimeoutError" do + let(:faraday_exception) { Faraday::TimeoutError.new("timeout") } let(:expected_exception) { OAuth2::TimeoutError } - it_behaves_like 'failed connection handler' + it_behaves_like "failed connection handler" end end end - describe '#get_token' do - it 'returns a configured AccessToken' do + 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')] + 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') + expect(token.token).to eq("the-token") end - context 'when parse: :automatic' do - it 'returns a configured AccessToken' do + 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')] + 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') + expect(token.token).to eq("the-token") end end - context 'when parse: :xml but response is JSON' do - it 'returns a configured AccessToken' do + 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')] + 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' - ) + expect { client.get_token(parse: :xml) }.to raise_error(MultiXml::ParseError) end end - context 'when parse is fuzzed' do - it 'returns a configured AccessToken' do + 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')] + stub.post("/oauth/token") do + [200, {"Content-Type" => "application/json"}, JSON.dump("access_token" => "the-token")] end end - token = client.get_token(parse: 'random') + token = client.get_token(parse: "random") expect(token).to be_a OAuth2::AccessToken - expect(token.token).to eq('the-token') + expect(token.token).to eq("the-token") end end - context 'when parse is correct' do - it 'returns a configured AccessToken' do + 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')] + 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') + 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 + 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')] + 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') + 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 + 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')] + 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'}) + 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') + 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 + 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')] + 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 + 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') + 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')] + [200, {"Content-Type" => "application/json"}, JSON.dump("access_token" => "the-token")] end end client.get_token({}) end - it 'authenticates with JSON' do + 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')] + 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'}) + client.get_token(headers: {"Content-Type" => "application/json"}) end - it 'sets the response object on the access token' do + 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')] + 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') + expect(token.response.parsed).to eq("access_token" => "the-token") end - context 'when the :raise_errors flag is set to false' do + context "when the :raise_errors flag is set to false" do let(:body) { nil } let(:status_code) { 500 } - let(:content_type) { 'application/json' } + 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] + stub.post("/oauth/token") do + [status_code, {"Content-Type" => content_type}, body] end end end - context 'when the request body is nil' do + context "when the request body is nil" do subject(:get_token) { client.get_token({}) } - it 'raises error JSON::ParserError' do + 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 + 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 + 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 + 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') } + 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 + 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') + 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' } + 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 + 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 + 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(: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)] + stub.post("/oauth/token") do + [200, {"Content-Type" => content_type}, JSON.dump(payload)] end end end @@ -696,96 +693,96 @@ attr_accessor :response def self.from_hash(client, hash) - new(client, hash.delete('custom_token')) + new(client, hash.delete("custom_token")) end def self.contains_token?(hash) - hash.key?('custom_token') + hash.key?("custom_token") end end - stub_const('CustomAccessToken', custom_class) + stub_const("CustomAccessToken", custom_class) end - it 'returns the parsed :custom_token from body' do + it "returns the parsed :custom_token from body" do client.get_token({}) end - context 'when the :raise_errors flag is set to true' do + 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 + it "raises an error" do expect { client.get_token({}) }.to raise_error(OAuth2::Error) end - context 'when the legacy extract_access_token' do + context "when the legacy extract_access_token" do let(:extract_access_token) do proc do |client, hash| - token = hash['data']['access_token'] + 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 + 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 + context "when status code is 200" do let(:status_code) { 200 } - context 'when the request body is blank' do + context "when the request body is blank" do let(:payload) { {} } - it 'raises an error' do + 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' } + context "when Content-Type is not JSON" do + let(:content_type) { "text/plain" } + let(:body) { "hello world" } - it 'raises an error' do + 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 + context "when access token instance responds to response=" do let(:options) { {access_token_class: CustomAccessToken, raise_errors: false} } - it 'sets response' do + it "sets response" do expect(client.get_token({}).response).to be_a(OAuth2::Response) end end - context 'when request has a block' do + context "when request has a block" do subject(:request) do client.get_token({}) do |req| - raise 'Block is executing' + 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') + 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' } + 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)] + stub.post("/oauth/token") do + [200, {"Content-Type" => content_type}, JSON.dump(payload)] end end end @@ -796,31 +793,31 @@ def initialize(client, hash) end def self.from_hash(client, hash) - new(client, hash.delete('custom_token')) + new(client, hash.delete("custom_token")) end def self.contains_token?(hash) - hash.key?('custom_token') + hash.key?("custom_token") end end - stub_const('StrangeAccessToken', custom_class) + stub_const("StrangeAccessToken", custom_class) end - context 'when the :raise_errors flag is set to true' do + 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 + 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 + context "when access token instance does not responds to response=" do let(:options) { {access_token_class: StrangeAccessToken} } - let(:payload) { {'custom_token' => 'the-token'} } + let(:payload) { {"custom_token" => "the-token"} } - it 'sets response' do + it "sets response" do token_access = client.get_token({}) expect(token_access).to be_a(StrangeAccessToken) expect(token_access).not_to respond_to(:response=) @@ -828,58 +825,58 @@ def self.contains_token?(hash) end end - context 'when request has a block' do + context "when request has a block" do subject(:request) do client.get_token({}) do |req| - raise 'Block is executing' + 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') + it "sets response" do + block_is_expected.to raise_error("Block is executing") end end end - describe 'with extract_access_token option' do + 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'})] + 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'] + token = hash["data"]["access_token"] OAuth2::AccessToken.new(client, token, hash) end end - it 'returns a configured AccessToken' do + it "returns a configured AccessToken" do token = client.get_token({}) expect(token).to be_a OAuth2::AccessToken - expect(token.token).to eq('the-token') + expect(token.token).to eq("the-token") end - context 'with deprecation' do + context "with deprecation" do subject(:printed) do capture(:stderr) do client.get_token({}) end end - it 'warns on STDERR' do + 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 + context "on request" do subject(:printed) do capture(:stderr) do client.get_token({}, {}, extract_access_token) @@ -888,13 +885,13 @@ def self.contains_token?(hash) let(:client) do stubbed_client do |stub| - stub.post('/oauth/token') do - [200, {'Content-Type' => 'application/json'}, JSON.dump('data' => {'access_token' => 'the-token'})] + 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 + 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 @@ -904,73 +901,73 @@ def self.contains_token?(hash) end end - it 'forwards given token parameters' do + 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')] + 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 + 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 + 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')] + 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 + 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| + 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) + it "instantiates an HTTP Method with this client" do + expect(subject.http_method).to be_a(Symbol) end - it 'instantiates an AuthCode strategy with this client' do - expect(subject.auth_code).to be_kind_of(OAuth2::Strategy::AuthCode) + it "instantiates an AuthCode strategy with this client" do + expect(subject.auth_code).to be_a(OAuth2::Strategy::AuthCode) end - it 'instantiates an Implicit strategy with this client' do - expect(subject.implicit).to be_kind_of(OAuth2::Strategy::Implicit) + it "instantiates an Implicit strategy with this client" do + expect(subject.implicit).to be_a(OAuth2::Strategy::Implicit) end - context 'with SSL options' do + context "with SSL options" do subject do - cli = described_class.new('abc', 'def', site: 'https://api.example.com', ssl: {ca_file: 'foo.pem'}) + 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') + 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 + context "without a connection-configuration block" do subject do - described_class.new('abc', 'def', site: 'https://api.example.com') + described_class.new("abc", "def", site: "https://api.example.com") end - it 'applies default faraday middleware to the connection' do + it "applies default faraday middleware to the connection" do expect(subject.connection.builder.handlers).to include(Faraday::Request::UrlEncoded) end end - describe '#inspect' do - it 'filters out the @secret value' do - expect(subject.inspect).to include('@secret=[FILTERED]') + describe "#inspect" do + it "filters out the @secret value" do + expect(subject.inspect).to include("@secret=[FILTERED]") end end end diff --git a/spec/oauth2/error_spec.rb b/spec/oauth2/error_spec.rb index fb1ed492..893dce35 100644 --- a/spec/oauth2/error_spec.rb +++ b/spec/oauth2/error_spec.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# encoding: utf-8 # frozen_string_literal: true class StirredHash < Hash @@ -14,7 +14,7 @@ class XmledString < String � Cool � XmledString -'.freeze +' def to_str XML end @@ -27,119 +27,119 @@ def to_str raw_response = Faraday::Response.new( status: 418, response_headers: response_headers, - body: response_body + 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 } + 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 + 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 + describe "attr_readers" do + it "has code" do expect(subject).to respond_to(:code) end - it 'has description' do + it "has description" do expect(subject).to respond_to(:description) end - it 'has response' do + it "has response" do expect(subject).to respond_to(:response) end end - context 'when the response is parsed' do + context "when the response is parsed" do let(:response_body) { response_hash.to_json } - let(:response_hash) { {text: 'Coffee brewing failed'} } + let(:response_hash) { {text: "Coffee brewing failed"} } - context 'when the response has an error and error_description' do + 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' + 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') + 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') + 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 + 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 "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 + context "with invalid characters present" do before do - response_body.gsub!('stout', "\255 invalid \255") + response_body.gsub!("stout", "\255 invalid \255") end - it 'replaces them' do + 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')) + 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 �') + 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 + context "with undefined characters present" do before do - response_hash['error_description'] += ": 'A magical voyage of tea 🍵'" + response_hash["error_description"] += ": 'A magical voyage of tea 🍵'" end - it 'replaces them' do - raise 'Undefined characters not replaced' unless subject.message.include?('tea �') + 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'} } + 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 + 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'} } + context "when using :json parser with non-encodable data" do + let(:response_headers) { {"Content-Type" => "application/hal+json"} } let(:response_body) do - StirredHash.new( + StirredHash[ "_links": { - "self": {"href": '/orders/523'}, - "warehouse": {"href": '/warehouse/56'}, - "invoice": {"href": '/invoices/873'}, + "self": {"href": "/orders/523"}, + "warehouse": {"href": "/warehouse/56"}, + "invoice": {"href": "/invoices/873"}, }, - "currency": 'USD', - "status": 'shipped', - "total": 10.20 - ) + "currency": "USD", + "status": "shipped", + "total": 10.20, + ] end before do @@ -147,13 +147,13 @@ def to_str expect(response_body).to respond_to(:to_str) end - it 'does not force encode the message' do + 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'} } + context "when using :xml parser" do + let(:response_headers) { {"Content-Type" => "text/xml"} } let(:response_body) do XmledString.new(XmledString::XML) end @@ -162,482 +162,492 @@ def to_str expect(response_body).to respond_to(:to_str) end - it 'parses the XML' do + 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'} } + 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}') + it "just returns the thing if it can" do + expect(subject.message).to eq({hello: :world}.to_s) end end end - it 'sets the code attribute to nil' do + it "sets the code attribute to nil" do expect(subject.code).to be_nil end - it 'sets the description attribute' do + it "sets the description attribute" do expect(subject.description).to be_nil end - context 'when there is no error description' do + 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') + 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 + 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 + it "does not set code" do expect(subject.code).to be_nil end - it 'does not set description' do + it "does not set description" do expect(subject.description).to be_nil end end - context 'when there is code (error)' do + 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' + 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 + 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', + "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 "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 + context "with invalid characters present" do before do - response_body.gsub!('stout', "\255 invalid \255") + response_body.gsub!("stout", "\255 invalid \255") end - it 'replaces them' do + 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')) + 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 �') + 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 + context "with undefined characters present" do before do - response_hash['error_description'] += ": 'A magical voyage of tea 🍵'" + response_hash["error_description"] += ": 'A magical voyage of tea 🍵'" end - it 'replaces them' do - raise 'Undefined characters not replaced' unless subject.message.include?('tea �') + 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'} } + 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 + 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') + 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') + 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 + 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' + 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 + 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', + "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 "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 + context "with invalid characters present" do before do - response_body.gsub!('brewing', "\255 invalid \255") + response_body.gsub!("brewing", "\255 invalid \255") end - it 'replaces them' do + 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')) + 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 �') + 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'} } + 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 + 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') + 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 + 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 + context "when there is error_description but no code (error)" do before do - response_hash['error_description'] = 'Short and stout' - response_hash.delete('error') + response_hash["error_description"] = "Short and stout" + response_hash.delete("error") end - it 'prepends to the error message with a return character' do + 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', + "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 "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 + context "with invalid characters present" do before do - response_body.gsub!('stout', "\255 invalid \255") + response_body.gsub!("stout", "\255 invalid \255") end - it 'replaces them' do + 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')) + 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 �') + 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 + context "with undefined characters present" do before do - response_hash['error_description'] += ": 'A magical voyage of tea 🍵'" + response_hash["error_description"] += ": 'A magical voyage of tea 🍵'" end - it 'replaces them' do - raise 'Undefined characters not replaced' unless subject.message.include?('tea �') + 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'} } + 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 + 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 + 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') + 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 + context "when the response is simple hash, not parsed" do subject { described_class.new(response_hash) } - let(:response_hash) { {text: 'Coffee brewing failed'} } + let(:response_hash) { {text: "Coffee brewing failed"} } - it 'sets the code attribute to nil' do + it "sets the code attribute to nil" do expect(subject.code).to be_nil end - it 'sets the description attribute' do + it "sets the description attribute" do expect(subject.description).to be_nil end - context 'when the response has an error and error_description' do + 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' + 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') + 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') + 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 + 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"}', - ] + {:text => "Coffee brewing failed", "error_description" => "Short and stout", "error" => "i_am_a_teapot"}.to_s, + ], ) end - context 'when using :xml parser with non-String-like thing' do - let(:response_headers) { {'Content-Type' => 'text/xml'} } + 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\"}") + it "just returns whatever it can" do + expect(subject.message.each_line.to_a).to eq( + [ + "i_am_a_teapot: Short and stout\n", + {:hello => :world, "error_description" => "Short and stout", "error" => "i_am_a_teapot"}.to_s, + ], + ) end end end - context 'when using :xml parser with non-String-like thing' do - let(:response_headers) { {'Content-Type' => 'text/xml'} } + 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}') + it "just returns the thing if it can" do + expect(subject.message).to eq({hello: :world}.to_s) end end - context 'when there is no error description' do + 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') + 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 + 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"}' + expect(subject.message).to eq({text: "Coffee brewing failed"}.to_s) end - it 'does not set code' do + it "does not set code" do expect(subject.code).to be_nil end - it 'does not set description' do + it "does not set description" do expect(subject.description).to be_nil end end - context 'when there is code (error)' do + 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' + 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 + 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"}', - ] + {:text => "Coffee brewing failed", "error_description" => "Short and stout", "error" => "i_am_a_teapot", "status" => "418"}.to_s, + ], ) end - it 'sets the code attribute' do - expect(subject.code).to eq('i_am_a_teapot') + 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') + 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 + 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' + 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') + 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 + 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 + 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"}', - ] + {:text => "Coffee brewing failed", "error" => "i_am_a_teapot", "status" => "418"}.to_s, + ], ) end end - context 'when there is error_description but no code (error)' do + context "when there is error_description but no code (error)" do before do - response_hash['error_description'] = 'Short and stout' - response_hash.delete('error') + response_hash["error_description"] = "Short and stout" + response_hash.delete("error") end - it 'prepends to the error message with a return character' do + 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"}', - ] + {:text => "Coffee brewing failed", "error_description" => "Short and stout"}.to_s, + ], ) 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'} } + 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\"}") + it "does not try to encode the message string" do + expect(subject.message.each_line.to_a).to eq( + [ + "Short and stout\n", + {:text => "Coffee brewing failed", "error_description" => "Short and stout"}.to_s, + ], + ) end end - it 'sets the code attribute' do + 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') + 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 + context "when the response is not a hash, not parsed" do subject { described_class.new(response_thing) } - let(:response_thing) { [200, 'Success'] } + let(:response_thing) { [200, "Success"] } - it 'sets the code attribute to nil' do + it "sets the code attribute to nil" do expect(subject.code).to be_nil end - it 'sets the description attribute' do + it "sets the description attribute" do expect(subject.description).to be_nil end - it 'sets the body attribute' do + it "sets the body attribute" do expect(subject.body).to eq(response_thing) end - it 'sets the response attribute' do + 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' } + 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 + 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 + it "does not set code" do expect(subject.code).to be_nil end - it 'does not set description' do + it "does not set description" do expect(subject.description).to be_nil end end - describe 'parsing json' do - it 'does not blow up' do + 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 diff --git a/spec/oauth2/response_spec.rb b/spec/oauth2/response_spec.rb index 4ceb228f..5a0689f2 100644 --- a/spec/oauth2/response_spec.rb +++ b/spec/oauth2/response_spec.rb @@ -5,201 +5,204 @@ let(:raw_response) { Faraday::Response.new(status: status, response_headers: headers, body: body) } let(:status) { 200 } - let(:headers) { {'foo' => 'bar'} } - let(:body) { 'foo' } + let(:headers) { {"foo" => "bar"} } + let(:body) { "foo" } - describe '#initialize' do - it 'returns the status, headers and body' do + 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 + describe ".register_parser" do let(:response) do - double('response', headers: {'Content-Type' => 'application/foo-bar'}, - status: 200, - body: 'baz') + 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| + described_class.register_parser(:foobar, ["application/foo-bar"]) do |body| "foobar #{body}" end end - it 'adds to the content types and parsers' do + 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') + 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') + 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 + describe "#content_type" do + context "when headers are blank" do let(:headers) { nil } - it 'returns nil' do + 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'} } + context "when content-type is not present" do + let(:headers) { {"a fuzzy" => "fuzzer"} } - it 'returns empty string' do - expect(subject.content_type).to eq('') + 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'} } + 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') + 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 + describe "#parsed" do subject(:parsed) do - headers = {'Content-Type' => content_type} - response = double('response', headers: headers, body: body) + 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 + 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') + 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') + 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') + 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') + 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') + 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') + 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) } + 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' + 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) } + 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' + 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) } + 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' + 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' } + 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 + 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') + 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') + 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') + 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') + 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') + 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') + 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'} + 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) + 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'} + 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) + 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) + 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') + 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 = '' + headers = {"Content-Type" => "text/html"} + body = "" - response = double('response', headers: headers, body: body) + response = double("response", headers: headers, body: body) expect(JSON).not_to receive(:parse) expect(Rack::Utils).not_to receive(:parse_query) @@ -209,10 +212,10 @@ 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} + 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) + response = double("response", headers: headers, body: body) expect(JSON).not_to receive(:parse) expect(Rack::Utils).not_to receive(:parse_query) @@ -221,93 +224,93 @@ 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) + 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') + 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) + 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 + 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 + it "supports registered parsers with arity == 0; passing nothing" do described_class.register_parser(:arity_0, []) do - 'a-ok' + "a-ok" end - headers = {'Content-Type' => 'text/html'} - body = '' - response = double('response', headers: headers, body: body) + 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') + 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) + 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' + "a-ok" end subject = described_class.new(response, parse: :arity_2) - expect(subject.parsed).to eq('a-ok') + 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) + 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' + "a-ok" end subject = described_class.new(response, parse: :arity_3) - expect(subject.parsed).to eq('a-ok') + 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) + 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' }) + subject = described_class.new(response, parse: -> { "a-ok" }) - expect(subject.parsed).to eq('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) + 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) @@ -315,30 +318,30 @@ end end - context 'with xml parser registration' do - it 'tries to load multi_xml.rb and use it' do + 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'} + 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'}) + 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'} + 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'}) + 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 + describe "converting to json" do + it "does not blow up" do expect { subject.to_json }.not_to raise_error end end diff --git a/spec/oauth2/strategy/assertion_spec.rb b/spec/oauth2/strategy/assertion_spec.rb index 43e498eb..64498d53 100644 --- a/spec/oauth2/strategy/assertion_spec.rb +++ b/spec/oauth2/strategy/assertion_spec.rb @@ -1,26 +1,26 @@ # frozen_string_literal: true -require 'openssl' -require 'jwt' +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 = 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| + 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"}'] + 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!' + raise "Please define @response_format to choose a response content type!" end end end @@ -30,33 +30,33 @@ let(:auth_scheme) { :request_body } - describe '#authorize_url' do - it 'raises NotImplementedError' do + 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' } + 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', + 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', + sub: "12345", + custom_claim: "ling cod", } end before do - @response_format = 'json' + @response_format = "json" end - describe 'assembling a JWT assertion' do + describe "assembling a JWT assertion" do let(:jwt) do payload, header = JWT.decode(@request_body[:assertion], key, true, algorithm: algorithm) {payload: payload, header: header} @@ -65,13 +65,13 @@ let(:payload) { jwt[:payload] } let(:header) { jwt[:header] } - shared_examples_for 'encodes the JWT' do - it 'indicates algorithm in the header' do + 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) + expect(header["alg"]).to eq(algorithm) end - it 'has claims' do + 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| @@ -80,136 +80,136 @@ end end - context 'when encoding as HS256' do - let(:algorithm) { 'HS256' } - let(:key) { 'super_secret!' } + 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? + raise "No request made!" if @request_body.nil? end - it_behaves_like 'encodes the JWT' + it_behaves_like "encodes the JWT" - context 'with real key' do - let(:key) { '1883be842495c3b58f68ca71fbf1397fbb9ed2fdf8990f8404a25d0a1b995943' } + context "with real key" do + let(:key) { "1883be842495c3b58f68ca71fbf1397fbb9ed2fdf8990f8404a25d0a1b995943" } - it_behaves_like 'encodes the JWT' + it_behaves_like "encodes the JWT" end end - context 'when encoding as RS256' do - let(:algorithm) { 'RS256' } + 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? + raise "No request made!" if @request_body.nil? end - it_behaves_like 'encodes the JWT' + it_behaves_like "encodes the JWT" - context 'with private key' do - let(:private_key_file) { 'spec/fixtures/RS256/jwtRS256.key' } - let(:password) { '' } + 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' + it_behaves_like "encodes the JWT" end end - context 'with bad encoding params' do + 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' } + describe "non-supported algorithms" do + let(:algorithm) { "the blockchain" } + let(:key) { "machine learning" } - it 'raises NotImplementedError' do + it "raises JWT::EncodeError" 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) + expect { client_assertion.get_token(claims, encoding_opts) }.to raise_error(JWT::EncodeError, "Unsupported signing method") end end - describe 'of a wrong object type' do - let(:encoding_opts) { 'the cloud' } + describe "of a wrong object type" do + let(:encoding_opts) { "the cloud" } - it 'raises ArgumentError' do + 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 + describe "missing encoding_opts[:algorithm]" do before do encoding_opts.delete(:algorithm) end - it 'raises ArgumentError' do + 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 + describe "missing encoding_opts[:key]" do before do encoding_opts.delete(:key) end - it 'raises ArgumentError' do + 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 + 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 + 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[: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') + 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'}) + 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[: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') + 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 + context "when using :auth_scheme => :basic_auth" do let(:auth_scheme) { :basic_auth } - it 'includes assertion and grant_type by default' do + 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[: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'}) + 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[: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[:scope]).to eq("dover sole") end end end - describe 'returning the response' do + describe "returning the response" do let(:access_token) { client_assertion.get_token(claims, {algorithm: algorithm, key: key}, {}, response_opts) } let(:response_opts) { {} } @@ -219,42 +219,42 @@ @response_format = mode end - it 'returns an AccessToken' do + it "returns an AccessToken" do expect(access_token).to be_an(OAuth2::AccessToken) end - it 'returns AccessToken with same Client' do + 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') + it "returns AccessToken with #token" do + expect(access_token.token).to eq("salmon") end - it 'returns AccessToken with #expires_in' do + it "returns AccessToken with #expires_in" do expect(access_token.expires_in).to eq(600) end - it 'returns AccessToken with #expires_at' do + 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') + 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'} } + context "with custom response_opts" do + let(:response_opts) { {"custom_token_option" => "mackerel"} } - it 'passes them into the token params' do + 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 + context "when no custom opts are passed in" do let(:response_opts) { {} } - it 'does not set any params by default' do + it "does not set any params by default" do expect(access_token.params).to eq({}) end end diff --git a/spec/oauth2/strategy/auth_code_spec.rb b/spec/oauth2/strategy/auth_code_spec.rb index e1997b1a..ed3817b4 100644 --- a/spec/oauth2/strategy/auth_code_spec.rb +++ b/spec/oauth2/strategy/auth_code_spec.rb @@ -4,125 +4,125 @@ 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(: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| + 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] + 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| + 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] + 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] + 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') + 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') + 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') + 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) + 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) + 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' + 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 + describe "#get_token (with dynamic redirect_uri)" do before do - @mode = 'json' + @mode = "json" client.options[:token_method] = :post client.options[:auth_scheme] = :request_body client.options[:redirect_uri] = redirect_uri end - it 'does not raise error' do + it "does not raise error" do expect { subject.get_token(code, redirect_uri: redirect_uri) }.not_to raise_error end - it 'gets a token' do + it "gets a token" do access = subject.get_token(code, redirect_uri: redirect_uri) - expect(access.token).to eq('salmon') + expect(access.token).to eq("salmon") 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é') } + 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' + @mode = "json" client.options[:token_method] = :post client.options[:auth_scheme] = :request_body end - it 'does not raise an error' do + 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 + it "does not create an error instance" do expect(OAuth2::Error).not_to receive(:new) subject.get_token(code) end - it 'can get a token' do + it "can get a token" do access = subject.get_token(code) - expect(access.token).to eq('salmon') + expect(access.token).to eq("salmon") end end - describe '#get_token (from microsoft)' do + describe "#get_token (from microsoft)" do it "doesn't treat an OpenID Connect token with only an id_token (like from Microsoft) as invalid" do - @mode = 'from_microsoft' + @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') + expect(@access.token).to eq("i_am_MSFT") end end @@ -136,28 +136,28 @@ @access = subject.get_token(code) end - it 'returns AccessToken with same Client' do + 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') + 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') + it "returns AccessToken with #refresh_token" do + expect(@access.refresh_token).to eq("trout") end - it 'returns AccessToken with #expires_in' do + 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) + it "returns AccessToken with #expires_at" do + expect(@access.expires_at).to be_a(Integer) end - it 'returns AccessToken with params accessible via []' do - expect(@access['extra_param']).to eq('steve') + it "returns AccessToken with params accessible via []" do + expect(@access["extra_param"]).to eq("steve") end end end diff --git a/spec/oauth2/strategy/base_spec.rb b/spec/oauth2/strategy/base_spec.rb index 33b98389..4d0e4dec 100644 --- a/spec/oauth2/strategy/base_spec.rb +++ b/spec/oauth2/strategy/base_spec.rb @@ -1,7 +1,7 @@ # 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 + it "initializes with a Client" do + expect { described_class.new(OAuth2::Client.new("abc", "def")) }.not_to raise_error end end diff --git a/spec/oauth2/strategy/client_credentials_spec.rb b/spec/oauth2/strategy/client_credentials_spec.rb index b9480098..e0baad92 100644 --- a/spec/oauth2/strategy/client_credentials_spec.rb +++ b/spec/oauth2/strategy/client_credentials_spec.rb @@ -3,30 +3,30 @@ RSpec.describe OAuth2::Strategy::ClientCredentials do subject { client.client_credentials } - let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout' } + 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| + 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) + 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] + 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| + 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] + 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 @@ -34,8 +34,8 @@ end end - describe '#authorize_url' do - it 'raises NotImplementedError' do + describe "#authorize_url" do + it "raises NotImplementedError" do expect { subject.authorize_url }.to raise_error(NotImplementedError) end end @@ -49,49 +49,49 @@ @access = subject.get_token end - it 'returns AccessToken with same Client' do + 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') + 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') + it "returns AccessToken without #refresh_token" do + expect(@access.refresh_token).to eq("trout") end - it 'returns AccessToken with #expires_in' do + it "returns AccessToken with #expires_in" do expect(@access.expires_in).to eq(600) end - it 'returns AccessToken with #expires_at' do + 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 + describe "#get_token (with extra header parameters)" do before do - @mode = 'json' - @access = subject.get_token(headers: {'X-Extra-Header' => 'wow'}) + @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') + 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 + describe "#get_token (with option overriding response)" do before do - @mode = 'json' - @access = subject.get_token({}, {'refresh_token' => 'guppy'}) + @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') + it "override is applied" do + expect(@access.token).to eq("salmon") + expect(@access.refresh_token).to eq("guppy") end end end diff --git a/spec/oauth2/strategy/implicit_spec.rb b/spec/oauth2/strategy/implicit_spec.rb index 18588fea..b443da48 100644 --- a/spec/oauth2/strategy/implicit_spec.rb +++ b/spec/oauth2/strategy/implicit_spec.rb @@ -3,37 +3,37 @@ RSpec.describe OAuth2::Strategy::Implicit do subject { client.implicit } - let(:client) { OAuth2::Client.new('abc', 'def', site: 'http://api.example.com') } + 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') + 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') + 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') + 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) + 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) + 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' + 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 + describe "#get_token" do + it "raises NotImplementedError" do expect { subject.get_token }.to raise_error(NotImplementedError) end end diff --git a/spec/oauth2/strategy/password_spec.rb b/spec/oauth2/strategy/password_spec.rb index 040b6455..b3dbe9e8 100644 --- a/spec/oauth2/strategy/password_spec.rb +++ b/spec/oauth2/strategy/password_spec.rb @@ -4,16 +4,16 @@ subject { client.password } let(:client) do - cli = OAuth2::Client.new('abc', 'def', site: 'http://api.example.com') + 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| + 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"}'] + 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 @@ -22,8 +22,8 @@ cli end - describe '#authorize_url' do - it 'raises NotImplementedError' do + describe "#authorize_url" do + it "raises NotImplementedError" do expect { subject.authorize_url }.to raise_error(NotImplementedError) end end @@ -32,26 +32,26 @@ describe "#get_token (#{mode})" do before do @mode = mode - @access = subject.get_token('username', 'password') + @access = subject.get_token("username", "password") end - it 'returns AccessToken with same Client' do + 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') + 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') + it "returns AccessToken with #refresh_token" do + expect(@access.refresh_token).to eq("trout") end - it 'returns AccessToken with #expires_in' do + it "returns AccessToken with #expires_in" do expect(@access.expires_in).to eq(600) end - it 'returns AccessToken with #expires_at' do + it "returns AccessToken with #expires_at" do expect(@access.expires_at).not_to be_nil end end diff --git a/spec/oauth2/version_spec.rb b/spec/oauth2/version_spec.rb index 250a3039..1f2edbe1 100644 --- a/spec/oauth2/version_spec.rb +++ b/spec/oauth2/version_spec.rb @@ -1,39 +1,39 @@ # frozen_string_literal: true RSpec.describe OAuth2::Version do - it 'has a version number' do + it "has a version number" do expect(described_class).not_to be_nil end - it 'can be a string' do + it "can be a string" do expect(described_class.to_s).to be_a(String) end - it 'allows Constant access' do + 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) + 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 + it "major version is an integer" do expect(described_class.major).to be_a(Integer) end - it 'minor version is an integer' do + it "minor version is an integer" do expect(described_class.minor).to be_a(Integer) end - it 'patch version is an integer' do + it "patch version is an integer" do expect(described_class.patch).to be_a(Integer) end - it 'returns a Hash' do + 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 + it "returns an Array" do expect(described_class.to_a).to be_a(Array) end end diff --git a/spec/oauth2_spec.rb b/spec/oauth2_spec.rb index 62b824fd..51ed31fc 100644 --- a/spec/oauth2_spec.rb +++ b/spec/oauth2_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true RSpec.describe OAuth2 do - it 'has a default config for silence_extra_tokens_warning' do + it "has a default config for silence_extra_tokens_warning" do expect(described_class.config.silence_extra_tokens_warning).to eq(false) end - describe '.configure' do + describe ".configure" do subject(:configure) do described_class.configure do |config| config.silence_extra_tokens_warning = true @@ -24,7 +24,7 @@ end end - it 'can change setting of silence_extra_tokens_warning' do + it "can change setting of silence_extra_tokens_warning" do block_is_expected.to change(described_class.config, :silence_extra_tokens_warning).from(false).to(true) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a770c36d..4821f668 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,71 +1,37 @@ # frozen_string_literal: true # ensure test env -ENV['RACK_ENV'] = 'test' +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' +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' +require_relative "ext/backports" # Library Configs -require 'config/multi_xml' -require 'config/faraday' +require_relative "config/debug" +require_relative "config/multi_xml" +require_relative "config/faraday" +require_relative "config/constants" # RSpec Configs -require 'config/rspec/rspec_core' -require 'config/rspec/silent_stream' +require_relative "config/rspec/rspec_core" +require_relative "config/rspec/silent_stream" + +# NOTE: Gemfiles for older rubies won't have kettle-soup-cover. +# The rescue LoadError handles that scenario. +begin + require "kettle-soup-cover" + require "simplecov" if Kettle::Soup::Cover::DO_COV # `.simplecov` is run here! +rescue LoadError => error + # check the error message, if you are so inclined, and re-raise if not what is expected + raise error unless error.message.include?("kettle") +end -VERBS = %i[get post put delete patch].freeze +# This gem +require "oauth2"