Class: SAML::UserAttributes::SSOe

Inherits:
Object
  • Object
show all
Includes:
Identity::Parsers::GCIds, SentryLogging
Defined in:
lib/saml/user_attributes/ssoe.rb

Constant Summary collapse

SERIALIZABLE_ATTRIBUTES =
%i[email first_name middle_name last_name gender ssn birth_date
uuid idme_uuid logingov_uuid verified_at sec_id mhv_icn
mhv_correlation_id mhv_account_type edipi loa sign_in multifactor icn].freeze
INBOUND_AUTHN_CONTEXT =
'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'

Constants included from Identity::Parsers::GCIdsConstants

Identity::Parsers::GCIdsConstants::ACTIVE_MHV_IDS_REGEX, Identity::Parsers::GCIdsConstants::BIRLS_IDS_REGEX, Identity::Parsers::GCIdsConstants::CERNER_FACILITY_IDS_REGEX, Identity::Parsers::GCIdsConstants::CERNER_ID_REGEX, Identity::Parsers::GCIdsConstants::DOD_ROOT_OID, Identity::Parsers::GCIdsConstants::EDIPI_REGEX, Identity::Parsers::GCIdsConstants::ICN_ASSIGNING_AUTHORITY_ID, Identity::Parsers::GCIdsConstants::ICN_REGEX, Identity::Parsers::GCIdsConstants::IDENTIFIERS_SPLIT_TOKEN, Identity::Parsers::GCIdsConstants::IDME_ID_REGEX, Identity::Parsers::GCIdsConstants::IDS_SPLIT_TOKEN, Identity::Parsers::GCIdsConstants::ID_MAPPINGS, Identity::Parsers::GCIdsConstants::LOGINGOV_ID_REGEX, Identity::Parsers::GCIdsConstants::MHV_IDS_REGEX, Identity::Parsers::GCIdsConstants::MHV_IEN_REGEX, Identity::Parsers::GCIdsConstants::PERMANENT_ICN_REGEX, Identity::Parsers::GCIdsConstants::SEC_ID_REGEX, Identity::Parsers::GCIdsConstants::VA_ROOT_OID, Identity::Parsers::GCIdsConstants::VBA_CORP_ID_REGEX, Identity::Parsers::GCIdsConstants::VET360_ID_REGEX, Identity::Parsers::GCIdsConstants::VHA_FACILITY_IDS_REGEX

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Identity::Parsers::GCIds

#build_hash, #parse_string_gcids, #parse_xml_gcids, #select, #select_extension, #select_icn_with_aaid, #select_token_position

Methods included from Identity::Parsers::GCIdsHelper

#sanitize_edipi, #sanitize_id, #sanitize_id_array

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_attributes, authn_context, tracker_uuid) ⇒ SSOe

Returns a new instance of SSOe.



19
20
21
22
23
24
# File 'lib/saml/user_attributes/ssoe.rb', line 19

def initialize(saml_attributes, authn_context, tracker_uuid)
  @attributes = saml_attributes # never default this to {}
  @authn_context = authn_context
  @tracker_uuid = tracker_uuid
  @warnings = []
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



17
18
19
# File 'lib/saml/user_attributes/ssoe.rb', line 17

def attributes
  @attributes
end

#authn_contextObject (readonly)

Returns the value of attribute authn_context.



17
18
19
# File 'lib/saml/user_attributes/ssoe.rb', line 17

def authn_context
  @authn_context
end

#tracker_uuidObject (readonly)

Returns the value of attribute tracker_uuid.



17
18
19
# File 'lib/saml/user_attributes/ssoe.rb', line 17

def tracker_uuid
  @tracker_uuid
end

#warningsObject (readonly)

Returns the value of attribute warnings.



17
18
19
# File 'lib/saml/user_attributes/ssoe.rb', line 17

def warnings
  @warnings
end

Instance Method Details

#account_typeObject



157
158
159
160
161
162
# File 'lib/saml/user_attributes/ssoe.rb', line 157

def 
  result = 
  result ||= 
  result ||= 'N/A'
  result
end

#attribute_has_multiple_values?(attribute) ⇒ Boolean (private)

Returns:

  • (Boolean)


284
285
286
287
# File 'lib/saml/user_attributes/ssoe.rb', line 284

