CMSgov/dpc-app

View on GitHub
engines/api_client/app/services/dpc_client.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

# Provides interaction with dpc-api
class DpcClient
  attr_reader :base_url, :response_body, :response_status

  def initialize
    @base_url = ENV.fetch('API_METADATA_URL')
  end

  def json_content
    'application/json'
  end

  def create_organization(org, fhir_endpoint: {})
    uri_string = "#{base_url}/Organization/$submit"
    json = OrganizationSubmitSerializer.new(org, fhir_endpoint:).to_json
    post_request(uri_string, json, fhir_headers(golden_macaroon))
    self
  end

  def get_organization(api_id)
    client = FHIR::Client.new(base_url)
    client.additional_headers = auth_header(delegated_macaroon(api_id))
    client.read(FHIR::Organization, api_id).resource
  end

  def get_organization_by_npi(npi)
    uri_string = "#{base_url}/Admin"
    client = FHIR::Client.new(uri_string)
    client.additional_headers = auth_header(golden_macaroon)
    client.search(FHIR::Organization, search: { parameters: { npis: "npi|#{npi}" } }).resource
  end

  def update_organization(reg_org, api_id, api_endpoint_ref)
    fhir_org = FhirResourceBuilder.new.fhir_org(reg_org, api_id, api_endpoint_ref)
    fhir_client_update_request(api_id, fhir_org, api_id)
    self
  end

  def update_endpoint(api_id, fhir_endpoint_id, fhir_endpoint)
    fhir_resource = FhirResourceBuilder.new.fhir_endpoint(api_id, fhir_endpoint_id, fhir_endpoint)
    fhir_client_update_request(api_id, fhir_resource, fhir_endpoint_id)
    self
  end

  def create_client_token(reg_org_api_id, params: {})
    uri_string = "#{base_url}/Token"

    json = params.to_json
    macaroon = delegated_macaroon(reg_org_api_id)
    post_request(uri_string, json, headers(macaroon))

    self
  end

  def delete_client_token(reg_org_api_id, token_id)
    uri_string = "#{base_url}/Token/#{token_id}"

    delete_request(uri_string, delegated_macaroon(reg_org_api_id))
  end

  def delete_public_key(reg_org_api_id, public_key_id)
    uri_string = "#{base_url}/Key/#{public_key_id}"

    delete_request(uri_string, delegated_macaroon(reg_org_api_id))
  end

  def get_client_tokens(reg_org_api_id)
    uri_string = "#{base_url}/Token"
    get_request(uri_string, delegated_macaroon(reg_org_api_id))
  end

  def create_public_key(reg_org_api_id, params: {})
    uri_string = "#{base_url}/Key"

    json = {
      key: params[:public_key],
      signature: params[:snippet_signature]
    }.to_json

    post_text_request(
      uri_string,
      json,
      { label: params[:label] },
      delegated_macaroon(reg_org_api_id)
    )

    self
  end

  def get_public_keys(reg_org_api_id)
    uri_string = "#{base_url}/Key"
    get_request(uri_string, delegated_macaroon(reg_org_api_id))
  end

  def create_ip_address(reg_org_api_id, params: {})
    post_text_request(
      "#{base_url}/IpAddress",
      { ip_address: params[:ip_address], label: params[:label] }.to_json,
      {},
      delegated_macaroon(reg_org_api_id)
    )
    self
  end

  def delete_ip_address(reg_org_api_id, addr_id)
    delete_request("#{base_url}/IpAddress/#{addr_id}", delegated_macaroon(reg_org_api_id))
  end

  def get_ip_addresses(reg_org_api_id)
    get_request("#{base_url}/IpAddress", delegated_macaroon(reg_org_api_id))
  end

  def response_successful?
    (200...299).cover? @response_status
  end

  def fhir_client
    @fhir_client ||= FHIR::Client.new(base_url)
  end

  private

  def auth_header(token)
    { Authorization: "Bearer #{token}" }
  end

  def delegated_macaroon(reg_org_api_id)
    m = Macaroon.from_binary(golden_macaroon)
    m.add_first_party_caveat("organization_id = #{reg_org_api_id}")
    m.add_first_party_caveat("expires = #{2.minutes.from_now.iso8601}")
    m.add_first_party_caveat('dpc_macaroon_version = 1')
    m.serialize
  end

  def golden_macaroon
    @golden_macaroon ||= ENV.fetch('GOLDEN_MACAROON')
  end

  def parsed_response(response)
    return self if response.body.blank?

    JSON.parse response.body
  end

  def delete_request(uri_string, token)
    uri = URI.parse uri_string
    request = Net::HTTP::Delete.new(uri.request_uri, headers(token))

    http_request(request, uri)
  end

  def get_request(uri_string, token)
    uri = URI.parse uri_string
    request = Net::HTTP::Get.new(uri.request_uri, headers(token))

    http_request(request, uri)
  end

  def post_request(uri_string, json, headers)
    uri = URI.parse uri_string
    request = Net::HTTP::Post.new(uri.request_uri, headers)
    request.body = json

    http_request(request, uri)
  end

  def post_text_request(uri_string, json, query_params, token)
    uri = URI.parse uri_string
    uri.query = URI.encode_www_form(query_params)
    text_headers = { 'Content-Type' => json_content, Accept: json_content }.merge(auth_header(token))

    request = Net::HTTP::Post.new(uri.request_uri, text_headers)
    request.body = json

    http_request(request, uri)
  end

  def http_request(request, uri)
    http = Net::HTTP.new(uri.host, uri.port)

    if use_ssl?
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_PEER
    end

    response = http.request(request)
    @response_status = response.code.to_i
    @response_body = response_successful? ? parsed_response(response) : response.body
  rescue Errno::ECONNREFUSED
    connection_error
  end

  def fhir_client_update_request(reg_org_api_id, resource, resource_id)
    fhir_client.additional_headers = auth_header(delegated_macaroon(reg_org_api_id))
    response = fhir_client.update(resource, resource_id)

    @response_status = response.response[:code].to_i
    @response_body = response.response[:body]
  end

  def headers(token)
    { 'Content-Type' => json_content, Accept: json_content }.merge(auth_header(token))
  end

  def fhir_headers(token)
    { 'Content-Type' => 'application/fhir+json', Accept: 'application/fhir+json' }.merge(auth_header(token))
  end

  def use_ssl?
    !(Rails.env.development? || Rails.env.test?)
  end

  def connection_error
    Rails.logger.warn 'Could not connect to API'
    @response_status = 500
    @response_body = { 'issue' => [{ 'details' => { 'text' => 'Connection error' } }] }
  end
end