Class: UserSessionForm

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Validations, SentryLogging
Defined in:
app/models/user_session_form.rb

Constant Summary collapse

VALIDATIONS_FAILED_ERROR_CODE =
'004'
SAML_REPLAY_VALID_SESSION_ERROR_CODE =
'002'
ERRORS =
{ validations_failed: { code: VALIDATIONS_FAILED_ERROR_CODE,
                                   tag: :validations_failed,
                                   short_message: 'on User/Session Validation',
                                   level: :error },
             saml_replay_valid_session: { code: SAML_REPLAY_VALID_SESSION_ERROR_CODE,
tag: :saml_replay_valid_session,
short_message: 'SamlResponse is too late but user has current session',
level: :warn } }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SentryLogging

#log_exception_to_sentry, #log_message_to_sentry, #non_nil_hash?, #normalize_level, #rails_logger, #set_sentry_metadata

Constructor Details

#initialize(saml_response) ⇒ UserSessionForm

Returns a new instance of UserSessionForm.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'app/models/user_session_form.rb', line 21

def initialize(saml_response)
  @saml_uuid = saml_response.in_response_to
  saml_user = SAML::User.new(saml_response)
  saml_attributes = normalize_saml(saml_user)
  uuid = saml_attributes[:uuid]
  existing_user = User.find(uuid)
  @session = Session.new(uuid:, ssoe_transactionid: saml_user.user_attributes.try(:transactionid))
  @user_identity = UserIdentity.new(saml_attributes)
  @user = User.new(uuid:)
  @user.session_handle = @session.token
  @user.instance_variable_set(:@identity, @user_identity)
  @user.invalidate_mpi_cache

  if saml_user.changing_multifactor?
    last_signed_in = existing_user&.last_signed_in || Time.current.utc
    @user.mhv_last_signed_in = last_signed_in
    @user.last_signed_in = last_signed_in
    log_existing_user_warning(uuid, saml_attributes[:mhv_icn]) unless existing_user
  else
    @user.last_signed_in = Time.current.utc
  end
end

Instance Attribute Details

#saml_uuidObject (readonly)

Returns the value of attribute saml_uuid.



19
20
21
# File 'app/models/user_session_form.rb', line 19

def saml_uuid
  @saml_uuid
end

#sessionObject (readonly)

Returns the value of attribute session.



19
20
21
# File 'app/models/user_session_form.rb', line 19

def session
  @session
end

#userObject (readonly)

Returns the value of attribute user.



19
20
21
# File 'app/models/user_session_form.rb', line 19

def user
  @user
end

#user_identityObject (readonly)

Returns the value of attribute user_identity.



19
20
21
# File 'app/models/user_session_form.rb', line 19

def user_identity
  @user_identity
end

Instance Method Details

#add_csp_id_to_mpi(saml_user_attributes, idme_uuid) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
# File 'app/models/user_session_form.rb', line 61

def add_csp_id_to_mpi(saml_user_attributes, idme_uuid)
  return unless saml_user_attributes[:loa][:current] == LOA::THREE

  Rails.logger.info("[UserSessionForm] Adding CSP ID to MPI, idme: #{idme_uuid}")
  mpi_response = MPI::Service.new.add_person_implicit_search(first_name: saml_user_attributes[:first_name],
                                                             last_name: saml_user_attributes[:last_name],
                                                             ssn: saml_user_attributes[:ssn],
                                                             birth_date: saml_user_attributes[:birth_date],
                                                             idme_uuid:)
  log_message_to_sentry("Failed Add CSP ID to MPI FAILED, idme: #{idme_uuid}", :warn) unless mpi_response.ok?
end

#error_codeObject



147
148
149
# File 'app/models/user_session_form.rb', line 147

def error_code
  errors_hash[:code] if errors.any?
end

#error_instrumentation_codeObject



151
152
153
# File 'app/models/user_session_form.rb', line 151

