require 'jwt' module SoarAuthenticationToken class JwtTokenValidator def initialize(configuration) @configuration = configuration set_configuration_defaults validate_configuration end def inject_store_provider(store_provider) @store_provider = store_provider end def validate(authentication_token:,flow_identifier: nil) decoded_token_payload = decode(authentication_token) return rejection_result(reason: 'Token decode/verification failure') if decoded_token_payload.nil? meta = compile_meta_from_payload(decoded_token_payload) return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta) return rejection_result(reason: "Unknown token for <#{meta['authenticated_identifier']}>") unless token_exist_in_store?(meta,flow_identifier) success_result(token_meta: meta) end private def set_configuration_defaults @configuration['expiry'] ||= 86400 end def compile_meta_from_payload(decoded_token_payload) { 'token_identifier' => decoded_token_payload[0]['token_identifier'], 'authenticated_identifier' => decoded_token_payload[0]['authenticated_identifier'], 'token_issue_time' => decoded_token_payload[0]['token_issue_time'], 'token_expiry_time' => decoded_token_payload[0]['token_expiry_time'], 'token_age' => token_age(decoded_token_payload[0]['token_issue_time']) } end def token_age(token_issue_time) Time.now - Time.parse(token_issue_time.to_s) end def validate_configuration raise "'keys' must be configured" unless @configuration['keys'] raise "'expiry' must be configured" unless @configuration['expiry'] raise "'expiry' must be an integer" unless Integer(@configuration['expiry']) end def decode(authentication_token) @configuration['keys'].sort.reverse_each do |key_name, key_data| payload = attempt_decode_using_a_key(authentication_token,key_data) return payload if payload end nil end def attempt_decode_using_a_key(authentication_token,key_data) public_key = OpenSSL::PKey::EC.new(key_data['public_key']) public_key.private_key = nil JWT.decode(authentication_token, public_key, true, { :algorithm => 'ES512' }) rescue JWT::VerificationError, JWT::DecodeError nil end def token_expired?(meta) Time.parse(meta['token_expiry_time'].to_s) < Time.now end def token_exist_in_store?(meta,flow_identifier) @store_provider.token_exist?( token_identifier: meta['token_identifier'], authenticated_identifier: meta['authenticated_identifier'], token_issue_time: meta['token_issue_time'], token_expiry_time: meta['token_expiry_time'], flow_identifier: flow_identifier) end def rejection_result(reason:) [false, nil, reason] end def success_result(token_meta:) [true, token_meta, "Valid token for <#{token_meta['authenticated_identifier']}>" ] end end end