diff --git a/app/controllers/api/irs_attempts_api_controller.rb b/app/controllers/api/irs_attempts_api_controller.rb deleted file mode 100644 index 4d523187e15..00000000000 --- a/app/controllers/api/irs_attempts_api_controller.rb +++ /dev/null @@ -1,214 +0,0 @@ -## -# This controller implements the Poll-based delivery method for Security Event -# Tokens as described RFC 8936 -# -# ref: https://datatracker.ietf.org/doc/html/rfc8936 -# -module Api - class IrsAttemptsApiController < ApplicationController - include RenderConditionConcern - include ActionController::Live - - check_or_render_not_found -> { IdentityConfig.store.irs_attempt_api_enabled } - - skip_before_action :verify_authenticity_token - before_action :authenticate_client - prepend_before_action :skip_session_load - prepend_before_action :skip_session_expiration - - respond_to :json - - def create - start_time = Time.zone.now.to_f - if timestamp - if s3_helper.attempts_serve_events_from_s3 - if IrsAttemptApiLogFile.find_by(requested_time: timestamp_key(key: timestamp)) - log_file_record = IrsAttemptApiLogFile.find_by( - requested_time: timestamp_key(key: timestamp), - ) - headers['X-Payload-Key'] = log_file_record.encrypted_key - headers['X-Payload-IV'] = log_file_record.iv - - serve_s3_response(log_file_record: log_file_record) - - else - render json: { status: :not_found, description: 'File not found for Timestamp' }, - status: :not_found - end - else - result = encrypted_security_event_log_result - - headers['X-Payload-Key'] = Base64.strict_encode64(result.encrypted_key) - headers['X-Payload-IV'] = Base64.strict_encode64(result.iv) - - send_data result.encrypted_data, - disposition: "filename=#{result.filename}" - end - else - render json: { status: :unprocessable_entity, description: 'Invalid timestamp parameter' }, - status: :unprocessable_entity - end - analytics.irs_attempts_api_events( - **analytics_properties( - authenticated: true, - elapsed_time: elapsed_time(start_time), - ), - ) - end - - private - - def buffer_range(current_buffer_index:, buffer_size:, file_size:) - buffer_end = [current_buffer_index + buffer_size, file_size].min - "bytes=#{current_buffer_index}-#{buffer_end}" - end - - def serve_s3_response(log_file_record:) - if IdentityConfig.store.irs_attempt_api_aws_s3_stream_enabled - response = s3_helper.s3_client.head_object( - bucket: s3_helper.attempts_bucket_name, - key: log_file_record.filename, - ) - - requested_data_size = response.content_length - - buffer_index = 0 - buffer_size = IdentityConfig.store.irs_attempt_api_aws_s3_stream_buffer_size - - send_stream( - type: response.content_type, - filename: log_file_record.filename, - ) do |stream| - while buffer_index < requested_data_size - requested_data = s3_helper.s3_client.get_object( - bucket: s3_helper.attempts_bucket_name, - key: log_file_record.filename, - range: buffer_range( - current_buffer_index: buffer_index, - buffer_size: buffer_size, - file_size: requested_data_size, - ), - ) - buffer_index += buffer_size + 1 - stream.write(requested_data.body.read) - end - end - else - requested_data = s3_helper.s3_client.get_object( - bucket: s3_helper.attempts_bucket_name, - key: log_file_record.filename, - ) - - send_data requested_data.body.read, - disposition: "filename=#{log_file_record.filename}" - end - end - - def authenticate_client - bearer, csp_id, token = request.authorization&.split(' ', 3) - if bearer != 'Bearer' || !valid_auth_token?(token) || - csp_id != IdentityConfig.store.irs_attempt_api_csp_id - analytics.irs_attempts_api_events( - **analytics_properties( - authenticated: false, - elapsed_time: 0, - ), - ) - render json: { status: 401, description: 'Unauthorized' }, status: :unauthorized - end - end - - def valid_auth_token?(token) - valid_auth_data = hashed_valid_auth_data - cost = valid_auth_data[:cost] - salt = valid_auth_data[:salt] - hashed_token = scrypt_digest(token: token, salt: salt, cost: cost) - - valid_auth_data[:digested_tokens].any? do |valid_hashed_token| - ActiveSupport::SecurityUtils.secure_compare( - valid_hashed_token, - hashed_token, - ) - end - end - - def scrypt_digest(token:, salt:, cost:) - scrypt_salt = cost + OpenSSL::Digest::SHA256.hexdigest(salt) - scrypted = SCrypt::Engine.hash_secret token, scrypt_salt, 32 - SCrypt::Password.new(scrypted).digest - end - - # @return [Array] JWE strings - def security_event_tokens - return [] unless timestamp - - events = redis_client.read_events(timestamp: timestamp) - events.values - end - - def encrypted_security_event_log_result - IrsAttemptsApi::EnvelopeEncryptor.encrypt( - data: security_event_tokens.join("\r\n"), - timestamp: timestamp, - public_key_str: IdentityConfig.store.irs_attempt_api_public_key, - ) - end - - def timestamp_key(key:) - IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(key) - end - - def redis_client - @redis_client ||= IrsAttemptsApi::RedisClient.new - end - - def s3_helper - @s3_helper ||= JobHelpers::S3Helper.new - end - - def hashed_valid_auth_data - key = IdentityConfig.store.irs_attempt_api_auth_tokens.map do |token| - OpenSSL::Digest::SHA256.hexdigest(token) - end.join(',') - - Rails.cache.fetch("irs_hashed_tokens:#{key}", expires_in: 48.hours) do - salt = SecureRandom.hex(32) - cost = IdentityConfig.store.scrypt_cost - digested_tokens = IdentityConfig.store.irs_attempt_api_auth_tokens.map do |token| - scrypt_digest(token: token, salt: salt, cost: cost) - end - - { - salt: salt, - cost: cost, - digested_tokens: digested_tokens, - } - end - end - - def analytics_properties(authenticated:, elapsed_time:) - { - rendered_event_count: security_event_tokens.count, - timestamp: timestamp&.iso8601, - elapsed_time: elapsed_time, - authenticated: authenticated, - success: authenticated && timestamp.present?, - } - end - - def timestamp - timestamp_param = params.permit(:timestamp)[:timestamp] - return nil if timestamp_param.nil? - - date_fmt = timestamp_param.include?('.') ? '%Y-%m-%dT%H:%M:%S.%N%z' : '%Y-%m-%dT%H:%M:%S%z' - - Time.strptime(timestamp_param, date_fmt) - rescue ArgumentError - nil - end - - def elapsed_time(start_time) - Time.zone.now.to_f - start_time - end - end -end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eb05017a38a..2be9690400b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -63,7 +63,6 @@ def analytics sp: current_sp&.issuer, session: session, ahoy: ahoy, - irs_session_id: irs_attempts_api_session_id, ) end @@ -72,24 +71,7 @@ def analytics_user end def irs_attempts_api_tracker - @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new( - session_id: irs_attempts_api_session_id, - request: request, - user: effective_user, - sp: current_sp, - cookie_device_uuid: cookies[:device], - sp_request_uri: decorated_session.request_url_params[:redirect_uri], - enabled_for_session: irs_attempts_api_enabled_for_session?, - analytics: analytics, - ) - end - - def irs_attempts_api_enabled_for_session? - current_sp&.irs_attempts_api_enabled? - end - - def irs_attempts_api_session_id - decorated_session.irs_attempts_api_session_id + @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new end def user_event_creator diff --git a/app/controllers/idv/image_uploads_controller.rb b/app/controllers/idv/image_uploads_controller.rb index af10ac2e358..353743ac1ee 100644 --- a/app/controllers/idv/image_uploads_controller.rb +++ b/app/controllers/idv/image_uploads_controller.rb @@ -29,8 +29,7 @@ def image_upload_form end def store_encrypted_images? - IdentityConfig.store.encrypted_document_storage_enabled && - irs_attempts_api_enabled_for_session? + IdentityConfig.store.encrypted_document_storage_enabled end end end diff --git a/app/decorators/service_provider_session_decorator.rb b/app/decorators/service_provider_session_decorator.rb index d6aeca42be5..2ac15d8b033 100644 --- a/app/decorators/service_provider_session_decorator.rb +++ b/app/decorators/service_provider_session_decorator.rb @@ -108,11 +108,6 @@ def url_options end end - def irs_attempts_api_session_id - @irs_attempts_api_session_id ||= - request_url_params['irs_attempts_api_session_id'] || request_url_params['tid'] - end - def request_url_params @request_url_params ||= begin if request_url.present? diff --git a/app/decorators/session_decorator.rb b/app/decorators/session_decorator.rb index a08123fde07..9e773a68078 100644 --- a/app/decorators/session_decorator.rb +++ b/app/decorators/session_decorator.rb @@ -41,8 +41,6 @@ def requested_more_recent_verification? false end - def irs_attempts_api_session_id; end - def request_url_params {} end diff --git a/app/jobs/irs_attempts_events_batch_job.rb b/app/jobs/irs_attempts_events_batch_job.rb deleted file mode 100644 index b00a1cc60ea..00000000000 --- a/app/jobs/irs_attempts_events_batch_job.rb +++ /dev/null @@ -1,91 +0,0 @@ -class IrsAttemptsEventsBatchJob < ApplicationJob - queue_as :default - - def perform(timestamp = Time.zone.now - 1.hour) - enabled = IdentityConfig.store.irs_attempt_api_enabled && s3_helper.attempts_s3_write_enabled - return nil unless enabled - - previous_hour = timestamp - 1.hour - # Check if previous hour was properly loaded - IrsAttemptsEventsBatchJob.perform_later(previous_hour) if missing_log_file?(previous_hour) - - start_time = Time.zone.now - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: timestamp) - event_values = events.values.join("\r\n") - - public_key = IdentityConfig.store.irs_attempt_api_public_key - - result = IrsAttemptsApi::EnvelopeEncryptor.encrypt( - data: event_values, timestamp: timestamp, public_key_str: public_key, - ) - - upload_to_s3_response = create_and_upload_to_attempts_s3_resource( - bucket_name: s3_helper.attempts_bucket_name, filename: result.filename, - encrypted_data: result.encrypted_data - ) - - encoded_iv = Base64.strict_encode64(result.iv) - encoded_encrypted_key = Base64.strict_encode64(result.encrypted_key) - - irs_attempts_api_log_file = IrsAttemptApiLogFile.create( - filename: result.filename, - iv: encoded_iv, - encrypted_key: encoded_encrypted_key, - requested_time: timestamp_key(key: timestamp), - ) - - log_irs_attempts_events_job_info(result, events, start_time) - redis_client.remove_events(timestamp: timestamp) if upload_to_s3_response&.etag - irs_attempts_api_log_file - end - - def timestamp_key(key:) - IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(key) - end - - def missing_log_file?(previous_hour) - previous_hour_log_file_hash = { - requested_time: timestamp_key(key: previous_hour), - } - - !IrsAttemptApiLogFile.find_by(previous_hour_log_file_hash) && - reasonable_timespan?(previous_hour) - end - - def reasonable_timespan?(check_time) - check_time.after?(3.days.ago) - end - - def create_and_upload_to_attempts_s3_resource(bucket_name:, filename:, encrypted_data:) - aws_object = Aws::S3::Resource.new(client: s3_helper.s3_client). - bucket(bucket_name).object(filename) - aws_object.put(body: encrypted_data, acl: 'private', content_type: 'text/plain') - end - - def redis_client - @redis_client ||= IrsAttemptsApi::RedisClient.new - end - - def s3_helper - @s3_helper ||= JobHelpers::S3Helper.new - end - - def log_irs_attempts_events_job_info(result, events, start_time) - logger_info_hash( - name: 'IRSAttemptsEventJob', - start_time: start_time, - end_time: Time.zone.now, - duration_ms: duration_ms(start_time), - events_count: events.values.count, - file_bytes_size: result.encrypted_data.bytesize, - ) - end - - def logger_info_hash(hash) - logger.info(hash.to_json) - end - - def duration_ms(start_time) - Time.zone.now.to_f - start_time.to_f - end -end diff --git a/app/jobs/job_helpers/s3_helper.rb b/app/jobs/job_helpers/s3_helper.rb index 9ea95b3b18d..614cf435ef6 100644 --- a/app/jobs/job_helpers/s3_helper.rb +++ b/app/jobs/job_helpers/s3_helper.rb @@ -25,17 +25,5 @@ def s3_client compute_checksums: false, ) end - - def attempts_bucket_name - IdentityConfig.store.irs_attempt_api_bucket_name - end - - def attempts_s3_write_enabled - (attempts_bucket_name && attempts_bucket_name != 'default-placeholder') - end - - def attempts_serve_events_from_s3 - IdentityConfig.store.irs_attempt_api_aws_s3_enabled && attempts_s3_write_enabled - end end end diff --git a/app/models/irs_attempt_api_log_file.rb b/app/models/irs_attempt_api_log_file.rb deleted file mode 100644 index fcc952d708a..00000000000 --- a/app/models/irs_attempt_api_log_file.rb +++ /dev/null @@ -1,2 +0,0 @@ -class IrsAttemptApiLogFile < ApplicationRecord -end diff --git a/app/models/profile.rb b/app/models/profile.rb index ae373fc7ae4..0598fdbf489 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -219,22 +219,7 @@ def has_proofed_before? end def irs_attempts_api_tracker - @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new( - session_id: nil, - request: nil, - user: user, - sp: initiating_service_provider, - cookie_device_uuid: nil, - sp_request_uri: nil, - enabled_for_session: initiating_service_provider&.irs_attempts_api_enabled?, - analytics: Analytics.new( - user: user, - request: nil, - sp: initiating_service_provider&.issuer, - session: {}, - ahoy: nil, - ), - ) + @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new end private @@ -244,14 +229,12 @@ def confirm_that_profile_can_be_activated! end def track_fraud_review_adjudication(decision:) - if IdentityConfig.store.irs_attempt_api_track_idv_fraud_review - fraud_review_request = user.fraud_review_requests.last - irs_attempts_api_tracker.fraud_review_adjudicated( - decision: decision, - cached_irs_session_id: fraud_review_request&.irs_session_id, - cached_login_session_id: fraud_review_request&.login_session_id, - ) - end + fraud_review_request = user.fraud_review_requests.last + irs_attempts_api_tracker.fraud_review_adjudicated( + decision: decision, + cached_irs_session_id: fraud_review_request&.irs_session_id, + cached_login_session_id: fraud_review_request&.login_session_id, + ) end def personal_key_generator diff --git a/app/services/account_reset/track_irs_event.rb b/app/services/account_reset/track_irs_event.rb index 57a8349d419..2081eab1d24 100644 --- a/app/services/account_reset/track_irs_event.rb +++ b/app/services/account_reset/track_irs_event.rb @@ -14,16 +14,7 @@ def track_irs_event end def irs_attempts_api_tracker - @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new( - session_id: nil, - request: request, - user: user, - sp: sp, - cookie_device_uuid: cookies[:device], - sp_request_uri: nil, - enabled_for_session: sp.irs_attempts_api_enabled?, - analytics: analytics, - ) + @irs_attempts_api_tracker ||= IrsAttemptsApi::Tracker.new end def cookies diff --git a/app/services/analytics.rb b/app/services/analytics.rb index 9c7f044cc69..bd7d1e2459a 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -4,15 +4,14 @@ class Analytics include AnalyticsEvents prepend Idv::AnalyticsEventsEnhancer - attr_reader :user, :request, :sp, :ahoy, :irs_session_id + attr_reader :user, :request, :sp, :ahoy - def initialize(user:, request:, sp:, session:, ahoy: nil, irs_session_id: nil) + def initialize(user:, request:, sp:, session:, ahoy: nil) @user = user @request = request @sp = sp @ahoy = ahoy || Ahoy::Tracker.new(request: request) @session = session - @irs_session_id = irs_session_id end def track_event(event, attributes = {}) @@ -27,7 +26,6 @@ def track_event(event, attributes = {}) locale: I18n.locale, } - analytics_hash[:irs_session_id] = irs_session_id if irs_session_id analytics_hash.merge!(request_attributes) if request ahoy.track(event, analytics_hash) diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 19e139085ea..4c5c08d0625 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -2236,24 +2236,6 @@ def invalid_authenticity_token( ) end - # @param [String] event_type - # @param [Integer] unencrypted_payload_num_bytes size of payload as JSON data - # @param [Boolean] recorded if the full event was recorded or not - def irs_attempts_api_event_metadata( - event_type:, - unencrypted_payload_num_bytes:, - recorded:, - **extra - ) - track_event( - 'IRS Attempt API: Event metadata', - event_type: event_type, - unencrypted_payload_num_bytes: unencrypted_payload_num_bytes, - recorded: recorded, - **extra, - ) - end - # @param [Integer] rendered_event_count how many events were rendered in the API response # @param [Boolean] authenticated whether the request was successfully authenticated # @param [Float] elapsed_time the amount of time the function took to run diff --git a/app/services/idv/steps/threat_metrix_step_helper.rb b/app/services/idv/steps/threat_metrix_step_helper.rb index 6f65c59701c..b1b946c22f2 100644 --- a/app/services/idv/steps/threat_metrix_step_helper.rb +++ b/app/services/idv/steps/threat_metrix_step_helper.rb @@ -48,7 +48,6 @@ def threatmetrix_iframe_url(session_id) end def log_irs_tmx_fraud_check_event(result, user) - return unless IdentityConfig.store.irs_attempt_api_track_tmx_fraud_check_event return unless FeatureManagement.proofing_device_profiling_collecting_enabled? success = result[:review_status] == 'pass' @@ -56,7 +55,6 @@ def log_irs_tmx_fraud_check_event(result, user) unless success FraudReviewRequest.create( user: user, - irs_session_id: irs_attempts_api_session_id, login_session_id: Digest::SHA1.hexdigest(user.unique_session_id.to_s), ) diff --git a/app/services/irs_attempts_api/attempt_event.rb b/app/services/irs_attempts_api/attempt_event.rb index 0581ca8c359..299b84d8282 100644 --- a/app/services/irs_attempts_api/attempt_event.rb +++ b/app/services/irs_attempts_api/attempt_event.rb @@ -1,97 +1,4 @@ module IrsAttemptsApi class AttemptEvent - attr_accessor :jti, :iat, :event_type, :session_id, :occurred_at, :event_metadata - - def initialize( - event_type:, - session_id:, - occurred_at:, - event_metadata:, - jti: SecureRandom.uuid, - iat: Time.zone.now.to_i - ) - @jti = jti - @iat = iat - @event_type = event_type - @session_id = session_id - @occurred_at = occurred_at - @event_metadata = event_metadata - end - - def to_jwe - JWE.encrypt( - payload_json, - event_data_encryption_key, - typ: 'secevent+jwe', - zip: 'DEF', - alg: 'RSA-OAEP', - enc: 'A256GCM', - kid: JWT::JWK.new(event_data_encryption_key).kid, - ) - end - - def self.from_jwe(jwe, private_key) - decrypted_event = JWE.decrypt(jwe, private_key) - parsed_event = JSON.parse(decrypted_event) - event_type = parsed_event['events'].keys.first.split('/').last - event_data = parsed_event['events'].values.first - jti = parsed_event['jti'].split(':').last - AttemptEvent.new( - jti: jti, - iat: parsed_event['iat'], - event_type: event_type, - session_id: event_data['subject']['session_id'], - occurred_at: Time.zone.at(event_data['occurred_at']), - event_metadata: event_data.symbolize_keys.except(:subject, :occurred_at), - ) - end - - def event_key - "#{event_data_encryption_key_id}:#{jti}" - end - - def payload - { - jti: jti, - iat: iat, - iss: Rails.application.routes.url_helpers.root_url, - aud: IdentityConfig.store.irs_attempt_api_audience, - events: { - long_event_type => event_data, - }, - } - end - - def payload_json - @payload_json ||= payload.to_json - end - - private - - def event_data - { - 'subject' => { - 'subject_type' => 'session', - 'session_id' => session_id, - }, - 'occurred_at' => occurred_at.to_f, - }.merge(event_metadata || {}) - end - - def long_event_type - dasherized_name = event_type.to_s.dasherize - "https://schemas.login.gov/secevent/irs-attempts-api/event-type/#{dasherized_name}" - end - - def event_data_encryption_key_id - IdentityConfig.store.irs_attempt_api_public_key_id - end - - def event_data_encryption_key - @event_data_encryption_key ||= begin - decoded_key_der = Base64.strict_decode64(IdentityConfig.store.irs_attempt_api_public_key) - OpenSSL::PKey::RSA.new(decoded_key_der) - end - end end end diff --git a/app/services/irs_attempts_api/envelope_encryptor.rb b/app/services/irs_attempts_api/envelope_encryptor.rb deleted file mode 100644 index 79ce75072d6..00000000000 --- a/app/services/irs_attempts_api/envelope_encryptor.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'base16' - -module IrsAttemptsApi - class EnvelopeEncryptor - Result = Struct.new(:filename, :iv, :encrypted_key, :encrypted_data, keyword_init: true) - - # A new key is generated for each encryption. This key is encrypted with the public_key - # provided so that only the owner of the private key may decrypt this data. - def self.encrypt(data:, timestamp:, public_key_str:) - compressed_data = Zlib.gzip(data) - cipher = OpenSSL::Cipher.new('aes-256-cbc') - cipher.encrypt - key = cipher.random_key - iv = cipher.random_iv - public_key = OpenSSL::PKey::RSA.new(Base64.strict_decode64(public_key_str)) - encrypted_data = cipher.update(compressed_data) + cipher.final - encoded_data = Base16.encode16(encrypted_data) - digest = Digest::SHA256.hexdigest(encoded_data) - encrypted_key = public_key.public_encrypt(key) - formatted_time = formatted_timestamp(timestamp) - - filename = - "FCI-Logingov_#{formatted_time}_#{digest}.dat.gz.hex" - - Result.new( - filename: filename, - iv: iv, - encrypted_key: encrypted_key, - encrypted_data: encoded_data, - ) - end - - def self.formatted_timestamp(timestamp) - timestamp.strftime('%Y%m%dT%HZ') - end - - def self.decrypt(encrypted_data:, key:, iv:) - cipher = OpenSSL::Cipher.new('aes-256-cbc') - cipher.decrypt - cipher.key = key - cipher.iv = iv - decrypted = cipher.update(Base16.decode16(encrypted_data)) + cipher.final - - Zlib.gunzip(decrypted) - end - end -end diff --git a/app/services/irs_attempts_api/redis_client.rb b/app/services/irs_attempts_api/redis_client.rb deleted file mode 100644 index 5478d83539b..00000000000 --- a/app/services/irs_attempts_api/redis_client.rb +++ /dev/null @@ -1,48 +0,0 @@ -module IrsAttemptsApi - class RedisClient - cattr_accessor :redis_pool do - ConnectionPool.new(size: IdentityConfig.store.redis_irs_attempt_api_pool_size) do - Redis.new(url: IdentityConfig.store.redis_irs_attempt_api_url) - end - end - - def write_event(event_key:, jwe:, timestamp:) - key = key(timestamp) - redis_pool.with do |client| - client.hset(key, event_key, jwe) - client.expire(key, IdentityConfig.store.irs_attempt_api_event_ttl_seconds) - end - end - - def read_events(timestamp:, batch_size: 5000) - key = key(timestamp) - events = {} - redis_pool.with do |client| - client.hscan_each(key, count: batch_size) do |k, v| - events[k] = v - end - end - events - end - - def remove_events(timestamp:) - return unless IdentityConfig.store.irs_attempt_api_delete_events_after_s3_upload - - key = key(timestamp) - redis_pool.with do |client| - client.del(key) - end - end - - def key(timestamp) - 'irs-attempt-api:' + timestamp.in_time_zone('UTC').change(min: 0, sec: 0).iso8601 - end - - def self.clear_attempts! - unless %w[test development].include?(Rails.env) - raise 'RedisClient.clear_attempts! should not be called outside of dev or test!' - end - Redis.new(url: IdentityConfig.store.redis_irs_attempt_api_url).flushall - end - end -end diff --git a/app/services/irs_attempts_api/tracker.rb b/app/services/irs_attempts_api/tracker.rb index 63ae85a043b..463ec760cdb 100644 --- a/app/services/irs_attempts_api/tracker.rb +++ b/app/services/irs_attempts_api/tracker.rb @@ -1,94 +1,12 @@ module IrsAttemptsApi class Tracker - attr_reader :session_id, :enabled_for_session, :request, :user, :sp, :cookie_device_uuid, - :sp_request_uri, :analytics - - def initialize(session_id:, request:, user:, sp:, cookie_device_uuid:, - sp_request_uri:, enabled_for_session:, analytics:) - @session_id = session_id # IRS session ID - @request = request - @user = user - @sp = sp - @cookie_device_uuid = cookie_device_uuid - @sp_request_uri = sp_request_uri - @enabled_for_session = enabled_for_session - @analytics = analytics - end + include TrackerEvents def track_event(event_type, metadata = {}) - return unless enabled? - - return if ignore_idv_event?(event_type) - - if metadata.has_key?(:failure_reason) && - (metadata[:failure_reason].blank? || - metadata[:success].present?) - metadata.delete(:failure_reason) - end - - event_metadata = { - user_agent: request&.user_agent, - unique_session_id: hashed_session_id, - user_uuid: sp && AgencyIdentityLinker.for(user: user, service_provider: sp)&.uuid, - device_fingerprint: hashed_cookie_device_uuid, - user_ip_address: request&.remote_ip, - irs_application_url: sp_request_uri, - client_port: CloudFrontHeaderParser.new(request).client_port, - }.merge(metadata) - - event = AttemptEvent.new( - event_type: event_type, - session_id: session_id, - occurred_at: Time.zone.now, - event_metadata: event_metadata, - ) - - if IdentityConfig.store.irs_attempt_api_payload_size_logging_enabled - analytics.irs_attempts_api_event_metadata( - event_type: event_type, - unencrypted_payload_num_bytes: event.payload_json.bytesize, - recorded: true, - ) - end - - redis_client.write_event( - event_key: event.event_key, - jwe: event.to_jwe, - timestamp: event.occurred_at, - ) - - event end def parse_failure_reason(result) return result.to_h[:error_details] || result.errors.presence end - - include TrackerEvents - - private - - def hashed_session_id - return nil unless user&.unique_session_id - Digest::SHA1.hexdigest(user&.unique_session_id) - end - - def hashed_cookie_device_uuid - return nil unless cookie_device_uuid - Digest::SHA1.hexdigest(cookie_device_uuid) - end - - def enabled? - IdentityConfig.store.irs_attempt_api_enabled && @enabled_for_session - end - - def ignore_idv_event?(event_type) - !IdentityConfig.store.irs_attempt_api_idv_events_enabled && - (event_type.to_s.starts_with? 'idv_') - end - - def redis_client - @redis_client ||= IrsAttemptsApi::RedisClient.new - end end end diff --git a/config/application.yml.default b/config/application.yml.default index 73df823a894..6935ba2f47e 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -152,21 +152,6 @@ in_person_completion_survey_url: 'https://login.gov' in_person_usps_outage_message_enabled: false in_person_stop_expiring_enrollments: false include_slo_in_saml_metadata: false -irs_attempt_api_audience: 'https://irs.gov' -irs_attempt_api_auth_tokens: '' -irs_attempt_api_csp_id: 'LOGIN.gov' -irs_attempt_api_enabled: false -irs_attempt_api_aws_s3_enabled: false -irs_attempt_api_aws_s3_stream_enabled: false -irs_attempt_api_aws_s3_stream_buffer_size: 16_777_216 -irs_attempt_api_event_ttl_seconds: 86400 -irs_attempt_api_event_count_default: 1000 -irs_attempt_api_event_count_max: 10000 -irs_attempt_api_idv_events_enabled: false -irs_attempt_api_payload_size_logging_enabled: true -irs_attempt_api_track_idv_fraud_review: false -irs_attempt_api_track_tmx_fraud_check_event: false -irs_attempt_api_delete_events_after_s3_upload: false key_pair_generation_percent: 0 logins_per_ip_track_only_mode: false # LexisNexis ##################################################### @@ -274,13 +259,11 @@ recaptcha_site_key_v3: '' recaptcha_secret_key_v2: '' recaptcha_secret_key_v3: '' recovery_code_length: 4 -redis_irs_attempt_api_url: redis://localhost:6379/2 redis_throttle_url: redis://localhost:6379/1 redis_url: redis://localhost:6379/0 redis_pool_size: 10 redis_session_pool_size: 10 redis_throttle_pool_size: 5 -redis_irs_attempt_api_pool_size: 1 reg_confirmed_email_max_attempts: 20 reg_confirmed_email_window_in_minutes: 60 reg_unconfirmed_email_max_attempts: 20 @@ -395,13 +378,6 @@ development: hmac_fingerprinter_key_queue: '["11111111111111111111111111111111", "22222222222222222222222222222222"]' identity_pki_local_dev: true in_person_proofing_enabled: true - irs_attempt_api_public_key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyut9Uio5XxsIUVrXARqCoHvcMVYT0p6WyU1BnbhxLRW4Q60p+4Bn32vVOt9nzeih7qvauYM5M0PZdKEmwOHflqPP+ABfKhL+6jxBhykN5P5UY375wTFBJZ20Fx8jOJbRhJD02oUQ49YKlDu3MG5Y0ApyD4ER4WKgxuB2OdyQKd9vg2ZZa+P2pw1HkFPEin0h8KBUFBeLGDZni8PIJdHBP6dA+xbayGBxSM/8xQC0JIg6KlGTcLql37QJIhP2oSv0nAJNb6idFPAz0uMCQDQWKKWV5FUDCsFVH7VuQz8xUCwnPn/SdaratB+29bwUpVhgHXrHdJ0i8vjBEX7smD7pI8CcFHuVgACt86NMlBnNCVkwumQgZNAAxe2mJoYcotEWOnhCuMc6MwSj985bj8XEdFlbf4ny9QO9rETd5aYcwXBiV/T6vd637uvHb0KenghNmlb1Tv9LMj2b9ZwNc9C6oeCnbN2YAfxSDrb8Ik+yq4hRewOvIK7f0CcpZYDXK25aHXnHm306Uu53KIwMGf1mha5T5LWTNaYy5XFoMWHJ9E+AnU/MUJSrwCAITH/S0JFcna5Oatn70aTE9pISATsqB5Iz1c46MvdrxD8hPoDjT7x6/EO316DZrxQfJhjbWsCB+R0QxYLkXPHczhB2Z0HPna9xB6RbJHzph7ifDizhZoMCAwEAAQ== - irs_attempt_api_public_key_id: key1 - irs_attempt_api_enabled: true - irs_attempt_api_aws_s3_enabled: false - irs_attempt_api_auth_tokens: 'abc123' - irs_attempt_api_bucket_name: default-placeholder - irs_attempt_api_idv_events_enabled: false logins_per_ip_limit: 5 logo_upload_enabled: true max_bad_passwords: 5 @@ -472,8 +448,6 @@ production: hmac_fingerprinter_key: hmac_fingerprinter_key_queue: '[]' idv_sp_required: true - irs_attempt_api_public_key: change-me-pls - irs_attempt_api_public_key_id: key1 lexisnexis_threatmetrix_mock_enabled: false logins_per_ip_limit: 20 logins_per_ip_period: 20 @@ -491,7 +465,6 @@ production: session_encryptor_alert_enabled: true reauthentication_for_second_factor_management_enabled: false recurring_jobs_disabled_names: "[]" - redis_irs_attempt_api_url: redis://redis.login.gov.internal:6379/2 redis_throttle_url: redis://redis.login.gov.internal:6379/1 redis_url: redis://redis.login.gov.internal:6379 report_timeout: 1_000_000 @@ -546,10 +519,6 @@ test: hmac_fingerprinter_key: a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c hmac_fingerprinter_key_queue: '["old-key-one", "old-key-two"]' identity_pki_disabled: true - irs_attempt_api_auth_tokens: 'test-token-1,test-token-2' - irs_attempt_api_public_key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyut9Uio5XxsIUVrXARqCoHvcMVYT0p6WyU1BnbhxLRW4Q60p+4Bn32vVOt9nzeih7qvauYM5M0PZdKEmwOHflqPP+ABfKhL+6jxBhykN5P5UY375wTFBJZ20Fx8jOJbRhJD02oUQ49YKlDu3MG5Y0ApyD4ER4WKgxuB2OdyQKd9vg2ZZa+P2pw1HkFPEin0h8KBUFBeLGDZni8PIJdHBP6dA+xbayGBxSM/8xQC0JIg6KlGTcLql37QJIhP2oSv0nAJNb6idFPAz0uMCQDQWKKWV5FUDCsFVH7VuQz8xUCwnPn/SdaratB+29bwUpVhgHXrHdJ0i8vjBEX7smD7pI8CcFHuVgACt86NMlBnNCVkwumQgZNAAxe2mJoYcotEWOnhCuMc6MwSj985bj8XEdFlbf4ny9QO9rETd5aYcwXBiV/T6vd637uvHb0KenghNmlb1Tv9LMj2b9ZwNc9C6oeCnbN2YAfxSDrb8Ik+yq4hRewOvIK7f0CcpZYDXK25aHXnHm306Uu53KIwMGf1mha5T5LWTNaYy5XFoMWHJ9E+AnU/MUJSrwCAITH/S0JFcna5Oatn70aTE9pISATsqB5Iz1c46MvdrxD8hPoDjT7x6/EO316DZrxQfJhjbWsCB+R0QxYLkXPHczhB2Z0HPna9xB6RbJHzph7ifDizhZoMCAwEAAQ== - irs_attempt_api_public_key_id: key1 - irs_attempt_api_bucket_name: test-bucket-name lexisnexis_trueid_account_id: 'test_account' lockout_period_in_minutes: 5 logins_per_email_and_ip_limit: 2 diff --git a/config/application.yml.default.docker b/config/application.yml.default.docker index cd914c1d4ed..20d11210f92 100644 --- a/config/application.yml.default.docker +++ b/config/application.yml.default.docker @@ -13,7 +13,6 @@ production: database_worker_jobs_password: ['env', 'POSTGRES_WORKER_PASSWORD'] database_worker_jobs_sslmode: ['env', 'POSTGRES_WORKER_SSLMODE'] hmac_fingerprinter_key: a2c813d4dca919340866ba58063e4072adc459b767a74cf2666d5c1eef3861db26708e7437abde1755eb24f4034386b0fea1850a1cb7e56bff8fae3cc6ade96c - redis_irs_attempt_api_url: ['env', 'REDIS_IRS_ATTEMPTS_API_URL'] redis_throttle_url: ['env', 'REDIS_THROTTLE_URL'] redis_url: ['env', 'REDIS_URL'] password_pepper: f22d4b2cafac9066fe2f4416f5b7a32c diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 7bfec532fb1..845bbe1bb41 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -68,29 +68,6 @@ ], "note": "" }, - { - "warning_type": "Weak Cryptography", - "warning_code": 126, - "fingerprint": "62a8c37ff0f723d2ebbbbf64c443a21632a2dcdc87fd20e6f61c2cec323482d2", - "check_name": "WeakRSAKey", - "message": "Use of padding mode PKCS1 (default if not specified), which is known to be insecure. Use OAEP instead", - "file": "app/services/irs_attempts_api/envelope_encryptor.rb", - "line": 19, - "link": "https://brakemanscanner.org/docs/warning_types/weak_cryptography/", - "code": "OpenSSL::PKey::RSA.new(Base64.strict_decode64(public_key_str)).public_encrypt(OpenSSL::Cipher.new(\"aes-256-cbc\").random_key)", - "render_path": null, - "location": { - "type": "method", - "class": "IrsAttemptsApi::EnvelopeEncryptor", - "method": "s(:self).encrypt" - }, - "user_input": null, - "confidence": "High", - "cwe_id": [ - 780 - ], - "note": "This is necessary due to the parameters of the IRS systems that we integrate with." - }, { "warning_type": "Dynamic Render Path", "warning_code": 15, diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 02c5d7bc611..03e3143d4c4 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -145,12 +145,6 @@ class: 'ThreatMetrixJsVerificationJob', cron: cron_1h, }, - # Batch up IRS Attempts API events - irs_attempt_events_aggregator: { - class: 'IrsAttemptsEventsBatchJob', - cron: cron_1h, - args: -> { [Time.zone.now - 1.hour] }, - }, # Weekly IRS report returning system demand irs_weekly_summary_report: { class: 'Reports::IrsWeeklySummaryReport', diff --git a/config/routes.rb b/config/routes.rb index 077fcc4db73..e7b06da0a8a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,7 +11,6 @@ match '/api/openid_connect/token' => 'openid_connect/token#options', via: :options get '/api/openid_connect/userinfo' => 'openid_connect/user_info#show' post '/api/risc/security_events' => 'risc/security_events#create' - post '/api/irs_attempts_api/security_events' => 'api/irs_attempts_api#create' namespace :api do namespace :internal do diff --git a/db/primary_migrate/20230625143140_drop_irs_attempt_api_log_files.rb b/db/primary_migrate/20230625143140_drop_irs_attempt_api_log_files.rb new file mode 100644 index 00000000000..c3064be95eb --- /dev/null +++ b/db/primary_migrate/20230625143140_drop_irs_attempt_api_log_files.rb @@ -0,0 +1,12 @@ +class DropIrsAttemptApiLogFiles < ActiveRecord::Migration[7.0] + def change + drop_table :irs_attempt_api_log_files do |t| + t.string "filename" + t.string "iv" + t.text "encrypted_key" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "requested_time" + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0e254d69d95..80c5901bf36 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -348,15 +348,6 @@ t.index ["service_provider_id"], name: "index_integrations_on_service_provider_id" end - create_table "irs_attempt_api_log_files", force: :cascade do |t| - t.string "filename" - t.string "iv" - t.text "encrypted_key" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "requested_time" - end - create_table "letter_requests_to_usps_ftp_logs", force: :cascade do |t| t.datetime "ftp_at", precision: nil, null: false t.integer "letter_requests_count", null: false diff --git a/keys.example/attempts_api_private_key.key b/keys.example/attempts_api_private_key.key deleted file mode 100644 index b5acede610b..00000000000 --- a/keys.example/attempts_api_private_key.key +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAre36TDUrZ9qSlP951pNfS8QT1oROWCYPAelarswnqDLwZ6v8 -xxrID4Om1JRDjW+YzLtc4b0G4fxrgZiOLgbW75OF4ql3BjHi0kgCRdxiQ7BWYajG -h4PolYQcc3JflfbZK0uNgRQBStrHRqlmiVYhBHalLvsOoK2hQXRhHAudWAVJ8Hca -vI/4QY5VKnj5/3E50m9npau6h3UEx2ySfIL9vZiZw0JOv/eKf2UamjmfHN2AXIqA -v1AY2iugX16yNNAgDMSDKZQLGgd4CBwTRNiinxnA2lKtJdVxttpRjhwXVYIsSjpb -JCp9YG51mDZTBpYwCG2VHnx9SJZDei/9WQ1TKXkK9dQsiYkRWjM0bJaX2zRrEY26 -Q92QDzi0w3BwAy3S7waeLQGfT3oJkfq6Jb6rlyrm1FtdxbqIN646EDXwA5Dv1nnj -G9372uIHR3K/UlDoSISyi1+6tU+1dpP5SDVVV419F/10piouql7UhTH6n4/9IhMk -0LwJcUGLpoVVks+jED/rn775Zs8Zktgu+ywQ61FybHrMwIEZUIifM583sFGCtnMO -GQqC9a1bApnUvGq5+I8YeWOeAipaWj6QmY35HmSb6PzBuOQyKqrr2E5Zp5dsyMFs -eNP3uudXVQN6T6uzf4AdKXKKJLaPUs3ehNyYPYRdP/u5GfPPYXbsyrbFvY0CAwEA -AQKCAgBxP3KqDDSm+npW6cG7JVn3S0RfeOZKHv3GkNqq1eM8cW53ElUUjyuUbnRC -FgemH/Ot2pEvqpTAsEqPp3VsIqfwdm3Gl/EX54N4x8ozErwotriH/gzGT+0w7URZ -Jy91xbTBrAAG+6+bvbfjGonZAmudLGOBo5ZyqJtGszBHwohAt7DdEbAm9tb+WF67 -uOmOmXq8ui0Sgi6B5H+oGdmHmQL+YE4+BP6hn9xd9sbLhJlFgjQCDWynJNAJvj49 -2rrOCHXV2l5MxEg2Ooa9QQqK2UOJtIsNnKeA6umP1bckgMXwt0F/U1/kvIXGNCX9 -ZeHJeCDuW+usXgJ1MOHevUCIq1RpqWs9HW55WUT8LIbehYYzdfgO6BuWIa8PHPLW -Wk+odvvAHg780cjUU548wLQ2quyzeqQI4QFuXGZ/nJJbvX/HVD8AkAHYkaXcasTW -prOXUEAc/sK7I4Q4ws8+vnppnOPlmwSM3vNNU4cE5agacQ+016bBzd4LvtS/SOUX -G9lH6t9FZJDqCLGiS3MWBmzxp/2VFPxJuUxawlIWNYCr4xhoONhirHWdZDeDo7dd -Iq9SqMMHMFj0RXu7m2cwmV4ZWmRmEe1ob5xCJIoBd7bXNl5QLfSS3eRf9aVZskYc -9pXV229u12Ndf21+YYEE5g0645SA9BFhZu/+IhGKg6LLO0yAAQKCAQEA3mjqBtrB -fUtpaWEkIws7zPW5EV8NTYZi4znM9cML9u3rYI27zNpkUeLY7h5/hp8Mj/Ko6JjA -SFmnjNIQHzAazhTStVHNnxKmTnm+q4SBzJgjmfxrduk0eTAuVlW1eTG2+NcJZkdu -Gmt2LxgFIYbS5NGz/HWhkyARjav1kpHzrNWBc/wSpqBFRdHZ3SVRVS35SXRwkckg -/D2cqQafyxj7wAWhYU0O9XRURG9zgYczTBZxJwQkrEokhYdQ9GK6NQZR9QSVuOj+ -ZEHZJUYR2yQEHijuQfDe0JgNfHmxHI+YupMA1AfUo84psNfmusS5bRnGb0q11HEy -m0A6kU8Zf8E/jQKCAQEAyDKpK5sr7EyUPzNM/AQS5pLI4wAhjsJA2e6byaNBFdlz -/eYL1pIzQ+Bche5YwwT3y6ute1jAJ9unPsRKevztundyQYePNWrlkxC9ylX9tEMz -zT6n+6LHifhcy74Eb9f5R7OLQgjEES0fws7VV7XmWWoNsFysQDvWve0hJxNE4d83 -zvgyzwoRI8nfm1Hk16iywn0Ucxq7XQrIFNP8VkEAS5sOtH//FKTN+hWAKBDvZPbA -NhGdlJR1jM+COzZ+u4qmGUbrzs8eg7ST73j0ioBfuoIlENx3ZbrGIvjPNmDNG4Wa -wXm0ljo3yetoxMMEEEcNikKAN36WILI1Q5q5MX/2AQKCAQEAg61XyUEgx16rhTF3 -JKxU9m8Q6AD/rkN/LoqdF3AoGEUaUyr24jz2oiiNSMWTuQ9Xb646ZKNzTCKQeWYx -F1XyuMpJSgKpm6F+Bv47be7grlHw2g1BTsCZSZrGBGVwMNXHP6KItR5bSJcQ/Kba -+tOI7uJdDn5Sb/nPirIhjWqN+2jY4ON/41kGSmUI+M5MTgMPEXc34ohS2CReKRB7 -8NHU0wZCWoo17gq2jWHy3+A89bJfwbrJP5wR7WCFBHujRRt90HOrdTO00YTO3CuV -DMpRCc4v6aiDMrqjWWvu+TrnMKcyGVadhXHc6w+YUkGAiuB0xJVV+/YdAR/QzREo -xqOSTQKCAQBL18V3G6YcMzbGaqzznKd8aodoWBheeQEQjvC+BvcVS/hBa/F4LJL3 -V/w1UQKXfz5RDoz+ebTyZKiLgtmklkWJqs7CPJToa8LlYCZGjSU8MWlijCYfkp99 -iXW+ShJsPKnXnBFZxvBggXPS3YC9ZdAKd78P3Uv0WbcU5Mz2fLpPx7zgz+6PuQSd -RlFMCk5j1SlDvRcONEZfDUKXgWfLVXzDJ87+Wq9MJtNRtuuCQkgvO7u9wBauvz95 -RPPVcuO4MOKUGOYkxxqMUtDonC4oelHJ2pwoMx2YkJWXkiMOOsX5czgZq/3aG9we -xuw9/pN/hyt+AYuJeo9te5XqMqeONSgBAoIBAQDDnkScRZ48zipt34vCoSSNECAP -fCU1rnGJulf0GP4iz0QV0TyUrLthUiPNbi1G9ZZrLbMgdChFvqtYV9U81fAKFGet -qGBjGA/UJW68CUDIOioyYp+f0x0BPMfudu38IRiqZXvwAm33yCf7byPbmsaNhJlf -HJ6FN79kUPESMLRbvERqSvgVv+j7cVUFLYuupvHrNQvqcu5KCw2cg8ggB7SqDTbA -vvOqBs7ULQRkP1FEpIN6Q1ZtmFJO+rokBtdecb/tetDxKEN8bHCGhlk3fuPQlG6V -oqB3fb7q5H5GGx1ZenDSdkKi6U8Yj4/iNW7nu9LbBh0PKi0vNiOYzGU246iS ------END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 90037789573..aa7da3762f8 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -243,24 +243,6 @@ def self.build_store(config_map) config.add(:in_person_usps_outage_message_enabled, type: :boolean) config.add(:in_person_stop_expiring_enrollments, type: :boolean) config.add(:include_slo_in_saml_metadata, type: :boolean) - config.add(:irs_attempt_api_audience) - config.add(:irs_attempt_api_auth_tokens, type: :comma_separated_string_list) - config.add(:irs_attempt_api_aws_s3_enabled, type: :boolean) - config.add(:irs_attempt_api_aws_s3_stream_enabled, type: :boolean) - config.add(:irs_attempt_api_aws_s3_stream_buffer_size, type: :integer) - config.add(:irs_attempt_api_bucket_name, type: :string, allow_nil: true) - config.add(:irs_attempt_api_csp_id) - config.add(:irs_attempt_api_delete_events_after_s3_upload, type: :boolean) - config.add(:irs_attempt_api_enabled, type: :boolean) - config.add(:irs_attempt_api_event_ttl_seconds, type: :integer) - config.add(:irs_attempt_api_event_count_default, type: :integer) - config.add(:irs_attempt_api_event_count_max, type: :integer) - config.add(:irs_attempt_api_idv_events_enabled, type: :boolean) - config.add(:irs_attempt_api_payload_size_logging_enabled, type: :boolean) - config.add(:irs_attempt_api_public_key) - config.add(:irs_attempt_api_public_key_id) - config.add(:irs_attempt_api_track_idv_fraud_review, type: :boolean) - config.add(:irs_attempt_api_track_tmx_fraud_check_event, type: :boolean) config.add(:lexisnexis_base_url, type: :string) config.add(:lexisnexis_request_mode, type: :string) config.add(:lexisnexis_account_id, type: :string) @@ -383,8 +365,6 @@ def self.build_store(config_map) config.add(:recaptcha_secret_key_v3, type: :string) config.add(:recovery_code_length, type: :integer) config.add(:recurring_jobs_disabled_names, type: :json) - config.add(:redis_irs_attempt_api_url, type: :string) - config.add(:redis_irs_attempt_api_pool_size, type: :integer) config.add(:redis_throttle_url, type: :string) config.add(:redis_url, type: :string) config.add(:redis_pool_size, type: :integer) diff --git a/lib/tasks/attempts.rake b/lib/tasks/attempts.rake deleted file mode 100644 index 59c1c60f030..00000000000 --- a/lib/tasks/attempts.rake +++ /dev/null @@ -1,94 +0,0 @@ -require 'base16' - -namespace :attempts do - desc 'Retrieve events via the API' - task fetch_events: :environment do - auth_token = IdentityConfig.store.irs_attempt_api_auth_tokens.sample - puts 'There are no configured irs_attempt_api_auth_tokens' if auth_token.nil? - private_key_path = 'keys/attempts_api_private_key.key' - - conn = Faraday.new(url: 'http://localhost:3000') - body = "timestamp=#{Time.zone.now.iso8601}" - - resp = conn.post('/api/irs_attempts_api/security_events', body) do |req| - req.headers['Authorization'] = - "Bearer #{IdentityConfig.store.irs_attempt_api_csp_id} #{auth_token}" - end - - iv = Base64.strict_decode64(resp.headers['x-payload-iv']) - encrypted_key = Base64.strict_decode64(resp.headers['x-payload-key']) - private_key = OpenSSL::PKey::RSA.new(File.read(private_key_path)) - key = private_key.private_decrypt(encrypted_key) - decrypted = IrsAttemptsApi::EnvelopeEncryptor.decrypt( - encrypted_data: resp.body, key: key, iv: iv, - ) - - events = decrypted.split("\r\n") - puts "Found #{events.count} events" - - if File.exist?(private_key_path) - puts events.any? ? 'Decrypted events:' : 'No events returned.' - - events.each do |jwe| - begin - pp JSON.parse(JWE.decrypt(jwe, private_key)) - rescue - puts 'Failed to parse/decrypt event!' - end - puts "\n" - end - else - puts "No decryption key in #{private_key_path}; cannot decrypt events." - pp events - end - end - - desc 'Confirm your dev setup is configured properly' - task check_enabled: :environment do - failed = false - auth_token = IdentityConfig.store.irs_attempt_api_auth_tokens.sample - puts 'There are no configured irs_attempt_api_auth_tokens' if auth_token.nil? - private_key_path = 'keys/attempts_api_private_key.key' - - if IdentityConfig.store.irs_attempt_api_enabled - puts '✅ Feature flag is enabled' - else - failed = true - puts '❌ FAILED: Set irs_attempt_api_enabled=true in application.yml.default' - end - - sp = ServiceProvider.find_by(friendly_name: 'Example Sinatra App') - if sp.irs_attempts_api_enabled - puts '✅ Sinatra app SP has irs_attempts_api_enabled=true' - else - failed = true - puts '❌ FAILED: Run rake attempts:enable_for_sinatra' - end - - if IdentityConfig.store.irs_attempt_api_auth_tokens.include?(auth_token) - puts "✅ #{auth_token} set as auth token" - else - failed = true - puts "❌ FAILED: set irs_attempt_api_auth_tokens='#{auth_token}' in application.yml.default" - end - - if File.exist?(private_key_path) - puts "✅ '#{private_key_path}' exists for decrypting events" - else - puts "❌ FAILED: Private key '#{private_key_path}' does not exist; unable to decrypt events" - end - - puts 'Remember to restart Rails after updating application.yml.default!' if failed - end - - desc 'Enable irs_attempts_api_enabled for Sinatra SP' - task enable_for_sinatra: :environment do - sp = ServiceProvider.find_by(friendly_name: 'Example Sinatra App') - sp.update(irs_attempts_api_enabled: true) - end - - desc 'Clear all events from Redis' - task purge_events: :environment do - IrsAttemptsApi::RedisClient.clear_attempts! - end -end diff --git a/spec/controllers/api/irs_attempts_api_controller_spec.rb b/spec/controllers/api/irs_attempts_api_controller_spec.rb deleted file mode 100644 index 498a7bf0494..00000000000 --- a/spec/controllers/api/irs_attempts_api_controller_spec.rb +++ /dev/null @@ -1,251 +0,0 @@ -require 'rails_helper' - -RSpec.describe Api::IrsAttemptsApiController do - before do - stub_analytics - - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_enabled).and_return(false) - - existing_events - - request.headers['Authorization'] = - "Bearer #{IdentityConfig.store.irs_attempt_api_csp_id} #{auth_token}" - end - let(:time) { Time.new(2022, 1, 1, 0, 0, 0, 'Z') } - - let(:auth_token) do - IdentityConfig.store.irs_attempt_api_auth_tokens.first - end - let(:existing_events) do - 3.times.map do - event = IrsAttemptsApi::AttemptEvent.new( - event_type: :test_event, - session_id: 'test-session-id', - occurred_at: time, - event_metadata: { - first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name], - }, - ) - jti = event.jti - jwe = event.to_jwe - event_key = event.event_key - IrsAttemptsApi::RedisClient.new.write_event( - event_key: event_key, - jwe: jwe, - timestamp: event.occurred_at, - ) - [jti, jwe] - end - end - let(:existing_event_jtis) { existing_events.map(&:first) } - - describe '#create' do - let(:test_object) { '{test: "test"}' } - before do - Aws.config[:s3] = { - stub_responses: { - get_object: { body: test_object }, - }, - } - end - - context 'with aws_s3 enabled' do - let(:wrong_time) { time - 1.year } - let(:timestamp) { '2022-11-08T18:00:00.000Z' } - - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_enabled).and_return(true) - - IrsAttemptApiLogFile.create( - filename: 'test_filename', - iv: Base64.strict_encode64('test_iv'), - encrypted_key: Base64.strict_encode64('test_encrypted_key'), - requested_time: IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(time), - ) - end - - it 'should return 404 when file not found' do - post :create, params: { timestamp: wrong_time.iso8601 } - - expect(response.status).to eq(404) - end - - it 'should render data from s3 correctly' do - post :create, params: { timestamp: time.iso8601 } - - expect(response).to be_ok - expect(Base64.strict_decode64(response.headers['X-Payload-IV'])).to be_present - expect(Base64.strict_decode64(response.headers['X-Payload-Key'])).to be_present - expect(response.body).to eq(test_object) - end - - context 'with aws_s3_stream enabled' do - let(:test_object) { '{test: "1234567890 12345"}' } - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_stream_enabled). - and_return(true) - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_stream_buffer_size). - and_return(10) - - Aws.config[:s3] = { - stub_responses: { - head_object: { content_length: test_object.bytesize }, - get_object: proc do |context| - range_string = context.params[:range] - _, byte_string = range_string.split('=') - start_byte, _ = byte_string.split('-') - { body: test_object.byteslice( - start_byte.to_i, - IdentityConfig.store.irs_attempt_api_aws_s3_stream_buffer_size + 1, - ) } - end, - }, - } - end - - it 'should render data streamed from s3 correctly' do - post :create, params: { timestamp: time.iso8601 } - - expect(response).to be_ok - expect(Base64.strict_decode64(response.headers['X-Payload-IV'])).to be_present - expect(Base64.strict_decode64(response.headers['X-Payload-Key'])).to be_present - expect(response.content_type).to eq('application/octet-stream') - expect(response['Content-Disposition']). - to eq("attachment; filename=\"test_filename\"; filename*=UTF-8''test_filename") - - expect(response.stream.body).to eq(test_object) - end - end - end - - context 'with timestamp problems' do - it 'returns unprocessable_entity when given no timestamp' do - post :create, params: { timestamp: nil } - - expect(response.status).to eq(422) - end - - it 'returns unprocessable_entity when timestamp is invalid' do - post :create, params: { timestamp: 'INVALID*TIME' } - - expect(response.status).to eq(422) - end - end - - context 'with aws_s3 disabled' do - let(:timestamp) { '2022-11-08T18:00:00.000Z' } - it 'should bypass s3 retrieval' do - expect_any_instance_of(Aws::S3::Client).not_to receive(:get_object) - - post :create, params: { timestamp: timestamp } - - expect(response).to be_ok - expect(Base64.strict_decode64(response.headers['X-Payload-IV'])).to be_present - expect(Base64.strict_decode64(response.headers['X-Payload-Key'])).to be_present - expect(Base64.strict_decode64(response.body)).to be_present - end - end - - context 'with CSRF protection enabled' do - around do |ex| - ActionController::Base.allow_forgery_protection = true - ex.run - ensure - ActionController::Base.allow_forgery_protection = false - end - - it 'allows authentication without error' do - request.headers['Authorization'] = - "Bearer #{IdentityConfig.store.irs_attempt_api_csp_id} #{auth_token}" - - post :create, params: { timestamp: time.iso8601 } - - expect(response.status).to eq(200) - end - end - - context 'with a timestamp including a fractional second' do - let(:timestamp) { '2022-11-08T18:00:00.000Z' } - - it 'accepts the timestamp as valid' do - post :create, params: { timestamp: timestamp } - expect(response.status).to eq(200) - end - end - - it 'renders a 404 if disabled' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(false) - - post :create, params: { timestamp: time.iso8601 } - - expect(response.status).to eq(404) - end - - it 'returns an error when required timestamp parameter is missing' do - post :create, params: {} - expect(response.status).to eq 422 - end - - it 'returns an error when timestamp parameter is empty' do - post :create, params: { timestamp: '' } - expect(response.status).to eq 422 - end - - it 'returns an error when timestamp parameter is invalid' do - post :create, params: { timestamp: 'abc' } - expect(response.status).to eq 422 - - post :create, params: { timestamp: 'T14' } - expect(response.status).to eq 422 - end - - it 'authenticates the client' do - request.headers['Authorization'] = auth_token # Missing Bearer prefix - - post :create, params: { timestamp: time.iso8601 } - expect(@analytics).to have_logged_event( - 'IRS Attempt API: Events submitted', - rendered_event_count: 3, - authenticated: false, - elapsed_time: 0, - success: false, - timestamp: time.iso8601, - ) - - expect(response.status).to eq(401) - - request.headers['Authorization'] = 'garbage-fake-token-nobody-likes' - - post :create, params: { timestamp: time.iso8601 } - - expect(response.status).to eq(401) - - request.headers['Authorization'] = nil - - post :create, params: { timestamp: time.iso8601 } - - expect(response.status).to eq(401) - end - - it 'renders encrypted events' do - allow_any_instance_of(described_class).to receive(:elapsed_time).and_return(0.1234) - - post :create, params: { timestamp: time.iso8601 } - - expect(response).to be_ok - expect(Base64.strict_decode64(response.headers['X-Payload-IV'])).to be_present - expect(Base64.strict_decode64(response.headers['X-Payload-Key'])).to be_present - expect(Base64.strict_decode64(response.body)).to be_present - - expect(@analytics).to have_logged_event( - 'IRS Attempt API: Events submitted', - rendered_event_count: existing_events.count, - authenticated: true, - elapsed_time: 0.1234, - success: true, - timestamp: time.iso8601, - ) - end - end -end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 818e8136556..b7104b005d1 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -237,7 +237,7 @@ def index expect(Analytics).to receive(:new). with(user: user, request: request, sp: sp.issuer, session: match_array({}), - ahoy: controller.ahoy, irs_session_id: nil) + ahoy: controller.ahoy) controller.analytics end @@ -252,7 +252,7 @@ def index expect(Analytics).to receive(:new). with(user: user, request: request, sp: nil, session: match_array({}), - ahoy: controller.ahoy, irs_session_id: nil) + ahoy: controller.ahoy) controller.analytics end diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index 1c32ea0b4a6..ff534a457c4 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -206,8 +206,6 @@ controller. idv_session.verify_info_step_document_capture_session_uuid = document_capture_session.uuid allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled) - allow(IdentityConfig.store).to receive(:irs_attempt_api_track_tmx_fraud_check_event). - and_return(true) end context 'when threatmetrix response is Pass' do diff --git a/spec/decorators/service_provider_session_decorator_spec.rb b/spec/decorators/service_provider_session_decorator_spec.rb index e8c360af671..90fecdf679a 100644 --- a/spec/decorators/service_provider_session_decorator_spec.rb +++ b/spec/decorators/service_provider_session_decorator_spec.rb @@ -268,38 +268,6 @@ end end - describe '#irs_attempts_api_session_id' do - context 'with a irs_attempts_api_session_id on the request url' do - let(:service_provider_request) do - url = 'https://example.com/auth?irs_attempts_api_session_id=123abc' - ServiceProviderRequest.new(url: url) - end - - it 'returns the value of irs_attempts_api_session_id' do - expect(subject.irs_attempts_api_session_id).to eq('123abc') - end - end - - context 'with a tid on the request url' do - let(:service_provider_request) do - url = 'https://example.com/auth?tid=123abc' - ServiceProviderRequest.new(url: url) - end - - it 'returns the value of irs_attempts_api_session_id' do - expect(subject.irs_attempts_api_session_id).to eq('123abc') - end - end - - context 'without a irs_attempts_api_session_id or tid on the request url' do - let(:service_provider_request) { ServiceProviderRequest.new } - - it 'returns nil' do - expect(subject.irs_attempts_api_session_id).to be_nil - end - end - end - describe '#request_url_params' do context 'without url params' do it 'returns an empty hash' do diff --git a/spec/features/account_reset/delete_account_spec.rb b/spec/features/account_reset/delete_account_spec.rb index ed6107670e1..924a67c25eb 100644 --- a/spec/features/account_reset/delete_account_spec.rb +++ b/spec/features/account_reset/delete_account_spec.rb @@ -185,92 +185,4 @@ expect(page).to have_current_path(new_user_session_path) end end - - context 'logs IRS attempts api events' do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - mock_irs_attempts_api_encryption_key - end - - it 'allows the user to delete their account after 24 hours and log irs event' do - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - ) - - signin(user_email, user.password) - click_link t('two_factor_authentication.login_options_link_text') - click_link t('two_factor_authentication.account_reset.link') - expect(page). - to have_content strip_tags( - t('account_reset.recovery_options.try_method_again'), - ) - click_link t('account_reset.request.yes_continue') - expect(page). - to have_content strip_tags( - t('account_reset.request.delete_account'), - ) - click_button t('account_reset.request.yes_continue') - - expect(page). - to have_content strip_tags( - t('account_reset.confirm_request.instructions_start'), - ) - expect(page). - to have_content user_email - expect(page). - to have_content strip_tags( - t('account_reset.confirm_request.instructions_end'), - ) - expect(page).to have_content t('account_reset.confirm_request.security_note') - expect(page).to have_content t('account_reset.confirm_request.close_window') - - reset_email - set_new_browser_session - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - expected_event_types = %w[mfa-login-phone-otp-sent login-email-and-password-auth - account-reset-request-submitted] - received_event_types = events.map(&:event_type) - - expect(events.count).to eq received_event_types.count - expect(received_event_types).to match_array(expected_event_types) - - travel_to(Time.zone.now + 2.days + 1.second) do - AccountReset::GrantRequestsAndSendEmails.new.perform(Time.zone.today) - open_last_email - click_email_link_matching(/delete_account\?token/) - - expect(page).to have_content(t('account_reset.delete_account.title')) - expect(page).to have_current_path(account_reset_delete_account_path) - - click_button t('account_reset.request.yes_continue') - - expect(page).to have_content( - strip_tags( - t( - 'account_reset.confirm_delete_account.info_html', - email: user_email, - link: t('account_reset.confirm_delete_account.link_text'), - ), - ), - ) - expect(page).to have_current_path(account_reset_confirm_delete_account_path) - expect(User.where(id: user.id)).to be_empty - deleted_user = DeletedUser.find_by(user_id: user.id) - expect(deleted_user.user_id).to eq(user.id) - expect(deleted_user.uuid).to eq(user.uuid) - expect(last_email.subject).to eq t('user_mailer.account_reset_complete.subject') - - click_link t('account_reset.confirm_delete_account.link_text') - - expect(page).to have_current_path(sign_up_email_path) - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - expected_event_types = %w[account-reset-account-deleted] - received_event_types = events.map(&:event_type) - - expect(events.count).to eq received_event_types.count - expect(received_event_types).to match_array(expected_event_types) - end - end - end end diff --git a/spec/features/idv/threat_metrix_pending_spec.rb b/spec/features/idv/threat_metrix_pending_spec.rb index bafd3f8334a..bf88bdf086c 100644 --- a/spec/features/idv/threat_metrix_pending_spec.rb +++ b/spec/features/idv/threat_metrix_pending_spec.rb @@ -9,12 +9,6 @@ before do allow(IdentityConfig.store).to receive(:proofing_device_profiling).and_return(:enabled) allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_org_id).and_return('test_org') - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - allow(IdentityConfig.store).to receive(:irs_attempt_api_track_tmx_fraud_check_event). - and_return(true) - allow(IdentityConfig.store).to receive(:irs_attempt_api_idv_events_enabled). - and_return(true) - mock_irs_attempts_api_encryption_key end let(:service_provider) do @@ -97,7 +91,6 @@ scenario 'users ThreatMetrix Pass, it logs idv_tmx_fraud_check event' do freeze_time do complete_all_idv_steps_with(threatmetrix: 'Pass') - expect_irs_event(expected_success: true, expected_failure_reason: nil) end end @@ -118,7 +111,6 @@ user = create(:user, :fully_registered) visit_idp_from_ial1_oidc_sp( client_id: service_provider.issuer, - irs_attempts_api_session_id: 'test-session-id', ) visit root_path sign_in_and_2fa_user(user) @@ -136,23 +128,5 @@ def expect_pending_failure_reason(threatmetrix:) complete_all_idv_steps_with(threatmetrix: threatmetrix) expect(page).to have_content(t('idv.failure.setup.heading')) expect(page).to have_current_path(idv_please_call_path) - expect_irs_event( - expected_success: false, - expected_failure_reason: DocAuthHelper::SAMPLE_TMX_SUMMARY_REASON_CODE, - ) - end - - def expect_irs_event(expected_success:, expected_failure_reason:) - event_name = 'idv-tmx-fraud-check' - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - received_event_types = events.map(&:event_type) - - idv_tmx_fraud_check_event = events.find { |x| x.event_type == event_name } - failure_reason = idv_tmx_fraud_check_event.event_metadata[:failure_reason] - success = idv_tmx_fraud_check_event.event_metadata[:success] - - expect(received_event_types).to include event_name - expect(failure_reason).to eq expected_failure_reason.as_json - expect(success).to eq expected_success end end diff --git a/spec/features/irs_attempts_api/event_tracking_spec.rb b/spec/features/irs_attempts_api/event_tracking_spec.rb deleted file mode 100644 index 3a027125dbf..00000000000 --- a/spec/features/irs_attempts_api/event_tracking_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'rails_helper' - -RSpec.feature 'IRS Attempts API Event Tracking' do - include OidcAuthHelper - include IrsAttemptsApiTrackingHelper - - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - mock_irs_attempts_api_encryption_key - end - - let(:service_provider) do - create( - :service_provider, - active: true, - redirect_uris: ['http://localhost:7654/auth/result'], - ial: 2, - irs_attempts_api_enabled: true, - ) - end - - scenario 'signing in from an IRS SP with an attempts api session id tracks events' do - freeze_time do - user = create(:user, :fully_registered) - - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - irs_attempts_api_session_id: 'test-session-id', - ) - - sign_in_user(user) - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - expected_event_types = %w[login-email-and-password-auth mfa-login-phone-otp-sent] - - received_event_types = events.map(&:event_type) - - expect(events.count).to eq received_event_types.count - expect(received_event_types).to match_array(expected_event_types) - - metadata = events.first.event_metadata - expect(metadata[:user_ip_address]).to eq '127.0.0.1' - expect(metadata[:irs_application_url]).to eq 'http://localhost:7654/auth/result' - expect(metadata[:unique_session_id]).to be_a(String) - expect(metadata[:success]).to be_truthy - end - end - - scenario 'signing in from an IRS SP with a tid tracks events' do - freeze_time do - user = create(:user, :fully_registered) - - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - tid: 'test-session-id', - ) - - sign_in_user(user) - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - expected_event_types = %w[login-email-and-password-auth mfa-login-phone-otp-sent] - - received_event_types = events.map(&:event_type) - - expect(events.count).to eq received_event_types.count - expect(received_event_types).to match_array(expected_event_types) - - metadata = events.first.event_metadata - expect(metadata[:user_ip_address]).to eq '127.0.0.1' - expect(metadata[:irs_application_url]).to eq 'http://localhost:7654/auth/result' - expect(metadata[:unique_session_id]).to be_a(String) - expect(metadata[:success]).to be_truthy - end - end - - scenario 'signing in from a non-IRS SP with a tid does not track events' do - freeze_time do - service_provider.update!(irs_attempts_api_enabled: false) - - user = create(:user, :fully_registered) - - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - tid: 'test-session_id', - ) - - sign_in_user(user) - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - - expect(events.count).to eq(0) - end - end - - scenario 'signing in from an IRS SP without an attempts api session id or tid tracks events' do - freeze_time do - user = create(:user, :fully_registered) - - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - ) - - sign_in_user(user) - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - - expect(events.count).to eq(2) - end - end - - scenario 'signing in from a non-IRS SP with an attempts api session id does not track events' do - freeze_time do - service_provider.update!(irs_attempts_api_enabled: false) - - user = create(:user, :fully_registered) - - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - irs_attempts_api_session_id: 'test-session_id', - ) - - sign_in_user(user) - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - - expect(events.count).to eq(0) - end - end - - scenario 'reset password from an IRS with new browser session and request_id tracks events' do - freeze_time do - user = create(:user, :fully_registered) - visit_idp_from_ial1_oidc_sp( - client_id: service_provider.issuer, - irs_attempts_api_session_id: 'test-session-id', - ) - - visit root_path - fill_forgot_password_form(user) - set_new_browser_session - click_reset_password_link_from_email - fill_reset_password_form - - events = irs_attempts_api_tracked_events(timestamp: Time.zone.now) - expected_event_types = %w[forgot-password-email-sent forgot-password-email-confirmed - forgot-password-new-password-submitted] - received_event_types = events.map(&:event_type) - - expect(events.count).to eq received_event_types.count - expect(received_event_types).to match_array(expected_event_types) - end - end -end diff --git a/spec/features/users/sign_in_irs_spec.rb b/spec/features/users/sign_in_irs_spec.rb deleted file mode 100644 index f3707faabbb..00000000000 --- a/spec/features/users/sign_in_irs_spec.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'rails_helper' - -RSpec.feature 'Sign in to the IRS' do - before(:all) do - @original_capyabara_wait = Capybara.default_max_wait_time - Capybara.default_max_wait_time = 5 - end - - after(:all) do - Capybara.default_max_wait_time = @original_capyabara_wait - end - - include IdvHelper - include SamlAuthHelper - - let(:irs) { create(:service_provider, :irs) } - let(:other_irs) { create(:service_provider, :irs) } - let(:not_irs) { create(:service_provider, active: true, ial: 2) } - - let(:initiating_service_provider_issuer) { irs.issuer } - - let(:user) do - create( - :profile, :active, :verified, - pii: { first_name: 'John', ssn: '111223333' }, - initiating_service_provider_issuer: initiating_service_provider_issuer - ).user - end - - context 'OIDC' do - context 'user verified with IRS returns to IRS' do - context 'user visits the same IRS SP they verified with' do - it "accepts the user's identity as verified" do - visit_idp_from_oidc_sp_with_ial2(client_id: irs.issuer) - fill_in_credentials_and_submit(user.email, user.password) - fill_in_code_with_last_phone_otp - click_submit_default - - expect(current_path).to eq(sign_up_completed_path) - end - end - - context 'user visits different IRS SP than the one they verified with' do - it "accepts the user's identity as verified" do - visit_idp_from_oidc_sp_with_ial2(client_id: other_irs.issuer) - fill_in_credentials_and_submit(user.email, user.password) - fill_in_code_with_last_phone_otp - click_submit_default - - expect(current_path).to eq(sign_up_completed_path) - end - end - end - - context 'user verified with other agency signs in to IRS' do - let(:initiating_service_provider_issuer) { not_irs.issuer } - - before do - visit_idp_from_oidc_sp_with_ial2(client_id: irs.issuer) - fill_in_credentials_and_submit(user.email, user.password) - fill_in_code_with_last_phone_otp - click_submit_default - end - - it 'displays the text explaining about IRS re-proofing' do - expect(page).to have_content(t('doc_auth.info.irs_reproofing_explanation')) - end - - it 'forces the user to re-verify their identity' do - expect(current_path).to eq(idv_welcome_path) - end - end - end - - context 'SAML', js: true do - context 'user verified with IRS returns to IRS' do - context 'user visits the same IRS SP they verified with' do - it "accepts the user's identity as verified" do - visit_idp_from_saml_sp_with_ial2(issuer: irs.issuer) - fill_in_credentials_and_submit(user.email, user.password) - fill_in_code_with_last_phone_otp - click_submit_default - - expect(current_path).to eq(sign_up_completed_path) - end - end - - context 'user visits different IRS SP than the one they verified with' do - it "accepts the user's identity as verified" do - visit_idp_from_saml_sp_with_ial2(issuer: other_irs.issuer) - fill_in_credentials_and_submit(user.email, user.password) - fill_in_code_with_last_phone_otp - click_submit_default - - expect(current_path).to eq(sign_up_completed_path) - end - end - end - - context 'user verified with other agency signs in to IRS' do - let(:initiating_service_provider_issuer) { not_irs.issuer } - - before do - visit_idp_from_saml_sp_with_ial2(issuer: irs.issuer) - fill_in_credentials_and_submit(user.email, user.password) - fill_in_code_with_last_phone_otp - click_submit_default - end - - it 'displays the text explaining about IRS re-proofing' do - expect(page).to have_content(t('doc_auth.info.irs_reproofing_explanation')) - end - - it 'forces the user to re-verify their identity' do - expect(current_path).to eq(idv_welcome_path) - end - end - end -end diff --git a/spec/jobs/irs_attempts_events_batch_job_spec.rb b/spec/jobs/irs_attempts_events_batch_job_spec.rb deleted file mode 100644 index ad1e15be1b4..00000000000 --- a/spec/jobs/irs_attempts_events_batch_job_spec.rb +++ /dev/null @@ -1,233 +0,0 @@ -require 'rails_helper' - -RSpec.describe IrsAttemptsEventsBatchJob, type: :job do - describe '#perform' do - context 'IRS attempts API is enabled' do - let(:start_time) { Time.new(2020, 1, 1, 12, 0, 0, 'UTC') } - let(:previous_hour_log_file) do - { - filename: 'prev_filename', - iv: 'mock_encoded_iv', - encrypted_key: 'mock_encoded_encrypted_key', - requested_time: - IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(start_time - 1.hour), - - } - end - let(:events) do - [ - { - event_key: 'key1', - jwe: 'some_event_data_encrypted_with_jwe', - timestamp: start_time + 10.minutes, - }, - { - event_key: 'key2', - jwe: 'some_other_event_data_encrypted_with_jwe', - timestamp: start_time + 15.minutes, - }, - ] - end - let(:private_key) { OpenSSL::PKey::RSA.new(4096) } - let(:encoded_public_key) { Base64.strict_encode64(private_key.public_key.to_der) } - let(:expected_encrypted_events) do - { - data: events.pluck(:jwe).join("\r\n"), - timestamp: start_time, - public_key_str: encoded_public_key, - } - end - - let(:bucket_name) { 'test-bucket-name' } - let(:envelope_encryptor_result) do - IrsAttemptsApi::EnvelopeEncryptor::Result.new( - filename: 'test-filename', - iv: 'test-iv', - encrypted_key: 'test-encrypted-key', - encrypted_data: 'test-encrypted-data', - ) - end - let(:expected_s3_call) do - { - bucket_name: bucket_name, - filename: envelope_encryptor_result[:filename], - encrypted_data: envelope_encryptor_result[:encrypted_data], - - } - end - let(:expected_batch_results) do - { filename: envelope_encryptor_result[:filename], - iv: Base64.strict_encode64(envelope_encryptor_result[:iv]), - encrypted_key: Base64.strict_encode64(envelope_encryptor_result[:encrypted_key]), - requested_time: IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(start_time) } - end - - let(:logger_info_attributes) do - { - name: 'IRSAttemptsEventJob', - start_time: Time.zone.now, - end_time: Time.zone.now, - duration_ms: 0.1234, - events_count: 2, - file_bytes_size: 19, - - } - end - - let!(:s3_put_object_response) { double(etag: true) } - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_enabled).and_return(true) - - allow(IdentityConfig.store).to receive(:irs_attempt_api_public_key). - and_return(encoded_public_key) - - allow(IrsAttemptsApi::EnvelopeEncryptor).to receive(:encrypt). - and_return(envelope_encryptor_result) - - allow_any_instance_of(described_class).to receive( - :create_and_upload_to_attempts_s3_resource, - ).and_return(s3_put_object_response) - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name). - and_return(bucket_name) - - allow_any_instance_of(described_class).to receive(:duration_ms).and_return(0.1234) - - travel_to start_time + 1.hour - - redis_client = IrsAttemptsApi::RedisClient.new - events.each do |event| - redis_client.write_event(**event) - end - end - - context 'When there are no missing previous files' do - before do - IrsAttemptApiLogFile.create(**previous_hour_log_file) - expect_any_instance_of(described_class).not_to receive(:perform_later) - end - - it 'batches/writes attempt events, and does not call BatchJob on previous hour' do - expect(IrsAttemptsApi::EnvelopeEncryptor).to receive(:encrypt).with( - **expected_encrypted_events, - ) - - expect_any_instance_of(described_class).to receive( - :create_and_upload_to_attempts_s3_resource, - ).with( - **expected_s3_call, - ) - - expect_any_instance_of(described_class).to receive(:logger_info_hash).with( - **logger_info_attributes, - ) - - result = IrsAttemptsEventsBatchJob.perform_now(start_time) - - expect(result).not_to be_nil - expect(result).to have_attributes(expected_batch_results) - end - - context 'When irs delete_events feature flag and s3 put_object response are true' do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_delete_events_after_s3_upload). - and_return(true) - end - - it 'delete the events from redis' do - IrsAttemptsEventsBatchJob.perform_now - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: start_time) - expect(events.count).to eq 0 - end - end - - context 'When irs delete_events feature flag is false' do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_delete_events_after_s3_upload). - and_return(false) - end - - it 'does not delete the events from redis' do - IrsAttemptsEventsBatchJob.perform_now - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: start_time) - expect(events.count).to eq 2 - end - end - - context 'When irs delete_events feature flag is true and s3 put_object response is false' do - let!(:s3_put_object_response) { double(etag: false) } - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_delete_events_after_s3_upload). - and_return(true) - end - - it 'does not delete the events from redis' do - IrsAttemptsEventsBatchJob.perform_now - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: start_time) - expect(events.count).to eq 2 - end - end - end - - context 'When there are missing previous files' do - it 'batches/writes expected attempt events and calls BatchJob on previous hour' do - expect(described_class).to receive(:perform_later).with( - start_time - 1.hour, - ) - - expect(IrsAttemptsApi::EnvelopeEncryptor).to receive(:encrypt).with( - **expected_encrypted_events, - ) - - expect_any_instance_of(described_class).to receive( - :create_and_upload_to_attempts_s3_resource, - ).with( - **expected_s3_call, - ) - - expect_any_instance_of(described_class).to receive(:logger_info_hash).with( - **logger_info_attributes, - ) - - result = IrsAttemptsEventsBatchJob.perform_now(start_time) - - expect(result).not_to be_nil - expect(result).to have_attributes(expected_batch_results) - end - end - end - - context 'IRS attempts API is not enabled' do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(false) - end - - it 'returns nil' do - result = IrsAttemptsEventsBatchJob.perform_now - expect(result).to eq(nil) - end - end - - context 'IRS attempts bucket name is not set' do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name).and_return(false) - end - - it 'returns nil' do - result = IrsAttemptsEventsBatchJob.perform_now - expect(result).to eq(nil) - end - end - end - - describe '#reasonable_timespan?' do - it 'returns true for yesterday' do - result = IrsAttemptsEventsBatchJob.new.reasonable_timespan?(Time.zone.now - 1.day) - expect(result).to eq(true) - end - it 'returns false for a week ago' do - result = IrsAttemptsEventsBatchJob.new.reasonable_timespan?(Time.zone.now - 7.days) - expect(result).to eq(false) - end - end -end diff --git a/spec/jobs/job_helpers/s3_helper_spec.rb b/spec/jobs/job_helpers/s3_helper_spec.rb index a144d34ed00..b3467924773 100644 --- a/spec/jobs/job_helpers/s3_helper_spec.rb +++ b/spec/jobs/job_helpers/s3_helper_spec.rb @@ -83,63 +83,4 @@ expect(s3_helper.download(url).encoding.name).to eq('ASCII-8BIT') end end - - let(:valid_bucket_name) { 'valid-attempts-api-s3-bucket' } - - describe '#attempts_bucket_methods' do - subject(:attempts_s3_write_enabled) { s3_helper.attempts_s3_write_enabled } - - context 'with no bucket name' do - it 'should return nil' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name).and_return(nil) - is_expected.to eq(nil) - end - end - - context 'with a default bucket name' do - it 'should return false' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name). - and_return('default-placeholder') - is_expected.to eq(false) - end - end - - context 'with a valid bucket name' do - it 'should return true' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name). - and_return(valid_bucket_name) - is_expected.to eq(true) - end - end - end - - describe '#attempts_serve_events_from_s3' do - subject(:attempts_serve_events_from_s3) { s3_helper.attempts_serve_events_from_s3 } - - context 'with s3 disabled and a valid s3 bucket' do - it 'should return false' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_enabled).and_return(false) - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name). - and_return(valid_bucket_name) - is_expected.to eq(false) - end - end - - context 'with s3 disabled and no s3 bucket' do - it 'should return false' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_enabled).and_return(false) - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name).and_return(nil) - is_expected.to eq(false) - end - end - - context 'with s3 enabled and a valid s3 bucket' do - it 'should return true' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_aws_s3_enabled).and_return(true) - allow(IdentityConfig.store).to receive(:irs_attempt_api_bucket_name). - and_return('valid-attempts-api-s3-bucket') - is_expected.to eq(true) - end - end - end end diff --git a/spec/models/irs_attempt_api_log_file_spec.rb b/spec/models/irs_attempt_api_log_file_spec.rb deleted file mode 100644 index cdcf7ccdf7a..00000000000 --- a/spec/models/irs_attempt_api_log_file_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe IrsAttemptApiLogFile, type: :model do -end diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 5ceef7b3864..47060c0f145 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -530,7 +530,6 @@ end it 'logs an attempt event' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) expect(profile.initiating_service_provider.irs_attempts_api_enabled?).to be_truthy expect(profile.irs_attempts_api_tracker).to receive(:fraud_review_adjudicated). @@ -540,38 +539,6 @@ profile.activate_after_passing_review end end - - context 'when the feature flag is disabled' do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_track_idv_fraud_review). - and_return(false) - end - - it 'does not log an attempt event' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - expect(profile.initiating_service_provider.irs_attempts_api_enabled?).to be_truthy - - expect(profile.irs_attempts_api_tracker).not_to receive(:fraud_review_adjudicated) - profile.activate_after_passing_review - end - end - end - - context 'when the initiating_sp is not the IRS' do - it 'does not log an attempt event' do - sp = create(:service_provider) - profile = create( - :profile, - user: user, - active: false, - fraud_review_pending_at: 1.day.ago, - initiating_service_provider: sp, - ) - expect(profile.initiating_service_provider.irs_attempts_api_enabled?).to be_falsey - - expect(profile.irs_attempts_api_tracker).not_to receive(:fraud_review_adjudicated) - profile.activate_after_passing_review - end end end @@ -683,7 +650,6 @@ context 'and notify_user is true' do it 'logs an event with manual_reject' do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) allow(IdentityConfig.store).to receive(:irs_attempt_api_track_idv_fraud_review). and_return(true) @@ -715,24 +681,6 @@ end end end - - context 'when the SP is not the IRS' do - it 'does not log an event' do - sp = create(:service_provider) - profile = user.profiles.create( - active: false, - fraud_review_pending_at: 1.day.ago, - initiating_service_provider: sp, - ) - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled).and_return(true) - - expect(profile.initiating_service_provider.irs_attempts_api_enabled?).to be_falsey - - expect(profile.irs_attempts_api_tracker).not_to receive(:fraud_review_adjudicated) - - profile.reject_for_fraud(notify_user: true) - end - end end describe 'scopes' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 912de11754c..463a043c697 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -121,10 +121,6 @@ class Analytics descendants.each(&:disable_test_adapter) end - config.before(:each) do - IrsAttemptsApi::RedisClient.clear_attempts! - end - config.before(:each) do Rails.cache.clear end diff --git a/spec/services/analytics_spec.rb b/spec/services/analytics_spec.rb index 71c59518756..1a0fcc2bc76 100644 --- a/spec/services/analytics_spec.rb +++ b/spec/services/analytics_spec.rb @@ -37,7 +37,6 @@ let(:request) { FakeRequest.new } let(:path) { 'fake_path' } let(:success_state) { 'GET|fake_path|Trackable Event' } - let(:irs_session_id) { nil } subject(:analytics) do Analytics.new( @@ -46,7 +45,6 @@ sp: 'http://localhost:3000', session: {}, ahoy: ahoy, - irs_session_id: irs_session_id, ) end @@ -85,31 +83,6 @@ analytics.track_event('Trackable Event', user_id: tracked_user.uuid) end - context 'with an irs_session_id' do - let(:irs_session_id) { 'abc123' } - - it 'includes irs_session_id' do - expect(ahoy).to receive(:track).with( - 'Trackable Event', - analytics_attributes.merge(irs_session_id: irs_session_id), - ) - - analytics.track_event('Trackable Event') - end - end - - context 'without an irs_session_id' do - let(:irs_session_id) { nil } - - it 'omits the irs_session_id key entirely' do - expect(ahoy).to receive(:track).with( - 'Trackable Event', - hash_excluding(irs_session_id: irs_session_id), - ) - analytics.track_event('Trackable Event') - end - end - context 'tracing headers' do let(:amazon_trace_id) { SecureRandom.hex } let(:request) do diff --git a/spec/services/irs_attempts_api/attempt_event_spec.rb b/spec/services/irs_attempts_api/attempt_event_spec.rb deleted file mode 100644 index a994db53a6d..00000000000 --- a/spec/services/irs_attempts_api/attempt_event_spec.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'rails_helper' - -RSpec.describe IrsAttemptsApi::AttemptEvent do - let(:irs_attempts_api_private_key) { OpenSSL::PKey::RSA.new(4096) } - let(:irs_attempts_api_public_key) { irs_attempts_api_private_key.public_key } - - before do - encoded_public_key = Base64.strict_encode64(irs_attempts_api_public_key.to_der) - allow(IdentityConfig.store).to receive(:irs_attempt_api_public_key). - and_return(encoded_public_key) - end - - let(:jti) { 'test-unique-id' } - let(:iat) { Time.zone.now.to_i } - let(:event_type) { 'test-event' } - let(:session_id) { 'test-session-id' } - let(:occurred_at) { Time.zone.now.round } - let(:event_metadata) { { 'foo' => 'bar' } } - - subject do - described_class.new( - jti: jti, - iat: iat, - event_type: event_type, - session_id: session_id, - occurred_at: occurred_at, - event_metadata: event_metadata, - ) - end - - describe '#to_jwe' do - it 'returns a JWE for the event' do - jwe = subject.to_jwe - - header_str, *_rest = JWE::Serialization::Compact.decode(jwe) - headers = JSON.parse(header_str) - - expect(headers['alg']).to eq('RSA-OAEP') - expect(headers['kid']).to eq(JWT::JWK.new(irs_attempts_api_public_key).kid) - - decrypted_jwe_payload = JWE.decrypt(jwe, irs_attempts_api_private_key) - - token = JSON.parse(decrypted_jwe_payload) - - expect(token['iss']).to eq('http://www.example.com/') - expect(token['jti']).to eq(jti) - expect(token['iat']).to eq(iat) - expect(token['aud']).to eq('https://irs.gov') - - event_key = 'https://schemas.login.gov/secevent/irs-attempts-api/event-type/test-event' - event_data = token['events'][event_key] - - expect(event_data['subject']).to eq( - 'subject_type' => 'session', 'session_id' => 'test-session-id', - ) - expect(event_data['foo']).to eq('bar') - expect(event_data['occurred_at']).to eq(occurred_at.to_f) - end - end - - describe '.from_jwe' do - it 'returns an event decrypted from the JWE' do - jwe = subject.to_jwe - - decrypted_event = described_class.from_jwe(jwe, irs_attempts_api_private_key) - - expect(decrypted_event.jti).to eq(subject.jti) - expect(decrypted_event.iat).to eq(subject.iat) - expect(decrypted_event.event_type).to eq(subject.event_type) - expect(decrypted_event.session_id).to eq(subject.session_id) - expect(decrypted_event.occurred_at).to eq(subject.occurred_at) - expect(decrypted_event.event_metadata).to eq(subject.event_metadata.symbolize_keys) - end - end -end diff --git a/spec/services/irs_attempts_api/envelope_encryptor_spec.rb b/spec/services/irs_attempts_api/envelope_encryptor_spec.rb deleted file mode 100644 index ad1c3b4d339..00000000000 --- a/spec/services/irs_attempts_api/envelope_encryptor_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'rails_helper' -RSpec.describe IrsAttemptsApi::EnvelopeEncryptor do - let(:private_key) { OpenSSL::PKey::RSA.new(4096) } - let(:public_key) { Base64.strict_encode64(private_key.public_key.to_der) } - describe '.encrypt' do - it 'returns encrypted result' do - text = Idp::Constants::MOCK_IDV_APPLICANT[:first_name] - time = Time.zone.now - result = IrsAttemptsApi::EnvelopeEncryptor.encrypt( - data: text, timestamp: time, public_key_str: public_key, - ) - - expect(result.encrypted_data).to_not eq text - expect(result.encrypted_data).to_not include(text) - expect(Base16.decode16(result.encrypted_data)).to_not include(text) - end - - it 'filename includes digest and truncated timestamp' do - text = Idp::Constants::MOCK_IDV_APPLICANT[:first_name] - time = Time.zone.now - result = IrsAttemptsApi::EnvelopeEncryptor.encrypt( - data: text, timestamp: time, - public_key_str: public_key - ) - digest = Digest::SHA256.hexdigest(result.encrypted_data) - - expect(result.filename).to include( - IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(time), - ) - expect(result.filename).to include(digest) - end - end - - describe '.decrypt' do - it 'returns decrypted text' do - text = Idp::Constants::MOCK_IDV_APPLICANT[:first_name] - time = Time.zone.now - result = IrsAttemptsApi::EnvelopeEncryptor.encrypt( - data: text, timestamp: time, - public_key_str: public_key - ) - key = private_key.private_decrypt(result.encrypted_key) - - expect( - IrsAttemptsApi::EnvelopeEncryptor.decrypt( - encrypted_data: result.encrypted_data, - key: key, - iv: result.iv, - ), - ).to eq(text) - end - end - - describe '.formatted_timestamp' do - it 'formats according to the specification' do - timestamp = Time.new(2022, 1, 1, 11, 1, 1, 'UTC') - result = IrsAttemptsApi::EnvelopeEncryptor.formatted_timestamp(timestamp) - - expect(result).to eq '20220101T11Z' - end - end -end diff --git a/spec/services/irs_attempts_api/redis_client_spec.rb b/spec/services/irs_attempts_api/redis_client_spec.rb deleted file mode 100644 index cdadd341e9c..00000000000 --- a/spec/services/irs_attempts_api/redis_client_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'rails_helper' - -RSpec.describe IrsAttemptsApi::RedisClient do - describe '#write_event' do - it 'writes the attempt data to redis with the event key as the key' do - freeze_time do - now = Time.zone.now - event = IrsAttemptsApi::AttemptEvent.new( - event_type: 'test_event', - session_id: 'test-session-id', - occurred_at: Time.zone.now, - event_metadata: { - first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name], - }, - ) - event_key = event.event_key - jwe = event.to_jwe - - subject.write_event(event_key: event_key, jwe: jwe, timestamp: now) - - result = subject.redis_pool.with do |client| - client.hget(subject.key(now), event_key) - end - expect(result).to eq(jwe) - end - end - end - - describe '#read_events' do - it 'reads the event events from redis' do - freeze_time do - now = Time.zone.now - events = {} - 3.times do - event = IrsAttemptsApi::AttemptEvent.new( - event_type: 'test_event', - session_id: 'test-session-id', - occurred_at: now, - event_metadata: { - first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name], - }, - ) - event_key = event.event_key - jwe = event.to_jwe - events[event_key] = jwe - end - events.each do |event_key, jwe| - subject.write_event(event_key: event_key, jwe: jwe, timestamp: now) - end - - result = subject.read_events(timestamp: now) - - expect(result).to eq(events) - end - end - - it 'stores events in hourly buckets' do - time1 = Time.new(2022, 1, 1, 1, 0, 0, 'Z') - time2 = Time.new(2022, 1, 1, 2, 0, 0, 'Z') - event1 = IrsAttemptsApi::AttemptEvent.new( - event_type: 'test_event', - session_id: 'test-session-id', - occurred_at: time1, - event_metadata: { - first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name], - }, - ) - event2 = IrsAttemptsApi::AttemptEvent.new( - event_type: 'test_event', - session_id: 'test-session-id', - occurred_at: time2, - event_metadata: { - first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name], - }, - ) - jwe1 = event1.to_jwe - jwe2 = event2.to_jwe - - subject.write_event(event_key: event1.event_key, jwe: jwe1, timestamp: event1.occurred_at) - subject.write_event(event_key: event2.event_key, jwe: jwe2, timestamp: event2.occurred_at) - - expect(subject.read_events(timestamp: time1)).to eq({ event1.event_key => jwe1 }) - expect(subject.read_events(timestamp: time2)).to eq({ event2.event_key => jwe2 }) - end - end -end diff --git a/spec/services/irs_attempts_api/tracker_spec.rb b/spec/services/irs_attempts_api/tracker_spec.rb deleted file mode 100644 index 03f51b40ae4..00000000000 --- a/spec/services/irs_attempts_api/tracker_spec.rb +++ /dev/null @@ -1,214 +0,0 @@ -require 'rails_helper' - -RSpec.describe IrsAttemptsApi::Tracker do - before do - allow(IdentityConfig.store).to receive(:irs_attempt_api_enabled). - and_return(irs_attempts_api_enabled) - allow(IdentityConfig.store).to receive(:irs_attempt_api_payload_size_logging_enabled). - and_return(irs_attempts_api_payload_size_logging_enabled) - allow(IdentityConfig.store).to receive(:irs_attempt_api_idv_events_enabled). - and_return(irs_attempts_api_idv_events_enabled) - allow(request).to receive(:user_agent).and_return('example/1.0') - allow(request).to receive(:remote_ip).and_return('192.0.2.1') - allow(request).to receive(:headers).and_return( - { 'CloudFront-Viewer-Address' => '192.0.2.1:1234' }, - ) - end - - let(:irs_attempts_api_enabled) { true } - let(:irs_attempts_api_payload_size_logging_enabled) { true } - let(:irs_attempts_api_idv_events_enabled) { true } - let(:session_id) { 'test-session-id' } - let(:enabled_for_session) { true } - let(:request) { instance_double(ActionDispatch::Request) } - let(:service_provider) { create(:service_provider) } - let(:cookie_device_uuid) { 'device_id' } - let(:sp_request_uri) { 'https://example.com/auth_page' } - let(:user) { create(:user) } - let(:analytics) { FakeAnalytics.new } - - subject do - described_class.new( - session_id: session_id, - request: request, - user: user, - sp: service_provider, - cookie_device_uuid: cookie_device_uuid, - sp_request_uri: sp_request_uri, - enabled_for_session: enabled_for_session, - analytics: analytics, - ) - end - - describe '#track_event' do - it 'omit failure reason when success is true' do - freeze_time do - event = subject.track_event(:test_event, foo: :bar, success: true, failure_reason: nil) - expect(event.event_metadata).to_not have_key(:failure_reason) - end - end - it 'omit failure reason when failure_reason is blank' do - freeze_time do - event = subject.track_event(:test_event, foo: :bar, failure_reason: nil) - expect(event.event_metadata).to_not have_key(:failure_reason) - end - end - it 'should not omit failure reason when success is false and failure_reason is not blank' do - freeze_time do - event = subject.track_event( - :test_event, foo: :bar, success: false, - failure_reason: { foo: [:bar] } - ) - expect(event.event_metadata).to have_key(:failure_reason) - expect(event.event_metadata).to have_key(:success) - end - end - it 'records the event in redis' do - freeze_time do - subject.track_event(:test_event, foo: :bar) - - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now) - - expect(events.values.length).to eq(1) - end - end - - it 'records an idv event in redis' do - freeze_time do - subject.track_event(:idv_test_event, foo: :bar) - - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now) - - expect(events.values.length).to eq(1) - end - end - - it 'does not store events in plaintext in redis' do - freeze_time do - subject.track_event(:event, first_name: Idp::Constants::MOCK_IDV_APPLICANT[:first_name]) - - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now) - - expect(events.keys.first).to_not include('first_name') - expect(events.values.first).to_not include(Idp::Constants::MOCK_IDV_APPLICANT[:first_name]) - end - end - - context 'with idv events disabled' do - let(:irs_attempts_api_idv_events_enabled) { false } - - it 'does not log or track anything about the event' do - expect(analytics).to_not receive(:irs_attempts_api_event_metadata).with( - event_type: :idv_test_event, - unencrypted_payload_num_bytes: kind_of(Integer), - recorded: true, - ) - - freeze_time do - subject.track_event(:idv_test_event, foo: :bar) - - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now) - - expect(events.values.length).to eq(0) - end - end - end - - context 'without a service provider' do - let(:service_provider) { nil } - - it 'still logs metadata about the event' do - expect(analytics).to receive(:irs_attempts_api_event_metadata).with( - event_type: :test_event, - unencrypted_payload_num_bytes: kind_of(Integer), - recorded: true, - ) - - event = subject.track_event(:test_event, foo: :bar) - - expect(event.payload[:events].first.last[:user_uuid]). - to eq(nil), 'has a nil user_uuid because there can be no pairwise uuid for no agency' - end - end - - context 'the current session is not an IRS attempt API session' do - let(:enabled_for_session) { false } - - it 'does not record any events in redis' do - freeze_time do - subject.track_event(:test_event, foo: :bar) - - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now) - - expect(events.values.length).to eq(0) - end - end - - it 'does not log metadata about the event' do - expect(analytics).to_not receive(:irs_attempts_api_event_metadata) - - subject.track_event(:test_event, foo: :bar) - end - end - - context 'the IRS attempts API is not enabled' do - let(:irs_attempts_api_enabled) { false } - - it 'does not record any events in redis' do - freeze_time do - subject.track_event(:test_event, foo: :bar) - - events = IrsAttemptsApi::RedisClient.new.read_events(timestamp: Time.zone.now) - - expect(events.values.length).to eq(0) - end - end - - it 'does not log metadata about the event' do - expect(analytics).to_not receive(:irs_attempts_api_event_metadata) - - subject.track_event(:test_event, foo: :bar) - end - end - - context 'metadata logging is disabled' do - let(:irs_attempts_api_payload_size_logging_enabled) { false } - - it 'does not log metadata about the event' do - expect(analytics).to_not receive(:irs_attempts_api_event_metadata) - - subject.track_event(:test_event, foo: :bar) - end - end - end - - describe '#parse_failure_reason' do - let(:mock_error_message) { 'failure_reason_from_error' } - let(:mock_error_details) { [{ mock_error: 'failure_reason_from_error_details' }] } - - it 'parses failure_reason from error_details' do - test_failure_reason = subject.parse_failure_reason( - { errors: mock_error_message, - error_details: mock_error_details }, - ) - - expect(test_failure_reason).to eq(mock_error_details) - end - - it 'parses failure_reason from errors when no error_details present' do - class MockFailureReason - def errors - 'failure_reason_from_error' - end - - def to_h - {} - end - end - - test_failure_reason = subject.parse_failure_reason(MockFailureReason.new) - - expect(test_failure_reason).to eq(mock_error_message) - end - end -end diff --git a/spec/support/fake_analytics.rb b/spec/support/fake_analytics.rb index aea20734278..2a3fe4ebe0e 100644 --- a/spec/support/fake_analytics.rb +++ b/spec/support/fake_analytics.rb @@ -92,12 +92,6 @@ def track_mfa_submit_event(_attributes) # no-op end - # no-op this event in fake analytics, because it adds a lot of noise to tests - # expecting other events - def irs_attempts_api_event_metadata(**_attrs) - # no-op - end - def browser_attributes {} end diff --git a/spec/support/features/doc_auth_helper.rb b/spec/support/features/doc_auth_helper.rb index 922f5857142..d947dc85bb2 100644 --- a/spec/support/features/doc_auth_helper.rb +++ b/spec/support/features/doc_auth_helper.rb @@ -275,7 +275,6 @@ def complete_all_idv_steps_with(threatmetrix:) user = create(:user, :fully_registered) visit_idp_from_ial1_oidc_sp( client_id: service_provider.issuer, - irs_attempts_api_session_id: 'test-session-id', ) visit root_path sign_in_and_2fa_user(user) diff --git a/spec/support/features/irs_attempts_api_tracking_helper.rb b/spec/support/features/irs_attempts_api_tracking_helper.rb index 4c0771c91e0..8bb29415fe9 100644 --- a/spec/support/features/irs_attempts_api_tracking_helper.rb +++ b/spec/support/features/irs_attempts_api_tracking_helper.rb @@ -1,31 +1,4 @@ module IrsAttemptsApiTrackingHelper - class IrsAttemptsApiEventDecryptor - def self.public_key - private_key.public_key - end - - def self.private_key - @private_key ||= OpenSSL::PKey::RSA.new(4096) - end - - def decrypted_events_from_store(timestamp:) - jwes = IrsAttemptsApi::RedisClient.new.read_events(timestamp: timestamp) - jwes.transform_values do |jwe| - IrsAttemptsApi::AttemptEvent.from_jwe(jwe, self.class.private_key) - end - end - end - - def mock_irs_attempts_api_encryption_key - encoded_key = Base64.strict_encode64(IrsAttemptsApiEventDecryptor.public_key.to_der) - allow(IdentityConfig.store).to receive(:irs_attempt_api_public_key). - and_return(encoded_key) - end - - def irs_attempts_api_tracked_events(timestamp:) - IrsAttemptsApiEventDecryptor.new.decrypted_events_from_store(timestamp: timestamp).values - end - def stub_attempts_tracker irs_attempts_api_tracker = FakeAttemptsTracker.new diff --git a/spec/support/oidc_auth_helper.rb b/spec/support/oidc_auth_helper.rb index e8540e4034b..81c42b79990 100644 --- a/spec/support/oidc_auth_helper.rb +++ b/spec/support/oidc_auth_helper.rb @@ -58,7 +58,6 @@ def ial1_params(prompt: nil, state: SecureRandom.hex, nonce: SecureRandom.hex, client_id: OIDC_ISSUER, - irs_attempts_api_session_id: nil, tid: nil) ial1_params = { client_id: client_id, @@ -69,9 +68,6 @@ def ial1_params(prompt: nil, state: state, nonce: nonce, } - if irs_attempts_api_session_id - ial1_params[:irs_attempts_api_session_id] = irs_attempts_api_session_id - end ial1_params[:tid] = tid if tid ial1_params[:prompt] = prompt if prompt ial1_params @@ -82,7 +78,6 @@ def ial2_params(prompt: nil, nonce: SecureRandom.hex, client_id: OIDC_ISSUER, acr_values: Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, - irs_attempts_api_session_id: nil, tid: nil) ial2_params = { client_id: client_id, @@ -93,9 +88,6 @@ def ial2_params(prompt: nil, state: state, nonce: nonce, } - if irs_attempts_api_session_id - ial2_params[:irs_attempts_api_session_id] = irs_attempts_api_session_id - end ial2_params[:tid] = tid if tid ial2_params[:prompt] = prompt if prompt ial2_params