diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock deleted file mode 100644 index 7eb18693..00000000 --- a/.rubocop_gradual.lock +++ /dev/null @@ -1,115 +0,0 @@ -{ - "lib/oauth/client/action_controller_request.rb:3345734736": [ - [30, 7, 22, "ThreadSafety/ClassAndModuleAttributes: Avoid mutating class and module attributes.", 1901418919], - [33, 5, 44, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 645541231], - [34, 7, 10, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 4071527614] - ], - "lib/oauth/consumer.rb:437026603": [ - [241, 9, 4, "Lint/UnderscorePrefixedVariableName: Do not use prefix `_` for a variable that is used.", 2089552532], - [399, 21, 4, "Lint/UnderscorePrefixedVariableName: Do not use prefix `_` for a variable that is used.", 2089552529] - ], - "lib/oauth/request_proxy.rb:1529370509": [ - [5, 5, 76, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 485336097], - [6, 7, 18, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 1482835337], - [9, 5, 467, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 4088662367] - ], - "lib/oauth/request_proxy/base.rb:744821102": [ - [11, 7, 93, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2934170116] - ], - "lib/oauth/signature.rb:745501939": [ - [6, 5, 66, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 4075572235], - [7, 7, 18, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 758649231], - [13, 5, 400, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 568477139], - [25, 5, 100, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1875734692], - [30, 5, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 934948179], - [37, 5, 129, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2209191795], - [42, 5, 105, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 411730387] - ], - "lib/oauth/signature/base.rb:113451864": [ - [16, 7, 208, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2665690194], - [17, 16, 11, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2038345433], - [19, 9, 11, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2038345433], - [20, 44, 11, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2038345433] - ], - "lib/oauth/tokens/consumer_token.rb:3696415131": [ - [9, 5, 155, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 349576019] - ], - "oauth.gemspec:2280471191": [ - [123, 3, 40, "Gemspec/DependencyVersion: Dependency version specification is required.", 2300588954], - [125, 3, 44, "Gemspec/DependencyVersion: Dependency version specification is required.", 1905290578], - [126, 3, 46, "Gemspec/DependencyVersion: Dependency version specification is required.", 4289565910] - ], - "spec/oauth/backwards_compatibility_spec.rb:4041711732": [ - [3, 16, 25, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 3956042931] - ], - "spec/oauth/consumer_integration_spec.rb:3934173775": [ - [6, 16, 29, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 694297794] - ], - "spec/oauth/consumer_spec.rb:3729402719": [ - [91, 40, 16, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1274889684], - [92, 7, 22, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 190162250], - [95, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [103, 40, 16, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1274889684], - [104, 7, 22, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 190162250], - [107, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [154, 7, 83, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [156].", 4182076280], - [156, 7, 96, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [154].", 3226867591], - [184, 7, 83, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [186].", 4182076280], - [186, 7, 96, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [184].", 3226867591], - [212, 7, 83, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [214].", 4182076280], - [214, 7, 96, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [212].", 3226867591] - ], - "spec/oauth/net_http_client_spec.rb:797264696": [ - [5, 16, 32, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 1925154217] - ], - "spec/oauth/request_proxy/net_http_spec.rb:3313532290": [ - [6, 1, 58, "RSpec/SpecFilePathFormat: Spec path should end with `oauth/request_proxy/net/http/http_request*_spec.rb`.", 3090491845] - ], - "spec/oauth/server_token_spec.rb:492172103": [ - [9, 7, 38, "RSpec/AnyInstance: Avoid stubbing using `allow_any_instance_of`.", 3627954156], - [10, 7, 38, "RSpec/AnyInstance: Avoid stubbing using `allow_any_instance_of`.", 3627954156], - [19, 7, 38, "RSpec/AnyInstance: Avoid stubbing using `allow_any_instance_of`.", 3627954156], - [20, 7, 38, "RSpec/AnyInstance: Avoid stubbing using `allow_any_instance_of`.", 3627954156] - ], - "spec/oauth/signature/hmac_sha1_spec.rb:820294695": [ - [5, 1, 43, "RSpec/SpecFilePathFormat: Spec path should end with `oauth/signature/hmac/sha1*_spec.rb`.", 2640418270] - ], - "spec/oauth/signature/hmac_sha256_spec.rb:1041219499": [ - [5, 1, 45, "RSpec/SpecFilePathFormat: Spec path should end with `oauth/signature/hmac/sha256*_spec.rb`.", 2082372222] - ], - "spec/oauth/signature/plaintext_spec.rb:1285578452": [ - [5, 16, 28, "RSpec/DescribeClass: The first argument to describe should be the class or module being tested.", 2918247297] - ], - "spec/oauth/signature/rsa_sha1_spec.rb:1684897149": [ - [9, 1, 42, "RSpec/SpecFilePathFormat: Spec path should end with `oauth/signature/rsa/sha1*_spec.rb`.", 3568722713] - ], - "spec/oauth/tokens/access_token_spec.rb:893947951": [ - [6, 1, 33, "RSpec/SpecFilePathFormat: Spec path should end with `oauth/access_token*_spec.rb`.", 3410051945] - ], - "spec/oauth/tokens/request_token_spec.rb:4004144261": [ - [6, 1, 34, "RSpec/SpecFilePathFormat: Spec path should end with `oauth/request_token*_spec.rb`.", 2527248508], - [59, 5, 147, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 455072902] - ], - "spec/oauth/tty/cli_spec.rb:3404752772": [ - [111, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [112, 38, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], - [113, 34, 10, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 4294324198], - [115, 7, 73, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [116].", 39742504], - [116, 7, 85, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [115].", 4144335528], - [132, 7, 20, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4235470523], - [156, 38, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], - [157, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [158, 39, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], - [160, 7, 73, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [161].", 39742504], - [161, 7, 85, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [160].", 4144335528], - [171, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [172, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [173, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [174, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [197, 23, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], - [198, 23, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [199, 23, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], - [201, 7, 73, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [202].", 39742504], - [202, 7, 85, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [201].", 4144335528] - ] -} diff --git a/.tool-versions b/.tool-versions index 3f03c7a7..efdfc37e 100755 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.4.7 +ruby 4.0.4 diff --git a/CHANGELOG.md b/CHANGELOG.md index a762d658..7b1c56fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,12 @@ Please file a bug if you notice a violation of semantic versioning. ### Added +- Add `auth-sanitizer` integration for inspect-time secret redaction in core OAuth objects. + ### Changed +- Redact sensitive values from `#inspect` in `OAuth::Consumer`, `OAuth::Token`, and `OAuth::Signature::Base`. + ### Deprecated ### Removed diff --git a/Gemfile b/Gemfile index be6c1816..b8abd26e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,22 @@ git_source(:gitlab) { |repo_name| "https://gitlab.com/#{repo_name}" } # Include dependencies from .gemspec gemspec +unless %w[false 0 no off].include?(ENV.fetch("RUBY_OAUTH_DEV", "false").downcase) + begin + require "nomono/bundler" unless defined?(Nomono) + rescue LoadError + require_relative "../nomono/lib/nomono/bundler" + end + + eval_nomono_gems( + gems: %w[auth-sanitizer], + prefix: "RUBY_OAUTH", + path_env: "RUBY_OAUTH_DEV", + root: %w[code src ruby-oauth], + debug_env: "RUBY_OAUTH_DEBUG", + ) +end + # Debugging eval_gemfile "gemfiles/modular/debug.gemfile" diff --git a/Gemfile.lock b/Gemfile.lock index 6b3d5da9..b80da982 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,52 +1,42 @@ -GIT - remote: https://github.com/pboling/yard-junk.git - revision: 54ccebabbfa9a9cd44d0b991687ebbfd22c32b55 - branch: next - specs: - yard-junk (0.0.10) - backports (>= 3.18) - benchmark - ostruct - rainbow - yard - PATH remote: . specs: oauth (1.1.3) + auth-sanitizer (~> 0.1, >= 0.1.2) base64 (~> 0.1) - oauth-tty (~> 1.0, >= 1.0.6) - snaky_hash (~> 2.0) + cgi + oauth-tty (~> 1.0, >= 1.0.7) + snaky_hash (~> 2.0, >= 2.0.4) version_gem (~> 1.1, >= 1.1.9) GEM remote: https://gem.coop/ specs: - action_text-trix (2.1.15) + action_text-trix (2.1.19) railties - actioncable (8.1.1) - actionpack (= 8.1.1) - activesupport (= 8.1.1) + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.1) - actionpack (= 8.1.1) - activejob (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (8.1.1) - actionpack (= 8.1.1) - actionview (= 8.1.1) - activejob (= 8.1.1) - activesupport (= 8.1.1) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.1) - actionview (= 8.1.1) - activesupport (= 8.1.1) + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -54,36 +44,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.1) + actiontext (8.1.3) action_text-trix (~> 2.1.15) - actionpack (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.1) - activesupport (= 8.1.1) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.1.1) - activesupport (= 8.1.1) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (8.1.1) - activesupport (= 8.1.1) - activerecord (8.1.1) - activemodel (= 8.1.1) - activesupport (= 8.1.1) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) - activestorage (8.1.1) - actionpack (= 8.1.1) - activejob (= 8.1.1) - activerecord (= 8.1.1) - activesupport (= 8.1.1) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (8.1.1) + activesupport (8.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -96,63 +86,66 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) - ansi (1.5.0) - appraisal2 (3.0.0) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + ansi (1.6.0) + appraisal2 (3.0.6) bundler (>= 1.17.3) rake (>= 10) thor (>= 0.14) ast (2.4.3) - backports (3.25.2) + auth-sanitizer (0.1.2) + version_gem (~> 1.1, >= 1.1.9) + backports (3.25.3) base64 (0.3.0) benchmark (0.5.0) - bigdecimal (3.3.1) + bigdecimal (4.1.2) builder (3.3.0) - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) + bundler-audit (0.9.3) + bundler (>= 1.2.0) thor (~> 1.0) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) + cgi (0.5.1) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) cookiejar (0.3.4) crack (1.0.1) bigdecimal rexml crass (1.0.6) - date (3.5.0) - debug (1.11.0) + date (3.5.1) + debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) - delegate (0.4.0) + delegate (0.6.1) diff-lcs (1.6.2) diffy (3.4.4) docile (1.4.1) domain_name (0.6.20240107) drb (2.2.3) - dry-configurable (1.3.0) - dry-core (~> 1.1) + dry-configurable (1.4.0) + dry-core (~> 1.0) zeitwerk (~> 2.6) - dry-core (1.1.0) + dry-core (1.2.0) concurrent-ruby (~> 1.0) logger zeitwerk (~> 2.6) - dry-inflector (1.2.0) + dry-inflector (1.3.1) 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) + dry-schema (1.16.0) 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) + dry-logic (~> 1.6) + dry-types (~> 1.9, >= 1.9.1) zeitwerk (~> 2.6) - dry-types (1.8.3) - bigdecimal (~> 3.0) + dry-types (1.9.1) + bigdecimal (>= 3.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0) dry-inflector (~> 1.0) @@ -169,10 +162,11 @@ GEM eventmachine (>= 1.0.0.beta.4) erb (5.1.3) erubi (1.13.1) - ethon (0.15.0) + ethon (0.18.0) ffi (>= 1.15.0) + logger eventmachine (1.2.7) - ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.4-x86_64-linux-gnu) gem_bench (2.0.5) bundler (>= 1.14) version_gem (~> 1.1, >= 1.1.4) @@ -181,21 +175,23 @@ GEM globalid (1.3.0) activesupport (>= 6.1) hashdiff (1.2.1) - hashie (5.0.0) + hashie (5.1.0) + logger http-accept (1.7.0) - http-cookie (1.1.0) + http-cookie (1.1.6) domain_name (~> 0.5) - http_parser.rb (0.8.0) - i18n (1.14.7) + http_parser.rb (0.8.1) + i18n (1.14.8) concurrent-ruby (~> 1.0) - io-console (0.8.1) - irb (1.15.3) + io-console (0.8.2) + irb (1.18.0) pp (>= 0.6.0) + prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.15.2) - kettle-dev (1.1.48) - kettle-soup-cover (1.0.10) + json (2.19.5) + kettle-dev (2.0.0) + kettle-soup-cover (1.1.1) simplecov (~> 0.22) simplecov-cobertura (~> 3.0) simplecov-console (~> 0.9, >= 0.9.3) @@ -203,9 +199,9 @@ GEM simplecov-lcov (~> 0.8) simplecov-rcov (~> 0.3, >= 0.3.7) simplecov_json_formatter (~> 0.1, >= 0.1.4) - version_gem (~> 1.1, >= 1.1.8) - kettle-test (1.0.6) - appraisal2 (~> 3.0) + version_gem (~> 1.1, >= 1.1.9) + kettle-test (1.0.10) + appraisal2 (~> 3.0, >= 3.0.2) backports (~> 3.0) rspec (~> 3.0) rspec-block_is_expected (~> 1.0, >= 1.0.6) @@ -215,14 +211,14 @@ GEM silent_stream (~> 1.0, >= 1.0.12) timecop-rspec (~> 1.0, >= 1.0.3) version_gem (~> 1.1, >= 1.1.9) - kramdown (2.5.1) - rexml (>= 3.3.9) + kramdown (2.5.2) + rexml (>= 3.4.4) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) - loofah (2.24.1) + loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.9.0) @@ -235,13 +231,15 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0924) + mime-types-data (3.2026.0414) mini_mime (1.1.5) - minitest (5.26.0) - mocha (2.7.1) + minitest (6.0.6) + drb (~> 2.0) + prism (~> 1.5) + mocha (3.1.0) ruby2_keywords (>= 0.0.5) mutex_m (0.3.0) - net-imap (0.5.12) + net-imap (0.6.4) date net-protocol net-pop (0.1.2) @@ -252,56 +250,58 @@ GEM net-protocol netrc (0.11.0) nio4r (2.7.5) - nokogiri (1.18.10-x86_64-linux-gnu) + nokogiri (1.19.3-x86_64-linux-gnu) racc (~> 1.4) - oauth-tty (1.0.6) + oauth-tty (1.0.7) + auth-sanitizer (~> 0.1) + cgi version_gem (~> 1.1, >= 1.1.9) ostruct (0.6.3) - parallel (1.27.0) - parser (3.3.10.0) + parallel (1.28.0) + parser (3.3.11.1) ast (~> 2.4.1) racc pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) - psych (5.2.6) + prism (1.9.0) + psych (5.3.1) date stringio - public_suffix (6.0.2) + public_suffix (7.0.5) racc (1.8.1) - rack (3.2.4) - rack-session (2.1.1) + rack (3.2.6) + rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.2.1) + rackup (2.3.1) rack (>= 3) - rails (8.1.1) - actioncable (= 8.1.1) - actionmailbox (= 8.1.1) - actionmailer (= 8.1.1) - actionpack (= 8.1.1) - actiontext (= 8.1.1) - actionview (= 8.1.1) - activejob (= 8.1.1) - activemodel (= 8.1.1) - activerecord (= 8.1.1) - activestorage (= 8.1.1) - activesupport (= 8.1.1) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 8.1.1) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.1.1) - actionpack (= 8.1.1) - activesupport (= 8.1.1) + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -309,10 +309,11 @@ GEM tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.1) - rbs (3.9.5) + rake (13.4.2) + rbs (3.10.4) logger - rdoc (6.15.1) + tsort + rdoc (6.17.0) erb psych (>= 4.0.0) tsort @@ -322,8 +323,8 @@ GEM parser (~> 3.3.0) rainbow (>= 2.0, < 4.0) rexml (~> 3.1) - regexp_parser (2.11.3) - reline (0.6.2) + regexp_parser (2.12.0) + reline (0.6.3) io-console (~> 0.5) require_bench (1.0.4) version_gem (>= 1.1.3, < 4) @@ -343,19 +344,19 @@ GEM rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-pending_for (0.1.19) + rspec-pending_for (0.1.20) rspec-core (~> 3.0) ruby_engine (~> 2.0) ruby_version (~> 1.0) version_gem (~> 1.1, >= 1.1.8) rspec-stubbed_env (1.0.4) - rspec-support (3.13.6) + rspec-support (3.13.7) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.80.2) + rubocop (1.84.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -363,12 +364,12 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.47.1) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) - prism (~> 1.4) + prism (~> 1.7) rubocop-gradual (0.3.6) diff-lcs (>= 1.2.0, < 2.0) diffy (~> 3.0) @@ -381,7 +382,7 @@ GEM version_gem (>= 1.1.2, < 3) rubocop-md (1.2.4) rubocop (>= 1.45) - rubocop-on-rbs (1.8.0) + rubocop-on-rbs (1.9.1) lint_roller (~> 1.1) rbs (~> 3.5) rubocop (>= 1.72.1, < 2.0) @@ -389,16 +390,16 @@ GEM rubocop-packaging (0.6.0) lint_roller (~> 1.1.0) rubocop (>= 1.72.1, < 2.0) - rubocop-performance (1.25.0) + rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) rubocop-rake (0.7.1) lint_roller (~> 1.1) rubocop (>= 1.72.1) - rubocop-rspec (3.7.0) + rubocop-rspec (3.9.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) + rubocop (~> 1.81) rubocop-ruby2_3 (2.0.5) rubocop-gradual (~> 0.3, >= 0.3.1) rubocop-md (~> 1.2) @@ -428,7 +429,7 @@ GEM simplecov-cobertura (3.1.0) rexml simplecov (~> 0.19) - simplecov-console (0.9.4) + simplecov-console (0.9.5) ansi simplecov terminal-table @@ -437,21 +438,21 @@ GEM simplecov-rcov (0.3.7) simplecov (>= 0.4.1) simplecov_json_formatter (0.1.4) - snaky_hash (2.0.3) + snaky_hash (2.0.4) hashie (>= 0.1.0, < 6) version_gem (>= 1.1.8, < 3) - standard (1.51.1) + standard (1.54.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.80.2) + rubocop (~> 1.84.0) 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) + standard-performance (1.9.0) lint_roller (~> 1.1) - rubocop-performance (~> 1.25.0) + rubocop-performance (~> 1.26.0) standard-rubocop-lts (1.0.10) rspec-block_is_expected (~> 1.0, >= 1.0.5) standard (>= 1.35.1, < 2) @@ -460,30 +461,29 @@ GEM version_gem (>= 1.1.4, < 3) stone_checksums (1.0.3) version_gem (~> 1.1, >= 1.1.9) - stringio (3.1.7) + stringio (3.2.0) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) - thor (1.4.0) - timecop (0.9.10) + thor (1.5.0) + timecop (0.9.11) timecop-rspec (1.0.3) delegate (~> 0.1) rspec (~> 3.0) timecop (>= 0.7, < 1) - timeout (0.4.4) + timeout (0.6.1) tsort (0.2.0) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) + typhoeus (1.6.0) + ethon (>= 0.18.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-emoji (4.2.0) uri (1.1.1) useragent (0.16.11) - vcr (6.3.1) - base64 + vcr (6.4.0) version_gem (1.1.9) - webmock (3.26.1) + webmock (3.26.2) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -491,28 +491,33 @@ GEM base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - yard (0.9.37) - yard-relative_markdown_links (0.5.0) + yard (0.9.43) + yard-junk (0.1.0) + benchmark + ostruct + rainbow + yard + yard-relative_markdown_links (0.6.0) nokogiri (>= 1.14.3, < 2) - zeitwerk (2.7.3) - zlib (3.2.2) + zeitwerk (2.7.5) + zlib (3.2.3) PLATFORMS - x86_64-linux-gnu + x86_64-linux DEPENDENCIES addressable (>= 2.8, < 3) appraisal2 (~> 3.0) backports (~> 3.25, >= 3.25.1) benchmark (~> 0.4, >= 0.4.1) - bundler-audit (~> 0.9.2) + bundler-audit (~> 0.9.3) debug (>= 1.1) em-http-request (~> 1.1.7) erb (~> 5.0) gem_bench (~> 2.0, >= 2.0.5) gitmoji-regex (~> 1.0, >= 1.0.3) irb (~> 1.15, >= 1.15.2) - kettle-dev (~> 1.1) + kettle-dev (~> 2.0) kettle-soup-cover (~> 1.0, >= 1.0.10) kettle-test (~> 1.0, >= 1.0.6) kramdown (~> 2.5, >= 2.5.1) @@ -541,8 +546,8 @@ DEPENDENCIES vcr (>= 4) webmock (>= 3) yard (~> 0.9, >= 0.9.37) - yard-junk (~> 0.0, >= 0.0.10)! - yard-relative_markdown_links (~> 0.5.0) + yard-junk (~> 0.1, >= 0.1.0) + yard-relative_markdown_links (~> 0.6) BUNDLED WITH - 2.7.2 + 4.0.11 diff --git a/IRP.md b/IRP.md new file mode 100644 index 00000000..51f0e112 --- /dev/null +++ b/IRP.md @@ -0,0 +1,107 @@ +# Incident Response Plan (IRP) + +Status: Draft + +## Purpose + +This Incident Response Plan (IRP) defines the steps the project maintainer(s) will follow when handling security incidents related to the `oauth` gem. It is written for a small project with a single primary maintainer and is intended to be practical, concise, and actionable. + +## Scope + +Applies to security incidents that affect the `oauth` codebase, releases (gems), CI/CD infrastructure related to building and publishing the gem, repository credentials, or any compromise of project infrastructure that could impact users. + +## Key assumptions +- This project is maintained primarily by a single maintainer. +- Public vulnerability disclosure is handled via Tidelift (see `SECURITY.md`). +- The maintainer will act as incident commander unless otherwise delegated. + +## Contact & Roles + +- Incident Commander: Primary maintainer (repo owner). Responsible for coordinating triage, remediation, and communications. +- Secondary Contact: (optional) A trusted collaborator or organization contact if available. + +### If you are an external reporter +- Do not publicly disclose details of an active vulnerability before coordination via Tidelift. +- See `SECURITY.md` for Tidelift disclosure instructions. If the reporter has questions and cannot use Tidelift, they may open a direct encrypted report as described in `SECURITY.md` (if available) or email the maintainer contact listed in the repository. + +## Incident Handling Workflow (high level) +1. Identification & Reporting + - Reports may arrive via Tidelift, issue tracker, direct email, or third-party advisories. + - Immediately acknowledge receipt (within 24-72 hours) via the reporting channel. + +2. Triage & Initial Assessment (first 72 hours) + - Confirm the report is not duplicative and gather: reproducer, affected versions, attack surface, exploitability, and CVSS-like severity estimate. + - Verify the issue against the codebase and reproduce locally if possible. + - Determine scope: which versions are affected, whether the issue is in code paths executed in common setups, and whether a workaround exists. + +3. Containment & Mitigation + - If a simple mitigation or workaround (configuration change, safe default, or recommended upgrade) exists, document it clearly in the issue/Tidelift advisory. + - If immediate removal of a release is required (rare), consult Tidelift for coordinated takedown and notify package hosts if applicable. + +4. Remediation & Patch + - Prepare a fix in a branch with tests and changelog entries. Prefer minimal, well-tested changes. + - Include tests that reproduce the faulty behavior and demonstrate the fix. + - Hardening: add fuzz tests, input validation, or additional checks as appropriate. + +5. Release & Disclosure + - Coordinate disclosure through Tidelift per `SECURITY.md` timelines. Aim for a coordinated disclosure and patch release to minimize risk to users. + - Publish a patch release (increment gem version) and an advisory via Tidelift. + - Update `CHANGELOG.md` and repository release notes with non-sensitive details. + +6. Post-Incident + - Produce a short postmortem: timeline, root cause, actions taken, and follow-ups. + - Add/adjust tests and CI checks to prevent regressions. + - If credentials or infrastructure were compromised, rotate secrets and audit access. + +## Severity classification (guidance) +- High/Critical: Remote code execution, data exfiltration, or any vulnerability that can be exploited without user interaction. Immediate action and prioritized patching. +- Medium: Privilege escalation, sensitive information leaks that require specific conditions. Patch in the next release cycle with advisory. +- Low: Minor information leaks, UI issues, or non-exploitable bugs. Fix normally and include in the next scheduled release. + +## Preservation of evidence +- Preserve all reporter-provided data, logs, and reproducer code in a secure location (local encrypted storage or private branch) for the investigation. +- Do not publish evidence that would enable exploitation before coordinated disclosure. + +## Communication templates +Acknowledgement (to reporter) + +"Thank you for reporting this issue. I've received your report and will triage it within 72 hours. If you can, please provide reproduction steps, affected versions, and any exploit PoC. I will coordinate disclosure through Tidelift per the project's security policy." + +Public advisory (after patch is ready) + +"A security advisory for oauth (versions X.Y.Z) has been published via Tidelift. Please upgrade to version A.B.C which patches [brief description]. See the advisory for details and recommended mitigations." + +## Runbook: Quick steps for a maintainer to patch and release +1. Create a branch: `git checkout -b fix/security-brief-description` +2. Reproduce the issue locally and add a regression spec in `spec/`. +3. Implement the fix and run the test suite: `bundle exec rspec` (or the project's preferred test command). +4. Bump version in `lib/oauth/version.rb` following semantic versioning. +5. Update `CHANGELOG.md` with an entry describing the fix (avoid exploit details). +6. Commit and push the branch, open a PR, and merge after approvals. +7. Build and push the gem: `gem build oauth.gemspec && gem push pkg/...` (coordinate with Tidelift before public push if disclosure is coordinated). +8. Publish a release on GitHub and ensure the Tidelift advisory is posted. + +## Operational notes +- Secrets: Use local encrypted storage for any sensitive reporter data. If repository or CI secrets may be compromised, rotate them immediately and update dependent services. +- Access control: Limit who can publish gems and who has admin access to the repo. Keep an up-to-date list of collaborators in a secure place. + +## Legal & regulatory +- If the incident involves user data or has legal implications, consult legal counsel or the maintainers' employer as appropriate. The maintainer should document the timeline and all communications. + +## Retrospective & continuous improvement +After an incident, perform a brief post-incident review covering: +- What happened and why +- What was done to contain and remediate +- What tests or process changes will prevent recurrence +- Assign owners and deadlines for follow-up tasks + +## References +- See `SECURITY.md` for the project's official disclosure channel (Tidelift). + +## Appendix: Example checklist for an incident +- [ ] Acknowledge report to reporter (24-72 hours) +- [ ] Reproduce and classify severity +- [ ] Prepare and test a fix in a branch +- [ ] Coordinate disclosure via Tidelift +- [ ] Publish patch release and advisory +- [ ] Postmortem and follow-up actions diff --git a/README.md b/README.md index 4597ae8a..fe8f2ce8 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ # ๐Ÿ”‘ Ruby OAuth 1.0 / 1.0a -[![Version][๐Ÿ‘ฝversioni]][๐Ÿ‘ฝversion] [![GitHub tag (latest SemVer)][โ›ณ๏ธtag-img]][โ›ณ๏ธtag] [![License: MIT][๐Ÿ“„license-img]][๐Ÿ“„license-ref] [![Downloads Rank][๐Ÿ‘ฝdl-ranki]][๐Ÿ‘ฝdl-rank] [![Open Source Helpers][๐Ÿ‘ฝoss-helpi]][๐Ÿ‘ฝoss-help] [![CodeCov Test Coverage][๐Ÿ€codecovi]][๐Ÿ€codecov] [![Coveralls Test Coverage][๐Ÿ€coveralls-img]][๐Ÿ€coveralls] [![QLTY Test Coverage][๐Ÿ€qlty-covi]][๐Ÿ€qlty-cov] [![QLTY Maintainability][๐Ÿ€qlty-mnti]][๐Ÿ€qlty-mnt] [![CI Heads][๐ŸšŽ3-hd-wfi]][๐ŸšŽ3-hd-wf] [![CI Runtime Dependencies @ HEAD][๐ŸšŽ12-crh-wfi]][๐ŸšŽ12-crh-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] [![Deps Locked][๐ŸšŽ13-๐Ÿ”’๏ธ-wfi]][๐ŸšŽ13-๐Ÿ”’๏ธ-wf] [![Deps Unlocked][๐ŸšŽ14-๐Ÿ”“๏ธ-wfi]][๐ŸšŽ14-๐Ÿ”“๏ธ-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] [![CodeQL][๐Ÿ–codeQL-img]][๐Ÿ–codeQL] [![Apache SkyWalking Eyes License Compatibility Check][๐ŸšŽ15-๐Ÿชช-wfi]][๐ŸšŽ15-๐Ÿชช-wf] +[![Version][๐Ÿ‘ฝversioni]][๐Ÿ‘ฝversion] [![GitHub tag (latest SemVer)][โ›ณ๏ธtag-img]][โ›ณ๏ธtag] [![License: MIT][๐Ÿ“„license-img]][๐Ÿ“„license-ref] [![Downloads Rank][๐Ÿ‘ฝdl-ranki]][๐Ÿ‘ฝdl-rank] [![CodeCov Test Coverage][๐Ÿ€codecovi]][๐Ÿ€codecov] [![Coveralls Test Coverage][๐Ÿ€coveralls-img]][๐Ÿ€coveralls] [![QLTY Test Coverage][๐Ÿ€qlty-covi]][๐Ÿ€qlty-cov] [![QLTY Maintainability][๐Ÿ€qlty-mnti]][๐Ÿ€qlty-mnt] [![CI Heads][๐ŸšŽ3-hd-wfi]][๐ŸšŽ3-hd-wf] [![CI Runtime Dependencies @ HEAD][๐ŸšŽ12-crh-wfi]][๐ŸšŽ12-crh-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] [![Deps Locked][๐ŸšŽ13-๐Ÿ”’๏ธ-wfi]][๐ŸšŽ13-๐Ÿ”’๏ธ-wf] [![Deps Unlocked][๐ŸšŽ14-๐Ÿ”“๏ธ-wfi]][๐ŸšŽ14-๐Ÿ”“๏ธ-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] [![CodeQL][๐Ÿ–codeQL-img]][๐Ÿ–codeQL] [![Apache SkyWalking Eyes License Compatibility Check][๐ŸšŽ15-๐Ÿชช-wfi]][๐ŸšŽ15-๐Ÿชช-wf] `if ci_badges.map(&:color).detect { it != "green"}` โ˜๏ธ [let me know][๐Ÿ–ผ๏ธgaltzo-discord], as I may have missed the [discord notification][๐Ÿ–ผ๏ธgaltzo-discord]. @@ -572,8 +572,6 @@ Thanks for RTFM. โ˜บ๏ธ [๐Ÿ“œgh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white [๐Ÿ‘ฝdl-rank]: https://bestgems.org/gems/oauth [๐Ÿ‘ฝdl-ranki]: https://img.shields.io/gem/rd/oauth.svg -[๐Ÿ‘ฝoss-help]: https://www.codetriage.com/ruby-oauth/oauth -[๐Ÿ‘ฝoss-helpi]: https://www.codetriage.com/ruby-oauth/oauth/badges/users.svg [๐Ÿ‘ฝversion]: https://bestgems.org/gems/oauth [๐Ÿ‘ฝversioni]: https://img.shields.io/gem/v/oauth.svg [๐Ÿ€qlty-mnt]: https://qlty.sh/gh/ruby-oauth/projects/oauth diff --git a/SECURITY.md b/SECURITY.md index a319529f..677d47f3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,6 +12,8 @@ To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. +More detailed explanation of the process is in [IRP.md][IRP]. + ## Additional Support If you are interested in support for versions older than the latest release, @@ -19,3 +21,4 @@ please consider sponsoring the project / maintainer @ https://liberapay.com/pbol or find other sponsorship links in the [README]. [README]: README.md +[IRP]: IRP.md diff --git a/examples/tumblr_cli.rb b/examples/tumblr_cli.rb index e0c13a2e..d81c12b8 100644 --- a/examples/tumblr_cli.rb +++ b/examples/tumblr_cli.rb @@ -124,10 +124,13 @@ def consumer puts "\nOAuth complete. Access token acquired.\n" + # Intentional fire-and-forget shutdown for the ephemeral local callback server. + # rubocop:disable ThreadSafety/NewThread Thread.new { sleep 1 Sinatra::Application.quit! } + # rubocop:enable ThreadSafety/NewThread "Authorization complete. You can close this window." end @@ -238,7 +241,7 @@ def menu puts "3) Exit" print("> ") - case STDIN.gets&.strip + case $stdin.gets&.strip when "1" then list_my_blogs when "2" then list_my_blogs_with_latest_posts when "3" then exit(0) diff --git a/gemfiles/modular/documentation.gemfile b/gemfiles/modular/documentation.gemfile index 78533908..12295ee5 100644 --- a/gemfiles/modular/documentation.gemfile +++ b/gemfiles/modular/documentation.gemfile @@ -4,8 +4,8 @@ gem "kramdown", "~> 2.5", ">= 2.5.1" # Ruby >= 2.5 gem "kramdown-parser-gfm", "~> 1.1" # Ruby >= 2.3 gem "yard", "~> 0.9", ">= 0.9.37", require: false -gem "yard-junk", "~> 0.0", ">= 0.0.10", github: "pboling/yard-junk", branch: "next", require: false -gem "yard-relative_markdown_links", "~> 0.5.0" +gem "yard-junk", "~> 0.1", ">= 0.1.0", require: false # Ruby >= 3.1 +gem "yard-relative_markdown_links", "~> 0.6" # Std Lib extractions gem "rdoc", "~> 6.11" diff --git a/lib/oauth.rb b/lib/oauth.rb index de0d4d11..b3fb6c1b 100644 --- a/lib/oauth.rb +++ b/lib/oauth.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true # third party gems +require "auth/sanitizer" require "snaky_hash" require "version_gem" diff --git a/lib/oauth/client/action_controller_request.rb b/lib/oauth/client/action_controller_request.rb index 200df55e..f3e66450 100644 --- a/lib/oauth/client/action_controller_request.rb +++ b/lib/oauth/client/action_controller_request.rb @@ -26,12 +26,16 @@ def process_with_oauth(request, response = nil) end class TestRequest + OAUTH_ENABLED_KEY = :oauth_action_controller_test_request_use_oauth + class << self - attr_writer :use_oauth - end + def use_oauth=(value) + Thread.current[OAUTH_ENABLED_KEY] = value + end - def self.use_oauth? - @use_oauth + def use_oauth? + Thread.current[OAUTH_ENABLED_KEY] + end end def configure_oauth(consumer = nil, token = nil, options = {}) diff --git a/lib/oauth/consumer.rb b/lib/oauth/consumer.rb index 50607e89..c513b510 100644 --- a/lib/oauth/consumer.rb +++ b/lib/oauth/consumer.rb @@ -8,7 +8,22 @@ require "cgi" module OAuth + # Consumer credentials and request configuration for OAuth 1.0 / 1.0a flows. + # + # Includes {Auth::Sanitizer::FilteredAttributes} so inspect output redacts the + # consumer secret while leaving non-sensitive configuration visible. class Consumer + include Auth::Sanitizer::FilteredAttributes + + # Instance attributes exposed by the consumer. + # + # @!attribute [rw] options + # @return [Hash] Consumer configuration options + # @!attribute [rw] key + # @return [String] OAuth consumer key + # @!attribute [rw] secret + # @return [String] OAuth consumer secret (redacted in `#inspect`) + # determine the certificate authority path to verify SSL certs if ENV["SSL_CERT_FILE"] if File.exist?(ENV["SSL_CERT_FILE"]) @@ -78,6 +93,7 @@ class Consumer ) attr_accessor :options, :key, :secret + filtered_attributes :secret attr_writer :site, :http # Create a new consumer instance by passing it a configuration hash: @@ -238,8 +254,8 @@ def get_request_token(request_options = {}, *arguments, &block) def request(http_method, path, token = nil, request_options = {}, *arguments) unless %r{^/} =~ path @http = create_http(path) - _uri = URI.parse(path) - path = "#{_uri.path}#{"?#{_uri.query}" if _uri.query}" + uri = URI.parse(path) + path = "#{uri.path}#{"?#{uri.query}" if uri.query}" end # override the request with your own, this is useful for file uploads which Net::HTTP does not do @@ -396,13 +412,13 @@ def proxy protected # Instantiates the http object - def create_http(_url = nil) - _url = request_endpoint unless request_endpoint.nil? + def create_http(url = nil) + url = request_endpoint unless request_endpoint.nil? - our_uri = if _url.nil? || _url[0] =~ %r{^/} + our_uri = if url.nil? || url[0] =~ %r{^/} URI.parse(site) else - your_uri = URI.parse(_url) + your_uri = URI.parse(url) if your_uri.host.nil? # If the _url is a path, missing the leading slash, then it won't have a host, # and our_uri *must* have a host, so we parse site instead. diff --git a/lib/oauth/request_proxy.rb b/lib/oauth/request_proxy.rb index 50dfeb04..92677d57 100644 --- a/lib/oauth/request_proxy.rb +++ b/lib/oauth/request_proxy.rb @@ -2,24 +2,28 @@ module OAuth module RequestProxy - def self.available_proxies # :nodoc: - @available_proxies ||= {} - end + AVAILABLE_PROXIES = {} - def self.proxy(request, options = {}) - return request if request.is_a?(OAuth::RequestProxy::Base) + class << self + def available_proxies # :nodoc: + AVAILABLE_PROXIES + end - klass = available_proxies[request.class] + def proxy(request, options = {}) + return request if request.is_a?(OAuth::RequestProxy::Base) - # Search for possible superclass matches. - if klass.nil? - request_parent = available_proxies.keys.find { |rc| request.is_a?(rc) } - klass = available_proxies[request_parent] - end + klass = available_proxies[request.class] - raise UnknownRequestType, request.class.to_s unless klass + # Search for possible superclass matches. + if klass.nil? + request_parent = available_proxies.keys.find { |rc| request.is_a?(rc) } + klass = available_proxies[request_parent] + end - klass.new(request, options) + raise UnknownRequestType, request.class.to_s unless klass + + klass.new(request, options) + end end class UnknownRequestType < RuntimeError; end diff --git a/lib/oauth/request_proxy/base.rb b/lib/oauth/request_proxy/base.rb index a8411cb3..5e6a4740 100644 --- a/lib/oauth/request_proxy/base.rb +++ b/lib/oauth/request_proxy/base.rb @@ -8,8 +8,10 @@ module RequestProxy class Base include OAuth::Helper - def self.proxies(klass) - OAuth::RequestProxy.available_proxies[klass] = self + class << self + def proxies(klass) + OAuth::RequestProxy.available_proxies[klass] = self + end end attr_accessor :request, :options, :unsigned_parameters diff --git a/lib/oauth/signature.rb b/lib/oauth/signature.rb index a8e37f06..4d6dc501 100644 --- a/lib/oauth/signature.rb +++ b/lib/oauth/signature.rb @@ -2,45 +2,49 @@ module OAuth module Signature - # Returns a list of available signature methods - def self.available_methods - @available_methods ||= {} - end - - # Build a signature from a +request+. - # - # Raises UnknownSignatureMethod exception if the signature method is unknown. - def self.build(request, options = {}, &block) - request = OAuth::RequestProxy.proxy(request, options) - klass = available_methods[ - (request.signature_method || - ((c = request.options[:consumer]) && c.options[:signature_method]) || - "").downcase] - raise UnknownSignatureMethod, request.signature_method unless klass - - klass.new(request, options, &block) - end - - # Sign a +request+ - def self.sign(request, options = {}, &block) - build(request, options, &block).signature - end - - # Verify the signature of +request+ - def self.verify(request, options = {}, &block) - build(request, options, &block).verify - end - - # Create the signature base string for +request+. This string is the normalized parameter information. - # - # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] - def self.signature_base_string(request, options = {}, &block) - build(request, options, &block).signature_base_string - end - - # Create the body hash for a request - def self.body_hash(request, options = {}, &block) - build(request, options, &block).body_hash + AVAILABLE_METHODS = {} + + class << self + # Returns a list of available signature methods + def available_methods + AVAILABLE_METHODS + end + + # Build a signature from a +request+. + # + # Raises UnknownSignatureMethod exception if the signature method is unknown. + def build(request, options = {}, &block) + request = OAuth::RequestProxy.proxy(request, options) + klass = available_methods[ + (request.signature_method || + ((c = request.options[:consumer]) && c.options[:signature_method]) || + "").downcase] + raise UnknownSignatureMethod, request.signature_method unless klass + + klass.new(request, options, &block) + end + + # Sign a +request+ + def sign(request, options = {}, &block) + build(request, options, &block).signature + end + + # Verify the signature of +request+ + def verify(request, options = {}, &block) + build(request, options, &block).verify + end + + # Create the signature base string for +request+. This string is the normalized parameter information. + # + # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1] + def signature_base_string(request, options = {}, &block) + build(request, options, &block).signature_base_string + end + + # Create the body hash for a request + def body_hash(request, options = {}, &block) + build(request, options, &block).body_hash + end end class UnknownSignatureMethod < RuntimeError; end diff --git a/lib/oauth/signature/base.rb b/lib/oauth/signature/base.rb index 90ad4ceb..86a24080 100644 --- a/lib/oauth/signature/base.rb +++ b/lib/oauth/signature/base.rb @@ -7,17 +7,27 @@ module OAuth module Signature + # Base class for OAuth signature implementations. + # + # Includes {Auth::Sanitizer::FilteredAttributes} so inspect output redacts + # secret-bearing fields captured during signature construction. class Base include OAuth::Helper + include Auth::Sanitizer::FilteredAttributes + # Signature construction options. + # + # @return [Hash] attr_accessor :options attr_reader :token_secret, :consumer_secret, :request + filtered_attributes :options, :consumer_secret, :token_secret - def self.implements(signature_method = nil) - return @implements if signature_method.nil? + class << self + def implements(signature_method = nil) + return OAuth::Signature.available_methods.key(self) if signature_method.nil? - @implements = signature_method - OAuth::Signature.available_methods[@implements] = self + OAuth::Signature.available_methods[signature_method] = self + end end def initialize(request, options = {}, &block) diff --git a/lib/oauth/tokens/consumer_token.rb b/lib/oauth/tokens/consumer_token.rb index 0dd25aa1..39e3d589 100644 --- a/lib/oauth/tokens/consumer_token.rb +++ b/lib/oauth/tokens/consumer_token.rb @@ -6,10 +6,12 @@ class ConsumerToken < Token attr_accessor :consumer, :params attr_reader :response - def self.from_hash(consumer, hash) - token = new(consumer, hash[:oauth_token], hash[:oauth_token_secret]) - token.params = hash - token + class << self + def from_hash(consumer, hash) + token = new(consumer, hash[:oauth_token], hash[:oauth_token_secret]) + token.params = hash + token + end end def initialize(consumer, token = "", secret = "") diff --git a/lib/oauth/tokens/token.rb b/lib/oauth/tokens/token.rb index bb0ffca7..7507cf1b 100644 --- a/lib/oauth/tokens/token.rb +++ b/lib/oauth/tokens/token.rb @@ -1,11 +1,23 @@ # frozen_string_literal: true module OAuth - # Superclass for the various tokens used by OAuth + # Superclass for the various tokens used by OAuth. + # + # Includes {Auth::Sanitizer::FilteredAttributes} so inspect output redacts the + # token value and token secret while leaving object identity and non-sensitive + # fields visible. class Token include OAuth::Helper + include Auth::Sanitizer::FilteredAttributes + # Token attributes. + # + # @!attribute [rw] token + # @return [String] OAuth token value (redacted in `#inspect`) + # @!attribute [rw] secret + # @return [String] OAuth token secret (redacted in `#inspect`) attr_accessor :token, :secret + filtered_attributes :token, :secret def initialize(token, secret) @token = token diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..ffddc46b --- /dev/null +++ b/mise.toml @@ -0,0 +1,28 @@ +# Shared development environment for this gem. +# Local overrides belong in .env.local (loaded via dotenvy through mise). + +[env] +K_JEM_TEMPLATING = "false" +K_SOUP_COV_DO = "true" +K_SOUP_COV_COMMAND_NAME = "Test Coverage" +K_SOUP_COV_FORMATTERS = "html,xml,rcov,lcov,json,tty" +K_SOUP_COV_MIN_BRANCH = "76" +K_SOUP_COV_MIN_LINE = "92" +K_SOUP_COV_MIN_HARD = "true" +K_SOUP_COV_MULTI_FORMATTERS = "true" +K_SOUP_COV_OPEN_BIN = "" +MAX_ROWS = "1" +KETTLE_TEST_SILENT = "true" +KETTLE_DEV_DEBUG = "false" +DEBUG = "false" +FLOSS_CFG_FUND_DEBUG = "false" +FLOSS_CFG_FUND_LOGFILE = "tmp/log/debug.log" +RUBOCOP_LTS_LOCAL = "false" +OPENCOLLECTIVE_HANDLE = "ruby-oauth" +FUNDING_ORG = "ruby-oauth" +_.path = ["exe", "bin"] +_.file = { path = ".env.local", redact = true } +_.source = ".config/mise/env.sh" + +[tools] +ruby = "4.0.4" diff --git a/oauth.gemspec b/oauth.gemspec index c4eb4409..6a62e42e 100644 --- a/oauth.gemspec +++ b/oauth.gemspec @@ -98,11 +98,13 @@ Gem::Specification.new do |spec| # "oauth-tty" was extracted from this gem with release 1.1 of this gem # It is now a dependency for backward compatibility. # The dependency will cease to be a direct dependency with release 2.0. - spec.add_dependency("oauth-tty", "~> 1.0", ">= 1.0.6") - spec.add_dependency("snaky_hash", "~> 2.0") + spec.add_dependency("auth-sanitizer", "~> 0.1", ">= 0.1.2") + spec.add_dependency("oauth-tty", "~> 1.0", ">= 1.0.7") + spec.add_dependency("snaky_hash", "~> 2.0", ">= 2.0.4") # Standard Library Extracted Gems spec.add_dependency("base64", "~> 0.1") # became a bundled gem in ruby 3.4 (was default from 3.0 to 3.3) + spec.add_dependency("cgi", ">= 0") # Utilities spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.9") # ruby >= 2.2.0 @@ -120,17 +122,17 @@ Gem::Specification.new do |spec| # Development dependencies that require strictly newer Ruby versions should be in a "gemfile", # and preferably a modular one (see gemfiles/modular/*.gemfile). - spec.add_development_dependency("mocha") + spec.add_development_dependency("mocha", ">= 0") spec.add_development_dependency("rack", ">= 2.0.0") - spec.add_development_dependency("rack-test") - spec.add_development_dependency("rest-client") + spec.add_development_dependency("rack-test", ">= 0") + spec.add_development_dependency("rest-client", ">= 0") spec.add_development_dependency("typhoeus", ">= 0.1.13") # Dev, Test, & Release Tasks - spec.add_development_dependency("kettle-dev", "~> 1.1") # ruby >= 2.3.0 + spec.add_development_dependency("kettle-dev", "~> 2.0") # ruby >= 2.3.0 # Security - spec.add_development_dependency("bundler-audit", "~> 0.9.2") # ruby >= 2.0.0 + spec.add_development_dependency("bundler-audit", "~> 0.9.3") # ruby >= 2.0.0 # Tasks spec.add_development_dependency("rake", "~> 13.0") # ruby >= 2.2.0 diff --git a/sig/oauth/consumer.rbs b/sig/oauth/consumer.rbs new file mode 100644 index 00000000..c443ab2a --- /dev/null +++ b/sig/oauth/consumer.rbs @@ -0,0 +1,9 @@ +module OAuth + class Consumer + include Auth::Sanitizer::FilteredAttributes + + attr_accessor options: untyped + attr_accessor key: untyped + attr_accessor secret: untyped + end +end diff --git a/sig/oauth/signature/base.rbs b/sig/oauth/signature/base.rbs new file mode 100644 index 00000000..7924ed62 --- /dev/null +++ b/sig/oauth/signature/base.rbs @@ -0,0 +1,12 @@ +module OAuth + module Signature + class Base + include Auth::Sanitizer::FilteredAttributes + + attr_accessor options: untyped + attr_reader token_secret: untyped + attr_reader consumer_secret: untyped + attr_reader request: untyped + end + end +end diff --git a/sig/oauth/tokens/token.rbs b/sig/oauth/tokens/token.rbs new file mode 100644 index 00000000..837276d6 --- /dev/null +++ b/sig/oauth/tokens/token.rbs @@ -0,0 +1,8 @@ +module OAuth + class Token + include Auth::Sanitizer::FilteredAttributes + + attr_accessor token: untyped + attr_accessor secret: untyped + end +end diff --git a/spec/config/debug.rb b/spec/config/debug.rb index 310c26c7..1c7e2677 100644 --- a/spec/config/debug.rb +++ b/spec/config/debug.rb @@ -1,4 +1,4 @@ load_debugger = ENV.fetch("DEBUG", "false").casecmp("true").zero? -puts "LOADING DEBUGGER: #{load_debugger}" if load_debugger +warn "LOADING DEBUGGER: #{load_debugger}" if load_debugger require "debug" if load_debugger diff --git a/spec/config/vcr.rb b/spec/config/vcr.rb index 0c7362a8..6d802ee4 100644 --- a/spec/config/vcr.rb +++ b/spec/config/vcr.rb @@ -1,4 +1,6 @@ # VCR/WebMock: record and replay HTTP to external services (RubyGems, GitHub, etc.) +require "cgi" +require "cgi/core" unless CGI.respond_to?(:parse) require "webmock/rspec" require "vcr" VCR.configure do |c| diff --git a/spec/oauth/net_http_client_spec.rb b/spec/net/http_spec.rb similarity index 93% rename from spec/oauth/net_http_client_spec.rb rename to spec/net/http_spec.rb index 528af974..fdb05a44 100644 --- a/spec/oauth/net_http_client_spec.rb +++ b/spec/net/http_spec.rb @@ -2,11 +2,11 @@ require "net/http" -RSpec.describe "Net::HTTP client OAuth helpers" do +RSpec.describe Net::HTTP do let(:consumer) { OAuth::Consumer.new("consumer_key_86cad9", "5888bf0345e5d237") } let(:token) { OAuth::Token.new("token_411a7f", "3196ffd991c8ebdb") } let(:uri) { URI.parse("http://example.com/test?key=value") } - let(:http) { Net::HTTP.new(uri.host, uri.port) } + let(:http) { described_class.new(uri.host, uri.port) } let(:nonce) { 225_579_211_881_198_842_005_988_698_334_675_835_446 } let(:timestamp) { "1199645624" } diff --git a/spec/oauth/tokens/access_token_spec.rb b/spec/oauth/access_token_spec.rb similarity index 100% rename from spec/oauth/tokens/access_token_spec.rb rename to spec/oauth/access_token_spec.rb diff --git a/spec/oauth/consumer_integration_spec.rb b/spec/oauth/consumer_integration_spec.rb index 63cb8b06..ab95796e 100644 --- a/spec/oauth/consumer_integration_spec.rb +++ b/spec/oauth/consumer_integration_spec.rb @@ -3,9 +3,9 @@ require "spec_helper" require "net/http" -RSpec.describe "OAuth::Consumer integration" do +RSpec.describe OAuth::Consumer do let(:consumer) do - OAuth::Consumer.new( + described_class.new( "consumer_key_86cad9", "5888bf0345e5d237", { @@ -161,7 +161,7 @@ end it "can perform a full token dance and call a protected resource" do - consumer2 = OAuth::Consumer.new( + consumer2 = described_class.new( "key", "secret", { @@ -198,7 +198,7 @@ end it "builds correct signature base string for request token" do - consumer2 = OAuth::Consumer.new( + consumer2 = described_class.new( "key", "secret", { diff --git a/spec/oauth/consumer_spec.rb b/spec/oauth/consumer_spec.rb index 16164db0..f69b9d35 100644 --- a/spec/oauth/consumer_spec.rb +++ b/spec/oauth/consumer_spec.rb @@ -56,6 +56,13 @@ expect(consumer.debug_output).to be($stdout) end + it "redacts secret from inspect" do + consumer = described_class.new("key", "super-secret", site: "http://twitter.com") + + expect(consumer.inspect).to include("@secret=[FILTERED]") + expect(consumer.inspect).not_to include("super-secret") + end + it "accepts an IO for debug_output" do io = StringIO.new consumer = described_class.new("key", "secret", debug_output: io) @@ -88,25 +95,31 @@ # We don't need to actually perform the HTTP call; we just want to verify # that the request path sent to Net::HTTP::Get.new is correct. - request_double = instance_double("Net::HTTP::Get").as_null_object - expect(Net::HTTP::Get).to receive(:new).with("/people", kind_of(Hash)).and_return(request_double) + request_double = instance_double(Net::HTTP::Get).as_null_object + allow(Net::HTTP::Get).to receive(:new).with("/people", kind_of(Hash)).and_return(request_double) http_double = double("http", request: double("response", to_hash: {}), address: "identi.ca") - expect(consumer).to receive(:create_http).and_return(http_double) + allow(consumer).to receive(:create_http).and_return(http_double) consumer.request(:get, "/people", nil, {}) + + expect(Net::HTTP::Get).to have_received(:new).with("/people", kind_of(Hash)) + expect(consumer).to have_received(:create_http) end it "prefixes site path when site includes a path" do consumer = described_class.new("key", "secret", site: "http://identi.ca/api") - request_double = instance_double("Net::HTTP::Get").as_null_object - expect(Net::HTTP::Get).to receive(:new).with("/api/people", kind_of(Hash)).and_return(request_double) + request_double = instance_double(Net::HTTP::Get).as_null_object + allow(Net::HTTP::Get).to receive(:new).with("/api/people", kind_of(Hash)).and_return(request_double) http_double = double("http", request: double("response", to_hash: {}), address: "identi.ca") - expect(consumer).to receive(:create_http).and_return(http_double) + allow(consumer).to receive(:create_http).and_return(http_double) consumer.request(:get, "/people", nil, {}) + + expect(Net::HTTP::Get).to have_received(:new).with("/api/people", kind_of(Hash)) + expect(consumer).to have_received(:create_http) end end @@ -151,9 +164,11 @@ allow(http_instance).to receive(:cert=) allow(http_instance).to receive(:key=) allow(http_instance).to receive(:set_debug_output) - allow(http_instance).to receive(:address).and_return("authentication.mysite.co.nz") + allow(http_instance).to receive_messages( + address: "authentication.mysite.co.nz", + request: double(to_hash: {}, code: "200", body: ""), + ) expect(http_instance).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) - allow(http_instance).to receive(:request).and_return(double(to_hash: {}, code: "200", body: "")) # The request inside get_request_token is not important beyond hitting the code path expect { consumer.get_request_token }.not_to raise_error @@ -181,9 +196,11 @@ allow(http_instance).to receive(:cert=) allow(http_instance).to receive(:key=) allow(http_instance).to receive(:set_debug_output) - allow(http_instance).to receive(:address).and_return("authentication.mysite.co.nz") + allow(http_instance).to receive_messages( + address: "authentication.mysite.co.nz", + request: double(to_hash: {}, code: "200", body: ""), + ) expect(http_instance).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) - allow(http_instance).to receive(:request).and_return(double(to_hash: {}, code: "200", body: "")) expect { consumer.get_request_token }.not_to raise_error end @@ -209,9 +226,11 @@ allow(http_instance).to receive(:cert=) allow(http_instance).to receive(:key=) allow(http_instance).to receive(:set_debug_output) - allow(http_instance).to receive(:address).and_return("authentication.mysite.co.nz") - allow(http_instance).to receive(:verify_mode=) - allow(http_instance).to receive(:request).and_return(double(to_hash: {}, code: "200", body: "")) + allow(http_instance).to receive_messages( + :verify_mode= => nil, + :address => "authentication.mysite.co.nz", + :request => double(to_hash: {}, code: "200", body: ""), + ) expect { consumer.get_request_token }.not_to raise_error end diff --git a/spec/oauth/request_proxy/net_http_spec.rb b/spec/oauth/request_proxy/net/http/http_request_spec.rb similarity index 100% rename from spec/oauth/request_proxy/net_http_spec.rb rename to spec/oauth/request_proxy/net/http/http_request_spec.rb diff --git a/spec/oauth/tokens/request_token_spec.rb b/spec/oauth/request_token_spec.rb similarity index 90% rename from spec/oauth/tokens/request_token_spec.rb rename to spec/oauth/request_token_spec.rb index 69a528c6..13f02c6d 100644 --- a/spec/oauth/tokens/request_token_spec.rb +++ b/spec/oauth/request_token_spec.rb @@ -56,14 +56,16 @@ end describe "private build_url (via stubbed subclass)" do - class StubbedToken < OAuth::RequestToken - def build_url_promoted(root_domain, params) - build_url(root_domain, params) + let(:stubbed_token_class) do + Class.new(described_class) do + def build_url_promoted(root_domain, params) + build_url(root_domain, params) + end end end it "percent-encodes values and joins with ?" do - token = StubbedToken.new(nil, nil, nil) + token = stubbed_token_class.new(nil, nil, nil) expect(token).to respond_to(:build_url_promoted) url = token.build_url_promoted("http://github.com/oauth/authorize", {foo: "bar bar"}) expect(url).to eq("http://github.com/oauth/authorize?foo=bar+bar") diff --git a/spec/oauth/server_token_spec.rb b/spec/oauth/server_token_spec.rb index fa70ccf3..0ebfa5e1 100644 --- a/spec/oauth/server_token_spec.rb +++ b/spec/oauth/server_token_spec.rb @@ -4,22 +4,30 @@ require "oauth/tokens/server_token" RSpec.describe OAuth::ServerToken do + let(:token_class) do + Class.new(described_class) do + def generate_key(size = 32) + (size == 16) ? "TOKEN16" : "SECRET32" + end + end + end + describe "initialization" do it "initializes with generated token and secret" do - allow_any_instance_of(described_class).to receive(:generate_key).with(16).and_return("TOKEN16") - allow_any_instance_of(described_class).to receive(:generate_key).with(no_args).and_return("SECRET32") - - st = described_class.new + st = token_class.new expect(st.token).to eq("TOKEN16") expect(st.secret).to eq("SECRET32") end it "defaults secret length to helper generate_key (no args)" do - allow_any_instance_of(described_class).to receive(:generate_key).with(16).and_return("A" * 8) - allow_any_instance_of(described_class).to receive(:generate_key).with(no_args).and_return("B" * 12) + token_class = Class.new(described_class) do + def generate_key(size = 32) + (size == 16) ? ("A" * 8) : ("B" * 12) + end + end - st = described_class.new + st = token_class.new expect(st.token).to eq("A" * 8) expect(st.secret).to eq("B" * 12) diff --git a/spec/oauth/signature/base_spec.rb b/spec/oauth/signature/base_spec.rb index c013d814..57abba59 100644 --- a/spec/oauth/signature/base_spec.rb +++ b/spec/oauth/signature/base_spec.rb @@ -27,5 +27,17 @@ described_class.new(request) { |_token| } end.not_to raise_error end + + it "redacts consumer and token secrets from inspect" do + raw_request = Net::HTTP::Get.new("/test") + request = OAuth::RequestProxy.proxy(raw_request) + + signature = described_class.new(request, consumer_secret: "consumer-secret", token_secret: "token-secret") + + expect(signature.inspect).to include("@consumer_secret=[FILTERED]") + expect(signature.inspect).to include("@token_secret=[FILTERED]") + expect(signature.inspect).not_to include("consumer-secret") + expect(signature.inspect).not_to include("token-secret") + end end end diff --git a/spec/oauth/signature/hmac_sha1_spec.rb b/spec/oauth/signature/hmac/sha1_spec.rb similarity index 100% rename from spec/oauth/signature/hmac_sha1_spec.rb rename to spec/oauth/signature/hmac/sha1_spec.rb diff --git a/spec/oauth/signature/hmac_sha256_spec.rb b/spec/oauth/signature/hmac/sha256_spec.rb similarity index 100% rename from spec/oauth/signature/hmac_sha256_spec.rb rename to spec/oauth/signature/hmac/sha256_spec.rb diff --git a/spec/oauth/signature/plaintext_spec.rb b/spec/oauth/signature/plaintext_spec.rb index 059d3311..2505909b 100644 --- a/spec/oauth/signature/plaintext_spec.rb +++ b/spec/oauth/signature/plaintext_spec.rb @@ -2,7 +2,7 @@ require "net/http" -RSpec.describe "OAuth::Signature PLAINTEXT" do +RSpec.describe OAuth::Signature::PLAINTEXT do describe "::available_methods" do it "includes 'plaintext'" do expect(OAuth::Signature.available_methods).to include("plaintext") diff --git a/spec/oauth/signature/rsa_sha1_spec.rb b/spec/oauth/signature/rsa/sha1_spec.rb similarity index 98% rename from spec/oauth/signature/rsa_sha1_spec.rb rename to spec/oauth/signature/rsa/sha1_spec.rb index 0466738f..342ecb13 100644 --- a/spec/oauth/signature/rsa_sha1_spec.rb +++ b/spec/oauth/signature/rsa/sha1_spec.rb @@ -8,8 +8,7 @@ RSpec.describe OAuth::Signature::RSA::SHA1 do def project_spec_dir - # two levels up from spec/oauth/signature -> spec - File.expand_path(File.join(__dir__, "../..")) + File.expand_path(File.join(__dir__, "../../..")) end def pem_path diff --git a/spec/oauth/token_spec.rb b/spec/oauth/token_spec.rb index a3262cf2..c63caece 100644 --- a/spec/oauth/token_spec.rb +++ b/spec/oauth/token_spec.rb @@ -9,5 +9,14 @@ expect(token.token).to eq("xyz") expect(token.secret).to eq("123") end + + it "redacts token and secret from inspect" do + token = described_class.new("xyz", "123") + + expect(token.inspect).to include("@token=[FILTERED]") + expect(token.inspect).to include("@secret=[FILTERED]") + expect(token.inspect).not_to include("xyz") + expect(token.inspect).not_to include("123") + end end end diff --git a/spec/oauth/tty/cli_spec.rb b/spec/oauth/tty/cli_spec.rb index c85e879e..ede7e1a9 100644 --- a/spec/oauth/tty/cli_spec.rb +++ b/spec/oauth/tty/cli_spec.rb @@ -108,12 +108,14 @@ def parse(command) end it "performs query and prints request/response" do - consumer = instance_double("OAuth::Consumer") - access_token = instance_double("OAuth::AccessToken") - response = instance_double("Response", code: "!code!", message: "!message!", body: "!body!") + consumer = instance_double(OAuth::Consumer) + access_token = instance_double(OAuth::AccessToken) + response = double("response", code: "!code!", message: "!message!", body: "!body!") - allow(OAuth::Helper).to receive(:generate_key).and_return("GENERATE_KEY") - allow(OAuth::Helper).to receive(:generate_timestamp).and_return("GENERATE_TIMESTAMP") + allow(OAuth::Helper).to receive_messages( + generate_key: "GENERATE_KEY", + generate_timestamp: "GENERATE_TIMESTAMP", + ) expect(OAuth::Consumer).to receive(:new) do |key, secret, options| expect(key).to eq("oauth_consumer_key") @@ -129,7 +131,7 @@ def parse(command) access_token end - expect(access_token).to receive(:request).with(:post, "http://example.com/oauth/url?oauth_consumer_key=oauth_consumer_key&oauth_nonce=GENERATE_KEY&oauth_timestamp=GENERATE_TIMESTAMP&oauth_token=TOKEN&oauth_signature_method=HMAC-SHA1&oauth_version=1.0").and_return(response) + allow(access_token).to receive(:request).with(:post, "http://example.com/oauth/url?oauth_consumer_key=oauth_consumer_key&oauth_nonce=GENERATE_KEY&oauth_timestamp=GENERATE_TIMESTAMP&oauth_token=TOKEN&oauth_signature_method=HMAC-SHA1&oauth_version=1.0").and_return(response) out = run_command %w[ query @@ -153,12 +155,14 @@ def parse(command) end it "performs authorize and prompts/prints response" do - access_token = instance_double("OAuth::AccessToken", params: {}) - consumer = instance_double("OAuth::Consumer") - request_token = instance_double("OAuth::RequestToken") + access_token = instance_double(OAuth::AccessToken, params: {}) + consumer = instance_double(OAuth::Consumer) + request_token = instance_double(OAuth::RequestToken) - allow(OAuth::Helper).to receive(:generate_key).and_return("GENERATE_KEY") - allow(OAuth::Helper).to receive(:generate_timestamp).and_return("GENERATE_TIMESTAMP") + allow(OAuth::Helper).to receive_messages( + generate_key: "GENERATE_KEY", + generate_timestamp: "GENERATE_TIMESTAMP", + ) expect(OAuth::Consumer).to receive(:new) do |key, secret, options| expected = {access_token_url: nil, authorize_url: nil, request_token_url: nil, scheme: :header, http_method: :get} @@ -168,10 +172,12 @@ def parse(command) consumer end - expect(consumer).to receive(:get_request_token).with({oauth_callback: nil}, {}).and_return(request_token) - expect(request_token).to receive(:callback_confirmed?).and_return(false) - expect(request_token).to receive(:authorize_url).and_return("!url1!") - expect(request_token).to receive(:get_access_token).with({oauth_verifier: nil}).and_return(access_token) + allow(consumer).to receive(:get_request_token).with({oauth_callback: nil}, {}).and_return(request_token) + allow(request_token).to receive_messages( + authorize_url: "!url1!", + callback_confirmed?: false, + ) + allow(request_token).to receive(:get_access_token).with({oauth_verifier: nil}).and_return(access_token) out = run_command %w[ authorize @@ -194,12 +200,14 @@ def parse(command) end it "signs a request and prints signature details and value" do - instance_double("OAuth::AccessToken", params: {}) - instance_double("OAuth::Consumer") - instance_double("OAuth::RequestToken") - - allow(OAuth::Helper).to receive(:generate_key).and_return("GENERATE_KEY") - allow(OAuth::Helper).to receive(:generate_timestamp).and_return("GENERATE_TIMESTAMP") + instance_double(OAuth::AccessToken, params: {}) + instance_double(OAuth::Consumer) + instance_double(OAuth::RequestToken) + + allow(OAuth::Helper).to receive_messages( + generate_key: "GENERATE_KEY", + generate_timestamp: "GENERATE_TIMESTAMP", + ) out = [] diff --git a/spec/oauth/backwards_compatibility_spec.rb b/spec/oauth_backwards_compatibility_spec.rb similarity index 78% rename from spec/oauth/backwards_compatibility_spec.rb rename to spec/oauth_backwards_compatibility_spec.rb index f691d24e..81c2d213 100644 --- a/spec/oauth/backwards_compatibility_spec.rb +++ b/spec/oauth_backwards_compatibility_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe "Backwards compatibility" do +RSpec.describe OAuth do it "aliases OAuth::CLI to OAuth::TTY::CLI" do require "oauth/cli" expect(OAuth::CLI).to be(OAuth::TTY::CLI)