From 5db30c9f9c6d6c88468fd1e0d57d5d3cfb3e909b Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Sun, 16 Jun 2024 14:39:10 -0700 Subject: [PATCH] Only issue invalid base64 warning on successful decode To make the patch minimally invasive, this attempts to decode the given jwt in strict base64 mode only after a successful full decode and validation. Most jwts are small, so this should have minimal performance impact, though it does require decoding twice. It is possible to fix the issue and only decode once, but it would be more invasive. A big advantage of this approach is the use of :uplevel when calling Kernel#warn, so that the calling location that triggers the warning is included in the warning message, greatly simplifying debugging. Co-authored-by: Joakim Antman --- CHANGELOG.md | 1 + lib/jwt.rb | 14 +++++++++++++- lib/jwt/base64.rb | 4 +--- spec/jwt/jwt_spec.rb | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40239a10..c5c92a97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ **Features:** +- Print deprecation warnings only on when token decoding succeeds [#601](https://github.com/jwt/ruby-jwt/pull/601) ([@jeremyevans](https://github.com/jeremyevans)) - Your contribution here **Fixes and enhancements:** diff --git a/lib/jwt.rb b/lib/jwt.rb index 19b987ce..5d6604c2 100644 --- a/lib/jwt.rb +++ b/lib/jwt.rb @@ -27,6 +27,18 @@ def encode(payload, key, algorithm = 'HS256', header_fields = {}) end def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter - Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments + if (res = Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments) + begin + jwt.split('.').each { |part| ::Base64.urlsafe_decode64(part) } + rescue ArgumentError + issue_warning = true + end + + if issue_warning + warn('[DEPRECATION WARNING] Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt', uplevel: 1) + end + end + + res end end diff --git a/lib/jwt/base64.rb b/lib/jwt/base64.rb index e6ee0622..1e1aa754 100644 --- a/lib/jwt/base64.rb +++ b/lib/jwt/base64.rb @@ -19,9 +19,7 @@ def url_decode(str) raise unless e.message == 'invalid base64' raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding - loose_urlsafe_decode64(str).tap do - Deprecations.warning('Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt') - end + loose_urlsafe_decode64(str) end def loose_urlsafe_decode64(str) diff --git a/spec/jwt/jwt_spec.rb b/spec/jwt/jwt_spec.rb index 796dabb8..b2ec511a 100644 --- a/spec/jwt/jwt_spec.rb +++ b/spec/jwt/jwt_spec.rb @@ -944,4 +944,23 @@ def valid_alg?(alg) end end end + + context 'when invalid token is valid loose base64' do + it 'does not output deprecations warnings' do + expect { + 2.times do + JWT.decode("#{JWT.encode('a', 'b')} 9", 'b') + rescue JWT::VerificationError + nil + end + JWT.decode(JWT.encode('a', 'b'), 'b') + }.not_to output(/DEPRECATION/).to_stderr + end + end + + context 'when valid token is invalid strict base64' do + it 'does outputs deprecation warning' do + expect { JWT.decode("#{JWT.encode('a', 'b')} ", 'b') }.to output(/DEPRECATION/).to_stderr + end + end end