18F/identity-idp

View on GitHub
app/services/identity_linker.rb

Summary

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

class IdentityLinker
  attr_reader :user, :service_provider

  def initialize(user, service_provider)
    @user = user
    @service_provider = service_provider
    @ial = nil
    @aal = nil
    @acr_values = nil
    @vtr = nil
    @requested_aal_value = nil
  end

  def link_identity(
    code_challenge: nil,
    ial: nil,
    aal: nil,
    acr_values: nil,
    vtr: nil,
    requested_aal_value: nil,
    nonce: nil,
    rails_session_id: nil,
    scope: nil,
    verified_attributes: nil,
    last_consented_at: nil,
    clear_deleted_at: nil,
    email_address_id: nil
  )
    return unless user && service_provider.present?

    process_ial(ial)

    identity.update!(
      identity_attributes.merge(
        code_challenge: code_challenge,
        ial: ial,
        aal: aal,
        acr_values: acr_values,
        vtr: vtr,
        requested_aal_value: requested_aal_value,
        nonce: nonce,
        rails_session_id: rails_session_id,
        scope: scope,
        verified_attributes: combined_verified_attributes(verified_attributes),
        email_address_id: email_address_id,
      ).tap do |hash|
        hash[:last_consented_at] = last_consented_at if last_consented_at
        hash[:deleted_at] = nil if clear_deleted_at
      end,
    )

    AgencyIdentityLinker.new(identity).link_identity
    identity
  end

  private

  def process_ial(ial)
    @ial = ial
    now = Time.zone.now
    process_ial_at(now)
    process_verified_at(now)
  end

  def process_ial_at(now)
    if @ial == Idp::Constants::IAL2 || (identity.verified_at.present? && @ial&.zero?)
      identity.last_ial2_authenticated_at = now
    else
      identity.last_ial1_authenticated_at = now
    end
  end

  def process_verified_at(now)
    return unless @ial == Idp::Constants::IAL2 && identity.verified_at.nil?
    identity.verified_at = now
  end

  def identity
    @identity ||= user.identities.create_or_find_by(service_provider: service_provider.issuer)
  end

  def identity_attributes
    {
      last_authenticated_at: Time.zone.now,
      session_uuid: SecureRandom.uuid,
      access_token: SecureRandom.urlsafe_base64,
    }
  end

  def combined_verified_attributes(verified_attributes)
    [*identity.verified_attributes, *verified_attributes.to_a.map(&:to_s)].uniq.sort
  end
end