def attribute_has_multiple_values?(attribute)
  var = safe_attr(attribute)&.split(',') || []
  var.reject(&:nil?).uniq.size > 1
end

#birth_dateObject



54
55
56
57
58
59
60
61
# File 'lib/saml/user_attributes/ssoe.rb', line 54

def birth_date
  bd = safe_attr('va_eauth_birthDate_v1')
  begin
    Date.parse(bd).strftime('%Y-%m-%d')
  rescue TypeError, ArgumentError
    nil
  end
end

#check_id_mismatch(ids, multiple_ids_error_type) ⇒ Object (private)



265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/saml/user_attributes/ssoe.rb', line 265

def check_id_mismatch(ids, multiple_ids_error_type)
  return if ids.blank?

  if ids.reject(&:nil?).uniq.size > 1
    mismatched_ids_error = SAML::UserAttributeError::ERRORS[multiple_ids_error_type]
    error_data = { mismatched_ids: ids, icn: mhv_icn }
    Rails.logger.warn("[SAML::UserAttributes::SSOe] #{mismatched_ids_error[:message]}, #{error_data}")
    unless mhv_outbound_redirect(mismatched_ids_error)
      raise SAML::UserAttributeError.new(message: mismatched_ids_error[:message],
                                         code: mismatched_ids_error[:code],
                                         tag: mismatched_ids_error[:tag])
    end
  end
end

#csidObject (private)



289
290
291
# File 'lib/saml/user_attributes/ssoe.rb', line 289

def csid
  safe_attr('va_eauth_csid')&.downcase
end

#dslogon_account_typeObject



106
107
108
# File 'lib/saml/user_attributes/ssoe.rb', line 106

def 
  safe_attr('va_eauth_dslogonassurance')
end

#dslogon_loa_highestObject



136
137
138
139
# File 'lib/saml/user_attributes/ssoe.rb', line 136

def dslogon_loa_highest
  dslogon_assurance = 
  LOA::DSLOGON_PREMIUM_VERIFIED.include?(dslogon_assurance) ? 3 : nil
end

#edipiObject



110
111
112
# File 'lib/saml/user_attributes/ssoe.rb', line 110

def edipi
  edipi_ids[:edipi]
end

#edipi_idsObject (private)



241
242
243
244
245
246
247
248
# File 'lib/saml/user_attributes/ssoe.rb', line 241

def edipi_ids
  @edipi_ids ||= begin
    gcids = safe_attr('va_eauth_gcIds')
    return {} unless gcids

    parse_string_gcids(gcids, DOD_ROOT_OID)
  end
end

#emailObject



63
64
65
# File 'lib/saml/user_attributes/ssoe.rb', line 63

def email
  safe_attr('va_eauth_emailaddress')
end

#first_nameObject

Personal attributes



27
28
29
# File 'lib/saml/user_attributes/ssoe.rb', line 27

def first_name
  safe_attr('va_eauth_firstname')
end

#genderObject



43
44
45
46
# File 'lib/saml/user_attributes/ssoe.rb', line 43

def gender
  gender = safe_attr('va_eauth_gender')&.chars&.first&.upcase
  %w[M F].include?(gender) ? gender : nil
end

#icnObject



39
40
41
# File 'lib/saml/user_attributes/ssoe.rb', line 39

def icn
  mvi_ids[:icn]
end

#idme_uuidObject



72
73
74
75
76
# File 'lib/saml/user_attributes/ssoe.rb', line 72

def idme_uuid
  return safe_attr('va_eauth_uid') if csid == SAML::User::IDME_CSID

  mvi_ids[:idme_id]
end

#last_nameObject



35
36
37
# File 'lib/saml/user_attributes/ssoe.rb', line 35

def last_name
  safe_attr('va_eauth_lastname')
end

#loaObject



164
165
166
# File 'lib/saml/user_attributes/ssoe.rb', line 164

def loa
  { current: loa_current, highest: [loa_current, loa_highest].max }
end

#loa_currentObject

va_eauth_credentialassurancelevel is supposed to roll up the federated assurance level from credential provider and broker. It is currently returning a value of “2” for DSLogon level 2 so we are interpreting any value greater than 1 as “LOA 3”.



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/saml/user_attributes/ssoe.rb', line 118

