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

improving code quality of jwt module #266

Merged
merged 2 commits into from
May 6, 2018
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
63 changes: 41 additions & 22 deletions lib/jwt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,61 @@ def encode(payload, key, algorithm = 'HS256', header_fields = {})
encoder.segments
end

def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
def decode(jwt, key = nil, verify = true, options = {}, &keyfinder)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT#decode has boolean parameter 'verify'

Read more about it here.

raise(JWT::DecodeError, 'Nil JSON web token') unless jwt

merged_options = DEFAULT_OPTIONS.merge(custom_options)

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

Verify.verify_claims(payload, merged_options) if verify
@jwt = jwt
@key = key
@verify = verify
@options = DEFAULT_OPTIONS.merge(options)
@header,
@payload,
@signature,
@signing_input = Decode.new(jwt, verify).decode_segments
if verify?
verify_signature(&keyfinder)
verify_claims
end

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

[payload, header]
[@payload, @header]
end

def decode_verify_signature(key, header, payload, signature, signing_input, options, &keyfinder)
algo, key = signature_algorithm_and_key(header, payload, key, &keyfinder)
private_class_method

raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms(options).empty?
raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless allowed_algorithms(options).include?(algo)
def verify_signature(&keyfinder)
@key = find_key(&keyfinder) if keyfinder

Signature.verify(algo, key, signing_input, signature)
raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?

Signature.verify(@header['alg'], @key, @signing_input, @signature)
end

def signature_algorithm_and_key(header, payload, key, &keyfinder)
key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header)) if keyfinder
def find_key(&keyfinder)
key = (keyfinder.arity == 2 ? yield(@header, @payload) : yield(@header))
raise JWT::DecodeError, 'No verification key available' unless key
[header['alg'], key]
key
end

def allowed_algorithms(options)
if options.key?(:algorithm)
[options[:algorithm]]
def allowed_algorithms
if @options.key?(:algorithm)
[@options[:algorithm]]
else
options[:algorithms] || []
@options[:algorithms] || []
end
end

def verify?
@verify
end

def verify_claims
Verify.verify_claims(@payload, @options)
end

def options_includes_algo_in_header?
allowed_algorithms.include? @header['alg']
end
end
50 changes: 34 additions & 16 deletions lib/jwt/decode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,60 @@
module JWT
# Decoding logic for JWT
class Decode
attr_reader :header, :payload, :signature

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

def initialize(jwt, verify)
@jwt = jwt
@segments = jwt.split('.')
@verify = verify
@header = ''
@payload = ''
@signature = ''
end

def decode_segments
header_segment, payload_segment, crypto_segment = raw_segments
@header, @payload = decode_header_and_payload(header_segment, payload_segment)
@signature = Decode.base64url_decode(crypto_segment.to_s) if @verify
signing_input = [header_segment, payload_segment].join('.')
[@header, @payload, @signature, signing_input]
validate_segment_count
decode_crypto if @verify
return_values
end

private

def raw_segments
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
def validate_segment_count
raise(JWT::DecodeError, 'Not enough or too many segments') unless
(@verify && segment_length != 3) ||
(segment_length != 3 || segment_length != 2)
end

def segment_length
@segments.count
end

def decode_crypto
@signature = Decode.base64url_decode(@segments[2])
end

def return_values
[header, payload, @signature, signing_input]
end

def header
parse_and_decode @segments[0]
end

def payload
parse_and_decode @segments[1]
end

def signing_input
@segments.first(2).join('.')
end

def decode_header_and_payload(header_segment, payload_segment)
header = JSON.parse(Decode.base64url_decode(header_segment))
payload = JSON.parse(Decode.base64url_decode(payload_segment))
[header, payload]
def parse_and_decode(segment)
JSON.parse(Decode.base64url_decode(segment))
rescue JSON::ParserError
raise JWT::DecodeError, 'Invalid segment encoding'
end
Expand Down