Skip to content

Commit

Permalink
Configurabe base64 behaviour and log deprecations once by default
Browse files Browse the repository at this point in the history
  • Loading branch information
anakinj committed Feb 29, 2024
1 parent bd3f80b commit db99c67
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,10 +6,12 @@

**Features:**

- Configurable base64 decode behaviour [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj))
- Your contribution here

**Fixes and enhancements:**

- Output deprecation warnings once [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj))
- Your contribution here

## [v2.8.0](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2024-02-17)
Expand Down
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -43,6 +43,23 @@ The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cr

See: [ JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)

### Deprecation warnings

Deprecation warnings are logged once (`:once` option) by default to avoid spam in logs. Other options are `:silent` to completely silence warnings and `:warn` to log every time a deprecated path is executed.

```ruby
JWT.configuration.deprecation_warnings = :warn # default is :once
```

### Base64 decoding

In the past the gem has been supporting the Base64 decoding specified in [RFC2045](https://www.rfc-editor.org/rfc/rfc2045) allowing newlines and blanks in the base64 encoded payload. In future versions base64 decoding will be stricter and only comply to [RFC4648](https://www.rfc-editor.org/rfc/rfc4648).

The stricter base64 decoding when processing tokens can be done via the `strict_base64_decoding` configuration accessor.
```ruby
JWT.configuration.strict_base64_decoding = true # default is false
```

### **NONE**

* none - unsigned token
Expand Down
1 change: 1 addition & 0 deletions lib/jwt.rb
Expand Up @@ -5,6 +5,7 @@
require 'jwt/json'
require 'jwt/decode'
require 'jwt/configuration'
require 'jwt/deprecations'
require 'jwt/encode'
require 'jwt/error'
require 'jwt/jwk'
Expand Down
6 changes: 4 additions & 2 deletions lib/jwt/base64.rb
Expand Up @@ -17,9 +17,11 @@ def url_decode(str)
::Base64.urlsafe_decode64(str)
rescue ArgumentError => e
raise unless e.message == 'invalid base64'
raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding

warn('[DEPRECATION] 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')
loose_urlsafe_decode64(str)
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
end

def loose_urlsafe_decode64(str)
Expand Down
17 changes: 14 additions & 3 deletions lib/jwt/configuration/container.rb
Expand Up @@ -6,15 +6,26 @@
module JWT
module Configuration
class Container
attr_accessor :decode, :jwk
attr_accessor :decode, :jwk, :strict_base64_decoding
attr_reader :deprecation_warnings

def initialize
reset!
end

def reset!
@decode = DecodeConfiguration.new
@jwk = JwkConfiguration.new
@decode = DecodeConfiguration.new
@jwk = JwkConfiguration.new
@strict_base64_decoding = false

self.deprecation_warnings = :once
end

DEPRECATION_WARNINGS_VALUES = %i[once warn silent].freeze
def deprecation_warnings=(value)
raise ArgumentError, "Invalid deprecation_warnings value #{value}. Supported values: #{DEPRECATION_WARNINGS_VALUES}" unless DEPRECATION_WARNINGS_VALUES.include?(value)

@deprecation_warnings = value
end
end
end
Expand Down
29 changes: 29 additions & 0 deletions lib/jwt/deprecations.rb
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module JWT
# Deprecations module to handle deprecation warnings in the gem
module Deprecations
class << self
def warning(message)
case JWT.configuration.deprecation_warnings
when :warn
warn("[DEPRECATION WARNING] #{message}")
when :once
return if record_warned(message)

warn("[DEPRECATION WARNING] #{message}")
end
end

private

def record_warned(message)
@warned ||= []
return true if @warned.include?(message)

@warned << message
false
end
end
end
end
1 change: 1 addition & 0 deletions lib/jwt/error.rb
Expand Up @@ -17,6 +17,7 @@ class InvalidSubError < DecodeError; end
class InvalidJtiError < DecodeError; end
class InvalidPayload < DecodeError; end
class MissingRequiredClaim < DecodeError; end
class Base64DecodeError < DecodeError; end

class JWKError < DecodeError; end
end
4 changes: 2 additions & 2 deletions lib/jwt/jwa/hmac_rbnacl.rb
Expand Up @@ -7,7 +7,7 @@ module HmacRbNaCl
SUPPORTED = MAPPING.keys
class << self
def sign(algorithm, msg, key)
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
if (hmac = resolve_algorithm(algorithm))
hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary'))
else
Expand All @@ -16,7 +16,7 @@ def sign(algorithm, msg, key)
end

def verify(algorithm, key, signing_input, signature)
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
if (hmac = resolve_algorithm(algorithm))
hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary'))
else
Expand Down
4 changes: 2 additions & 2 deletions lib/jwt/jwa/hmac_rbnacl_fixed.rb
Expand Up @@ -9,7 +9,7 @@ module HmacRbNaClFixed
class << self
def sign(algorithm, msg, key)
key ||= ''
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)

if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
Expand All @@ -21,7 +21,7 @@ def sign(algorithm, msg, key)

def verify(algorithm, key, signing_input, signature)
key ||= ''
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)

if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes
Expand Down
11 changes: 11 additions & 0 deletions spec/jwt/jwt_spec.rb
Expand Up @@ -774,6 +774,17 @@
end
end

context 'when token ends with a newline char and strict_decoding enabled' do
let(:token) { "#{JWT.encode(payload, 'secret', 'HS256')}\n" }
before do
JWT.configuration.strict_base64_decoding = true
end

it 'raises JWT::DecodeError' do
expect { JWT.decode(token, 'secret', true, algorithm: 'HS256') }.to raise_error(JWT::DecodeError, 'Invalid base64 encoding')
end
end

context 'when multiple algorithms given' do
let(:token) { JWT.encode(payload, 'secret', 'HS256') }

Expand Down
7 changes: 6 additions & 1 deletion spec/spec_helper.rb
Expand Up @@ -15,7 +15,12 @@
c.syntax = :expect
end
config.include(SpecSupport::TestKeys)
config.before(:example) { JWT.configuration.reset! }

config.before(:example) do
JWT.configuration.reset!
JWT.configuration.deprecation_warnings = :warn
end

config.run_all_when_everything_filtered = true
config.filter_run :focus
config.order = 'random'
Expand Down

0 comments on commit db99c67

Please sign in to comment.