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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ gem 'aws-sdk-ses', '~> 1.6'
gem 'aws-sdk-eventbridge'
gem 'base32-crockford'
gem 'delayed_job_active_record', '~> 4.1'
gem 'blueprinter', '~> 0.25.3'
gem 'device_detector'
gem 'devise', '~> 4.8'
gem 'dotiw', '>= 4.0.1'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ GEM
bindata (2.4.9)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
blueprinter (0.25.3)
bootsnap (1.5.1)
msgpack (~> 1.0)
brakeman (4.10.0)
Expand Down Expand Up @@ -723,6 +724,7 @@ DEPENDENCIES
base32-crockford
better_errors (>= 2.5.1)
binding_of_caller
blueprinter (~> 0.25.3)
bootsnap (~> 1.5.0)
brakeman
bullet (>= 6.0.2)
Expand Down
7 changes: 7 additions & 0 deletions app/blueprints/agreements/agency_blueprint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Agreements
class AgencyBlueprint < Blueprinter::Base
identifier :abbreviation

field :name
end
end
11 changes: 11 additions & 0 deletions app/blueprints/agreements/partner_account_blueprint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Agreements
class PartnerAccountBlueprint < Blueprinter::Base
identifier :requesting_agency

field :name
field :became_partner, datetime_format: '%Y-%m-%d'
field :status do |account, _options|
account.partner_status
end
end
end
2 changes: 2 additions & 0 deletions app/models/agency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ class Agency < ApplicationRecord
has_many :agency_identities, dependent: :destroy
# rubocop:disable Rails/HasManyOrHasOneDependent
has_many :service_providers, inverse_of: :agency
has_many :partner_accounts, class_name: 'Agreements::PartnerAccount'
# rubocop:enable Rails/HasManyOrHasOneDependent

validates :name, presence: true
validates :abbreviation, uniqueness: { case_sensitive: false }
end
4 changes: 4 additions & 0 deletions app/models/agreements/partner_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ class Agreements::PartnerAccount < ApplicationRecord

validates :name, presence: true, uniqueness: true
validates :requesting_agency, presence: true, uniqueness: true

def partner_status
partner_account_status.partner_name
end
end
4 changes: 4 additions & 0 deletions app/models/agreements/partner_account_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ class Agreements::PartnerAccountStatus < ApplicationRecord
validates :order, presence: true,
uniqueness: true,
numericality: { only_integer: true }

def partner_name
super || name
end
end
11 changes: 11 additions & 0 deletions app/services/agreements/db/accounts_by_agency.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Agreements
module Db
class AccountsByAgency
def self.call
PartnerAccount.
includes(:agency, :partner_account_status).
group_by { |pa| pa.agency }
end
end
end
end
21 changes: 21 additions & 0 deletions app/services/agreements/reports/agencies_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Agreements
module Reports
class AgenciesReport < BaseReport
def initialize(agencies:)
@agencies = agencies
end

def run
save_report(
'agencies',
AgencyBlueprint.render(agencies, root: :agencies),
'',
)
end

private

attr_reader :agencies
end
end
end
22 changes: 22 additions & 0 deletions app/services/agreements/reports/agency_partner_accounts_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Agreements
module Reports
class AgencyPartnerAccountsReport < BaseReport
def initialize(agency:, partner_accounts:)
@agency = agency.downcase
@partner_accounts = partner_accounts.sort_by(&:requesting_agency)
end

def run
save_report(
'partner_accounts',
PartnerAccountBlueprint.render(partner_accounts, root: :partner_accounts),
"agencies/#{agency}/",
)
end

private

attr_reader :agency, :partner_accounts
end
end
end
23 changes: 23 additions & 0 deletions app/services/agreements/reports/base_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Agreements
module Reports
class BaseReport < ::Reports::BaseReport
def gen_s3_bucket_name
prefix = IdentityConfig.store.partner_api_bucket_prefix
"#{prefix}.#{ec2_data.account_id}-#{ec2_data.region}"
end

def save_report(report_name, body, path = nil)
if !IdentityConfig.store.s3_reports_enabled
logger.info('Not uploading report to S3, s3_reports_enabled is false')
return body
end
upload_file_to_s3(report_name, body, path)
Comment thread
orenyk marked this conversation as resolved.
Outdated
end

