Skip to content

Commit db99c67

Browse files
committed
Configurabe base64 behaviour and log deprecations once by default
1 parent bd3f80b commit db99c67

File tree

11 files changed

+89
-10
lines changed

11 files changed

+89
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
**Features:**
88

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

1112
**Fixes and enhancements:**
1213

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

1517
## [v2.8.0](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2024-02-17)

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ The JWT spec supports NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms for cr
4343

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

46+
### Deprecation warnings
47+
48+
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.
49+
50+
```ruby
51+
JWT.configuration.deprecation_warnings = :warn # default is :once
52+
```
53+
54+
### Base64 decoding
55+
56+
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).
57+
58+
The stricter base64 decoding when processing tokens can be done via the `strict_base64_decoding` configuration accessor.
59+
```ruby
60+
JWT.configuration.strict_base64_decoding = true # default is false
61+
```
62+
4663
### **NONE**
4764

4865
* none - unsigned token

lib/jwt.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'jwt/json'
66
require 'jwt/decode'
77
require 'jwt/configuration'
8+
require 'jwt/deprecations'
89
require 'jwt/encode'
910
require 'jwt/error'
1011
require 'jwt/jwk'

lib/jwt/base64.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ def url_decode(str)
1717
::Base64.urlsafe_decode64(str)
1818
rescue ArgumentError => e
1919
raise unless e.message == 'invalid base64'
20+
raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding
2021

21-
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')
22-
loose_urlsafe_decode64(str)
22+
loose_urlsafe_decode64(str).tap do
23+
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')
24+
end
2325
end
2426

2527
def loose_urlsafe_decode64(str)

lib/jwt/configuration/container.rb

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,26 @@
66
module JWT
77
module Configuration
88
class Container
9-
attr_accessor :decode, :jwk
9+
attr_accessor :decode, :jwk, :strict_base64_decoding
10+
attr_reader :deprecation_warnings
1011

1112
def initialize
1213
reset!
1314
end
1415

1516
def reset!
16-
@decode = DecodeConfiguration.new
17-
@jwk = JwkConfiguration.new
17+
@decode = DecodeConfiguration.new
18+
@jwk = JwkConfiguration.new
19+
@strict_base64_decoding = false
20+
21+
self.deprecation_warnings = :once
22+
end
23+
24+
DEPRECATION_WARNINGS_VALUES = %i[once warn silent].freeze
25+
def deprecation_warnings=(value)
26+
raise ArgumentError, "Invalid deprecation_warnings value #{value}. Supported values: #{DEPRECATION_WARNINGS_VALUES}" unless DEPRECATION_WARNINGS_VALUES.include?(value)
27+
28+
@deprecation_warnings = value
1829
end
1930
end
2031
end

lib/jwt/deprecations.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
module JWT
4+
# Deprecations module to handle deprecation warnings in the gem
5+
module Deprecations
6+
class << self
7+
def warning(message)
8+
case JWT.configuration.deprecation_warnings
9+
when :warn
10+
warn("[DEPRECATION WARNING] #{message}")
11+
when :once
12+
return if record_warned(message)
13+
14+
warn("[DEPRECATION WARNING] #{message}")
15+
end
16+
end
17+
18+
private
19+
20+
def record_warned(message)
21+
@warned ||= []
22+
return true if @warned.include?(message)
23+
24+
@warned << message
25+
false
26+
end
27+
end
28+
end
29+
end

lib/jwt/error.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class InvalidSubError < DecodeError; end
1717
class InvalidJtiError < DecodeError; end
1818
class InvalidPayload < DecodeError; end
1919
class MissingRequiredClaim < DecodeError; end
20+
class Base64DecodeError < DecodeError; end
2021

2122
class JWKError < DecodeError; end
2223
end

lib/jwt/jwa/hmac_rbnacl.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module HmacRbNaCl
77
SUPPORTED = MAPPING.keys
88
class << self
99
def sign(algorithm, msg, key)
10-
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
10+
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
1111
if (hmac = resolve_algorithm(algorithm))
1212
hmac.auth(key_for_rbnacl(hmac, key).encode('binary'), msg.encode('binary'))
1313
else
@@ -16,7 +16,7 @@ def sign(algorithm, msg, key)
1616
end
1717

1818
def verify(algorithm, key, signing_input, signature)
19-
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
19+
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
2020
if (hmac = resolve_algorithm(algorithm))
2121
hmac.verify(key_for_rbnacl(hmac, key).encode('binary'), signature.encode('binary'), signing_input.encode('binary'))
2222
else

lib/jwt/jwa/hmac_rbnacl_fixed.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module HmacRbNaClFixed
99
class << self
1010
def sign(algorithm, msg, key)
1111
key ||= ''
12-
warn("[DEPRECATION] The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
12+
Deprecations.warning("The use of the algorithm #{algorithm} is deprecated and will be removed in the next major version of ruby-jwt")
1313
raise JWT::DecodeError, 'HMAC key expected to be a String' unless key.is_a?(String)
1414

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

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

2727
if (hmac = resolve_algorithm(algorithm)) && key.bytesize <= hmac.key_bytes

spec/jwt/jwt_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,17 @@
774774
end
775775
end
776776

777+
context 'when token ends with a newline char and strict_decoding enabled' do
778+
let(:token) { "#{JWT.encode(payload, 'secret', 'HS256')}\n" }
779+
before do
780+
JWT.configuration.strict_base64_decoding = true
781+
end
782+
783+
it 'raises JWT::DecodeError' do
784+
expect { JWT.decode(token, 'secret', true, algorithm: 'HS256') }.to raise_error(JWT::DecodeError, 'Invalid base64 encoding')
785+
end
786+
end
787+
777788
context 'when multiple algorithms given' do
778789
let(:token) { JWT.encode(payload, 'secret', 'HS256') }
779790

0 commit comments

Comments
 (0)