Class: Masks::Check

Inherits:
ApplicationModel show all
Defined in:
app/models/masks/check.rb

Overview

Checks track attempts to verify one attribute of a session, like the actor or their password.

Every session contains a list of checks that can be manipulated while it is masked. Credentials associated with the session are typical consumers of checks, but direct manipulation is possible as well.

Once a check’s consumers have reported their status (approved, denied, or skipped) it will report an overall status based on the results, either:

  • passed? - true if attempts were made and all were approved, not skipped, or the check is optional

  • failed? - true if attempts were made and any were denied

Note: Checks can exist in a middle state, neither passed or failed, in the case that no attempts were made.

See Also:

Instance Method Summary collapse

Instance Method Details

#approve!(id, **opts) ⇒ Object

Approves an attempt.

Additional metadata can be passed as keyword arguments, and it will be saved alongside the attempt data.

Parameters:

  • id (String)
  • opts (Hash)

Returns:

  • Boolean



106
107
108
109
110
111
112
113
# File 'app/models/masks/check.rb', line 106

def approve!(id, **opts)
  self.approved = true

  merge_attempt(
    id,
    opts.merge(approved_at: Time.current.iso8601, skipped_at: nil)
  )
end

#attempt_approved?(id) ⇒ Boolean

Returns true if a specific attempt was approved.

Parameters:

  • id (String)

Returns:

  • (Boolean)

    Boolean



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/masks/check.rb', line 67

def attempt_approved?(id)
  opts = attempts.fetch(id.to_s, {})

  return approved unless lifetime
  return false if opts["skipped_at"]

  time =
    case opts["approved_at"]
    when nil
      return false
    when String
      Time.try(:parse, opts["approved_at"])
    else
      time
    end

  if time
    time + ActiveSupport::Duration.parse(lifetime) > Time.current
  else
    false
  end
end

#attempt_skipped?(id) ⇒ Boolean

Returns true if a specific attempt was skipped.

Parameters:

  • id (String)

Returns:

  • (Boolean)

    Boolean



93
94
95
96
# File 'app/models/masks/check.rb', line 93

def attempt_skipped?(id)
  opts = attempts.fetch(id.to_s, {})
  opts["skipped_at"] && optional
end

#attemptsHash

Returns a hash of attempts for the check.

Each key in the hash is the name of a specific attemptee, like a class or credential. The value is a hash of data about the attempt (like when it was attempted, approved, denied, and/or skipped).

Returns:

  • (Hash)


37
38
39
# File 'app/models/masks/check.rb', line 37

def attempts
  attempted.deep_merge(@attempts || {}).deep_stringify_keys
end

#clear!(id) ⇒ Datetime

Clears all data for attempts by the given id.

Parameters:

  • id (String)

Returns:

  • (Datetime)


163
164
165
166
# File 'app/models/masks/check.rb', line 163

def clear!(id)
  @attempts&.except!(id)
  attempted.except!(id)
end

#deny!(id, **opts) ⇒ Object

Denies an attempt.

Additional metadata can be passed as keyword arguments, and it will be saved alongside the attempt data.

Parameters:

  • id (String)
  • opts (Hash)

Returns:

  • Boolean



140
141
142
143
144
# File 'app/models/masks/check.rb', line 140

def deny!(id, **opts)
  self.denied = true

  merge_attempt(id, opts.merge(approved_at: nil, skipped_at: nil))
end

#optional?Boolean

Whether or not the check is optional.

Optional checks always return true for passed?.

Returns:

  • (Boolean)

    Boolean



46
47
48
# File 'app/models/masks/check.rb', line 46

def optional?
  optional
end

#passed?Boolean

Whether or not the check passed.

true if attempts were made and all were approved, not skipped, or the check is optional

Returns:

  • (Boolean)

    Boolean



55
56
57
58
59
60
61
62
# File 'app/models/masks/check.rb', line 55

def passed?
  return true if optional? && !failed?
  return false if attempts.keys.empty?

  attempts.all? do |id, _opts|
    attempt_approved?(id) || attempt_skipped?(id)
  end
end

#passed_atDatetime

Returns the time the check passed, if it did.

Returns:

  • (Datetime)


148
149
150
151
152
153
154
155
156
157
158
# File 'app/models/masks/check.rb', line 148

def passed_at
  return unless passed?

  attempts
    .map do |_id, opts|
      time = opts["approved_at"] || opts["skipped_at"]
      Time.try(:parse, time) if time
    end
    .compact
    .max
end

#skip!(id, **opts) ⇒ Object

Skips an attempt. Skips count as approvals.

Additional metadata can be passed as keyword arguments, and it will be saved alongside the attempt data.

Parameters:

  • id (String)
  • opts (Hash)

Returns:

  • Boolean



123
124
125
126
127
128
129
130
# File 'app/models/masks/check.rb', line 123

def skip!(id, **opts)
  self.skipped = true

  merge_attempt(
    id,
    opts.merge(approved_at: nil, skipped_at: Time.current.iso8601)
  )
end

#to_sessionHash

Returns a version of the check intended for the rails session.

Returns:

  • (Hash)


170
171
172
173
174
# File 'app/models/masks/check.rb', line 170

def to_session
  return { optional:, attempted: } unless lifetime

  { optional:, attempted: attempts }
end