def upload_file_to_s3(report_name, body, path)
s3_path = "#{path}#{report_name}"
upload_file_to_s3_bucket(path: s3_path, body: body, content_type: 'application/json')
end
end
end
end
38 changes: 38 additions & 0 deletions app/services/agreements/reports/partner_api_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Agreements
module Reports
class PartnerApiReport
def run
return unless IdentityConfig.store.enable_partner_api

collect_account_data
upload_json_files
true
end

private

attr_reader :accounts_by_agency, :agencies

def collect_account_data
@accounts_by_agency = Agreements::Db::AccountsByAgency.call
@agencies = accounts_by_agency.keys
@accounts_by_agency = accounts_by_agency.transform_keys(&:abbreviation)
end

def upload_json_files
upload_agencies
upload_accounts
end

def upload_agencies
AgenciesReport.new(agencies: agencies).run
end

def upload_accounts
accounts_by_agency.each do |agency_abbr, accounts|
AgencyPartnerAccountsReport.new(agency: agency_abbr, partner_accounts: accounts).run
end
end
end
end
end
2 changes: 2 additions & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ doc_capture_request_valid_for_minutes: '15'
email_from: no-reply@login.gov
email_from_display_name: Login.gov
enable_load_testing_mode: 'false'
enable_partner_api: 'false'
enable_rate_limiting: 'true'
enable_test_routes: 'true'
enable_usps_verification: 'true'
Expand Down Expand Up @@ -122,6 +123,7 @@ otps_per_ip_period: '300'
otps_per_ip_track_only_mode: 'true'
outbound_connection_check_url: 'https://checkip.amazonaws.com'
participate_in_dap: 'false'
partner_api_bucket_prefix: ''
password_max_attempts: '3'
personal_key_retired: 'true'
phone_format_e164_opt_out_list: '[]'
Expand Down
3 changes: 3 additions & 0 deletions config/initializers/blueprinter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Blueprinter.configure do |config|
config.sort_fields_by = :definition
end
8 changes: 8 additions & 0 deletions config/initializers/job_configurations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,11 @@
timeout: 300,
callback: -> { Reports::MonthlyGpoLetterRequestsReport.new.call },
)

# Send Partner API reports to S3
JobRunner::Runner.add_config JobRunner::JobConfiguration.new(
name: 'Partner API report',
interval: 24 * 60 * 60, # 24 hours
timeout: 300,
callback: -> { Agreements::Reports::PartnerApiReport.new.call },
)
2 changes: 2 additions & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def self.build_store(config_map)
config.add(:email_from, type: :string)
config.add(:email_from_display_name, type: :string)
config.add(:enable_load_testing_mode, type: :boolean)
config.add(:enable_partner_api, type: :boolean)
config.add(:enable_rate_limiting, type: :boolean)
config.add(:enable_test_routes, type: :boolean)
config.add(:enable_usps_verification, type: :boolean)
Expand Down Expand Up @@ -182,6 +183,7 @@ def self.build_store(config_map)
config.add(:otps_per_ip_track_only_mode, type: :boolean)
config.add(:outbound_connection_check_url)
config.add(:participate_in_dap, type: :boolean)
config.add(:partner_api_bucket_prefix, type: :string)
config.add(:password_max_attempts, type: :integer)
config.add(:password_pepper, type: :string)
config.add(:personal_key_retired, type: :boolean)
Expand Down
21 changes: 21 additions & 0 deletions spec/blueprints/agreements/agency_blueprint_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'rails_helper'

RSpec.describe Agreements::AgencyBlueprint do
let(:agency) do
create(:agency, abbreviation: 'ABC', name: 'Awesome Bureau of Comedy')
end
let(:expected) do
{
agencies: [
{
abbreviation: 'ABC',
name: 'Awesome Bureau of Comedy',
},
],
}.to_json
end

it 'renders the appropriate json' do
expect(described_class.render([agency], root: :agencies)).to eq(expected)
end
end
36 changes: 36 additions & 0 deletions spec/blueprints/agreements/partner_account_blueprint_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'rails_helper'

