18F/identity-idp

View on GitHub
app/services/doc_auth/lexis_nexis/request.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
# frozen_string_literal: true

module DocAuth
  module LexisNexis
    class Request
      attr_reader :config, :user_uuid, :uuid_prefix

      def initialize(config:, user_uuid: nil, uuid_prefix: nil)
        @config = config
        @user_uuid = user_uuid
        @uuid_prefix = uuid_prefix
      end

      def fetch
        # return DocAuth::Respose with DocAuth:Error if workflow invalid
        http_response = send_http_request
        return handle_invalid_response(http_response) unless http_response.success?

        handle_http_response(http_response)
      rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
        handle_connection_error(exception: e)
      end

      def metric_name
        raise NotImplementedError
      end

      private

      def send_http_request
        case method.downcase.to_sym
        when :post
          send_http_post_request
        when :get
          send_http_get_request
        end
      end

      def handle_http_response(_response)
        raise NotImplementedError
      end

      def handle_invalid_response(http_response)
        message = [
          self.class.name,
          'Unexpected HTTP response',
          http_response.status,
        ].join(' ')
        exception = DocAuth::RequestError.new(message, http_response.status)

        response_body = begin
          http_response.body.present? ? JSON.parse(http_response.body) : {}
        rescue JSON::JSONError
          {}
        end

        handle_connection_error(
          exception: exception,
          status_code: response_body.dig('status', 'code'),
          status_message: response_body.dig('status', 'message'),
        )
      end

      def handle_connection_error(exception:, status_code: nil, status_message: nil)
        NewRelic::Agent.notice_error(exception)
        DocAuth::Response.new(
          success: false,
          errors: { network: true },
          exception: exception,
          extra: {
            vendor: 'TrueID',
            selfie_live: false,
            selfie_quality_good: false,
            vendor_status_code: status_code,
            vendor_status_message: status_message,
            reference: @reference,
          }.compact,
        )
      end

      def send_http_get_request
        faraday_connection.get do |req|
          req.options.context = { service_name: metric_name }
        end
      end

      def send_http_post_request
        faraday_connection.post do |req|
          req.options.context = { service_name: metric_name }
          req.body = body
        end
      end

      def faraday_connection
        retry_options = {
          max: 2,
          interval: 0.05,
          interval_randomness: 0.5,
          backoff_factor: 2,
          retry_statuses: [404, 500],
          retry_block: lambda do |env:, options:, retry_count:, exception:, will_retry_in:|
            NewRelic::Agent.notice_error(exception, custom_params: { retry: retry_count })
          end,
        }

        Faraday.new(url: url.to_s, headers: request_headers) do |conn|
          conn.request :retry, retry_options
          conn.request :instrumentation, name: 'request_metric.faraday'
          conn.request :authorization, :basic, username, password unless hmac_auth_enabled?
          conn.adapter :net_http
          conn.options.timeout = timeout
          conn.options.read_timeout = timeout
          conn.options.open_timeout = timeout
          conn.options.write_timeout = timeout
        end
      end

      def path
        "/restws/identity/v3/accounts/#{account_id}/workflows/#{workflow}/conversations"
      end

      def method
        :get
      end

      def url
        URI.join(config.base_url, path)
      end

      def request_headers
        headers = {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        }
        headers['Authorization'] = hmac_authorization if hmac_auth_enabled?
        headers
      end

      def hmac_auth_enabled?
        IdentityConfig.store.lexisnexis_hmac_auth_enabled
      end

      # Example HMAC auth header from RDP_REST_V3_DecisioningGuide_March22.pdf, page 21
      def hmac_authorization
        Proofing::LexisNexis::RequestSigner.new(
          config: config,
          message_body: body,
          path: path,
        ).hmac_authorization
      end

      def settings
        @reference = uuid
        {
          Type: 'Initiate',
          Settings: {
            Mode: request_mode,
            Locale: config.locale,
            Venue: 'online',
            Reference: @reference,
          },
        }
      end

      def uuid
        return SecureRandom.uuid unless user_uuid

        uuid = user_uuid

        if uuid_prefix.present?
          "#{uuid_prefix}:#{uuid}"
        else
          uuid
        end
      end

      def username
        raise NotImplementedError
      end

      def password
        raise NotImplementedError
      end

      def account_id
        config.account_id
      end

      def workflow
        raise NotImplementedError
      end

      def body
        raise NotImplementedError
      end

      def request_mode
        config.request_mode
      end

      def timeout
        IdentityConfig.store.lexisnexis_trueid_timeout
      end
    end
  end
end