diff --git a/Gemfile.lock b/Gemfile.lock index 31f48051c..43f759f4f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -226,7 +226,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.3.3) + concurrent-ruby (1.3.4) crack (1.0.0) bigdecimal rexml @@ -260,7 +260,7 @@ GEM activesupport (>= 5.0) gyoku (1.3.1) builder (>= 2.1.2) - hashdiff (1.1.0) + hashdiff (1.1.1) hashie (4.1.0) httparty (0.18.1) mime-types (~> 3.0) @@ -310,7 +310,7 @@ GEM mini_magick (4.11.0) mini_mime (1.1.5) mini_portile2 (2.8.7) - minitest (5.24.1) + minitest (5.25.0) moment_timezone-rails (0.5.14) momentjs-rails (~> 2.15.1) momentjs-rails (2.15.1) @@ -358,7 +358,7 @@ GEM public_suffix (5.1.1) puma (5.6.4) nio4r (~> 2.0) - racc (1.8.0) + racc (1.8.1) rack (2.2.9) rack-cors (1.1.1) rack (>= 2.0.0) @@ -426,7 +426,7 @@ GEM regexp_parser (2.8.3) request_store (1.5.0) rack (>= 1.4) - rexml (3.3.1) + rexml (3.3.5) strscan rspec (3.10.0) rspec-core (~> 3.10.0) @@ -570,7 +570,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) zaru (0.3.0) - zeitwerk (2.6.16) + zeitwerk (2.6.17) zero_downtime_migrations (0.0.7) activerecord @@ -653,4 +653,4 @@ DEPENDENCIES zero_downtime_migrations BUNDLED WITH - 2.4.19 + 2.4.22 diff --git a/app/assets/stylesheets/_commons.scss b/app/assets/stylesheets/_commons.scss index 12d196411..b4f1eecdd 100644 --- a/app/assets/stylesheets/_commons.scss +++ b/app/assets/stylesheets/_commons.scss @@ -258,6 +258,16 @@ dd { } } +.cf-icon-external-link { + vertical-align: -.45ex; + width: 1em; + height: 1em; + + path { + fill: $cf-link; + } +} + .cf-icon-close { display: block; margin: auto; diff --git a/app/controllers/api/v1/application_controller.rb b/app/controllers/api/v1/application_controller.rb index 17581100f..66385a934 100644 --- a/app/controllers/api/v1/application_controller.rb +++ b/app/controllers/api/v1/application_controller.rb @@ -28,7 +28,12 @@ def sensitive_record end def forbidden(reason = "Forbidden: unspecified") - render json: { status: reason }, status: 403 + render json: { + status: reason, + featureToggles: { + checkUserSensitivity: FeatureToggle.enabled?(:check_user_sensitivity) + } + }, status: :forbidden end def missing_header(header) diff --git a/app/controllers/api/v2/manifests_controller.rb b/app/controllers/api/v2/manifests_controller.rb index d7a40f3a9..ae70f0925 100644 --- a/app/controllers/api/v2/manifests_controller.rb +++ b/app/controllers/api/v2/manifests_controller.rb @@ -6,6 +6,8 @@ def start manifest = Manifest.includes(:sources, :records).find_or_create_by_user(user: current_user, file_number: file_number) manifest.start! render json: json_manifests(manifest) + rescue BGS::SensitivityLevelCheckFailure + forbidden("This user does not have permission to access this information") end def refresh @@ -15,6 +17,8 @@ def refresh manifest.start! render json: json_manifests(manifest) + rescue BGS::SensitivityLevelCheckFailure + forbidden("This user does not have permission to access this information") end def progress @@ -23,6 +27,7 @@ def progress files_download ||= FilesDownload.find_with_manifest(manifest_id: params[:id], user_id: current_user.id) end return record_not_found unless files_download + render json: json_manifests(files_download.manifest) end diff --git a/app/exceptions/bgs_errors.rb b/app/exceptions/bgs_errors.rb index a61d1a594..082d4637a 100644 --- a/app/exceptions/bgs_errors.rb +++ b/app/exceptions/bgs_errors.rb @@ -5,4 +5,5 @@ class InvalidApplication < StandardError; end class NoActiveStations < StandardError; end class NoCaseflowAccess < StandardError; end class StationAssertionRequired < StandardError; end + class SensitivityLevelCheckFailure < StandardError; end end diff --git a/app/models/manifest.rb b/app/models/manifest.rb index 86f97247c..8b974447c 100644 --- a/app/models/manifest.rb +++ b/app/models/manifest.rb @@ -31,7 +31,18 @@ def start! # Reset stale manifests. update!(fetched_files_status: :initialized) if ready_for_refresh? - vbms_source.start! + if FeatureToggle.enabled?(:check_user_sensitivity) + if sensitivity_checker.sensitivity_levels_compatible?( + user: user, + veteran_file_number: file_number + ) + vbms_source.start! + else + raise BGS::SensitivityLevelCheckFailure.new, "Unauthorized" + end + else + vbms_source.start! + end vva_source.start! unless FeatureToggle.enabled?(:skip_vva) end @@ -41,6 +52,7 @@ def download_and_package_files! expiration: SECONDS_TO_AUTO_UNLOCK) s.lock(SECONDS_TO_AUTO_UNLOCK) do return if pending? + update(fetched_files_status: :pending) end @@ -168,4 +180,8 @@ def update_veteran_info veteran_last_name: veteran.last_name || "", veteran_last_four_ssn: veteran.last_four_ssn || "") end + + def sensitivity_checker + @sensitivity_checker ||= SensitivityChecker.new + end end diff --git a/app/models/manifest_source.rb b/app/models/manifest_source.rb index 6a74f4be3..2a1ed1a68 100644 --- a/app/models/manifest_source.rb +++ b/app/models/manifest_source.rb @@ -19,6 +19,7 @@ class ManifestSource < ApplicationRecord def start! return if current? || processing? + V2::DownloadManifestJob.perform_later(self, RequestStore[:current_user]) rescue StandardError update(status: :initialized) diff --git a/app/services/external_api/bgs_service.rb b/app/services/external_api/bgs_service.rb index 42658082a..adae14727 100644 --- a/app/services/external_api/bgs_service.rb +++ b/app/services/external_api/bgs_service.rb @@ -38,6 +38,51 @@ def initialize(client: nil) @client = client || self.class.init_client end + def sensitivity_level_for_user(user) + fail "Invalid user" if !user.instance_of?(User) + + participant_id = user.participant_id + + Rails.cache.fetch("sensitivity_level_for_user_id_#{user.id}", expires_in: 1.hour) do + MetricsService.record( + "Efolder BGS: sensitivity level for user #{user.id}", + service: :bgs, + name: "security.find_person_scrty_log_by_ptcpnt_id" + ) do + response = client.security.find_person_scrty_log_by_ptcpnt_id(participant_id) + + response.key?(:scrty_level_type_cd) ? Integer(response[:scrty_level_type_cd]) : 0 + rescue BGS::ShareError + 0 + end + end + end + + def sensitivity_level_for_veteran(veteran_file_number) + vet_info = fetch_veteran_info(veteran_file_number) + + participant_id = vet_info.present? ? vet_info[:participant_id] : nil + + fail "Invalid veteran" if participant_id.blank? + + Rails.cache.fetch("sensitivity_level_for_veteran_participant_id_#{participant_id}", expires_in: 1.hour) do + MetricsService.record( + "Efolder BGS: sensitivity level for veteran participant ID #{participant_id}", + service: :bgs, + name: "security.find_sensitivity_level_by_participant_id" + ) do + response = client.security.find_sensitivity_level_by_participant_id(participant_id) + + # guard clause for no response + return 0 if response.blank? + + response&.key?(:scrty_level_type_cd) ? Integer(response[:scrty_level_type_cd]) : 0 + rescue BGS::ShareError + 0 + end + end + end + def parse_veteran_info(veteran_data) ssn = veteran_data[:ssn] ? veteran_data[:ssn] : veteran_data[:soc_sec_number] last_four_ssn = ssn ? ssn[ssn.length - 4..ssn.length] : nil diff --git a/app/services/external_api/vbms_service.rb b/app/services/external_api/vbms_service.rb index 08fe2d8c2..e8332c709 100644 --- a/app/services/external_api/vbms_service.rb +++ b/app/services/external_api/vbms_service.rb @@ -26,6 +26,8 @@ def self.fetch_documents_for(download) end def self.v2_fetch_documents_for(veteran_file_number) + verify_user_veteran_access(veteran_file_number) + documents = [] if FeatureToggle.enabled?(:use_ce_api) @@ -44,6 +46,8 @@ def self.v2_fetch_documents_for(veteran_file_number) end def self.fetch_delta_documents_for(veteran_file_number, begin_date_range, end_date_range = Time.zone.now) + verify_user_veteran_access(veteran_file_number) + documents = [] if FeatureToggle.enabled?(:use_ce_api) @@ -69,6 +73,8 @@ def self.fetch_document_file(document) end def self.v2_fetch_document_file(document) + verify_user_veteran_access(document.file_number) + if FeatureToggle.enabled?(:use_ce_api) # Not using #send_and_log_request because logging to MetricService implemeneted in CE API gem # Method call returns the response body, so no need to return response.content/body @@ -110,4 +116,14 @@ def self.vbms_client @vbms_client = init_client end + + def self.verify_user_veteran_access(veteran_file_number) + return if !FeatureToggle.enabled?(:check_user_sensitivity) + + raise "User does not have permission to access this information" unless + SensitivityChecker.new.sensitivity_levels_compatible?( + user: RequestStore[:current_user], + veteran_file_number: veteran_file_number + ) + end end diff --git a/app/services/sensitivity_checker.rb b/app/services/sensitivity_checker.rb new file mode 100644 index 000000000..8a7e33b17 --- /dev/null +++ b/app/services/sensitivity_checker.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class SensitivityChecker + def sensitivity_levels_compatible?(user:, veteran_file_number:) + bgs_service.sensitivity_level_for_user(user) >= + bgs_service.sensitivity_level_for_veteran(veteran_file_number) + rescue StandardError => error + ExceptionLogger.capture(error) + + false + end + + private + + def bgs_service + @bgs_service ||= BGSService.new + end +end diff --git a/client/src/actionTypes.js b/client/src/actionTypes.js index fa1e30206..4538727af 100644 --- a/client/src/actionTypes.js +++ b/client/src/actionTypes.js @@ -11,6 +11,7 @@ export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE'; export const SET_MANIFEST_ID = 'SET_MANIFEST_ID'; export const SET_RECENT_DOWNLOADS = 'SET_RECENT_DOWNLOADS'; export const SET_SEARCH_TEXT = 'SET_SEARCH_TEXT'; +export const SET_SHOW_UNAUTHORIZED_VETERAN_MESSAGE = 'SET_SHOW_UNAUTHORIZED_VETERAN_MESSAGE'; export const SET_VETERAN_ID = 'SET_VETERAN_ID'; export const SET_VETERAN_NAME = 'SET_VETERAN_NAME'; export const SHOW_CONFIRM_DOWNLOAD_MODAL = 'SHOW_CONFIRM_DOWNLOAD_MODAL'; diff --git a/client/src/actions.jsx b/client/src/actions.jsx index 874930b9a..75e75fd2f 100644 --- a/client/src/actions.jsx +++ b/client/src/actions.jsx @@ -12,6 +12,7 @@ import { SET_MANIFEST_ID, SET_RECENT_DOWNLOADS, SET_SEARCH_TEXT, + SET_SHOW_UNAUTHORIZED_VETERAN_MESSAGE, SET_VETERAN_ID, SET_VETERAN_NAME, SHOW_CONFIRM_DOWNLOAD_MODAL @@ -56,6 +57,11 @@ export const setDocumentSources = (sources) => ({ payload: sources }); +export const setShowUnauthorizedVeteranMessage = (showMessage) => ({ + type: SET_SHOW_UNAUTHORIZED_VETERAN_MESSAGE, + payload: showMessage +}); + export const setErrorMessage = (msg) => ({ type: SET_ERROR_MESSAGE, payload: msg diff --git a/client/src/apiActions.jsx b/client/src/apiActions.jsx index 50a1e4143..c4ad77905 100644 --- a/client/src/apiActions.jsx +++ b/client/src/apiActions.jsx @@ -8,6 +8,7 @@ import { setDocumentsFetchStatus, setDocumentSources, setErrorMessage, + setShowUnauthorizedVeteranMessage, setManifestId, setRecentDownloads, setVeteranId, @@ -165,6 +166,10 @@ export const restartManifestFetch = (manifestId, csrfToken) => (dispatch) => { }; export const startManifestFetch = (veteranId, csrfToken, redirectFunction) => (dispatch) => { + // Reset any error messages currently being displayed + dispatch(setShowUnauthorizedVeteranMessage(false)); + dispatch(setErrorMessage('')); + postRequest('/api/v2/manifests/', csrfToken, { 'FILE-NUMBER': veteranId }). then( (resp) => { @@ -175,7 +180,13 @@ export const startManifestFetch = (veteranId, csrfToken, redirectFunction) => (d dispatch(setManifestId(manifestId)); redirectFunction(`/downloads/${manifestId}`); }, - (err) => dispatch(setErrorMessage(buildErrorMessageFromResponse(err.response))) + (err) => { + if (err.response.statusCode === 403 && err.response.body.featureToggles.checkUserSensitivity === true) { + dispatch(setShowUnauthorizedVeteranMessage(true)); + } else { + dispatch(setErrorMessage(buildErrorMessageFromResponse(err.response))); + } + } ); }; diff --git a/client/src/components/AlertBanner.jsx b/client/src/components/AlertBanner.jsx index 2bef06677..5fb804108 100644 --- a/client/src/components/AlertBanner.jsx +++ b/client/src/components/AlertBanner.jsx @@ -25,7 +25,7 @@ export default class AlertBanner extends React.PureComponent { break; } - return
+ return

{this.props.title}

{this.props.children}
diff --git a/client/src/components/Icons.jsx b/client/src/components/Icons.jsx index 1e05d23d6..39a959dcb 100644 --- a/client/src/components/Icons.jsx +++ b/client/src/components/Icons.jsx @@ -148,3 +148,61 @@ export class SuccessIcon extends React.PureComponent { ); } } + +export class ExternalLinkIcon extends React.PureComponent { + render() { + return ( + + + + + + + + + + + ); + } +} diff --git a/client/src/containers/WelcomeContainer.jsx b/client/src/containers/WelcomeContainer.jsx index 793ac0cf0..84085c322 100644 --- a/client/src/containers/WelcomeContainer.jsx +++ b/client/src/containers/WelcomeContainer.jsx @@ -10,8 +10,12 @@ import { clearErrorMessage, clearSearchInputText, setVeteranId, - setSearchInputText + setSearchInputText, + setShowUnauthorizedVeteranMessage } from '../actions'; +import { + ExternalLinkIcon +} from '../components/Icons'; import { startManifestFetch } from '../apiActions'; import AlertBanner from '../components/AlertBanner'; @@ -20,8 +24,14 @@ const searchBarNoteTextStyling = css({ textAlign: 'center' }); +const alertBannerStyling = { + marginTop: '0px', + marginBottom: '30px' +}; + class WelcomeContainer extends React.PureComponent { componentDidMount() { + this.props.setShowUnauthorizedVeteranMessage(false); this.props.clearErrorMessage(); this.props.clearSearchInputText(); } @@ -41,11 +51,33 @@ class WelcomeContainer extends React.PureComponent { render() { return { this.props.errorMessage.title && - +

{this.props.errorMessage.message}

} + { this.props.showUnauthorizedVeteranMessage && + +

+ Please try searching for another veteran or  + request access  +  to search for this veteran ID. +

+
+ } +

Welcome to eFolder Express

eFolder Express allows VA employees to bulk-download VBMS eFolders. @@ -86,6 +118,7 @@ Note: eFolder Express now includes Virtual VA documents from the Legacy Content const mapStateToProps = (state) => ({ csrfToken: state.csrfToken, errorMessage: state.errorMessage, + showUnauthorizedVeteranMessage: state.showUnauthorizedVeteranMessage, searchInputText: state.searchInputText }); @@ -94,7 +127,8 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ clearSearchInputText, setVeteranId, startManifestFetch, - setSearchInputText + setSearchInputText, + setShowUnauthorizedVeteranMessage }, dispatch); export default connect(mapStateToProps, mapDispatchToProps)(WelcomeContainer); diff --git a/client/src/reducer.js b/client/src/reducer.js index f93a21b1d..9651e0e72 100644 --- a/client/src/reducer.js +++ b/client/src/reducer.js @@ -16,6 +16,7 @@ export const initState = { errorMessage: { title: '', message: '' }, recentDownloads: [], searchInputText: '', + showUnauthorizedVeteranMessage: false, ...defaultManifestState }; @@ -58,6 +59,12 @@ export default function reducer(state = {}, action = {}) { return { ...state, documentsFetchStatus: action.payload }; + case Actions.SET_SHOW_UNAUTHORIZED_VETERAN_MESSAGE: + return { + ...state, + showUnauthorizedVeteranMessage: action.payload + }; + case Actions.SET_ERROR_MESSAGE: return { ...state, diff --git a/lib/fakes/bgs_service.rb b/lib/fakes/bgs_service.rb index 641415651..0ede4b9bc 100644 --- a/lib/fakes/bgs_service.rb +++ b/lib/fakes/bgs_service.rb @@ -120,6 +120,14 @@ def record_found?(veteran_info) ExternalApi::BGSService.new(client: true).record_found?(veteran_info) end + def sensitivity_level_for_user(user) + 0 + end + + def sensitivity_level_for_veteran(veteran_file_number) + 9 + end + # Methods to be stubbed out in tests: def veteran_info; end diff --git a/spec/models/manifest_spec.rb b/spec/models/manifest_spec.rb index aa115ef04..aa73aac28 100644 --- a/spec/models/manifest_spec.rb +++ b/spec/models/manifest_spec.rb @@ -1,38 +1,69 @@ describe Manifest do - context "#start!" do - before do - Timecop.freeze(Time.utc(2015, 12, 2, 17, 0, 0)) - allow(V2::DownloadManifestJob).to receive(:perform_later) - end - - let(:manifest) { Manifest.create(file_number: "1234") } + describe "#start!" do + let(:user) { User.create(css_id: "Foo", station_id: "112") } + let(:manifest) { Manifest.create(file_number: "1234", user: user) } subject { manifest.start! } - context "when never fetched" do - it "starts all jobs" do - expect(manifest.sources.size).to eq 0 - subject - expect(manifest.sources.size).to eq 2 - expect(V2::DownloadManifestJob).to have_received(:perform_later).twice + context "without sensitivity level check" do + before do + Timecop.freeze(Time.utc(2015, 12, 2, 17, 0, 0)) + allow(V2::DownloadManifestJob).to receive(:perform_later) + expect(FeatureToggle).to receive(:enabled?).with(:check_user_sensitivity).and_return(false) + expect(FeatureToggle).to receive(:enabled?).with(:skip_vva).and_return(false) end - end - context "when all manifests are current" do - it "does not start any jobs" do - manifest.sources.create(name: "VVA", status: :success, fetched_at: 2.hours.ago) - manifest.sources.create(name: "VBMS", status: :success, fetched_at: 2.hours.ago) - subject - expect(V2::DownloadManifestJob).to_not have_received(:perform_later) + context "when never fetched" do + it "starts all jobs" do + expect(manifest.sources.size).to eq 0 + subject + expect(manifest.sources.size).to eq 2 + expect(V2::DownloadManifestJob).to have_received(:perform_later).twice + end + end + + context "when all manifests are current" do + it "does not start any jobs" do + manifest.sources.create(name: "VVA", status: :success, fetched_at: 2.hours.ago) + manifest.sources.create(name: "VBMS", status: :success, fetched_at: 2.hours.ago) + subject + expect(V2::DownloadManifestJob).to_not have_received(:perform_later) + end + end + + context "when one manifest is expired" do + it "starts one job" do + manifest.sources.create(name: "VVA", status: :success, fetched_at: 2.hours.ago) + manifest.sources.create(name: "VBMS", status: :success, fetched_at: 5.hours.ago) + subject + expect(V2::DownloadManifestJob).to have_received(:perform_later).once + end end end - context "when one manifest is expired" do - it "starts one job" do - manifest.sources.create(name: "VVA", status: :success, fetched_at: 2.hours.ago) - manifest.sources.create(name: "VBMS", status: :success, fetched_at: 5.hours.ago) + context "with sensitivity level check" do + let(:mock_sensitivity_checker) { instance_double(SensitivityChecker) } + + before do + allow(SensitivityChecker).to receive(:new).and_return(mock_sensitivity_checker) + allow(FeatureToggle).to receive(:enabled?).with(:skip_vva).and_return(false) + expect(FeatureToggle).to receive(:enabled?).with(:check_user_sensitivity).and_return(true) + end + + it "enqueues a job if the sensitivity check passes" do + expect(mock_sensitivity_checker).to receive(:sensitivity_levels_compatible?) + .with(user: user, veteran_file_number: "1234").and_return(true) + expect(V2::DownloadManifestJob).to receive(:perform_later).twice + subject - expect(V2::DownloadManifestJob).to have_received(:perform_later).once + end + + it "raises an exception if the sensitivity check fails" do + expect(mock_sensitivity_checker).to receive(:sensitivity_levels_compatible?) + .with(user: user, veteran_file_number: "1234").and_return(false) + expect(V2::DownloadManifestJob).to_not receive(:perform_later) + + expect { subject }.to raise_error(BGS::SensitivityLevelCheckFailure) end end end diff --git a/spec/services/bgs_service_spec.rb b/spec/services/bgs_service_spec.rb index 3d9b09a61..cef54e327 100644 --- a/spec/services/bgs_service_spec.rb +++ b/spec/services/bgs_service_spec.rb @@ -121,6 +121,45 @@ .with(file_number) { bgs_benefit_claims_response } end + describe "#sensitivity_level_for_user" do + let(:user) { User.create(css_id: "Foo", station_id: "112", participant_id: "abc123") } + + it "validates the user param" do + expect { bgs_service.sensitivity_level_for_user(nil) }.to raise_error(RuntimeError, "Invalid user") + end + + it "calls the security service and caches the result" do + sensitivity_level = Random.new.rand(1..9) + + expect(bgs_client).to receive(:security).and_return(bgs_security_service) + expect(bgs_security_service).to receive(:find_person_scrty_log_by_ptcpnt_id) + .with(user.participant_id).and_return({ scrty_level_type_cd: sensitivity_level.to_s }) + + expect(bgs_service.sensitivity_level_for_user(user)).to eq(sensitivity_level) + + expect(Rails.cache.exist?("sensitivity_level_for_user_id_#{user.id}")).to be true + end + end + + describe "#sensitivity_level_for_veteran" do + it "validates the veteran_file_number param" do + expect(bgs_veteran_service).to receive(:find_by_file_number).with(nil).and_return(nil) + expect { bgs_service.sensitivity_level_for_veteran(nil) }.to raise_error(RuntimeError, "Invalid veteran") + end + + it "calls the security service and caches the result" do + sensitivity_level = Random.new.rand(1..9) + + expect(bgs_client).to receive(:security).and_return(bgs_security_service) + expect(bgs_security_service).to receive(:find_sensitivity_level_by_participant_id) + .with(participant_id).and_return({ scrty_level_type_cd: sensitivity_level.to_s }) + + expect(bgs_service.sensitivity_level_for_veteran(file_number)).to eq(sensitivity_level) + + expect(Rails.cache.exist?("sensitivity_level_for_veteran_participant_id_#{participant_id}")).to be true + end + end + context "#parse_veteran_info" do before do @veteran_data = { diff --git a/spec/services/external_api/vbms_service_spec.rb b/spec/services/external_api/vbms_service_spec.rb index 57c356575..99a009a00 100644 --- a/spec/services/external_api/vbms_service_spec.rb +++ b/spec/services/external_api/vbms_service_spec.rb @@ -3,13 +3,59 @@ describe ExternalApi::VBMSService do subject(:described) { described_class } + let(:mock_sensitivity_checker) { instance_double(SensitivityChecker, sensitivity_levels_compatible?: true) } + + before do + allow(SensitivityChecker).to receive(:new).and_return(mock_sensitivity_checker) + end + + describe ".verify_user_veteran_access" do + context "with check_user_sensitivity feature flag enabled" do + before { FeatureToggle.enable!(:check_user_sensitivity) } + after { FeatureToggle.disable!(:check_user_sensitivity) } + + let!(:user) do + user = User.create(css_id: "VSO", station_id: "283", participant_id: "1234") + RequestStore.store[:current_user] = user + end + + it "checks the user's sensitivity" do + expect(mock_sensitivity_checker).to receive(:sensitivity_levels_compatible?) + .with(user: user, veteran_file_number: "123456789").and_return(true) + + described.verify_user_veteran_access("123456789") + end + + it "raises an exception when the sensitivity level is not compatible" do + expect(mock_sensitivity_checker).to receive(:sensitivity_levels_compatible?) + .with(user: user, veteran_file_number: "123456789").and_return(false) + + expect { described.verify_user_veteran_access("123456789") } + .to raise_error(RuntimeError, "User does not have permission to access this information") + end + end + + context "with check_user_sensitivity feature flag disabled" do + before { FeatureToggle.disable!(:check_user_sensitivity) } + + it "does not check the user's sensitivity" do + expect(mock_sensitivity_checker).not_to receive(:sensitivity_levels_compatible?) + + described.verify_user_veteran_access("123456789") + end + end + end + describe ".v2_fetch_documents_for" do let(:mock_json_adapter) { instance_double(JsonApiResponseAdapter) } before do allow(JsonApiResponseAdapter).to receive(:new).and_return(mock_json_adapter) + FeatureToggle.enable!(:check_user_sensitivity) end + after { FeatureToggle.disable!(:check_user_sensitivity) } + context "with use_ce_api feature toggle enabled" do before { FeatureToggle.enable!(:use_ce_api) } after { FeatureToggle.disable!(:use_ce_api) } @@ -33,6 +79,7 @@ it "calls the PagedDocuments SOAP API endpoint" do veteran_id = "123456789" + expect(FeatureToggle).to receive(:enabled?).with(:check_user_sensitivity).and_return(false) expect(FeatureToggle).to receive(:enabled?).with(:use_ce_api).and_return(false) expect(FeatureToggle).to receive(:enabled?).with(:vbms_pagination, user: user).and_return(true) expect(described_class).to receive(:vbms_client) @@ -53,6 +100,7 @@ it "calls the FindDocumentVersionReference SOAP API endpoint" do veteran_id = "123456789" + expect(FeatureToggle).to receive(:enabled?).with(:check_user_sensitivity).and_return(false) expect(FeatureToggle).to receive(:enabled?).with(:use_ce_api).and_return(false) expect(FeatureToggle).to receive(:enabled?).with(:vbms_pagination, user: user).and_return(false) expect(VBMS::Requests::FindDocumentVersionReference).to receive(:new) @@ -70,8 +118,11 @@ before do allow(JsonApiResponseAdapter).to receive(:new).and_return(mock_json_adapter) + FeatureToggle.enable!(:check_user_sensitivity) end + after { FeatureToggle.disable!(:check_user_sensitivity) } + context "with use_ce_api feature toggle enabled" do before { FeatureToggle.enable!(:use_ce_api) } after { FeatureToggle.disable!(:use_ce_api) } @@ -94,8 +145,17 @@ describe ".v2_fetch_document_file" do context "with use_ce_api feature toggle enabled" do - before { FeatureToggle.enable!(:use_ce_api) } - after { FeatureToggle.disable!(:use_ce_api) } + before do + FeatureToggle.enable!(:use_ce_api) + FeatureToggle.disable!(:check_user_sensitivity) + end + + after do + FeatureToggle.disable!(:use_ce_api) + end + + let(:manifest) { Manifest.create(file_number: "1234") } + let(:source) { ManifestSource.create(name: %w[VBMS VVA].sample, manifest: manifest) } let(:fake_record) do Record.create( @@ -103,7 +163,8 @@ series_id: "{4444-4444}", received_at: Time.utc(2015, 9, 6, 1, 0, 0), type_id: "825", - mime_type: "application/pdf" + mime_type: "application/pdf", + manifest_source: source ) end diff --git a/spec/services/sensitivity_checker_spec.rb b/spec/services/sensitivity_checker_spec.rb new file mode 100644 index 000000000..d4670ea36 --- /dev/null +++ b/spec/services/sensitivity_checker_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +describe SensitivityChecker do + subject(:described) { described_class.new } + + let(:user) { User.create(css_id: "Foo", station_id: "112") } + let(:mock_sensitivity_checker) { instance_double(BGSService) } + + before do + allow(BGSService).to receive(:new).and_return(mock_sensitivity_checker) + end + + describe "#sensitivity_levels_compatible?" do + context "when the sensitivity levels are compatible" do + it "returns true" do + expect(mock_sensitivity_checker).to receive(:sensitivity_level_for_user) + .with(user).and_return(Random.new.rand(4..9)) + expect(mock_sensitivity_checker).to receive(:sensitivity_level_for_veteran) + .with("1234").and_return(Random.new.rand(1..4)) + + expect(described.sensitivity_levels_compatible?(user: user, veteran_file_number: "1234")).to eq true + end + end + + context "when the sensitivity levels are NOT compatible" do + it "returns false" do + expect(mock_sensitivity_checker).to receive(:sensitivity_level_for_user) + .with(user).and_return(Random.new.rand(1..4)) + expect(mock_sensitivity_checker).to receive(:sensitivity_level_for_veteran) + .with("1234").and_return(Random.new.rand(4..9)) + + expect(described.sensitivity_levels_compatible?(user: user, veteran_file_number: "1234")).to eq false + end + end + + context "when the BGS call raises an exception" do + it "returns false" do + error = StandardError.new + + expect(mock_sensitivity_checker).to receive(:sensitivity_level_for_user) + .with(user).and_raise(error) + expect(ExceptionLogger).to receive(:capture).with(error) + + expect(described.sensitivity_levels_compatible?(user: user, veteran_file_number: "1234")).to eq false + end + end + end +end