Module: Users::EmailOtpEnrollment

Extended by:
ActiveSupport::Concern
Included in:
User
Defined in:
app/models/concerns/users/email_otp_enrollment.rb

Instance Method Summary collapse

Instance Method Details

#can_modify_email_otp_enrollment?Boolean

Returns:

  • (Boolean)


21
22
23
# File 'app/models/concerns/users/email_otp_enrollment.rb', line 21

def can_modify_email_otp_enrollment?
  email_otp_enrollment_restriction.nil?
end

#email_otp_enrollment_restrictionObject



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/models/concerns/users/email_otp_enrollment.rb', line 25

def email_otp_enrollment_restriction
  return :feature_disabled            unless Feature.enabled?(:email_based_mfa, self)
  return :uses_external_authenticator if password_automatically_set?

  # TwoFactorAuthVerifier provides Group, Global, and Admin 2FA
  # restriction logic
  reason = Gitlab::Auth::TwoFactorAuthVerifier.new(self).two_factor_authentication_reason
  return :"#{reason}_enforcement" if reason

  return :future_enforcement          if email_otp_required_after&.future?
  return :email_otp_required          if must_require_email_otp?

  nil
end

#email_otp_required_as_booleanObject

These two methods allow us to treat email OTP enrollment as a boolean, while still preserving the enrollment date itself.



9
10
11
# File 'app/models/concerns/users/email_otp_enrollment.rb', line 9

def email_otp_required_as_boolean
  email_otp_required_after.present?
end

#email_otp_required_as_boolean=(value) ⇒ Object



13
14
15
16
17
18
19
# File 'app/models/concerns/users/email_otp_enrollment.rb', line 13

def email_otp_required_as_boolean=(value)
  if ActiveModel::Type::Boolean.new.cast(value)
    self.email_otp_required_after ||= Time.current
  else
    self.email_otp_required_after = nil
  end
end

#must_require_email_otp?Boolean

Returns:

  • (Boolean)


40
41
42
43
44
# File 'app/models/concerns/users/email_otp_enrollment.rb', line 40

def must_require_email_otp?
  !password_automatically_set? &&
    Gitlab::CurrentSettings.require_minimum_email_based_otp_for_users_with_passwords? &&
    !two_factor_enabled?
end

#set_email_otp_required_after_based_on_restrictions(save: false) ⇒ Object

Ensures email_otp_required_after is in a valid state based on the restrictions applied to the user (instance settings, group policies, etc).

Sets the value directly on user_detail because delegated attributes don’t propagate dirty state to the parent User model, which would prevent changes from being saved.

Parameters:

  • save (Boolean) (defaults to: false)

    whether to persist changes if email_otp_required_after is modified (default: false)



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'app/models/concerns/users/email_otp_enrollment.rb', line 56

def set_email_otp_required_after_based_on_restrictions(save: false)
  return unless Feature.enabled?(:email_based_mfa, self)

  if email_otp_required_after.nil? && must_require_email_otp?
    # Revert if being changed to nil, or set to Time.current if
    # it was always nil but shouldn't be
    user_detail.email_otp_required_after = user_detail.email_otp_required_after_was || Time.current
  elsif Gitlab::Auth::TwoFactorAuthVerifier.new(self).two_factor_authentication_required? && two_factor_enabled?
    # Email OTP has less security assurance than 2FA. Therefore,
    # don't allow email OTP when 2FA is required & configured.
    user_detail.email_otp_required_after = nil
  end
  # If neither condition is true, email_otp_required_after does not
  # need to be modified.

  return unless user_detail.email_otp_required_after_changed?

  Gitlab::AppLogger.info(
    message: "set_email_otp_required_after_based_on_restrictions is modifying email_otp_required_after",
    change: { before: user_detail.email_otp_required_after_was, after: user_detail.email_otp_required_after },
    user_id: id
  )

  # Conditionally save the record, with error logging instead of
  # raising.
  return unless save && !user_detail.save

  Gitlab::AppLogger.warn(
    message: 'set_email_otp_required_after_based_on_restrictions failed to save',
    change: { before: user_detail.email_otp_required_after_was, after: user_detail.email_otp_required_after },
    errors: user_detail.errors.full_messages
  )
end