Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ def confirm_piv_cac?(proposed_uuid)
end

def piv_cac_enabled?
FeatureManagement.piv_cac_enabled? && x509_dn_uuid.present?
PivCacLoginOptionPolicy.new(self).enabled?
end

def piv_cac_available?
piv_cac_enabled? || identities.any?(&:piv_cac_available?)
PivCacLoginOptionPolicy.new(self).available?
end

def need_two_factor_authentication?(_request)
Expand Down
25 changes: 25 additions & 0 deletions app/policies/piv_cac_login_option_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,32 @@ def configured?
FeatureManagement.piv_cac_enabled? && user.x509_dn_uuid.present?
end

def enabled?
configured?
end

def available?
enabled? || available_for_email? || user.identities.any?(&:piv_cac_available?)
end

private

attr_reader :user

def available_for_email?
piv_cac_email_domains = Figaro.env.piv_cac_email_domains
return if piv_cac_email_domains.blank?

domain_list = JSON.parse(piv_cac_email_domains)
(_, email_domain) = user.email.split(/@/, 2)
domain_list.any? { |supported_domain| domain_match?(email_domain, supported_domain) }
end

def domain_match?(given, matcher)
if matcher[0] == '.'
given.end_with?(matcher)
else
given == matcher
end
end
end
3 changes: 3 additions & 0 deletions config/application.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ development:
password_pepper: 'f22d4b2cafac9066fe2f4416f5b7a32c'
password_strength_enabled: 'true'
piv_cac_agencies: '["Test Government Agency"]'
piv_cac_email_domains: '[".mil"]'
piv_cac_enabled: 'true'
piv_cac_verify_token_secret: 'ee7f20f44cdc2ba0c6830f70470d1d1d059e1279cdb58134db92b35947b1528ef5525ece5910cf4f2321ab989a618feea12ef95711dbc62b9601e8520a34ee12'
piv_cac_service_url: 'https://localhost:8443/'
Expand Down Expand Up @@ -276,6 +277,7 @@ production:
password_pepper: # generate via `rake secret`
password_strength_enabled: 'true'
piv_cac_agencies: '["DOD","NGA","EOP"]'
piv_cac_email_domains: '[".mil"]'
piv_cac_enabled: 'false'
pkcs11_lib: '/opt/cloudhsm/lib/libcloudhsm_pkcs11.so'
programmable_sms_countries: 'US,CA,MX'
Expand Down Expand Up @@ -397,6 +399,7 @@ test:
password_pepper: 'f22d4b2cafac9066fe2f4416f5b7a32c'
password_strength_enabled: 'false'
piv_cac_agencies: '["Test Government Agency"]'
piv_cac_email_domains: '[".mil"]'
piv_cac_enabled: 'true'
piv_cac_service_url: 'https://localhost:8443/'
piv_cac_verify_token_secret: '3ac13bfa23e22adae321194c083e783faf89469f6f85dcc0802b27475c94b5c3891b5657bd87d0c1ad65de459166440512f2311018db90d57b15d8ab6660748f'
Expand Down
115 changes: 115 additions & 0 deletions spec/policies/piv_cac_login_option_policy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
require 'rails_helper'

describe PivCacLoginOptionPolicy do
let(:subject) { described_class.new(user) }

describe '#configured?' do
context 'without a piv configured' do
let(:user) { build(:user) }

it { expect(subject.configured?).to be_falsey }
end

context 'with a piv configured' do
let(:user) { build(:user, :with_piv_or_cac) }

it { expect(subject.configured?).to be_truthy }
end
end

describe '#enabled?' do
context 'without a piv configured' do
let(:user) { build(:user) }

it { expect(subject.configured?).to be_falsey }
end

context 'with a piv configured' do
let(:user) { build(:user, :with_piv_or_cac) }

it { expect(subject.configured?).to be_truthy }
end
end

describe '#available?' do
let(:user) { build(:user) }

context 'when enabled' do
before(:each) do
allow(subject).to receive(:enabled?).and_return(true)
end

it { expect(subject.available?).to be_truthy }
end

context 'when available for the email' do
before(:each) do
allow(subject).to receive(:available_for_email?).and_return(true)
end

it { expect(subject.available?).to be_truthy }
end

context 'when associated with a supported identity' do
before(:each) do
identity = double
allow(identity).to receive(:piv_cac_available?).and_return(true)
allow(user).to receive(:identities).and_return([identity])
end

it { expect(subject.available?).to be_truthy }
end

context 'when not enabled and not available for the email and not a supported identity' do
before(:each) do
identity = double
allow(identity).to receive(:piv_cac_available?).and_return(false)
allow(user).to receive(:identities).and_return([identity])
allow(subject).to receive(:enabled?).and_return(false)
allow(subject).to receive(:available_for_email?).and_return(false)
end

it { expect(subject.available?).to be_falsey }
end
end

describe '#available_for_email?' do
let(:result) { subject.send(:available_for_email?) }

context 'with a configured parent domain' do
before(:each) do
allow(Figaro.env).to receive(:piv_cac_email_domains).and_return('[".example.com"]')
end

context 'and a supported email subdomain' do
let(:user) { build(:user, email: 'someone@foo.example.com') }

it { expect(result).to be_truthy }
end

context 'and a an email at that domain' do
let(:user) { build(:user, email: 'someone@example.com') }

it { expect(result).to be_falsey }
end
end

context 'with a configured full domain' do
before(:each) do
allow(Figaro.env).to receive(:piv_cac_email_domains).and_return('["example.com"]')
end

context 'and an email subdomain' do
let(:user) { build(:user, email: 'someone@foo.example.com') }

it { expect(result).to be_falsey }
end

context 'and a an email at that domain' do
let(:user) { build(:user, email: 'someone@example.com') }

it { expect(result).to be_truthy }
end
end
end
end