def loa_current
  assurance =
    if csid == 'logingov'
      safe_attr('va_eauth_ial')&.to_i
    else
      safe_attr('va_eauth_credentialassurancelevel')&.to_i
    end
  @loa_current ||= assurance.present? && assurance > 1 ? 3 : 1
rescue NoMethodError, KeyError => e
  @warnings << "loa_current error: #{e.message}"
  @loa_current = 1
end

#loa_highestObject

This is the ID.me highest level of assurance attained



142
143
144
145
146
147
# File 'lib/saml/user_attributes/ssoe.rb', line 142

def loa_highest
  result = mhv_loa_highest
  result ||= dslogon_loa_highest
  result ||= %w[2 classic_loa3].include?(safe_attr('va_eauth_ial_idme_highest')) ? 3 : 1
  result
end

#logingov_uuidObject



78
79
80
81
82
# File 'lib/saml/user_attributes/ssoe.rb', line 78

def logingov_uuid
  return safe_attr('va_eauth_uid') if csid == SAML::User::LOGINGOV_CSID

  mvi_ids[:logingov_id]
end

#mhv_account_typeObject



102
103
104
# File 'lib/saml/user_attributes/ssoe.rb', line 102

def 
  safe_attr('va_eauth_mhvassurance')
end

#mhv_correlation_idObject



98
99
100
# File 'lib/saml/user_attributes/ssoe.rb', line 98

def mhv_correlation_id
  safe_attr('va_eauth_mhvuuid') || mvi_ids[:mhv_ien]
end

#mhv_icnObject



94
95
96
# File 'lib/saml/user_attributes/ssoe.rb', line 94

def mhv_icn
  safe_attr('va_eauth_icn')
end

#mhv_iensObject (private)



254
255
256
257
# File 'lib/saml/user_attributes/ssoe.rb', line 254

def mhv_iens
  mhv_iens = mvi_ids[:mhv_iens] || []
  mhv_iens.append(safe_attr('va_eauth_mhvuuid')).reject(&:nil?).uniq
end

#mhv_loa_highestObject



131
132
133
134
# File 'lib/saml/user_attributes/ssoe.rb', line 131

def mhv_loa_highest
  mhv_assurance = 
  LOA::MHV_PREMIUM_VERIFIED.include?(mhv_assurance) ? 3 : nil
end

#mhv_outbound_redirect(mismatched_ids_error) ⇒ Object (private)



259
260
261
262
263
# File 'lib/saml/user_attributes/ssoe.rb', line 259

def mhv_outbound_redirect(mismatched_ids_error)
  return false if mismatched_ids_error[:tag] == :multiple_edipis

  @mhv_outbound_redirect ||= %w[mhv myvahealth].include?(tracker&.payload_attr(:application))
end

#middle_nameObject



31
32
33
# File 'lib/saml/user_attributes/ssoe.rb', line 31

def middle_name
  safe_attr('va_eauth_middlename')
end

#multifactorObject



149
150
151
152
153
154
155
# File 'lib/saml/user_attributes/ssoe.rb', line 149

def multifactor
  if csid == SAML::User::LOGINGOV_CSID
    safe_attr('va_eauth_aal') == AAL::TWO
  else
    safe_attr('va_eauth_multifactor')&.downcase == 'true'
  end
end

#multiple_id_validationsObject (private)



213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/saml/user_attributes/ssoe.rb', line 213

def multiple_id_validations
  # ICN, CORP ID, EDIPI, and IEN all trigger errors if multiple unique IDs are found
  check_id_mismatch([safe_attr('va_eauth_icn'), safe_attr('va_eauth_mhvicn')], :mhv_icn_mismatch)
  check_id_mismatch(mvi_ids[:vba_corp_ids], :multiple_corp_ids)
  check_id_mismatch(edipi_ids[:edipis], :multiple_edipis)
  check_id_mismatch(mhv_iens, :multiple_mhv_ids)
  if sec_id_mismatch?
    log_message_to_sentry('User attributes contains multiple sec_id values',
                          'warn',
                          { sec_id: @attributes['va_eauth_secid'] })
  end
end

#mvi_idsObject (private)



230
231
232
233
234
235
236
237
238
239
# File 'lib/saml/user_attributes/ssoe.rb', line 230