RSpec.describe Agreements::PartnerAccountBlueprint do
let(:status) do
create(
:partner_account_status,
name: 'secret',
partner_name: 'public',
)
end
let(:account) do
create(
:partner_account,
partner_account_status: status,
requesting_agency: 'ABC-DEF',
name: 'Department of Energy Fusion',
became_partner: '2021-01-01',
)
end
let(:expected) do
{
partner_accounts: [
{
requesting_agency: 'ABC-DEF',
name: 'Department of Energy Fusion',
became_partner: '2021-01-01',
status: 'public',
},
],
}.to_json
end

it 'renders the appropriate json' do
expect(described_class.render([account], root: :partner_accounts)).to eq(expected)
end
end
4 changes: 3 additions & 1 deletion spec/models/agency_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

describe Agency do
describe 'Associations' do
it { is_expected.to have_many(:agency_identities) }
it { is_expected.to have_many(:agency_identities).dependent(:destroy) }
it { is_expected.to have_many(:service_providers).inverse_of(:agency) }
it { is_expected.to have_many(:partner_accounts).class_name('Agreements::PartnerAccount') }
end
describe 'validations' do
let(:agency) { build_stubbed(:agency) }
Expand Down
8 changes: 8 additions & 0 deletions spec/models/agreements/partner_account_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@
it { is_expected.to have_many(:iaa_orders).through(:iaa_gtcs) }
it { is_expected.to have_many(:integrations) }
end

describe '#partner_status' do
it 'returns the partner_name of the associated partner_account_status' do
status = build(:partner_account_status, partner_name: 'foo')
account = build(:partner_account, partner_account_status: status)
expect(account.partner_status).to eq('foo')
end
end
end
12 changes: 12 additions & 0 deletions spec/models/agreements/partner_account_status_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,16 @@

it { is_expected.to have_many(:partner_accounts) }
end

describe '#partner_name' do
it 'returns the partner_name if set' do
status = build(:partner_account_status, name: 'foo', partner_name: 'bar')
expect(status.partner_name).to eq('bar')
end

it 'returns the name if no partner_name is set' do
status = build(:partner_account_status, name: 'foo', partner_name: nil)
expect(status.partner_name).to eq('foo')
end
end
end
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
config.include Features::MailerHelper, type: :feature
config.include Features::SessionHelper, type: :feature
config.include Features::StripTagsHelper, type: :feature
config.include AgreementsHelper
config.include AnalyticsHelper
config.include AwsKmsClientHelper
config.include KeyRotationHelper
Expand Down
21 changes: 21 additions & 0 deletions spec/services/agreements/db/accounts_by_agency_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'rails_helper'

RSpec.describe Agreements::Db::AccountsByAgency do
let(:agency1) { create(:agency) }
let(:agency2) { create(:agency) }

before { clear_agreements_data }

describe '.call' do
it 'returns all partner accounts grouped by agency' do
account1, account2 = create_pair(:partner_account, agency: agency1)
account3 = create(:partner_account, agency: agency2)
expected = {
agency1 => [account1, account2],
agency2 => [account3],
}

expect(described_class.call).to eq(expected)
end
end
end
10 changes: 10 additions & 0 deletions spec/services/agreements/reports/agencies_report_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'rails_helper'

RSpec.describe Agreements::Reports::AgenciesReport do
it 'uploads the JSON serialization of the passed agencies' do
agency = build(:agency)
expected = Agreements::AgencyBlueprint.render([agency], root: :agencies)

expect(described_class.new(agencies: [agency]).run).to eq(expected)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require 'rails_helper'

RSpec.describe Agreements::Reports::AgencyPartnerAccountsReport do
let(:agency) { create(:agency) }
let(:partner_accounts) do
build_pair(:partner_account, agency: agency).sort_by(&:requesting_agency)
end

it 'uploads the JSON serialization of the passed partner accounts' do
expected = Agreements::PartnerAccountBlueprint.render(partner_accounts, root: :partner_accounts)
report_object = described_class.new(
agency: agency.abbreviation,
partner_accounts: partner_accounts,
)
expect(report_object.run).to eq(expected)
end
end
Loading