From 0dabd4776fe88490be9539ca687adb31de478137 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 17:42:06 -0600 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=8D=B1=20Fix=20logo=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 36baf72..61bf5ee 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -[![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![oauth-tty Logo by Aboling0, CC BY-SA 4.0][🖼️oauth-tty-i]][🖼️oauth-tty] +[![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![oauth Logo by Chris Messina, CC BY-SA 3.0][🖼️oauth-tty-i]][🖼️oauth-tty] [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg [🖼️ruby-lang]: https://www.ruby-lang.org/ -[🖼️oauth-tty-i]: https://logos.galtzo.com/assets/images/ruby-oauth/oauth-tty/avatar-192px.svg +[🖼️oauth-tty-i]: https://logos.galtzo.com/assets/images/oauth/avatar-192px.svg [🖼️oauth-tty]: https://github.com/ruby-oauth/oauth-tty # 🖥️ OAuth::TTY From 20fee75015e6fd00cd5276ed48d085a555163f85 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 17:43:57 -0600 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9D=20Update=20gemspec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oauth-tty.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauth-tty.gemspec b/oauth-tty.gemspec index 69772fa..0433818 100644 --- a/oauth-tty.gemspec +++ b/oauth-tty.gemspec @@ -18,8 +18,8 @@ Gem::Specification.new do |spec| spec.authors = ["Thiago Pinto", "Peter Boling"] spec.email = ["floss@galtzo.com", "oauth-ruby@googlegroups.com"] - spec.summary = "🖥️ OAuth 1.0 TTY CLI" - spec.description = "🖥️ OAuth 1.0 TTY Command Line Interface" + spec.summary = "🖥️ OAuth 1.0 / 1.0a TTY CLI" + spec.description = "🖥️ OAuth 1.0 / 1.0a TTY Command Line Interface" spec.homepage = "https://github.com/ruby-oauth/oauth-tty" spec.licenses = ["MIT"] spec.required_ruby_version = ">= 2.3.0" From a98bfa24cd61bf3f243a67399966db2279c33ae6 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 19:10:43 -0600 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20issues=20in=20option?= =?UTF-8?q?=20parsing=20by=20implementing=20Command#parse=5Foptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use Shellwords for proper tokenization - Verified options file loading and CLI flag precedence - Added specs for oauth.opts usage - Added specs for all commands --- .idea/misc.xml | 2 +- .idea/vcs.xml | 1 + .rubocop_gradual.lock | 79 +++++----- .rubocop_rspec.yml | 6 +- CHANGELOG.md | 6 + README.md | 143 ++++++++++++++++++ lib/oauth/tty/command.rb | 22 ++- spec/oauth/tty/command_spec.rb | 5 + .../{ => commands}/authorize_command_spec.rb | 0 spec/oauth/tty/commands/help_command_spec.rb | 31 ++++ spec/oauth/tty/commands/query_command_spec.rb | 69 +++++++++ spec/oauth/tty/commands/sign_command_spec.rb | 125 +++++++++++++++ .../tty/commands/version_command_spec.rb | 27 ++++ spec/oauth/tty/sign_command_spec.rb | 105 ------------- spec/support/fixtures/oauth.opts | 11 ++ 15 files changed, 479 insertions(+), 153 deletions(-) rename spec/oauth/tty/{ => commands}/authorize_command_spec.rb (100%) create mode 100644 spec/oauth/tty/commands/help_command_spec.rb create mode 100644 spec/oauth/tty/commands/query_command_spec.rb create mode 100644 spec/oauth/tty/commands/sign_command_spec.rb create mode 100644 spec/oauth/tty/commands/version_command_spec.rb delete mode 100644 spec/oauth/tty/sign_command_spec.rb create mode 100644 spec/support/fixtures/oauth.opts diff --git a/.idea/misc.xml b/.idea/misc.xml index b4353a6..b43a841 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 4c6280e..4ba488d 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock index cc33db0..29b2177 100644 --- a/.rubocop_gradual.lock +++ b/.rubocop_gradual.lock @@ -2,10 +2,10 @@ "lib/oauth/tty/cli.rb:904168046": [ [6, 7, 77, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2883780555] ], - "lib/oauth/tty/command.rb:3092098200": [ - [126, 23, 4, "Security/Open: The use of `Kernel#open` is a serious security risk.", 2087926481] + "lib/oauth/tty/command.rb:587864942": [ + [146, 23, 4, "Security/Open: The use of `Kernel#open` is a serious security risk.", 2087926481] ], - "oauth-tty.gemspec:3319279878": [ + "oauth-tty.gemspec:2020207654": [ [129, 3, 40, "Gemspec/DependencyVersion: Dependency version specification is required.", 2300588954], [131, 3, 44, "Gemspec/DependencyVersion: Dependency version specification is required.", 1905290578], [132, 3, 46, "Gemspec/DependencyVersion: Dependency version specification is required.", 4289565910] @@ -13,33 +13,7 @@ "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/tty/authorize_command_spec.rb:3270431476": [ - [3, 1, 53, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth/tty/commands/authorize_command*_spec.rb`.", 2667806271], - [14, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [15, 39, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], - [16, 38, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], - [19, 7, 64, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [20].", 1559313276], - [20, 7, 69, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [19].", 3030878101], - [22, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], - [23, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [25, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [26, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [32, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [52, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [54, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], - [55, 7, 44, "RSpec/LeakyConstantDeclaration: Stub constant instead of declaring explicitly.", 2395720961], - [57, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [68, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [69, 39, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], - [70, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], - [71, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [73, 7, 45, "RSpec/LeakyConstantDeclaration: Stub constant instead of declaring explicitly.", 1997245299], - [75, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [76, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], - [77, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262] - ], "spec/oauth/tty/cli_spec.rb:361981118": [ - [3, 1, 30, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth/tty/cli*_spec.rb`.", 2849860169], [109, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], [110, 38, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], [111, 34, 10, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 4294324198], @@ -65,25 +39,40 @@ [212, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], [213, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262] ], - "spec/oauth/tty/command_spec.rb:513689811": [ - [3, 1, 34, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth/tty/command*_spec.rb`.", 667110152], + "spec/oauth/tty/command_spec.rb:2516268945": [ [9, 3, 275, "RSpec/LeakyConstantDeclaration: Stub class constant instead of declaring explicitly.", 2810654211] ], - "spec/oauth/tty/sign_command_spec.rb:622094174": [ - [3, 1, 48, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth/tty/commands/sign_command*_spec.rb`.", 3251857167], - [21, 7, 27, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 340848961], - [69, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], - [70, 35, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], - [71, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], - [72, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], - [73, 7, 67, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [74, 75].", 42619244], - [74, 7, 87, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [73, 75].", 1827647110], - [75, 7, 89, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [73, 74].", 1585010367], - [75, 81, 13, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 4026407386], - [77, 7, 27, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 340848961] + "spec/oauth/tty/commands/authorize_command_spec.rb:3270431476": [ + [14, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], + [15, 39, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], + [16, 38, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], + [19, 7, 64, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [20].", 1559313276], + [20, 7, 69, "RSpec/ReceiveMessages: Use `receive_messages` instead of multiple stubs on lines [19].", 3030878101], + [22, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], + [23, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], + [25, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], + [26, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], + [32, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], + [52, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], + [54, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], + [55, 7, 44, "RSpec/LeakyConstantDeclaration: Stub constant instead of declaring explicitly.", 2395720961], + [57, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], + [68, 34, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], + [69, 39, 21, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 3390344648], + [70, 7, 23, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4174421602], + [71, 7, 16, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 3492346277], + [73, 7, 45, "RSpec/LeakyConstantDeclaration: Stub constant instead of declaring explicitly.", 1997245299], + [75, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], + [76, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262], + [77, 7, 21, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 2407753262] + ], + "spec/oauth/tty/commands/query_command_spec.rb:1247725853": [ + [20, 32, 17, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 285748316], + [23, 36, 20, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 1228090493], + [26, 37, 19, "RSpec/VerifiedDoubleReference: Use a constant class reference for verified doubles. String references are not verifying unless the class is loaded.", 511534081], + [55, 5, 20, "RSpec/StubbedMock: Prefer `allow` over `expect` when configuring a response.", 4235470523] ], "spec/oauth/tty_spec.rb:1891755344": [ - [3, 1, 25, "RSpec/EmptyExampleGroup: Empty example group detected.", 208109039], - [3, 1, 25, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth/tty*_spec.rb`.", 208109039] + [3, 1, 25, "RSpec/EmptyExampleGroup: Empty example group detected.", 208109039] ] } diff --git a/.rubocop_rspec.yml b/.rubocop_rspec.yml index df5911b..45f84ad 100644 --- a/.rubocop_rspec.yml +++ b/.rubocop_rspec.yml @@ -18,7 +18,7 @@ RSpec/InstanceVariable: RSpec/NestedGroups: Enabled: false - + RSpec/ExpectInHook: Enabled: false @@ -28,3 +28,7 @@ RSpec/DescribeClass: RSpec/MultipleMemoizedHelpers: Enabled: false + +RSpec/SpecFilePathFormat: + CustomTransform: + "OAuth": "oauth" diff --git a/CHANGELOG.md b/CHANGELOG.md index fac49b6..18d32f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ Please file a bug if you notice a violation of semantic versioning. - (test) many new tests (@pboling) - (docs) CITATION.cff - support window increased, down to Ruby 2.3 (@pboling) +- (test) added specs for oauth.opts usage (@pboling) +- (test) added specs for all commands (@pboling) ### Changed @@ -42,6 +44,10 @@ Please file a bug if you notice a violation of semantic versioning. ### Fixed +- Fixed issues in option parsing by implementing Command#parse_options (@pboling) + - Use Shellwords for proper tokenization + - Verified options file loading and CLI flag precedence + ### Security ## [1.0.5] - 2022-09-20 diff --git a/README.md b/README.md index 61bf5ee..9b302dc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,21 @@ ## 🌻 Synopsis +OAuth 1.0a is an industry-standard protocol for authorization. +It is an update to the original OAuth 1.0 protocol, and is used by many popular services. +This is a RubyGem for implementing OAuth 1.0 or 1.0a _clients_ and _servers_ in Ruby applications. +See the sibling `oauth2` gem for OAuth 2.0, 2.1, & OIDC clients in Ruby. + +All dependencies of this gem are signed, so it can be installed with a `HighSecurity` profile. + +* [OAuth 1.0 Spec][oauth1-spec] +* [oauth gem][sibling-gem] for OAuth 1.0 / 1.0a client and server implementations in Ruby. +* [oauth2 sibling gem][sibling2-gem] for OAuth 2.0 / 2.1, & OIDC client implementations in Ruby. + +[oauth1-spec]: http://oauth.net/core/1.0/ +[sibling-gem]: https://gitlab.com/ruby-oauth/oauth +[sibling2-gem]: https://gitlab.com/ruby-oauth/oauth2 ## 💡 Info you can shake a stick at @@ -138,12 +152,141 @@ NOTE: Be prepared to track down certs for signed gems and add them the same way ## ⚙️ Configuration +The oauth-tty gem is a thin CLI over the oauth gem. You supply your consumer credentials, token credentials (when applicable), a target URI, and optional parameters, and the tool signs requests or helps you complete an OAuth 1.0/1.0a 3-legged flow. + +What you can configure +- Locations for OAuth parameters: + - --header (default): send OAuth params in Authorization header + - --body: send OAuth params in the request body + - --query-string: send OAuth params on the query string +- HTTP method: --method GET|POST|PUT|DELETE|… (default: POST) +- Signature method: --signature-method HMAC-SHA1|RSA-SHA1|PLAINTEXT (default: HMAC-SHA1) +- OAuth version: --version 1.0 (default: 1.0) or --no-version to omit +- Nonce/timestamp: auto-generated by default; can be overridden via --nonce and --timestamp +- Verbose output: --verbose prints the full signing breakdown, headers, and signature base string +- XMPP mode: --xmpp emits OAuth as an XMPP stanza instead of HTTP artifacts + +Required inputs (by command) +- sign, query: --consumer-key, --consumer-secret, --token, --secret, and --uri +- authorize: --consumer-key, --consumer-secret, the OAuth endpoints below, and --uri (service resource you’re authorizing for) + +Authorization endpoints (for oauth authorize) +- --request-token-url URL +- --authorize-url URL +- --access-token-url URL +- Optional: --callback-url URL (for 1.0a), --scope SCOPE (provider-specific) + +Providing options +- CLI flags (preferred for quick usage) +- Options file: use -O or --options to read additional arguments from a file. The file is tokenized by whitespace; put the same flags you’d pass on the command line, spread across lines as needed. + +Example options file (oauth.opts) +```text +--consumer-key ck_123 +--consumer-secret cs_456 +--token at_789 +--secret ats_abc +--method GET +--uri https://api.example.com/v1/profile +--parameters foo:bar +--parameters "status=active" +--header +``` +Run with: oauth sign -O oauth.opts +Defaults at a glance +- scheme: header +- method: POST +- signature method: HMAC-SHA1 +- oauth_version: 1.0 (omit with --no-version) +- nonce, timestamp: auto-generated each run + +Tips +- For parameters you can use either key:value or already-escaped pairs like key=value. Repeat --parameters to add multiple pairs. +- When using --body, only methods that support bodies should be used (e.g., POST/PUT/PATCH). +- Some providers require exact parameter ordering and inclusion; use --verbose to see normalized parameters and the signature base string. ## 🔧 Basic Usage In a shell run `oauth` to start the console. +Quick help and version +```console +oauth --help +oauth --version # or oauth -v +``` + +Sign a request (minimal) +Print just the OAuth signature value for a GET request: +```console +oauth sign \ + --consumer-key ck \ + --consumer-secret cs \ + --token at \ + --secret ats \ + --method GET \ + --uri "https://api.example.com/v1/resource?limit=10" +``` + +Sign a request (verbose, header output) +```console +oauth sign \ + --consumer-key ck \ + --consumer-secret cs \ + --token at \ + --secret ats \ + --method POST \ + --uri https://api.example.com/v1/resource \ + --parameters "status=active" \ + --header \ + --verbose +``` +This prints OAuth parameters, normalized parameters, signature base string, Authorization header, and both raw and escaped signatures. + +Query a protected resource +Performs the signed HTTP request and prints the HTTP status and body. +```console +oauth query \ + --consumer-key ck \ + --consumer-secret cs \ + --token at \ + --secret ats \ + --method GET \ + --uri https://api.example.com/v1/profile \ + --parameters "fields=id,name" +``` +Notes: +- The CLI will append any --parameters to the request URI’s query string and sign the request. +- Use --body or --header/--query-string to influence where OAuth params go; query also constructs OAuth via the consumer internally. + +Start an OAuth 1.0a authorization flow +Guides you to obtain an access token and token secret from a provider. +```console +oauth authorize \ + --consumer-key ck \ + --consumer-secret cs \ + --request-token-url https://provider.example.com/oauth/request_token \ + --authorize-url https://provider.example.com/oauth/authorize \ + --access-token-url https://provider.example.com/oauth/access_token \ + --callback-url https://yourapp.example.com/oauth/callback +``` +What happens: +- You’ll be shown an authorization URL to open in a browser. +- After approving, the provider shows a verifier (PIN). Paste it back into the prompt. +- The tool prints the access token and secret under “Response:”. Save those and use them with sign/query. + +Using an options file +```console +oauth sign -O oauth.opts +``` +You can still add/override flags after -O; later flags win. + +For more examples +- Run any command without args to see its specific help. +- Browse the specs under spec/oauth/tty for additional scenarios and edge cases. + +In a shell run `oauth` to start the console. + For now, please see the tests for other usage. ## 🦷 FLOSS Funding diff --git a/lib/oauth/tty/command.rb b/lib/oauth/tty/command.rb index 71b437d..a810758 100644 --- a/lib/oauth/tty/command.rb +++ b/lib/oauth/tty/command.rb @@ -88,6 +88,25 @@ def option_parser end end + # Parse an array of CLI-like arguments into an options hash without mutating current state + # This is used by the -O/--options FILE feature to load args from a file and merge them + def parse_options(arguments) + original_options = @options + begin + temp_options = {} + @options = temp_options + _option_parser_defaults + OptionParser.new do |opts| + _option_parser_common(opts) + _option_parser_sign_and_query(opts) + _option_parser_authorization(opts) + end.parse!(arguments) + temp_options + ensure + @options = original_options + end + end + def _option_parser_defaults options[:oauth_nonce] = OAuth::Helper.generate_key options[:oauth_signature_method] = "HMAC-SHA1" @@ -123,7 +142,8 @@ def _option_parser_common(opts) end opts.on("-O", "--options FILE", "Read options from a file") do |v| - arguments = open(v).readlines.map { |l| l.chomp.split }.flatten + require "shellwords" + arguments = File.open(v).readlines.flat_map { |l| Shellwords.shellsplit(l.chomp) } options2 = parse_options(arguments) options.merge!(options2) end diff --git a/spec/oauth/tty/command_spec.rb b/spec/oauth/tty/command_spec.rb index ddb7855..c2a1e7e 100644 --- a/spec/oauth/tty/command_spec.rb +++ b/spec/oauth/tty/command_spec.rb @@ -186,4 +186,9 @@ def build_cmd(args = []) expect(stderr.read).to eq("err!\n") end end + + it "has no required options by default" do + command = described_class.new(stdout, stdin, stderr, []) + expect(command.required_options).to eq([]) + end end diff --git a/spec/oauth/tty/authorize_command_spec.rb b/spec/oauth/tty/commands/authorize_command_spec.rb similarity index 100% rename from spec/oauth/tty/authorize_command_spec.rb rename to spec/oauth/tty/commands/authorize_command_spec.rb diff --git a/spec/oauth/tty/commands/help_command_spec.rb b/spec/oauth/tty/commands/help_command_spec.rb new file mode 100644 index 0000000..c026940 --- /dev/null +++ b/spec/oauth/tty/commands/help_command_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +RSpec.describe OAuth::TTY::Commands::HelpCommand, :check_output do + let(:stdout) { StringIO.new } + let(:stdin) { StringIO.new } + let(:stderr) { StringIO.new } + + def run_cli(command, argv = []) + cli = OAuth::TTY::CLI.new(stdout, stdin, stderr, command, argv.dup) + cli.run + stdout.rewind + out = stdout.read + stdout.truncate(0) + stdout.rewind + out + end + + it "prints usage and lists available commands" do + out = run_cli("help", []) + + expect(out).to include("Usage: oauth COMMAND") + expect(out).to include("authorize") + expect(out).to include("query") + expect(out).to include("sign") + expect(out).to include("version") + expect(out).to include("help") + + # no errors expected + expect(stderr.string).to eq("") + end +end diff --git a/spec/oauth/tty/commands/query_command_spec.rb b/spec/oauth/tty/commands/query_command_spec.rb new file mode 100644 index 0000000..b316b4d --- /dev/null +++ b/spec/oauth/tty/commands/query_command_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +RSpec.describe OAuth::TTY::Commands::QueryCommand, :check_output do + let(:stdout) { StringIO.new } + let(:stdin) { StringIO.new } + let(:stderr) { StringIO.new } + + def run_cli(command, argv) + cli = OAuth::TTY::CLI.new(stdout, stdin, stderr, command, argv.dup) + cli.run + stdout.rewind + out = stdout.read + stdout.truncate(0) + stdout.rewind + out + end + + it "appends parameters to the URI, performs the request, and prints status and body" do + # Stub network objects so we don't make real HTTP calls + consumer = instance_double("OAuth::Consumer") + allow(OAuth::Consumer).to receive(:new).and_return(consumer) + + access_token = instance_double("OAuth::AccessToken") + allow(OAuth::AccessToken).to receive(:new).with(consumer, "at_789", "ats_abc").and_return(access_token) + + fake_response = instance_double("Net::HTTPResponse", code: "200", message: "OK", body: "Hello world") + + # Build args + uri = "https://api.example.com/v1/profile" + argv = [ + "--consumer-key", + "ck_123", + "--consumer-secret", + "cs_456", + "--token", + "at_789", + "--secret", + "ats_abc", + "--method", + "GET", + "--nonce", + "abc123", + "--timestamp", + "1699999999", + "--uri", + uri, + "--parameters", + "foo:bar", + "--parameters", + "status=active", + ] + + expected_url = "#{uri}?oauth_consumer_key=ck_123&oauth_nonce=abc123&oauth_timestamp=1699999999&oauth_token=at_789&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&foo=bar&status=active" + + expect(access_token).to receive(:request).with(:get, expected_url).and_return(fake_response) + + out = run_cli("query", argv) + + # First line prints the final URL used + # Second line prints status line + # Third line prints response body + expect(out).to include(expected_url) + expect(out).to include("200 OK") + expect(out).to include("Hello world") + + # no errors expected + expect(stderr.string).to eq("") + end +end diff --git a/spec/oauth/tty/commands/sign_command_spec.rb b/spec/oauth/tty/commands/sign_command_spec.rb new file mode 100644 index 0000000..d625146 --- /dev/null +++ b/spec/oauth/tty/commands/sign_command_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +RSpec.describe OAuth::TTY::Commands::SignCommand, :check_output do + let(:stdout) { StringIO.new } + # Use a path relative to the project spec/ root so specs are idempotent regardless of CWD + let(:fixture_path) { File.expand_path("../../../support/fixtures/oauth.opts", __dir__) } + let(:stdin) { StringIO.new } + let(:stderr) { StringIO.new } + + # Helper to run CLI and capture stdout string + def run_cli(command, argv) + cli = OAuth::TTY::CLI.new(stdout, stdin, stderr, command, argv.dup) + cli.run + stdout.rewind + out = stdout.read + stdout.truncate(0) + stdout.rewind + out + end + + it "loads options from a file with -O and produces same signature as inline args (quoted values supported)" do + require "shellwords" + # Signature via -O file + sig_from_file = run_cli("sign", ["-O", fixture_path]) + + # Signature via equivalent inline args (tokens parsed like a shell) + inline_args = File.readlines(fixture_path, chomp: true).flat_map { |l| Shellwords.shellsplit(l) } + sig_inline = run_cli("sign", inline_args) + + expect(sig_from_file).to eq(sig_inline) + # Basic sanity: looks like a base64 signature string + expect(sig_from_file.strip).to match(/^[A-Za-z0-9+\/]+=*\n?$/) + end + + it "allows later flags to override values loaded from the options file (later flags win)" do + require "shellwords" + # Compute expected signature when overriding the method to POST + inline_args = File.readlines(fixture_path, chomp: true).flat_map { |l| Shellwords.shellsplit(l) } + overridden_inline = inline_args.dup + # Replace method to POST (remove any existing --method value then add POST at the end) + # Simpler approach: just append --method POST to override prior value + overridden_inline += ["--method", "POST"] + + expected_sig = run_cli("sign", overridden_inline) + + # Now via -O file + later CLI flags + sig_from_file_then_override = run_cli("sign", ["-O", fixture_path, "--method", "POST"]) + + expect(sig_from_file_then_override).to eq(expected_sig) + end + + it "prints non-OAuth parameters block and a trailing blank line in verbose mode" do + args = [ + "--consumer-key", + "ck_123", + "--consumer-secret", + "cs_456", + "--token", + "at_789", + "--secret", + "ats_abc", + "--nonce", + "4d7b2e0f9a", + "--timestamp", + "1699999999", + "--method", + "GET", + "--uri", + "https://api.example.com/v1/profile", + "--parameters", + "foo:bar", + "--parameters", + "status=active", + "--header", + "--verbose", + ] + + out = run_cli("sign", args) + + expect(out).to include("OAuth parameters:") + # Ensure the non-oauth Parameters section is printed + expect(out).to include("Parameters:") + expect(out).to include(" foo: bar") + expect(out).to include(" status: active") + # Ensure there is a blank line after the non-oauth parameters block + expect(out).to match(/Parameters:\n(?: .*\n)+\n/) + end + + it "in XMPP verbose mode prints stanza and note, and omits normalized params" do + args = [ + "--consumer-key", + "ck_123", + "--consumer-secret", + "cs_456", + "--token", + "at_789", + "--secret", + "ats_abc", + "--nonce", + "4d7b2e0f9a", + "--timestamp", + "1699999999", + "--uri", + "https://api.example.com/v1/xmpp", + "--parameters", + "foo:bar", + "--verbose", + "--xmpp", + ] + + out = run_cli("sign", args) + + # In XMPP mode, normalized params line is suppressed + expect(out).not_to include("Normalized params:") + + expect(out).to include("Signature base string:") + expect(out).to include("XMPP Stanza:") + expect(out).to include("") + expect(out).to include("Note: You may want to use bare JIDs in your URI.") + + # Still prints signature summaries + expect(out).to include("Signature:") + expect(out).to include("Escaped signature:") + end +end diff --git a/spec/oauth/tty/commands/version_command_spec.rb b/spec/oauth/tty/commands/version_command_spec.rb new file mode 100644 index 0000000..c9188f5 --- /dev/null +++ b/spec/oauth/tty/commands/version_command_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.describe OAuth::TTY::Commands::VersionCommand, :check_output do + let(:stdout) { StringIO.new } + let(:stdin) { StringIO.new } + let(:stderr) { StringIO.new } + + def run_cli(command, argv = []) + cli = OAuth::TTY::CLI.new(stdout, stdin, stderr, command, argv.dup) + cli.run + stdout.rewind + out = stdout.read + stdout.truncate(0) + stdout.rewind + out + end + + it "prints versions for oauth and oauth-tty gems" do + out = run_cli("version") + + expect(out).to include("OAuth Gem #{OAuth::Version::VERSION}") + expect(out).to include("OAuth TTY Gem #{OAuth::TTY::Version::VERSION}") + + # no errors expected + expect(stderr.string).to eq("") + end +end diff --git a/spec/oauth/tty/sign_command_spec.rb b/spec/oauth/tty/sign_command_spec.rb deleted file mode 100644 index 5a8aa5a..0000000 --- a/spec/oauth/tty/sign_command_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe OAuth::TTY::Commands::SignCommand do - let(:stdout) { StringIO.new } - let(:stdin) { StringIO.new } - let(:stderr) { StringIO.new } - - def build_cmd(args = []) - described_class.new(stdout, stdin, stderr, args) - end - - describe "non-verbose output", :check_output do - it "prints only the signature" do - request = double( - "Request", - oauth_parameters: {}, - non_oauth_parameters: {}, - oauth_signature: "SIG", - ) - - expect(OAuth::RequestProxy).to receive(:proxy).and_return(request) - expect(request).to receive(:sign!).with(consumer_secret: "CS", token_secret: "TS") - - build_cmd(%w[ - --consumer-key - CK - --consumer-secret - CS - --token - TK - --secret - TS - --uri - https://example.com - ]).run - - stdout.rewind - expect(stdout.read).to eq("SIG\n") - end - end - - describe "verbose output with xmpp", :check_output do - it "prints detailed information and an XMPP stanza" do - request = double( - "Request", - oauth_parameters: { - "oauth_consumer_key" => "CK", - "oauth_token" => "TK", - "oauth_signature_method" => "HMAC-SHA1", - "oauth_timestamp" => "TS", - "oauth_nonce" => "NONCE", - "oauth_version" => "1.0", - }, - non_oauth_parameters: {}, - method: "POST", - uri: "https://example.com", - normalized_parameters: "a=1&b=2", - signature_base_string: "BASE", - oauth_signature: "SIG", - oauth_consumer_key: "CK", - oauth_token: "TK", - oauth_signature_method: "HMAC-SHA1", - oauth_timestamp: "TS", - oauth_nonce: "NONCE", - oauth_version: "1.0", - ) - - # Stub out silent verbose interactions with OAuth::Consumer - consumer = instance_double("OAuth::Consumer") - req_token = instance_double("OAuth::RequestToken") - expect(OAuth::Consumer).to receive(:new).and_return(consumer) - expect(consumer).to receive(:get_request_token).and_return(req_token) - allow(req_token).to receive(:callback_confirmed?).and_return(false) - allow(req_token).to receive(:authorize_url).and_return("https://example.com/authorize") - allow(req_token).to receive(:get_access_token).and_return(instance_double("AccessToken")) - - expect(OAuth::RequestProxy).to receive(:proxy).and_return(request) - expect(request).to receive(:sign!).with(consumer_secret: "CS", token_secret: "TS") - - build_cmd(%w[ - --consumer-key - CK - --consumer-secret - CS - --token - TK - --secret - TS - --uri - https://example.com - --verbose - --xmpp - ]).run - - stdout.rewind - out = stdout.read - expect(out).to include("OAuth parameters:") - expect(out).to include("XMPP Stanza:") - expect(out).to include("CK") - expect(out).to include("Escaped signature: ") - # In XMPP mode, it should not print normalized params line - expect(out).not_to include("Normalized params:") - end - end -end diff --git a/spec/support/fixtures/oauth.opts b/spec/support/fixtures/oauth.opts new file mode 100644 index 0000000..9277049 --- /dev/null +++ b/spec/support/fixtures/oauth.opts @@ -0,0 +1,11 @@ +--consumer-key ck_123 +--consumer-secret cs_456 +--token at_789 +--secret ats_abc +--nonce 4d7b2e0f9a +--timestamp 1699999999 +--method GET +--uri https://api.example.com/v1/profile +--parameters foo:bar +--parameters "status=active" +--header From 69ea197cb43ae45cd06116e6b04f4df9856eea56 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 21:39:52 -0600 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9E=96=20em-http-request?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/vcs.xml | 1 - .rubocop_gradual.lock | 11 ++++------- Gemfile.lock | 15 +-------------- oauth-tty.gemspec | 1 - 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 4ba488d..7ddfc9e 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -8,6 +8,5 @@ - \ No newline at end of file diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock index 29b2177..123cf76 100644 --- a/.rubocop_gradual.lock +++ b/.rubocop_gradual.lock @@ -2,13 +2,10 @@ "lib/oauth/tty/cli.rb:904168046": [ [6, 7, 77, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2883780555] ], - "lib/oauth/tty/command.rb:587864942": [ - [146, 23, 4, "Security/Open: The use of `Kernel#open` is a serious security risk.", 2087926481] - ], - "oauth-tty.gemspec:2020207654": [ - [129, 3, 40, "Gemspec/DependencyVersion: Dependency version specification is required.", 2300588954], - [131, 3, 44, "Gemspec/DependencyVersion: Dependency version specification is required.", 1905290578], - [132, 3, 46, "Gemspec/DependencyVersion: Dependency version specification is required.", 4289565910] + "oauth-tty.gemspec:3045486337": [ + [128, 3, 40, "Gemspec/DependencyVersion: Dependency version specification is required.", 2300588954], + [130, 3, 44, "Gemspec/DependencyVersion: Dependency version specification is required.", 1905290578], + [131, 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] diff --git a/Gemfile.lock b/Gemfile.lock index a1ed51a..516207f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,7 +12,7 @@ GIT GIT remote: https://github.com/ruby-oauth/oauth - revision: 2ae700b99c264baf2b179dfb79749cefc36915db + revision: 0430b449f51e3006fdfeea62c080bdfc85307d5f branch: main specs: oauth (1.1.1) @@ -45,7 +45,6 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) concurrent-ruby (1.3.5) - cookiejar (0.3.4) crack (1.0.0) bigdecimal rexml @@ -87,19 +86,9 @@ GEM dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - em-http-request (1.1.7) - addressable (>= 2.3.4) - cookiejar (!= 0.3.1) - em-socksify (>= 0.3) - eventmachine (>= 1.0.3) - http_parser.rb (>= 0.6.0) - em-socksify (0.3.3) - base64 - eventmachine (>= 1.0.0.beta.4) erb (5.0.2) ethon (0.15.0) ffi (>= 1.15.0) - eventmachine (1.2.7) ffi (1.17.2-aarch64-linux-gnu) ffi (1.17.2-aarch64-linux-musl) ffi (1.17.2-arm-linux-gnu) @@ -118,7 +107,6 @@ GEM http-accept (1.7.0) http-cookie (1.0.8) domain_name (~> 0.5) - http_parser.rb (0.8.0) io-console (0.8.1) irb (1.15.2) pp (>= 0.6.0) @@ -385,7 +373,6 @@ DEPENDENCIES benchmark (~> 0.4, >= 0.4.1) bundler-audit (~> 0.9.2) 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) diff --git a/oauth-tty.gemspec b/oauth-tty.gemspec index 0433818..00326a8 100644 --- a/oauth-tty.gemspec +++ b/oauth-tty.gemspec @@ -124,7 +124,6 @@ Gem::Specification.new do |spec| # Testing spec.add_development_dependency("appraisal2", "~> 3.0") # ruby >= 1.8.7, for testing against multiple versions of dependencies - spec.add_development_dependency("em-http-request", "~> 1.1.7") spec.add_development_dependency("kettle-test", "~> 1.0") # ruby >= 2.3 spec.add_development_dependency("mocha") spec.add_development_dependency("rack", "~> 2.0") From b1ae0259982442125356dfa2835c04cd9e7cfd2f Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 21:55:40 -0600 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9E=95=20cgi=20is=20necessary=20for=20ru?= =?UTF-8?q?by@head?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Appraisals | 2 +- gemfiles/head.gemfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Appraisals b/Appraisals index 411ade4..381bd92 100644 --- a/Appraisals +++ b/Appraisals @@ -32,7 +32,7 @@ end # Split into discrete appraisals if one of them needs a dependency locked discretely. appraise "head" do # Why is gem "cgi" here? See: https://github.com/vcr/vcr/issues/1057 - # gem "cgi", ">= 0.5" + gem "cgi", ">= 0.5" gem "oauth", github: "ruby-oauth/oauth", branch: "main" gem "benchmark", "~> 0.4", ">= 0.4.1" eval_gemfile "modular/x_std_libs.gemfile" diff --git a/gemfiles/head.gemfile b/gemfiles/head.gemfile index 1cce6a3..252ea6d 100644 --- a/gemfiles/head.gemfile +++ b/gemfiles/head.gemfile @@ -2,6 +2,7 @@ source "https://rubygems.org" +gem "cgi", ">= 0.5" gem "oauth", branch: "main", git: "https://github.com/ruby-oauth/oauth" gem "benchmark", "~> 0.4", ">= 0.4.1" From a9717e488e4b4471ad3a9a0d3113f7489d65159f Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 22:37:44 -0600 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=92=9A=20Set=20minimum=20line=20&=20b?= =?UTF-8?q?ranch=20coverage=20to=20100=20/=2096?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .envrc | 4 ++-- .github/workflows/coverage.yml | 4 ++-- .gitlab-ci.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.envrc b/.envrc index 800bc2d..673e034 100644 --- a/.envrc +++ b/.envrc @@ -21,8 +21,8 @@ export K_SOUP_COV_DO=true # Means you want code coverage export K_SOUP_COV_COMMAND_NAME="Test Coverage" # Available formats are html, xml, rcov, lcov, json, tty export K_SOUP_COV_FORMATTERS="html,xml,rcov,lcov,json,tty" -export K_SOUP_COV_MIN_BRANCH=80 # Means you want to enforce X% branch coverage -export K_SOUP_COV_MIN_LINE=96 # Means you want to enforce X% line coverage +export K_SOUP_COV_MIN_BRANCH=96 # 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 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c9d6a2e..b725611 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,7 +6,7 @@ permissions: id-token: write env: - K_SOUP_COV_MIN_BRANCH: 100 + K_SOUP_COV_MIN_BRANCH: 96 K_SOUP_COV_MIN_LINE: 100 K_SOUP_COV_MIN_HARD: true K_SOUP_COV_FORMATTERS: "xml,rcov,lcov,tty" @@ -115,7 +115,7 @@ jobs: hide_complexity: true indicators: true output: both - thresholds: '100 100' + thresholds: '100 96' continue-on-error: ${{ matrix.experimental != 'false' }} - name: Add Coverage PR Comment diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3390138..bf3b74a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ variables: K_SOUP_COV_DEBUG: true K_SOUP_COV_DO: true K_SOUP_COV_HARD: true - K_SOUP_COV_MIN_BRANCH: 100 + K_SOUP_COV_MIN_BRANCH: 96 K_SOUP_COV_MIN_LINE: 100 K_SOUP_COV_VERBOSE: true K_SOUP_COV_FORMATTERS: "tty" From 4266b85c8563bc38a858841ed49c8fd200f36851 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 20 Sep 2025 23:37:04 -0600 Subject: [PATCH 7/8] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20kettle-dev=20v1.1.27?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 516207f..1372c10 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -113,7 +113,7 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.14.1) - kettle-dev (1.1.26) + kettle-dev (1.1.27) kettle-soup-cover (1.0.10) simplecov (~> 0.22) simplecov-cobertura (~> 3.0) From 257f03e0f17430de1117e9774658adcf9b58b2a2 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sun, 21 Sep 2025 00:15:54 -0600 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=8E=A8=20kettle-dev=20v1.1.27=20templ?= =?UTF-8?q?ate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/current.yml | 40 +++++++++++++++++++++++++++------ .github/workflows/dep-heads.yml | 36 ++++++++++++++++++++--------- .github/workflows/heads.yml | 36 ++++++++++++++++++++--------- .github/workflows/truffle.yml | 12 +++++++--- README.md | 6 ++--- Rakefile | 2 +- 6 files changed, 96 insertions(+), 36 deletions(-) diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index f8a9e64..54beab4 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -63,11 +63,11 @@ jobs: steps: - name: Checkout - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v5 - name: Setup Ruby & RubyGems - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -79,11 +79,37 @@ jobs: # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - name: Install Root Appraisal - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - - name: Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }} - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + + - name: "[Attempt 1] Install Root Appraisal" + id: bundleAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle exec appraisal ${{ matrix.appraisal }} bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }}@${{ matrix.appraisal }} via ${{ matrix.exec_cmd }} - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/dep-heads.yml b/.github/workflows/dep-heads.yml index f691568..524cb0c 100644 --- a/.github/workflows/dep-heads.yml +++ b/.github/workflows/dep-heads.yml @@ -67,11 +67,11 @@ jobs: steps: - name: Checkout - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v5 - name: Setup Ruby & RubyGems - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -82,24 +82,38 @@ jobs: # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - - name: "Install Root Appraisal" - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + - name: Install Root Appraisal + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true # Effectively an automatic retry of the previous step. - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" - # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} - id: bundleAttempt2 + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }}@${{ matrix.appraisal }} via ${{ matrix.exec_cmd }} - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/heads.yml b/.github/workflows/heads.yml index f8c92d1..7321a15 100644 --- a/.github/workflows/heads.yml +++ b/.github/workflows/heads.yml @@ -64,11 +64,11 @@ jobs: steps: - name: Checkout - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v5 - name: Setup Ruby & RubyGems - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -79,24 +79,38 @@ jobs: # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - - name: "Install Root Appraisal" - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + - name: Install Root Appraisal + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + # Continue to the next step on failure + continue-on-error: true + + # Effectively an automatic retry of the previous step. + - name: "[Attempt 2] Install Root Appraisal" + id: bundleAttempt2 + # If bundleAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + run: bundle + + - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" + id: bundleAppraisalAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true # Effectively an automatic retry of the previous step. - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" - # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} - id: bundleAttempt2 + id: bundleAppraisalAttempt2 + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }}@${{ matrix.appraisal }} via ${{ matrix.exec_cmd }} - if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }} + - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/truffle.yml b/.github/workflows/truffle.yml index db65188..b44416e 100644 --- a/.github/workflows/truffle.yml +++ b/.github/workflows/truffle.yml @@ -47,9 +47,11 @@ jobs: steps: - name: Checkout + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v5 - name: Setup Ruby & RubyGems + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -61,10 +63,12 @@ jobs: # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - name: Install Root Appraisal + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle # Continue to the next step on failure continue-on-error: true @@ -73,11 +77,12 @@ jobs: - name: "[Attempt 2] Install Root Appraisal" id: bundleAttempt2 # If bundleAttempt1 failed, try again here; Otherwise skip. - if: steps.bundleAttempt1.outcome == 'failure' + if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt1 + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true @@ -85,9 +90,10 @@ jobs: # Effectively an automatic retry of the previous step. - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt2 - # If bundleAttempt1 failed, try again here; Otherwise skip. - if: steps.bundleAppraisalAttempt1.outcome == 'failure' + # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} + if: ${{ !(env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/README.md b/README.md index 9b302dc..c5dd3b0 100644 --- a/README.md +++ b/README.md @@ -266,8 +266,8 @@ oauth authorize \ --consumer-key ck \ --consumer-secret cs \ --request-token-url https://provider.example.com/oauth/request_token \ - --authorize-url https://provider.example.com/oauth/authorize \ - --access-token-url https://provider.example.com/oauth/access_token \ + --authorize-url https://provider.example.com/oauth/authorize \ + --access-token-url https://provider.example.com/oauth/access_token \ --callback-url https://yourapp.example.com/oauth/callback ``` What happens: @@ -662,7 +662,7 @@ Thanks for RTFM. ☺️ [📌gitmoji]:https://gitmoji.dev [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ -[🧮kloc-img]: https://img.shields.io/badge/KLOC-1.225-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue +[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.007-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue [🔐security]: SECURITY.md [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year diff --git a/Rakefile b/Rakefile index b6d8ffc..0355728 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # frozen_string_literal: true -# kettle-dev Rakefile v1.1.26 - 2025-09-20 +# kettle-dev Rakefile v1.1.27 - 2025-09-20 # Ruby 2.3 (Safe Navigation) or higher required # # MIT License (see License.txt)