CMSgov/dpc-app

View on GitHub
dpc-portal/app/controllers/invitations_controller.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

# Handles acceptance of invitations
class InvitationsController < ApplicationController
  before_action :load_organization
  before_action :load_invitation
  before_action :authenticate_user!, except: %i[login]
  before_action :invitation_matches_user, only: %i[confirm]

  def accept
    if current_user.email != @invitation.invited_email
      return render(Page::Invitations::BadInvitationComponent.new('pii_mismatch', 'error'),
                    status: :forbidden)
    end

    render(Page::Invitations::AcceptInvitationComponent.new(@organization, @invitation))
  end

  def confirm
    if @invitation.credential_delegate?
      create_cd_org_link
    elsif @invitation.authorized_official?
      create_ao_org_link
    else
      return render(Page::Invitations::BadInvitationComponent.new('invalid', 'warning'),
                    status: :unprocessable_entity)
    end
    redirect_to organization_path(@organization)
  end

  def login
    login_session
    client_id = "urn:gov:cms:openidconnect.profiles:sp:sso:cms:dpc:#{ENV.fetch('ENV')}"
    url = URI::HTTPS.build(host: ENV.fetch('IDP_HOST'),
                           path: '/openid_connect/authorize',
                           query: { acr_values: 'http://idmanagement.gov/ns/assurance/ial/2',
                                    client_id:,
                                    redirect_uri: "#{redirect_host}/portal/users/auth/openid_connect/callback",
                                    response_type: 'code',
                                    scope: 'openid email all_emails profile phone social_security_number',
                                    nonce: @nonce,
                                    state: @state }.to_query)
    redirect_to url, allow_other_host: true
  end

  private

  def create_cd_org_link
    CdOrgLink.create!(user: current_user, provider_organization: @organization, invitation: @invitation)
    @invitation.update!(invited_given_name: nil, invited_family_name: nil, invited_phone: nil, invited_email: nil)
    flash[:notice] = "Invitation accepted. You can now manage this organization's credentials. Learn more."
  end

  def create_ao_org_link
    AoOrgLink.create!(user: current_user, provider_organization: @organization, invitation: @invitation)
    @invitation.update!(invited_given_name: nil, invited_family_name: nil, invited_phone: nil, invited_email: nil)
    flash[:notice] = 'Invitation accepted.'
  end

  def authenticate_user!
    return if current_user

    render(Page::Session::InvitationLoginComponent.new(@invitation))
  end

  def invitation_matches_user
    user_info = UserInfoService.new.user_info(session)
    unless @invitation.match_user?(user_info)
      return render(Page::Invitations::BadInvitationComponent.new('pii_mismatch', 'error'),
                    status: :forbidden)
    end
    check_code if @invitation.credential_delegate?
  rescue UserInfoServiceError => e
    handle_user_info_service_error(e)
  rescue InvitationError => e
    render(Page::Invitations::BadInvitationComponent.new(e.message, 'error'),
           status: :forbidden)
  end

  def check_code
    return if params[:verification_code] == @invitation.verification_code

    @invitation.errors.add(:verification_code, :bad_code, message: 'tbd')
    render(Page::Invitations::AcceptInvitationComponent.new(@organization, @invitation),
           status: :bad_request)
  end

  def handle_user_info_service_error(error)
    case error.message
    when 'unauthorized'
      render(Page::Session::InvitationLoginComponent.new(@invitation))
    else
      render(Page::Invitations::BadInvitationComponent.new('server_error', 'warning'),
             status: :service_unavailable)
    end
  end

  def load_invitation
    @invitation = Invitation.find(params[:id])
    if @organization != @invitation.provider_organization
      render(Page::Invitations::BadInvitationComponent.new('invalid', 'warning'), status: :not_found)
    elsif @invitation.expired? || @invitation.accepted? || @invitation.cancelled_at.present?
      render(Page::Invitations::BadInvitationComponent.new('invalid', 'warning'),
             status: :forbidden)
    end
  rescue ActiveRecord::RecordNotFound
    render(Page::Invitations::BadInvitationComponent.new('invalid', 'warning'), status: :not_found)
  end

  def login_session
    session[:user_return_to] = accept_organization_invitation_url(@organization, params[:id])
    session['omniauth.nonce'] = @nonce = SecureRandom.hex(16)
    session['omniauth.state'] = @state = SecureRandom.hex(16)
  end
end

def redirect_host
  case ENV.fetch('ENV', nil)
  when 'local'
    'http://localhost:3100'
  when 'prod'
    'https://dpc.cms.gov'
  else
    "https://#{ENV.fetch('ENV', nil)}.dpc.cms.gov"
  end
end