Skip to content

Commit

Permalink
Merge pull request #295 from jamesstonehill/claims-validation
Browse files Browse the repository at this point in the history
Claims Validation
  • Loading branch information
excpt committed Jan 26, 2019
2 parents 7a9f1b2 + 9f9913e commit faaa3a1
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 43 deletions.
8 changes: 0 additions & 8 deletions Manifest

This file was deleted.

33 changes: 33 additions & 0 deletions lib/jwt/claims_validator.rb
@@ -0,0 +1,33 @@
require_relative './error'

module JWT
class ClaimsValidator
INTEGER_CLAIMS = %i[
exp
iat
nbf
].freeze

def initialize(payload)
@payload = payload.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
end

def validate!
validate_int_claims

true
end

private

def validate_int_claims
INTEGER_CLAIMS.each do |claim|
validate_is_int(claim) if @payload.key?(claim)
end
end

def validate_is_int(claim)
raise InvalidPayload, "#{claim} claim must be an Integer but it is a #{@payload[claim].class}" unless @payload[claim].is_a?(Integer)
end
end
end
21 changes: 6 additions & 15 deletions lib/jwt/encode.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true

require_relative './claims_validator'

# JWT::Encode module
module JWT
# Encoding logic for JWT
class Encode
ALG_NONE = 'none'.freeze
ALG_KEY = 'alg'.freeze
EXP_KEY = 'exp'.freeze
EXP_KEYS = [EXP_KEY, EXP_KEY.to_sym].freeze

def initialize(options)
@payload = options[:payload]
Expand All @@ -22,18 +22,6 @@ def segments

private

def validate_payload!
return unless @payload && @payload.is_a?(Hash)

validate_exp!
end

def validate_exp!
return if EXP_KEYS.all? { |key| !@payload.key?(key) || @payload[key].is_a?(Integer) }

raise InvalidPayload, 'exp claim must be an integer'
end

def encoded_header
@encoded_header ||= encode_header
end
Expand All @@ -55,7 +43,10 @@ def encode_header
end

def encode_payload
validate_payload!
if @payload && @payload.is_a?(Hash)
ClaimsValidator.new(@payload).validate!
end

encode(@payload)
end

Expand Down
45 changes: 45 additions & 0 deletions spec/jwt/claims_validator_spec.rb
@@ -0,0 +1,45 @@
require 'spec_helper'
require 'jwt/claims_validator'

RSpec.describe JWT::ClaimsValidator do
describe '#validate!' do
it 'returns true if the payload is valid' do
valid_payload = { 'exp' => 12345 }
subject = described_class.new(valid_payload)

expect(subject.validate!).to eq(true)
end

shared_examples_for 'an integer claim' do |claim|
it "raises an error when the value of the #{claim} claim is a string" do
subject = described_class.new({ claim => '1' })
expect { subject.validate! }.to raise_error JWT::InvalidPayload
end

it "raises an error when the value of the #{claim} claim is a Time object" do
subject = described_class.new({ claim => Time.now })
expect { subject.validate! }.to raise_error JWT::InvalidPayload
end

it "validates the #{claim} claim when the key is either a string or a symbol" do
symbol = described_class.new({ claim.to_sym => true })
expect { symbol.validate! }.to raise_error JWT::InvalidPayload

string = described_class.new({ claim.to_s => true })
expect { string.validate! }.to raise_error JWT::InvalidPayload
end
end

context 'exp claim' do
it_should_behave_like 'an integer claim', :exp
end

context 'iat claim' do
it_should_behave_like 'an integer claim', :iat
end

context 'nbf claim' do
it_should_behave_like 'an integer claim', :nbf
end
end
end
31 changes: 11 additions & 20 deletions spec/jwt_spec.rb
Expand Up @@ -60,30 +60,21 @@
end

context 'payload validation' do
subject { JWT.encode(payload, nil, 'none') }
let(:payload) { { 'exp' => exp } }
it 'validates the payload with the ClaimsValidator if the payload is a hash' do
validator = double()
expect(JWT::ClaimsValidator).to receive(:new) { validator }
expect(validator).to receive(:validate!) { true }

context 'when exp is given as a non Integer' do
let(:exp) { Time.now.to_i.to_s }
it 'raises an JWT::InvalidPayload error' do
expect { subject }.to raise_error(JWT::InvalidPayload, 'exp claim must be an integer')
end
end

context 'when exp is given as an Integer' do
let(:exp) { 1234 }

it 'encodes the payload' do
expect(subject).to be_a(String)
end
payload = {}
JWT.encode payload, "secret", JWT::Algos::Hmac::SUPPORTED.sample
end

context 'when the key for exp is a symbol' do
let(:payload) { { :exp => 'NotAInteger' } }
it 'does not validate the payload if it is not present' do
validator = double()
expect(JWT::ClaimsValidator).not_to receive(:new) { validator }

it 'raises an JWT::InvalidPayload error' do
expect { subject }.to raise_error(JWT::InvalidPayload, 'exp claim must be an integer')
end
payload = nil
JWT.encode payload, "secret", JWT::Algos::Hmac::SUPPORTED.sample
end
end

Expand Down

0 comments on commit faaa3a1

Please sign in to comment.