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

Constructor Details

#initialize(saml_response) ⇒ UserSessionForm

rubocop:disable Metrics/MethodLength



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/models/user_session_form.rb', line 22

def initialize(saml_response)
  @saml_uuid = saml_response.in_response_to
  saml_user = SAML::User.new(saml_response)
  normalized_attributes = normalize_saml(saml_user)
  existing_user = User.find(normalized_attributes[:uuid])
  @user_identity = UserIdentity.new(normalized_attributes)
  @user = User.new(uuid: @user_identity.attributes[:uuid])
  @user.instance_variable_set(:@identity, @user_identity)
  if saml_user.changing_multifactor?
    if existing_user.present?
      @user.mhv_last_signed_in = existing_user.last_signed_in
      @user.last_signed_in = existing_user.last_signed_in
    else
      @user.last_signed_in = Time.current.utc
      @user.mhv_last_signed_in = Time.current.utc
      log_message_to_sentry(
        "Couldn't locate exiting user after MFA establishment",
        :warn,
        { saml_uuid: normalized_attributes[:uuid], saml_icn: normalized_attributes[:mhv_icn] }
      )
    end
  else
    @user.last_signed_in = Time.current.utc
  end
  @session = Session.new(
    uuid: @user.uuid,
    ssoe_transactionid: saml_user.user_attributes.try(:transactionid)
  )
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



70
71
72
73
74
75
76
77
78
79
80
# File 'app/models/user_session_form.rb', line 70

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



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

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

#error_instrumentation_codeObject



160
161
162
# File 'app/models/user_session_form.rb', line 160

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

#errors_contextObject



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'app/models/user_session_form.rb', line 126

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



122
123
124
# File 'app/models/user_session_form.rb', line 122

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

#errors_messageObject



118
119
120
# File 'app/models/user_session_form.rb', line 118

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

#get_session_errorsObject



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

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

#mvi_contextObject



147
148
149
150
151
152
153
154
# File 'app/models/user_session_form.rb', line 147

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

rubocop:enable Metrics/MethodLength



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/models/user_session_form.rb', line 53

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



110
111
112
113
114
115
116
# File 'app/models/user_session_form.rb', line 110

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

#saveObject



106
107
108
# File 'app/models/user_session_form.rb', line 106

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

#uuid_from_account(identifier) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
# File 'app/models/user_session_form.rb', line 82

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)


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

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