def error_instrumentation_code
  "error:#{errors_hash[:tag]}" if errors.any?
end

#errors_contextObject



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'app/models/user_session_form.rb', line 117

def errors_context
  errors_hash.merge(
    uuid: @user.uuid,
    user: {
      valid: @user&.valid?,
      errors: @user&.errors&.full_messages
    },
    session: {
      valid: @session.valid? && !@session.uuid.nil?,
      errors: get_session_errors
    },
    identity: {
      valid: @user_identity&.valid?,
      errors: @user_identity&.errors&.full_messages,
      authn_context: @user_identity&.authn_context,
      loa: @user_identity&.loa
    },
    mvi: mvi_context
  )
end

#errors_hashObject



113
114
115
# File 'app/models/user_session_form.rb', line 113

def errors_hash
  ERRORS[:validations_failed] if errors.any?
end

#errors_messageObject



109
110
111
# File 'app/models/user_session_form.rb', line 109

def errors_message
  @errors_message ||= "Login Failed! #{errors_hash[:short_message]}" if errors.any?
end

#get_session_errorsObject



92
93
94
95
# File 'app/models/user_session_form.rb', line 92

def get_session_errors
  @session.errors.add(:uuid, "can't be blank") if @session.uuid.nil?
  @session.errors&.full_messages
end

#log_existing_user_warning(saml_uuid, saml_icn) ⇒ Object (private)



157
158
159
160
# File 'app/models/user_session_form.rb', line 157

def log_existing_user_warning(saml_uuid, saml_icn)
  message = "Couldn't locate exiting user after MFA establishment"
  log_message_to_sentry(message, :warn, { saml_uuid:, saml_icn: })
end

#mvi_contextObject



138
139
140
141
142
143
144
145
# File 'app/models/user_session_form.rb', line 138

def mvi_context
  latest_outage = MPI::Configuration.instance.breakers_service.latest_outage
  if latest_outage && !latest_outage.ended?
    'breakers is closed for MVI'
  else
    'breakers is open for MVI'
  end
end

#normalize_saml(saml_user) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/models/user_session_form.rb', line 44

def normalize_saml(saml_user)
  saml_user.validate!
  saml_user_attributes = saml_user.to_hash
  add_csp_id_to_mpi(saml_user_attributes, saml_user_attributes[:idme_uuid]) if saml_user.needs_csp_id_mpi_update?
  saml_user_attributes
rescue SAML::UserAttributeError => e
  raise unless e.code == SAML::UserAttributeError::UUID_MISSING_CODE

  idme_uuid = (e&.identifier)
  raise if idme_uuid.blank?

  Rails.logger.info('Account UUID injected into user SAML attributes')
  saml_user_attributes = saml_user.to_hash
  add_csp_id_to_mpi(saml_user_attributes, idme_uuid)
  saml_user_attributes.merge({ uuid: idme_uuid, idme_uuid: })
end

#persistObject



101
102
103
104
105
106
107
# File 'app/models/user_session_form.rb', line 101

def persist
  if save
    [user, session]
  else
    [nil, nil]
  end
end

#saveObject



97
98
99
# File 'app/models/user_session_form.rb', line 97

def save
  valid? && session.save && user.save && @user_identity.save
end

#uuid_from_account(identifier) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
# File 'app/models/user_session_form.rb', line 73

def (identifier)
  return if identifier.blank?

   = UserAccount.find_by(icn: identifier)
  return unless 

  idme_uuid_array = .user_verifications.map(&:idme_uuid) +
                    .user_verifications.map(&:backing_idme_uuid)

  idme_uuid_array.compact.first
end

#valid?Boolean

Returns:

  • (Boolean)


85
86
87
88
89
90
# File 'app/models/user_session_form.rb', line 85

def valid?
  errors.add(:session, :invalid) unless session.valid?
  errors.add(:user, :invalid) unless user.valid?
  errors.add(:user_identity, :invalid) unless @user_identity.valid?
  errors.empty?
end