-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DPC-4390] Log organization credential status via Sidekiq (#2327)
## 🎫 Ticket [DPC-4390](https://jira.cms.gov/browse/DPC-4390) ## 🛠 Changes - added new sidekiq job to call api and get status of organization's api access - log credential status for ea organization - log aggregate stats of total organizations with/without api access <!-- What was added, updated, or removed in this PR? --> ## ℹ️ Context - this ticket was created in order to enable [DPC-4381](https://jira.cms.gov/browse/DPC-4381) <!-- Why were these changes made? Add background context suitable for a non-technical audience. --> <!-- If any of the following security implications apply, this PR must not be merged without Stephen Walter's approval. Explain in this section and add @SJWalter11 as a reviewer. - Adds a new software dependency or dependencies. - Modifies or invalidates one or more of our security controls. - Stores or transmits data that was not stored or transmitted before. - Requires additional review of security implications for other reasons. --> ## 🧪 Validation <!-- How were the changes verified? Did you fully test the acceptance criteria in the ticket? Provide reproducible testing instructions and screenshots if applicable. --> - see dpc-portal/spec/jobs/log_organizations_access_spec.rb
- Loading branch information
1 parent
ca9ebf9
commit ba65696
Showing
3 changed files
with
148 additions
and
0 deletions.
There are no files selected for viewing
53 changes: 53 additions & 0 deletions
53
dpc-portal/app/jobs/log_organizations_api_credential_status_job.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# frozen_string_literal: true | ||
|
||
# A background job that determines the number of active Provider Organizations that | ||
# have access to make API calls. | ||
# This is determined by checking for tokens, public keys, and ip addresses for all | ||
# organizations that are in the portal with completed Terms of Service agreement. | ||
class LogOrganizationsApiCredentialStatusJob < ApplicationJob | ||
queue_as :portal | ||
|
||
def perform | ||
@start = Time.now | ||
organizations_credential_aggregate_status = { | ||
have_active_credentials: 0, | ||
have_incomplete_or_no_credentials: 0 | ||
} | ||
ProviderOrganization.where.not(terms_of_service_accepted_by: nil).find_each do |organization| | ||
credential_status = fetch_credential_status(organization.dpc_api_organization_id) | ||
Rails.logger.info(['Credential status for organization', | ||
{ name: organization.name, | ||
dpc_api_org_id: organization.dpc_api_organization_id, | ||
credential_status: }]) | ||
update_organization_aggregate_hash(organizations_credential_aggregate_status, credential_status) | ||
end | ||
Rails.logger.info(['Organizations API credential status', organizations_credential_aggregate_status]) | ||
end | ||
|
||
def update_organization_aggregate_hash(aggregate_stats, credential_status) | ||
if credential_status[:num_tokens].zero? || credential_status[:num_keys].zero? || credential_status[:num_ips].zero? | ||
aggregate_stats[:have_incomplete_or_no_credentials] += 1 | ||
else | ||
aggregate_stats[:have_active_credentials] += 1 | ||
end | ||
aggregate_stats | ||
end | ||
|
||
def fetch_credential_status(organization_id) | ||
tokens = dpc_client.get_client_tokens(organization_id) | ||
current_datetime = DateTime.now | ||
active_tokens = tokens['entities'].select { |tok| tok['expiresAt'] > current_datetime } | ||
pub_keys = dpc_client.get_public_keys(organization_id) | ||
ip_addresses = dpc_client.get_ip_addresses(organization_id) | ||
|
||
{ | ||
num_tokens: active_tokens.length, | ||
num_keys: pub_keys['count'], | ||
num_ips: ip_addresses['count'] | ||
} | ||
end | ||
|
||
def dpc_client | ||
@dpc_client ||= DpcClient.new | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
dpc-portal/spec/jobs/log_organizations_api_credential_status_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
|
||
RSpec.describe LogOrganizationsApiCredentialStatusJob, type: :job do | ||
include ActiveJob::TestHelper | ||
|
||
let(:mock_dpc_client) { instance_double(DpcClient) } | ||
before do | ||
allow(DpcClient).to receive(:new).and_return(mock_dpc_client) | ||
end | ||
|
||
let(:user) do | ||
create(:user, provider: :openid_connect, uid: '12345', | ||
verification_status: 'rejected', verification_reason: 'ao_med_sanctions') | ||
end | ||
let(:provider_organization) do | ||
create( | ||
:provider_organization, | ||
name: 'Test', | ||
dpc_api_organization_id: 'foo', | ||
terms_of_service_accepted_by: user, | ||
terms_of_service_accepted_at: 1.day.ago | ||
) | ||
end | ||
let(:mock_no_tokens_response) do | ||
{ | ||
'entities' => [], | ||
'count' => 0, | ||
'created_at' => '2024-11-19T16:52:49.760+00:00' | ||
} | ||
end | ||
let(:mock_one_token_response) do | ||
{ | ||
'entities' => [ | ||
{ | ||
'id' => 'f5b5559d-17b1-4951-a704-2f74b9b8587f', | ||
'tokenType' => 'MACAROON', | ||
'label' => 'Token for organization 46ac7ad6-7487-4dd0-baa0-6e2c8cae76a0.', | ||
'createdAt' => '2024-08-14T16:51:10.702+00:00', | ||
'expiresAt' => '2025-08-14T16:51:10.687+00:00' | ||
} | ||
], | ||
'count' => 1, | ||
'created_at' => '2024-11-19T16:52:49.760+00:00' | ||
} | ||
end | ||
|
||
describe 'perform' do | ||
it 'organization has no api credentials' do | ||
provider_organization.save! | ||
|
||
expect(mock_dpc_client).to receive(:get_client_tokens).and_return(mock_no_tokens_response).once | ||
expect(mock_dpc_client).to receive(:get_public_keys).and_return({ 'count' => 0 }).once | ||
expect(mock_dpc_client).to receive(:get_ip_addresses).and_return({ 'count' => 0 }).once | ||
allow(Rails.logger).to receive(:info) | ||
expect(Rails.logger).to receive(:info).with(['Organizations API credential status', | ||
{ have_active_credentials: 0, | ||
have_incomplete_or_no_credentials: 1 }]) | ||
|
||
described_class.perform_now | ||
end | ||
end | ||
it 'updates log with 1 organization that has all 3 credentials' do | ||
provider_organization.save! | ||
|
||
expect(mock_dpc_client).to receive(:get_client_tokens).and_return(mock_one_token_response).once | ||
expect(mock_dpc_client).to receive(:get_public_keys).and_return({ 'count' => 2 }).once | ||
expect(mock_dpc_client).to receive(:get_ip_addresses).and_return({ 'count' => 3 }).once | ||
allow(Rails.logger).to receive(:info) | ||
expect(Rails.logger).to receive(:info).with(['Organizations API credential status', | ||
{ have_active_credentials: 1, | ||
have_incomplete_or_no_credentials: 0 }]) | ||
|
||
described_class.perform_now | ||
end | ||
it 'updates log with 1 organization that has partial credentials' do | ||
provider_organization.save! | ||
|
||
expect(mock_dpc_client).to receive(:get_client_tokens).and_return(mock_one_token_response).once | ||
expect(mock_dpc_client).to receive(:get_public_keys).and_return({ 'count' => 2 }).once | ||
expect(mock_dpc_client).to receive(:get_ip_addresses).and_return({ 'count' => 0 }).once | ||
allow(Rails.logger).to receive(:info) | ||
expect(Rails.logger).to receive(:info).with(['Organizations API credential status', | ||
{ have_active_credentials: 0, | ||
have_incomplete_or_no_credentials: 1 }]) | ||
|
||
described_class.perform_now | ||
end | ||
end |