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 28, 2024
1 parent bd3f80b commit e6fc40c
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)
loosly_decoded = loose_urlsafe_decode64(str)
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')
loosly_decoded
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 e6fc40c

Please sign in to comment.