diff --git a/app/models/identity.rb b/app/models/identity.rb index 3ed9bb5f986..73f7aa6f19e 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -29,8 +29,8 @@ def decorate end def piv_cac_available? - PivCacService.piv_cac_available_for_agency?( - sp_metadata[:agency], + PivCacService.piv_cac_available_for_sp?( + ServiceProvider.from_issuer(service_provider), user.email_addresses.map(&:email) ) end diff --git a/app/models/service_provider.rb b/app/models/service_provider.rb index e4658cd443a..5bd83e74c8d 100644 --- a/app/models/service_provider.rb +++ b/app/models/service_provider.rb @@ -43,7 +43,7 @@ def live? end def piv_cac_available?(user = nil) - PivCacService.piv_cac_available_for_agency?(agency, user&.email_addresses&.map(&:email)) + PivCacService.piv_cac_available_for_sp?(self, user&.email_addresses&.map(&:email)) end private diff --git a/app/services/piv_cac_service.rb b/app/services/piv_cac_service.rb index 45eda21533b..f075e954dec 100644 --- a/app/services/piv_cac_service.rb +++ b/app/services/piv_cac_service.rb @@ -28,20 +28,14 @@ def piv_cac_verify_token_link Figaro.env.piv_cac_verify_token_url end - def piv_cac_available_for_agency?(agency, emails = []) - available_for_agency?(agency) || available_for_email?(agency, emails) + def piv_cac_available_for_sp?(sp, emails = []) + sp.piv_cac || available_for_email?(sp, emails) end private - def available_for_agency?(agency) - return if agency.blank? - piv_cac_agencies = JSON.parse(Figaro.env.piv_cac_agencies || '[]') - piv_cac_agencies.include?(agency) - end - - def available_for_email?(agency, emails) - return unless emails.any? && agency_scoped_by_email?(agency) + def available_for_email?(sp, emails) + return unless emails.any? && sp.piv_cac_scoped_by_email piv_cac_email_domains = Figaro.env.piv_cac_email_domains || '[]' supported_domains = JSON.parse(piv_cac_email_domains) @@ -51,15 +45,6 @@ def available_for_email?(agency, emails) emails_match_domains?(email_domains, supported_domains) end - def agency_scoped_by_email?(agency) - return if agency.blank? - - piv_cac_agencies_email_scope = - JSON.parse(Figaro.env.piv_cac_agencies_scoped_by_email || '[]') - - piv_cac_agencies_email_scope.include?(agency) - end - def emails_match_domains?(email_domains, supported_domains) partial_domains, exact_domains = supported_domains.partition { |domain| domain[0] == '.' } @@ -79,12 +64,6 @@ def randomize_uri(uri) uri.gsub('{random}') { |_| SecureRandom.hex(RANDOM_HOSTNAME_BYTES) } end - # Only used in tests - def reset_piv_cac_avaialable_agencies - @piv_cac_agencies = nil - @piv_cac_agencies_email_scope = nil - end - def token_present(token) raise ArgumentError, 'token missing' if token.blank? true diff --git a/config/application.yml.example b/config/application.yml.example index 91f15b899fb..845916b863c 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -149,7 +149,6 @@ development: otp_valid_for: '10' password_pepper: 'f22d4b2cafac9066fe2f4416f5b7a32c' password_strength_enabled: 'true' - piv_cac_agencies: '["Test Government Agency"]' piv_cac_email_domains: '[".mil","state.gov"]' piv_cac_verify_token_secret: 'ee7f20f44cdc2ba0c6830f70470d1d1d059e1279cdb58134db92b35947b1528ef5525ece5910cf4f2321ab989a618feea12ef95711dbc62b9601e8520a34ee12' piv_cac_service_url: 'https://localhost:8443/' @@ -275,8 +274,6 @@ production: participate_in_dap: 'false' # pair with google_analytics_key password_pepper: # generate via `rake secret` password_strength_enabled: 'true' - piv_cac_agencies: '["DOD","NGA","USDS"]' - piv_cac_agencies_scoped_by_email: '["GSA"]' piv_cac_email_domains: '[".mil","state.gov"]' pkcs11_lib: '/opt/cloudhsm/lib/libcloudhsm_pkcs11.so' platform_authenticator_analytics_enabled: 'true' @@ -396,7 +393,6 @@ test: otp_valid_for: '10' password_pepper: 'f22d4b2cafac9066fe2f4416f5b7a32c' password_strength_enabled: 'false' - piv_cac_agencies: '["Test Government Agency"]' piv_cac_email_domains: '[".mil","state.gov"]' piv_cac_service_url: 'https://localhost:8443/' piv_cac_verify_token_secret: '3ac13bfa23e22adae321194c083e783faf89469f6f85dcc0802b27475c94b5c3891b5657bd87d0c1ad65de459166440512f2311018db90d57b15d8ab6660748f' diff --git a/config/service_providers.yml b/config/service_providers.yml index 6fe9e972d2a..7c6b8000376 100644 --- a/config/service_providers.yml +++ b/config/service_providers.yml @@ -499,6 +499,7 @@ production: - 'https://training.vgihub.geointservices.io/op_redirect' - 'https://wiki.vgihub.geointservices.io/op_redirect' restrict_to_deploy_env: 'prod' + piv_cac: true # NGA GEOWorks Landing Page 'urn:gov:gsa:openidconnect.profiles:sp:sso:nga:landingpage': @@ -517,6 +518,7 @@ production: - 'https://nga-geoworks.io/auth/login-gov/callback/loa-1' - 'https://ngageoworks.io/auth/login-gov/callback/loa-1' restrict_to_deploy_env: 'prod' + piv_cac: true # NGA GEOINT Viewer 'urn:gov:gsa:openidconnect.profiles:sp:sso:nga:geoint_viewer': @@ -534,6 +536,7 @@ production: - 'https://gv.nga-geoworks.io/protected/callback' - 'https://gv.ngageoworks.io/protected/callback' restrict_to_deploy_env: 'prod' + piv_cac: true # NGA HiPER CLOUD 'urn:gov:gsa:openidconnect.profiles:sp:sso:nga:hiper_look': @@ -559,6 +562,7 @@ production: - 'https://hiperlook.nga-geoworks.com/auth_redirect' - 'https://hiperlook.nga-geoworks.com:443/auth_redirect' restrict_to_deploy_env: 'prod' + piv_cac: true # NGA MAGE 'urn:gov:gsa:openidconnect.profiles:sp:sso:nga:mage': @@ -576,6 +580,7 @@ production: - 'https://mage.nga-geoworks.io/auth/login-gov/callback/loa-1' - 'https://mage.ngageoworks.io/auth/login-gov/callback/loa-1' restrict_to_deploy_env: 'prod' + piv_cac: true # DOT 'urn:gov:gsa:openidconnect.profiles:sp:sso:dot:login': @@ -605,6 +610,7 @@ production: - 'https://symphony.nga-geoworks.com/guacamole/#/' - 'https://symphony.nga-geoworks.com/secured' restrict_to_deploy_env: 'prod' + piv_cac: true # Secret Service PIX 'urn:gov:gsa:SAML:2.0.profiles:sp:sso:usss:pix': @@ -638,6 +644,7 @@ production: attribute_bundle: - x509_subject - x509_presented + piv_cac: true # My Move.mil 'urn:gov:gsa:openidconnect.profiles:sp:sso:dod:mymovemilprod': @@ -656,6 +663,7 @@ production: attribute_bundle: - x509_subject - x509_presented + piv_cac: true # DOT – National Registry of Certified Medical Examiners App 'urn:gov:dot:openidconnect.profiles:sp:sso:dot:nr_auth': @@ -686,6 +694,8 @@ production: - 'https://sam.gov/portal/SAM' - 'https://www.sam.gov/portal/SAM' restrict_to_deploy_env: 'prod' + piv_cac: true + piv_cac_scoped_by_email: true # SAM – System for Award Management / testing prod from UAT 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:sam_uat': @@ -698,6 +708,8 @@ production: redirect_uris: - 'https://uat.sam.gov/portal/SAM' restrict_to_deploy_env: 'prod' + piv_cac: true + piv_cac_scoped_by_email: true # DOE - Fossil Energy - Import/Export Authorization Portal for Natural Gas 'urn:gov:gsa:openidconnect.profiles:sp:sso:doe:fergas': @@ -740,6 +752,7 @@ production: attribute_bundle: - email restrict_to_deploy_env: 'prod' + piv_cac: true # Forest Service Open Forest Permits 'urn:gov:gsa:open-id-connect:sp:sso:usda-forestservice:epermit-prod': @@ -824,6 +837,7 @@ production: attribute_bundle: - email restrict_to_deploy_env: 'prod' + piv_cac: true # Pre-mod SAM – System for Award Management 'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:pmsam': @@ -837,6 +851,8 @@ production: - 'https://sam.gov/SAM' - 'https://www.sam.gov/SAM' restrict_to_deploy_env: 'prod' + piv_cac: true + piv_cac_scoped_by_email: true # OPM Secure Portal 'urn:gov:gsa:SAML:2.0.profiles:sp:sso:OPM:TibcoMFT': diff --git a/db/migrate/20181121223714_add_piv_cac_to_service_provider.rb b/db/migrate/20181121223714_add_piv_cac_to_service_provider.rb new file mode 100644 index 00000000000..67824638b69 --- /dev/null +++ b/db/migrate/20181121223714_add_piv_cac_to_service_provider.rb @@ -0,0 +1,14 @@ +class AddPivCacToServiceProvider < ActiveRecord::Migration[5.1] + def up + add_column :service_providers, :piv_cac, :boolean + change_column_default :service_providers, :piv_cac, false + + add_column :service_providers, :piv_cac_scoped_by_email, :boolean + change_column_default :service_providers, :piv_cac_scoped_by_email, false + end + + def down + remove_column :service_providers, :piv_cac + remove_column :service_providers, :piv_cac_scoped_by_email + end +end diff --git a/db/schema.rb b/db/schema.rb index cb124630707..7c160838273 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181029203754) do +ActiveRecord::Schema.define(version: 20181121223714) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -207,6 +207,8 @@ t.text "failure_to_proof_url" t.integer "aal" t.integer "ial" + t.boolean "piv_cac", default: false + t.boolean "piv_cac_scoped_by_email", default: false t.index ["issuer"], name: "index_service_providers_on_issuer", unique: true end diff --git a/spec/features/users/piv_cac_management_spec.rb b/spec/features/users/piv_cac_management_spec.rb index 7dd7e80f183..f91ebd35b99 100644 --- a/spec/features/users/piv_cac_management_spec.rb +++ b/spec/features/users/piv_cac_management_spec.rb @@ -22,10 +22,7 @@ def find_form(page, attributes) before(:each) do user.identities << [identity_with_sp] - allow(Figaro.env).to receive(:piv_cac_agencies).and_return( - ['Test Government Agency'].to_json - ) - PivCacService.send(:reset_piv_cac_avaialable_agencies) + allow_any_instance_of(ServiceProvider).to receive(:piv_cac).and_return(true) end scenario 'allows association of a piv/cac with an account' do @@ -132,8 +129,7 @@ def find_form(page, attributes) before(:each) do user.identities << [identity_with_sp] - allow(Figaro.env).to receive(:piv_cac_agencies).and_return('[]') - PivCacService.send(:reset_piv_cac_avaialable_agencies) + allow_any_instance_of(ServiceProvider).to receive(:piv_cac).and_return(false) end scenario "doesn't advertise association of a piv/cac with an account" do diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb index c18316701b4..5727048d600 100644 --- a/spec/features/users/sign_up_spec.rb +++ b/spec/features/users/sign_up_spec.rb @@ -167,7 +167,7 @@ end it 'does not allow a user to choose piv/cac as 2FA method during sign up' do - allow(PivCacService).to receive(:piv_cac_available_for_agency?).and_return(false) + allow(PivCacService).to receive(:piv_cac_available_for_sp?).and_return(false) begin_sign_up_with_sp_and_loa(loa3: false) expect(page).to have_current_path two_factor_options_path diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 196c20b6162..811cd55bd34 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -131,10 +131,10 @@ end describe '#piv_cac_available?' do - context 'when agency configured to support piv/cac' do + context 'when sp is configured to support piv/cac' do before(:each) do - allow(PivCacService).to receive(:piv_cac_available_for_agency?).with( - service_provider.agency, identity_with_sp.user.email_addresses.map(&:email) + allow(PivCacService).to receive(:piv_cac_available_for_sp?).with( + service_provider, identity_with_sp.user.email_addresses.map(&:email) ).and_return(true) end @@ -143,10 +143,10 @@ end end - context 'when agency is not configured to support piv/cac' do + context 'when sp is not configured to support piv/cac' do before(:each) do - allow(PivCacService).to receive(:piv_cac_available_for_agency?).with( - service_provider.agency, identity_with_sp.user.email_addresses.map(&:email) + allow(PivCacService).to receive(:piv_cac_available_for_sp?).with( + service_provider, identity_with_sp.user.email_addresses.map(&:email) ).and_return(false) end diff --git a/spec/models/service_provider_spec.rb b/spec/models/service_provider_spec.rb index e39f6e40f7e..c3ff760621e 100644 --- a/spec/models/service_provider_spec.rb +++ b/spec/models/service_provider_spec.rb @@ -68,16 +68,16 @@ end describe 'piv_cac_available?' do - context 'when the service provider is with an enabled agency' do + context 'when the service provider has piv_cac enabled' do it 'is truthy' do - allow(PivCacService).to receive(:piv_cac_available_for_agency?).and_return(true) + allow(PivCacService).to receive(:piv_cac_available_for_sp?).and_return(true) expect(service_provider.piv_cac_available?).to be_truthy end end - context 'when the service provider agency is not enabled' do + context 'when the service provider does not have piv_cac enabled' do it 'is falsey' do - allow(PivCacService).to receive(:piv_cac_available_for_agency?).and_return(false) + allow(PivCacService).to receive(:piv_cac_available_for_sp?).and_return(false) expect(service_provider.piv_cac_available?).to be_falsey end @@ -88,8 +88,8 @@ it 'calls with the user email' do expect(PivCacService).to receive( - :piv_cac_available_for_agency? - ).with(service_provider.agency, user.email_addresses.map(&:email)) + :piv_cac_available_for_sp? + ).with(service_provider, user.email_addresses.map(&:email)) service_provider.piv_cac_available?(user) end diff --git a/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb b/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb index 115c3760ac8..9d839c1d4b7 100644 --- a/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb +++ b/spec/policies/two_factor_authentication/piv_cac_policy_spec.rb @@ -38,13 +38,10 @@ context 'allowing it' do before(:each) do - allow(Figaro.env).to receive(:piv_cac_agencies).and_return( - [service_provider.agency].to_json - ) - PivCacService.send(:reset_piv_cac_avaialable_agencies) + allow_any_instance_of(ServiceProvider).to receive(:piv_cac).and_return(true) end - it 'does allows piv/cac' do + it 'does allow piv/cac' do expect(subject.available?).to be_truthy end end diff --git a/spec/services/piv_cac_service_spec.rb b/spec/services/piv_cac_service_spec.rb index d40528635a2..8ea8675efb2 100644 --- a/spec/services/piv_cac_service_spec.rb +++ b/spec/services/piv_cac_service_spec.rb @@ -159,12 +159,12 @@ end end - describe '#piv_cac_available_for_agency?' do - let(:subject) { PivCacService.piv_cac_available_for_agency?('foo', ['foo@example.com']) } + describe '#piv_cac_available?' do + let(:subject) { PivCacService.piv_cac_available_for_sp?(sp, ['foo@example.com']) } context 'with an agency not encouraged to use piv/cac for anyone' do + let(:sp) { ServiceProvider.new(issuer: 'foo', piv_cac: false) } before(:each) do - allow(PivCacService).to receive(:available_for_agency?).and_return(false) allow(PivCacService).to receive(:available_for_email?).and_return(false) end @@ -172,8 +172,10 @@ end context 'with an agency encouraged to use piv/cac for everyone' do + let(:sp) { ServiceProvider.new(issuer: 'foo', piv_cac: true) } + let(:subject) { PivCacService.piv_cac_available_for_sp?(sp, ['foo@example.com']) } + before(:each) do - allow(PivCacService).to receive(:available_for_agency?).and_return(true) allow(PivCacService).to receive(:available_for_email?).and_return(false) end @@ -181,29 +183,11 @@ end context 'with an agency encouraged to use piv/cac for certain email domains' do - before(:each) do - allow(PivCacService).to receive(:available_for_agency?).and_return(false) - allow(PivCacService).to receive(:available_for_email?).and_return(true) - end - - it { expect(subject).to eq true } - end - end - - describe '#available_for_agency?' do - let(:subject) { PivCacService.send(:available_for_agency?, 'foo') } + let(:sp) { ServiceProvider.new(issuer: 'foo', piv_cac: false) } + let(:subject) { PivCacService.piv_cac_available_for_sp?(sp, ['foo@example.com']) } - context 'with the agency not configured to be available' do before(:each) do - allow(Figaro.env).to receive(:piv_cac_agencies).and_return('["bar"]') - end - - it { expect(subject).to be_falsey } - end - - context 'with the agency configured to be available' do - before(:each) do - allow(Figaro.env).to receive(:piv_cac_agencies).and_return('["bar","foo"]') + allow(PivCacService).to receive(:available_for_email?).and_return(true) end it { expect(subject).to eq true } @@ -211,7 +195,8 @@ end describe '#available_for_email?' do - let(:subject) { PivCacService.send(:available_for_email?, 'foo', ['foo@bar.example.com']) } + let(:sp) { ServiceProvider.new(issuer: 'foo', piv_cac: true, piv_cac_scoped_by_email: true) } + let(:subject) { PivCacService.send(:available_for_email?, sp, ['foo@bar.example.com']) } context 'with the agency not configured to be available' do before(:each) do diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index 68c4c634ce0..ba51e1b8d30 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -415,7 +415,7 @@ def set_up_2fa_with_authenticator_app end def register_user_with_piv_cac(email = 'test@test.com') - allow(PivCacService).to receive(:piv_cac_available_for_agency?).and_return(true) + allow(PivCacService).to receive(:piv_cac_available_for_sp?).and_return(true) confirm_email_and_password(email) expect(page).to have_current_path two_factor_options_path