def mvi_ids
  return @mvi_ids if @mvi_ids

  # the gcIds are a pipe-delimited concatenation of the MVI correlation IDs
  # (minus the weird "base/extension" cruft)
  gcids = safe_attr('va_eauth_gcIds')
  return {} unless gcids

  @mvi_ids = parse_string_gcids(gcids)
end

#needs_csp_id_mpi_update?Boolean

Returns:

  • (Boolean)


191
192
193
# File 'lib/saml/user_attributes/ssoe.rb', line 191

def needs_csp_id_mpi_update?
  idme_uuid == safe_attr('va_eauth_uid') && mvi_ids[:idme_id].blank?
end

#safe_attr(key) ⇒ Object (private)



250
251
252
# File 'lib/saml/user_attributes/ssoe.rb', line 250

def safe_attr(key)
  @attributes[key] == 'NOT_FOUND' ? nil : @attributes[key]
end

#sec_idObject



90
91
92
# File 'lib/saml/user_attributes/ssoe.rb', line 90

def sec_id
  safe_attr('va_eauth_secid')
end

#sec_id_mismatch?Boolean (private)

Returns:

  • (Boolean)


280
281
282
# File 'lib/saml/user_attributes/ssoe.rb', line 280

def sec_id_mismatch?
  attribute_has_multiple_values?('va_eauth_secid')
end

#should_raise_missing_uuid_errorObject (private)



226
227
228
# File 'lib/saml/user_attributes/ssoe.rb', line 226

def should_raise_missing_uuid_error
  idme_uuid.blank? && logingov_uuid.blank?
end

#sign_inObject



180
181
182
183
184
185
186
187
188
189
# File 'lib/saml/user_attributes/ssoe.rb', line 180

def 
   = if authn_context == INBOUND_AUTHN_CONTEXT
              { service_name: csid }
            else
              SAML::User::AUTHN_CONTEXTS.fetch(authn_context).fetch(:sign_in)
            end
  .merge(account_type:,
                auth_broker: SAML::URLService::BROKER_CODE,
                client_id: tracker&.payload_attr(:application))
end

#ssnObject

This attribute may sometimes be TIN, Patient identifier, etc. It is not guaranteed to be a SSN



50
51
52
# File 'lib/saml/user_attributes/ssoe.rb', line 50

def ssn
  safe_attr('va_eauth_pnid')&.delete('-') if safe_attr('va_eauth_pnidtype') == 'SSN'
end

#to_hashObject



195
196
197
# File 'lib/saml/user_attributes/ssoe.rb', line 195

def to_hash
  SERIALIZABLE_ATTRIBUTES.index_with { |k| send(k) }.merge(authn_context:)
end

#trackerObject (private)



293
294
295
# File 'lib/saml/user_attributes/ssoe.rb', line 293

def tracker
  @tracker ||= SAMLRequestTracker.find(@tracker_uuid)
end

#transactionidObject



168
169
170
# File 'lib/saml/user_attributes/ssoe.rb', line 168

def transactionid
  safe_attr('va_eauth_transactionid')
end

#uuidObject

Identifiers



68
69
70
# File 'lib/saml/user_attributes/ssoe.rb', line 68

def uuid
  idme_uuid || logingov_uuid
end

#validate!Object

Raise any fatal exceptions due to validation issues



200
201
202
203
204
205
206
207
208
209
# File 'lib/saml/user_attributes/ssoe.rb', line 200

def validate!
  multiple_id_validations
  if should_raise_missing_uuid_error
    data = SAML::UserAttributeError::ERRORS[:uuid_missing]
    raise SAML::UserAttributeError.new(code: data[:code],
                                       message: data[:message],
                                       tag: data[:tag],
                                       identifier: mhv_icn)
  end
end

#verified_atObject

only applies to Login.gov IAL2 verification used to automatically upcert IAL1 users without additional service calls



86
87
88
# File 'lib/saml/user_attributes/ssoe.rb', line 86

def verified_at
  safe_attr('va_eauth_verifiedAt')
end

#vha_facility_hashObject



176
177
178
# File 'lib/saml/user_attributes/ssoe.rb', line 176

def vha_facility_hash
  mvi_ids[:vha_facility_hash]
end

#vha_facility_idsObject



172
173
174
# File 'lib/saml/user_attributes/ssoe.rb', line 172

def vha_facility_ids
  mvi_ids[:vha_facility_ids]
end