Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #196

Merged
merged 6 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions lib/jwt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,22 @@
require 'jwt/default_options'
require 'jwt/encode'
require 'jwt/error'
require 'jwt/json'
require 'jwt/signature'
require 'jwt/verify'

# JSON Web Token implementation
#
# Should be up to date with the latest spec:
# https://tools.ietf.org/html/rfc7519#section-4.1.5
module JWT
extend JWT::Json
include JWT::DefaultOptions

module_function

def decoded_segments(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
def decoded_segments(jwt, verify = true)
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt

merged_options = DEFAULT_OPTIONS.merge(custom_options)

decoder = Decode.new jwt, key, verify, merged_options, &keyfinder
decoder = Decode.new jwt, verify
decoder.decode_segments
end

Expand All @@ -35,10 +32,12 @@ def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt

merged_options = DEFAULT_OPTIONS.merge(custom_options)
decoder = Decode.new jwt, key, verify, merged_options, &keyfinder

decoder = Decode.new jwt, verify
header, payload, signature, signing_input = decoder.decode_segments
decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify
decoder.verify

Verify.verify_claims(payload, merged_options)

raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload

Expand All @@ -65,12 +64,4 @@ def signature_algorithm_and_key(header, payload, key, &keyfinder)
end
[header['alg'], key]
end

def base64url_decode(str)
Decode.base64url_decode(str)
end

def base64url_encode(str)
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
end
end
38 changes: 13 additions & 25 deletions lib/jwt/decode.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
# frozen_string_literal: true
require 'jwt/json'
require 'jwt/verify'
require 'json'

# JWT::Decode module
module JWT
extend JWT::Json

# Decoding logic for JWT
class Decode
attr_reader :header, :payload, :signature

def initialize(jwt, key, verify, options, &keyfinder)
def self.base64url_decode(str)
str += '=' * (4 - str.length.modulo(4))
Base64.decode64(str.tr('-_', '+/'))
end

def initialize(jwt, verify)
@jwt = jwt
@key = key
@verify = verify
@options = options
@keyfinder = keyfinder
end

def decode_segments
Expand All @@ -26,32 +25,21 @@ def decode_segments
[@header, @payload, @signature, signing_input]
end

private

def raw_segments(jwt, verify)
segments = jwt.split('.')
required_num_segments = verify ? [3] : [2, 3]
raise(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
segments
end
private :raw_segments

def decode_header_and_payload(header_segment, payload_segment)
header = JWT.decode_json(Decode.base64url_decode(header_segment))
payload = JWT.decode_json(Decode.base64url_decode(payload_segment))
header = JSON.parse(Decode.base64url_decode(header_segment))
payload = JSON.parse(Decode.base64url_decode(payload_segment))
[header, payload]
end
private :decode_header_and_payload

def self.base64url_decode(str)
str += '=' * (4 - str.length.modulo(4))
Base64.decode64(str.tr('-_', '+/'))
end

def verify
@options.each do |key, val|
next unless key.to_s =~ /verify/

Verify.send(key, payload, @options) if val
end
rescue JSON::ParserError
raise JWT::DecodeError, 'Invalid segment encoding'
end
end
end
14 changes: 8 additions & 6 deletions lib/jwt/encode.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# frozen_string_literal: true
require 'jwt/json'
require 'json'

# JWT::Encode module
module JWT
extend JWT::Json

# Encoding logic for JWT
class Encode
attr_reader :payload, :key, :algorithm, :header_fields, :segments

def self.base64url_encode(str)
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
end

def initialize(payload, key, algorithm, header_fields)
@payload = payload
@key = key
Expand All @@ -21,20 +23,20 @@ def initialize(payload, key, algorithm, header_fields)

def encoded_header(algorithm, header_fields)
header = { 'alg' => algorithm }.merge(header_fields)
JWT.base64url_encode(JWT.encode_json(header))
Encode.base64url_encode(JSON.generate(header))
end

def encoded_payload(payload)
raise InvalidPayload, 'exp claim must be an integer' if payload['exp'] && payload['exp'].is_a?(Time)
JWT.base64url_encode(JWT.encode_json(payload))
Encode.base64url_encode(JSON.generate(payload))
end

def encoded_signature(signing_input, key, algorithm)
if algorithm == 'none'
''
else
signature = JWT::Signature.sign(algorithm, signing_input, key)
JWT.base64url_encode(signature)
Encode.base64url_encode(signature)
end
end

Expand Down
17 changes: 0 additions & 17 deletions lib/jwt/json.rb

This file was deleted.

27 changes: 15 additions & 12 deletions lib/jwt/verify.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ class << self
new(payload, options).send(method_name)
end
end

def verify_claims(payload, options)
options.each do |key, val|
next unless key.to_s =~ /verify/
Verify.send(key, payload, options) if val
end
end
end

def initialize(payload, options)
Expand All @@ -18,7 +25,7 @@ def initialize(payload, options)
end

def verify_aud
return unless (options_aud = extract_option(:aud))
return unless (options_aud = @options[:aud])
raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{@payload['aud'] || '<none>'}") if ([*@payload['aud']] & [*options_aud]).empty?
end

Expand All @@ -33,12 +40,12 @@ def verify_iat
end

def verify_iss
return unless (options_iss = extract_option(:iss))
return unless (options_iss = @options[:iss])
raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{@payload['iss'] || '<none>'}") if @payload['iss'].to_s != options_iss.to_s
end

def verify_jti
options_verify_jti = extract_option(:verify_jti)
options_verify_jti = @options[:verify_jti]
if options_verify_jti.respond_to?(:call)
raise(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(@payload['jti'])
elsif @payload['jti'].to_s.strip.empty?
Expand All @@ -52,30 +59,26 @@ def verify_not_before
end

def verify_sub
return unless (options_sub = extract_option(:sub))
return unless (options_sub = @options[:sub])
raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{@payload['sub'] || '<none>'}") unless @payload['sub'].to_s == options_sub.to_s
end

private

def extract_option(key)
@options.values_at(key.to_sym, key.to_s).compact.first
end

def global_leeway
extract_option :leeway
@options[:leeway]
end

def exp_leeway
extract_option(:exp_leeway) || global_leeway
@options[:exp_leeway] || global_leeway
end

def iat_leeway
extract_option(:iat_leeway) || global_leeway
@options[:iat_leeway] || global_leeway
end

def nbf_leeway
extract_option(:nbf_leeway) || global_leeway
@options[:nbf_leeway] || global_leeway
end
end
end
1 change: 0 additions & 1 deletion ruby-jwt.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ Gem::Specification.new do |spec|

spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'json'
spec.add_development_dependency 'rspec'
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'simplecov-json'
Expand Down
35 changes: 0 additions & 35 deletions spec/jwt/verify_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,56 +25,21 @@ module JWT
end.to raise_error JWT::InvalidAudError
end

it 'must raise JWT::InvalidAudError when the singular audience does not match and the options aud key is a string' do
expect do
Verify.verify_aud(scalar_payload, options.merge('aud' => 'no-match'))
end.to raise_error JWT::InvalidAudError
end

it 'must allow a matching singular audience to pass' do
Verify.verify_aud(scalar_payload, options.merge(aud: scalar_aud))
end

it 'must allow a matching audence to pass when the options key is a string' do
Verify.verify_aud(scalar_payload, options.merge('aud' => scalar_aud))
end

it 'must allow an array with any value matching the one in the options' do
Verify.verify_aud(array_payload, options.merge(aud: array_aud.first))
end

it 'must allow an array with any value matching the one in the options with a string options key' do
Verify.verify_aud(array_payload, options.merge('aud' => array_aud.first))
end

it 'must allow an array with any value matching any value in the options array' do
Verify.verify_aud(array_payload, options.merge(aud: array_aud))
end

it 'must allow an array with any value matching any value in the options array with a string options key' do
Verify.verify_aud(array_payload, options.merge('aud' => array_aud))
end

it 'must allow a singular audience payload matching any value in the options array' do
Verify.verify_aud(scalar_payload, options.merge(aud: array_aud))
end

it 'must allow a singular audience payload matching any value in the options array with a string options key' do
Verify.verify_aud(scalar_payload, options.merge('aud' => array_aud))
end

it 'should allow strings or symbols in options array' do
options['aud'] = [
'ruby-jwt-aud',
'test-aud',
'ruby-ruby-ruby',
:test
]

array_payload['aud'].push('test')

Verify.verify_aud(array_payload, options)
end
end

context '.verify_expiration(payload, options)' do
Expand Down
3 changes: 2 additions & 1 deletion spec/jwt_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'spec_helper'
require 'jwt'
require 'jwt/encode'
require 'jwt/decode'

describe JWT do
Expand Down Expand Up @@ -225,7 +226,7 @@
context 'Base64' do
it 'urlsafe replace + / with - _' do
allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
expect(JWT::Encode.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
end
end
end