Skip to content

Commit

Permalink
Move nbf validator into it's own class
Browse files Browse the repository at this point in the history
  • Loading branch information
anakinj committed Jul 17, 2023
1 parent 7f8887d commit 3192bf5
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 34 deletions.
1 change: 1 addition & 0 deletions lib/jwt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
require_relative 'jwt/validators/noop'
require_relative 'jwt/validators/claims_validator'
require_relative 'jwt/validators/numeric_claims_validator'
require_relative 'jwt/validators/not_before_claim_validator'
require_relative 'jwt/decoders/base64_json'

module JWT
Expand Down
9 changes: 7 additions & 2 deletions lib/jwt/decode_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

module JWT
class DecodeContext
attr_reader :token, :allowed_algorithms, :verification_key
attr_reader :token, :allowed_algorithms, :verification_key, :validators

def initialize(token:, decoder:, allowed_algorithms:, verification_key:)
def initialize(token:, decoder:, allowed_algorithms:, verification_key:, validators:)
@token = Token.new(value: token, decoder: decoder)
@allowed_algorithms = allowed_algorithms
@verification_key = verification_key
@validators = validators
end

def header
Expand All @@ -30,6 +31,10 @@ def algorithm_match?
!allowed_and_valid_algorithms.empty?
end

def validate!
validators.each { |validator| validator.validate!(payload: payload, header: header) }
end

private

def resolve_verification_keys
Expand Down
7 changes: 6 additions & 1 deletion lib/jwt/default_decoder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

module JWT
class DefaultDecoder
def self.define_decoder(options)
def self.define_decoder(options) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
JWT.define do
allowed_algorithms(*options[:allowed_algorithms])

Expand All @@ -28,6 +28,10 @@ def self.define_decoder(options)
X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
end
end

if options[:verify_not_before]
validators << Validators::NotBeforeClaimValidator.new(leeway: options[:nbf_leeway] || options[:leeway])
end
end
end

Expand All @@ -49,6 +53,7 @@ def decode_segments
if @verify
verify_algo
verify_signature
decode_context.validate!
verify_claims
end

Expand Down
7 changes: 6 additions & 1 deletion lib/jwt/dsl/decoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ def decoding_validator(value = nil)
@decoding_validator || Validators::Noop
end

def validators
@validators ||= []
end

def decode(token:, **options)
DecodeContext.new(**{ token: token,
decoder: decoder,
allowed_algorithms: allowed_algorithms,
verification_key: verification_key }.merge(options))
verification_key: verification_key,
validators: validators }.merge(options))
end
end
end
Expand Down
13 changes: 2 additions & 11 deletions lib/jwt/validators/claims_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ class ClaimsValidator
}.freeze

class << self
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].each do |method_name|
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_sub verify_required_claims].each do |method_name|
define_method method_name do |payload, options|
new(payload, options).send(method_name)
end
end

def verify_claims(payload, options)
options.each do |key, val|
next unless key.to_s =~ /verify/
next unless key.to_s =~ /verify/ && respond_to?(key)

send(key, payload, options) if val
end
Expand Down Expand Up @@ -74,11 +74,6 @@ def verify_jti
end
end

def verify_not_before
return unless @payload.include?('nbf')
raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
end

def verify_sub
return unless (options_sub = @options[:sub])

Expand All @@ -103,10 +98,6 @@ def global_leeway
def exp_leeway
@options[:exp_leeway] || global_leeway
end

def nbf_leeway
@options[:nbf_leeway] || global_leeway
end
end
end
end
22 changes: 22 additions & 0 deletions lib/jwt/validators/not_before_claim_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module JWT
module Validators
class NotBeforeClaimValidator
def initialize(leeway:)
@leeway = leeway
end

def validate!(payload:, **_args)
return unless payload.is_a?(Hash)
return unless payload.key?('nbf')

raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if payload['nbf'].to_i > (Time.now.to_i + leeway)
end

private

attr_reader :leeway
end
end
end
20 changes: 1 addition & 19 deletions spec/jwt/validators/claims_validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,24 +249,6 @@ def issuer_start_with_ruby?(issuer)
end
end

context '.verify_not_before(payload, options)' do
let(:payload) { base_payload.merge('nbf' => (Time.now.to_i + 5)) }

it 'must raise JWT::ImmatureSignature when the nbf in the payload is in the future' do
expect do
described_class.verify_not_before(payload, options)
end.to raise_error JWT::ImmatureSignature
end

it 'must allow some leeway in the token age when global leeway is configured' do
described_class.verify_not_before(payload, options.merge(leeway: 10))
end

it 'must allow some leeway in the token age when nbf_leeway is configured' do
described_class.verify_not_before(payload, options.merge(nbf_leeway: 10))
end
end

context '.verify_sub(payload, options)' do
let(:sub) { 'ruby jwt subject' }

Expand Down Expand Up @@ -294,7 +276,7 @@ def issuer_start_with_ruby?(issuer)
}
}

%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method|
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_sub].each do |method|
let(:payload) { base_payload.merge(fail_verifications_payload) }
it "must skip verification when #{method} option is set to false" do
described_class.verify_claims(payload, options.merge(method => false))
Expand Down
27 changes: 27 additions & 0 deletions spec/jwt/validators/not_before_claim_validator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

RSpec.describe ::JWT::Validators::NotBeforeClaimValidator do
let(:payload) { { 'nbf' => (Time.now.to_i + 5) } }

describe '#validate!' do
context 'when nbf is in the future' do
it 'raises JWT::ImmatureSignature' do
expect { described_class.new(leeway: 0).validate!(payload: payload) }.to raise_error JWT::ImmatureSignature
end
end

context 'when nbf is in the past' do
let(:payload) { { 'nbf' => (Time.now.to_i - 5) } }

it 'does not raise error' do
expect { described_class.new(leeway: 0).validate!(payload: payload) }.not_to raise_error
end
end

context 'when leeway is given' do
it 'does not raise error' do
expect { described_class.new(leeway: 10).validate!(payload: payload) }.not_to raise_error
end
end
end
end

0 comments on commit 3192bf5

Please sign in to comment.