require 'soar_xt' require 'jwt' require 'securerandom' require 'time' require 'net/http' require 'uri' require 'json' module SoarAuthenticationToken class TokenGenerator DEFAULT_CONFIGURATION = { } unless defined? DEFAULT_CONFIGURATION; DEFAULT_CONFIGURATION.freeze def initialize(configuration) @configuration = merge_with_default_configuration(configuration) validate_configuration @private_key = OpenSSL::PKey::EC.new(@configuration['private_key']) end def generate(authenticated_identifier:, flow_identifier: nil) return generate_locally(authenticated_identifier) if 'local' == @configuration['mode'] return generate_remotely(authenticated_identifier,flow_identifier) end private def generate_locally(authenticated_identifier) encode(payload(authenticated_identifier)) end def generate_remotely(authenticated_identifier,flow_identifier) uri = URI.parse(@configuration['generator-url']) request = Net::HTTP::Post.new uri request.set_form_data({'flow_identifier' => flow_identifier}) request.body = { 'authenticated_identifier' => authenticated_identifier }.to_json response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') { |http| http.request request } validate_and_extract_token_from_response(response) end def validate_and_extract_token_from_response(response) raise "Failure generating token with token generation service. Code #{response.code}" if '200' != response.code body = JSON.parse(response.body) raise 'Failure generating token by token service' if 'success' != body['status'] raise 'Token service did not provide token' if body['data'].nil? or body['data']['token'].nil? body['data']['token'] end def payload(authenticated_identifier) { 'authenticated_identifier' => authenticated_identifier, 'issue_time' => Time.now.utc.iso8601(3), 'nounce' => SecureRandom.hex(32) } end def encode(payload) JWT.encode(payload, @private_key, 'ES512') end def validate_configuration raise "'mode' must be configured" unless @configuration['mode'] raise "'mode' must be configured as either 'local' or 'remote'" unless ['local','remote'].include?(@configuration['mode']) if 'remote' == @configuration['mode'] raise "'generator-url' must be configured in remote mode" if @configuration['generator-url'].nil? else raise "'private_key' must be configured in local mode" unless @configuration['private_key'] end end def merge_with_default_configuration(configuration) configuration = {} unless configuration Hash.deep_merge(DEFAULT_CONFIGURATION,configuration) end end end