masks 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE +21 -0
  4. data/README.md +235 -29
  5. data/app/controllers/masks/rails/avatars_controller.rb +51 -0
  6. data/app/controllers/masks/rails/base_controller.rb +13 -0
  7. data/app/controllers/masks/rails/handshakes_controller.rb +86 -0
  8. data/app/controllers/masks/rails/sessions_controller.rb +118 -0
  9. data/app/views/layouts/masks/rails/plain.html.erb +47 -0
  10. data/app/views/masks/rails/handshakes/refused.html.erb +12 -0
  11. data/app/views/masks/rails/handshakes/show.html.erb +40 -0
  12. data/config/routes.rb +11 -82
  13. data/lib/generators/masks/install/install_generator.rb +35 -22
  14. data/lib/generators/masks/install/templates/masks.rb.tt +38 -0
  15. data/lib/masks/client/claims.rb +144 -0
  16. data/lib/masks/client/errors.rb +60 -0
  17. data/lib/masks/client/handshake.rb +104 -0
  18. data/lib/masks/client/http.rb +96 -0
  19. data/lib/masks/client/introspection.rb +29 -0
  20. data/lib/masks/client/issuer.rb +92 -0
  21. data/lib/masks/client/pkce.rb +27 -0
  22. data/lib/masks/client/rack.rb +62 -0
  23. data/lib/masks/client/registration.rb +115 -0
  24. data/lib/masks/client/registry.rb +25 -0
  25. data/lib/masks/client/resource.rb +92 -0
  26. data/lib/masks/client/session.rb +146 -0
  27. data/lib/masks/client/stores.rb +40 -0
  28. data/lib/masks/client/tokens.rb +70 -0
  29. data/lib/masks/client/tracker.rb +122 -0
  30. data/lib/masks/client/verifier.rb +42 -0
  31. data/lib/masks/client.rb +51 -0
  32. data/lib/masks/rails/authentication.rb +260 -0
  33. data/lib/masks/rails/configurable.rb +11 -0
  34. data/lib/masks/rails/configuration.rb +184 -0
  35. data/lib/masks/rails/credentials.rb +60 -0
  36. data/lib/masks/rails/engine.rb +20 -0
  37. data/lib/masks/rails/protected_resource.rb +51 -0
  38. data/lib/masks/rails.rb +10 -0
  39. data/lib/masks/version.rb +1 -3
  40. data/lib/masks.rb +3 -183
  41. metadata +48 -416
  42. data/MIT-LICENSE +0 -20
  43. data/Rakefile +0 -11
  44. data/app/assets/builds/masks/application.css +0 -1
  45. data/app/assets/builds/masks/application.js +0 -8549
  46. data/app/assets/builds/masks/application.js.map +0 -7
  47. data/app/assets/images/masks.png +0 -0
  48. data/app/assets/javascripts/application.js +0 -2
  49. data/app/assets/javascripts/controllers/application.js +0 -9
  50. data/app/assets/javascripts/controllers/emails_controller.js +0 -28
  51. data/app/assets/javascripts/controllers/index.js +0 -21
  52. data/app/assets/javascripts/controllers/keys_controller.js +0 -20
  53. data/app/assets/javascripts/controllers/recover_controller.js +0 -21
  54. data/app/assets/javascripts/controllers/recover_password_controller.js +0 -21
  55. data/app/assets/javascripts/controllers/session_controller.js +0 -94
  56. data/app/assets/javascripts/controllers/table_controller.js +0 -15
  57. data/app/assets/manifest.js +0 -2
  58. data/app/assets/masks_manifest.js +0 -2
  59. data/app/assets/stylesheets/application.css +0 -34
  60. data/app/controllers/concerns/masks/controller.rb +0 -114
  61. data/app/controllers/masks/actors_controller.rb +0 -15
  62. data/app/controllers/masks/application_controller.rb +0 -35
  63. data/app/controllers/masks/backup_codes_controller.rb +0 -34
  64. data/app/controllers/masks/debug_controller.rb +0 -9
  65. data/app/controllers/masks/devices_controller.rb +0 -20
  66. data/app/controllers/masks/emails_controller.rb +0 -60
  67. data/app/controllers/masks/error_controller.rb +0 -14
  68. data/app/controllers/masks/keys_controller.rb +0 -45
  69. data/app/controllers/masks/manage/actors_controller.rb +0 -83
  70. data/app/controllers/masks/manage/base_controller.rb +0 -20
  71. data/app/controllers/masks/manage/clients_controller.rb +0 -84
  72. data/app/controllers/masks/manage/dashboard_controller.rb +0 -15
  73. data/app/controllers/masks/manage/devices_controller.rb +0 -19
  74. data/app/controllers/masks/one_time_code_controller.rb +0 -49
  75. data/app/controllers/masks/openid/authorizations_controller.rb +0 -45
  76. data/app/controllers/masks/openid/discoveries_controller.rb +0 -55
  77. data/app/controllers/masks/openid/tokens_controller.rb +0 -45
  78. data/app/controllers/masks/openid/userinfo_controller.rb +0 -28
  79. data/app/controllers/masks/passwords_controller.rb +0 -33
  80. data/app/controllers/masks/recoveries_controller.rb +0 -43
  81. data/app/controllers/masks/sessions_controller.rb +0 -53
  82. data/app/helpers/masks/application_helper.rb +0 -49
  83. data/app/jobs/masks/application_job.rb +0 -7
  84. data/app/jobs/masks/expire_actors_job.rb +0 -15
  85. data/app/jobs/masks/expire_recoveries_job.rb +0 -15
  86. data/app/mailers/masks/actor_mailer.rb +0 -22
  87. data/app/mailers/masks/application_mailer.rb +0 -15
  88. data/app/models/concerns/masks/access.rb +0 -162
  89. data/app/models/concerns/masks/actor.rb +0 -132
  90. data/app/models/concerns/masks/adapter.rb +0 -68
  91. data/app/models/concerns/masks/role.rb +0 -9
  92. data/app/models/concerns/masks/scoped.rb +0 -54
  93. data/app/models/masks/access/actor_password.rb +0 -21
  94. data/app/models/masks/access/actor_scopes.rb +0 -18
  95. data/app/models/masks/access/actor_signup.rb +0 -21
  96. data/app/models/masks/actors/anonymous.rb +0 -40
  97. data/app/models/masks/actors/system.rb +0 -24
  98. data/app/models/masks/adapters/active_record.rb +0 -85
  99. data/app/models/masks/application_model.rb +0 -15
  100. data/app/models/masks/application_record.rb +0 -8
  101. data/app/models/masks/check.rb +0 -192
  102. data/app/models/masks/credential.rb +0 -166
  103. data/app/models/masks/credentials/access_token.rb +0 -60
  104. data/app/models/masks/credentials/backup_code.rb +0 -30
  105. data/app/models/masks/credentials/device.rb +0 -59
  106. data/app/models/masks/credentials/email.rb +0 -48
  107. data/app/models/masks/credentials/factor2.rb +0 -71
  108. data/app/models/masks/credentials/key.rb +0 -38
  109. data/app/models/masks/credentials/last_login.rb +0 -12
  110. data/app/models/masks/credentials/masquerade.rb +0 -32
  111. data/app/models/masks/credentials/nickname.rb +0 -63
  112. data/app/models/masks/credentials/one_time_code.rb +0 -34
  113. data/app/models/masks/credentials/password.rb +0 -28
  114. data/app/models/masks/credentials/recovery.rb +0 -71
  115. data/app/models/masks/credentials/return_to.rb +0 -27
  116. data/app/models/masks/credentials/session.rb +0 -67
  117. data/app/models/masks/device.rb +0 -30
  118. data/app/models/masks/error.rb +0 -51
  119. data/app/models/masks/event.rb +0 -14
  120. data/app/models/masks/mask.rb +0 -266
  121. data/app/models/masks/openid/authorization.rb +0 -116
  122. data/app/models/masks/openid/token.rb +0 -56
  123. data/app/models/masks/rails/actor.rb +0 -212
  124. data/app/models/masks/rails/actor_role.rb +0 -12
  125. data/app/models/masks/rails/device.rb +0 -47
  126. data/app/models/masks/rails/email.rb +0 -96
  127. data/app/models/masks/rails/key.rb +0 -61
  128. data/app/models/masks/rails/openid/access_token.rb +0 -55
  129. data/app/models/masks/rails/openid/authorization.rb +0 -45
  130. data/app/models/masks/rails/openid/client.rb +0 -186
  131. data/app/models/masks/rails/openid/id_token.rb +0 -43
  132. data/app/models/masks/rails/recovery.rb +0 -116
  133. data/app/models/masks/rails/role.rb +0 -20
  134. data/app/models/masks/rails/scope.rb +0 -15
  135. data/app/models/masks/session.rb +0 -447
  136. data/app/models/masks/sessions/access.rb +0 -27
  137. data/app/models/masks/sessions/inline.rb +0 -16
  138. data/app/models/masks/sessions/request.rb +0 -42
  139. data/app/resources/masks/actor_resource.rb +0 -9
  140. data/app/resources/masks/session_resource.rb +0 -15
  141. data/app/views/layouts/masks/application.html.erb +0 -17
  142. data/app/views/layouts/masks/mailer.html.erb +0 -17
  143. data/app/views/layouts/masks/mailer.text.erb +0 -1
  144. data/app/views/layouts/masks/manage.html.erb +0 -42
  145. data/app/views/masks/actor_mailer/recover_credentials.html.erb +0 -32
  146. data/app/views/masks/actor_mailer/recover_credentials.text.erb +0 -1
  147. data/app/views/masks/actor_mailer/verify_email.html.erb +0 -33
  148. data/app/views/masks/actor_mailer/verify_email.text.erb +0 -8
  149. data/app/views/masks/actors/current.html.erb +0 -145
  150. data/app/views/masks/application/_header.html.erb +0 -30
  151. data/app/views/masks/backup_codes/new.html.erb +0 -117
  152. data/app/views/masks/emails/new.html.erb +0 -109
  153. data/app/views/masks/emails/verify.html.erb +0 -51
  154. data/app/views/masks/keys/new.html.erb +0 -127
  155. data/app/views/masks/manage/actors/index.html.erb +0 -104
  156. data/app/views/masks/manage/actors/show.html.erb +0 -172
  157. data/app/views/masks/manage/clients/index.html.erb +0 -102
  158. data/app/views/masks/manage/clients/show.html.erb +0 -156
  159. data/app/views/masks/manage/dashboard/index.html.erb +0 -10
  160. data/app/views/masks/manage/devices/index.html.erb +0 -47
  161. data/app/views/masks/one_time_code/new.html.erb +0 -167
  162. data/app/views/masks/openid/authorizations/error.html.erb +0 -23
  163. data/app/views/masks/openid/authorizations/new.html.erb +0 -46
  164. data/app/views/masks/passwords/edit.html.erb +0 -71
  165. data/app/views/masks/recoveries/new.html.erb +0 -69
  166. data/app/views/masks/recoveries/password.html.erb +0 -63
  167. data/app/views/masks/sessions/new.html.erb +0 -152
  168. data/config/brakeman.ignore +0 -28
  169. data/config/initializers/inflections.rb +0 -5
  170. data/config/locales/en.yml +0 -307
  171. data/db/migrate/20231205173845_create_actors.rb +0 -94
  172. data/db/migrate/20240329182422_support_openid.rb +0 -64
  173. data/lib/generators/masks/install/USAGE +0 -8
  174. data/lib/generators/masks/install/templates/initializer.rb +0 -5
  175. data/lib/generators/masks/install/templates/masks.json +0 -9
  176. data/lib/masks/configuration.rb +0 -249
  177. data/lib/masks/engine.rb +0 -25
  178. data/lib/masks/middleware.rb +0 -70
  179. data/lib/tasks/masks_tasks.rake +0 -72
  180. data/masks.json +0 -315
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Credentials
5
- # Checks for an :actor given a matching nickname.
6
- class Nickname < Masks::Credential
7
- checks :actor
8
-
9
- delegate :actor_id, :nickname, to: :actor, allow_nil: true
10
-
11
- validates :nickname, :actor, presence: true
12
- validate :validates_custom, if: :nickname
13
-
14
- def lookup
15
- return if actor
16
-
17
- actor =
18
- (
19
- if nickname_param
20
- config.find_actor(session, nickname: nickname_param)
21
- end
22
- )
23
- actor ||=
24
- if nickname_param
25
- config
26
- .build_actor(session, nickname: nickname_param)
27
- .tap { |actor| actor.signup = true }
28
- end
29
-
30
- if actor&.new_record?
31
- actor.nickname =
32
- prefix_nickname(actor.nickname, default: actor.nickname)
33
- end
34
-
35
- actor
36
- end
37
-
38
- def maskup
39
- approve! if valid?
40
- end
41
-
42
- private
43
-
44
- def nickname_param
45
- @nickname_param ||=
46
- prefix_nickname(
47
- session_params[:nickname],
48
- default: session_params[:nickname]
49
- )
50
- end
51
-
52
- def validates_custom
53
- return unless nickname
54
-
55
- validates_length :nickname, nickname_config&.length
56
-
57
- return unless nickname_format
58
-
59
- errors.add(:nickname, :format) unless nickname_format.match?(nickname)
60
- end
61
- end
62
- end
63
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Credentials
5
- # Checks :factor2 for a valid one-time code.
6
- class OneTimeCode < Masks::Credential
7
- include Factor2
8
-
9
- private
10
-
11
- def param
12
- :one_time_code
13
- end
14
-
15
- def secret
16
- actor&.totp_secret
17
- end
18
-
19
- def verify(code)
20
- valid_code?(code, secret)
21
- end
22
-
23
- def valid_code?(code, secret)
24
- if code && secret
25
- actor.totp.verify(code)
26
- else
27
- false
28
- end
29
- rescue OpenSSL::HMACError
30
- false
31
- end
32
- end
33
- end
34
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Credentials
5
- # Checks :password for a match.
6
- class Password < Masks::Credential
7
- checks :password
8
-
9
- def lookup
10
- actor.password = password if actor&.new_record? && password
11
-
12
- nil
13
- end
14
-
15
- def maskup
16
- return unless password
17
-
18
- actor&.authenticate(password) && actor&.valid? ? approve! : deny!
19
- end
20
-
21
- private
22
-
23
- def password
24
- session_params[:password]
25
- end
26
- end
27
- end
28
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Credentials
5
- # Checks for a :recovery on the session.
6
- class Recovery < Masks::Credential
7
- checks :recovery
8
-
9
- def lookup
10
- return if actor
11
-
12
- @recovery =
13
- if recovery_key
14
- config.find_recovery(session, token: recovery_key)
15
- else
16
- config.build_recovery(
17
- session,
18
- nickname: nickname_param,
19
- email: email_param,
20
- phone: phone_param,
21
- value: recovery_value
22
- )
23
- end
24
-
25
- session.extras(recovery: @recovery)
26
-
27
- @recovery&.actor
28
- end
29
-
30
- def maskup
31
- return unless valid? && actor
32
-
33
- if recovery_key
34
- if recovery_password && @recovery&.valid?
35
- @recovery.reset_password!(recovery_password)
36
- approve!
37
- end
38
- elsif recovery_value && @recovery&.valid?
39
- @recovery.notify!
40
- end
41
- end
42
-
43
- private
44
-
45
- def recovery_value
46
- params.dig(:recover, :value) if writable?
47
- end
48
-
49
- def recovery_key
50
- params[:token]
51
- end
52
-
53
- def recovery_password
54
- params.dig(:recover, :password) if writable?
55
- end
56
-
57
- def phone_param
58
- @phone_param ||= Phonelib.valid?(recovery_value) ? recovery_value : nil
59
- end
60
-
61
- def email_param
62
- @email_param ||=
63
- ValidateEmail.valid?(recovery_value) ? recovery_value : nil
64
- end
65
-
66
- def nickname_param
67
- @nickname_param ||= prefix_nickname(recovery_value, default: nil)
68
- end
69
- end
70
- end
71
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Credentials
5
- # Assigns the request URL to session data, under the key +return_to+.
6
- #
7
- # This credential is intended to keep track of an actor's attempts to
8
- # access protected resources, so it only assigns the value if the session
9
- # has not passed.
10
- #
11
- # In some cases, you may want to selectively disable the functionality
12
- # of this credential. To do so, set +return_to+ to +false+ in the
13
- # mask configuration.
14
- class ReturnTo < Masks::Credential
15
- def backup
16
- return if session&.passed?
17
- return unless session.try(:request) && session.request.get?
18
- if session.mask.extras.key?(:return_to) &&
19
- !session.mask.extras[:return_to]
20
- return
21
- end
22
-
23
- session.data[:return_to] = session.request.url
24
- end
25
- end
26
- end
27
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Credentials
5
- # Checks for past :actor(s) on a session.
6
- #
7
- # This can be used in lieu of supplying identifying details like an
8
- # email/nickname (usually provided they were supplied in the past).
9
- class Session < Masks::Credential
10
- checks :actor
11
-
12
- def lookup
13
- return if session_params[:actor_id]
14
-
15
- actor_ids = session.data[:actors]&.keys || []
16
- actor_id = session.data[:actor]
17
- actors = (actor_ids.any? ? config.find_actors(session, actor_ids) : [])
18
-
19
- # only lookup and return the current actor if
20
- # it's not provided via a param (e.g. someone
21
- # is trying to login)
22
- actor =
23
- if actor_id
24
- actors.find do |a|
25
- a.actor_id == actor_id &&
26
- a.session_key == session.data[:actors][a.actor_id]
27
- end
28
- end
29
-
30
- actor = Actors::Anonymous.new(session:) if optional? && !actors.present?
31
-
32
- actor
33
- end
34
-
35
- def maskup
36
- return approve! if optional? && actor&.anonymous?
37
-
38
- actor_id = actor&.actor_id
39
-
40
- return unless actor_id && session.data[:actors]&.fetch(actor_id, nil)
41
- return unless session.data[:actor] == actor_id
42
-
43
- if session.data.dig(:actors, actor_id) == actor.session_key
44
- approve!
45
- else
46
- cleanup
47
- end
48
- end
49
-
50
- def backup
51
- return unless actor && passed?
52
-
53
- session.data[:actors] ||= {}
54
- session.data[:actors][actor.actor_id] = actor.session_key
55
- session.data[:actor] = actor.actor_id
56
- end
57
-
58
- def cleanup
59
- actor_id = actor&.actor_id || session.data[:actor]
60
-
61
- session.data[:actor] = nil
62
- session.data[:actors] ||= {}
63
- session.data[:actors].delete(actor_id)
64
- end
65
- end
66
- end
67
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- # Represents a device, used for interacting with a session.
5
- #
6
- # Device detection is optionally added with the +Device+ credential,
7
- # using the +device-detector+ gem.
8
- #
9
- # @see Masks::Credentials::Device Masks::Credentials::Device
10
- class Device < ApplicationModel
11
- attribute :session
12
-
13
- def fingerprint
14
- return unless detector.known?
15
-
16
- input = [
17
- detector.name,
18
- detector.os_name,
19
- detector.device_name,
20
- detector.device_type
21
- ].compact.join("-")
22
-
23
- Digest::SHA512.hexdigest(input)
24
- end
25
-
26
- def detector
27
- @detector ||= DeviceDetector.new(session.user_agent)
28
- end
29
- end
30
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- module Error
5
- # Base class for Masks errors
6
- class Base < RuntimeError
7
- end
8
-
9
- # Thrown when a session is required but not provided
10
- class InvalidSession < Base
11
- def initialize
12
- super("a session was expected, but none was provided")
13
- end
14
- end
15
-
16
- # Thrown when invalid configuration is detected
17
- class InvalidConfiguration < Base
18
- def initialize(value)
19
- super("cannot use '#{value}' for masks.json")
20
- end
21
- end
22
-
23
- # Thrown when configuration is not found
24
- class ConfigurationNotFound < Base
25
- def initialize
26
- super("cannot find masks.json")
27
- end
28
- end
29
-
30
- # Thrown when Masks encounters an unauthorized session
31
- class Unauthorized < Base
32
- def initialize
33
- super("unauthorized")
34
- end
35
- end
36
-
37
- # Thrown when Masks cannot find a mask for a session
38
- class UnknownMask < Base
39
- def initialize(session)
40
- super("could not determine mask for #{session}")
41
- end
42
- end
43
-
44
- # Thrown when Masks cannot find an access class for the given name
45
- class UnknownAccess < Base
46
- def initialize(name)
47
- super("could not determine access class for #{name}")
48
- end
49
- end
50
- end
51
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- # Publishes events using +ActiveSupport::Notifications+
5
- class Event
6
- class << self
7
- def emit(name, **opts, &block)
8
- ActiveSupport::Notifications.instrument("masks.#{name}", **opts) do
9
- block.call
10
- end
11
- end
12
- end
13
- end
14
- end
@@ -1,266 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Masks
4
- # Represents an individual mask, its properties, and methods for interpreting them.
5
- #
6
- # When a session is created it finds the first matching mask to use for
7
- # dictating how to control access. A Mask contains rules that allow sessions
8
- # to match them.
9
- #
10
- # @see Masks::Check Masks::Check
11
- # @see Masks::Credentials Masks::Credentials
12
- class Mask < ApplicationModel
13
- # @!attribute [rw] name
14
- # a unique name for the mask
15
- # @return [String]
16
- attribute :name
17
- # @!attribute [rw] skip
18
- # Whether or not to skip processing by masks
19
- # @return [Boolean]
20
- attribute :skip, default: false
21
- # @!attribute [rw] type
22
- # A type name to inherit configuration from
23
- # @return [String]
24
- attribute :type
25
- # @!attribute [rw] types
26
- # A list of required type names
27
- # @return [String]
28
- attribute :types
29
- # @!attribute [rw] checks
30
- # A hash of checks required to pass the session
31
- # @return [Hash]
32
- attribute :checks
33
- # @!attribute [rw] credentials
34
- # An array of credentials that will check the session
35
- # @return [Array<Class>]
36
- attribute :credentials
37
- # @!attribute [rw] scopes
38
- # An array of scopes required to access the session
39
- # @return [Array<String>]
40
- attribute :scopes
41
- # @!attribute [rw] request
42
- # A hash of properties an HTTP request must match to use this mask
43
- # @return [Hash]
44
- attribute :request
45
- # @!attribute [rw] access
46
- # A list of access classes allowed during this session
47
- # @return [Array<String>]
48
- attribute :access
49
- # @!attribute [rw] actor
50
- # The expected class name of the actor using the sesssion, as a string
51
- # @return [String]
52
- attribute :actor
53
- # @!attribute [rw] anon
54
- # Whether or not to allow "anonymous" actors
55
- # @return [Boolean]
56
- attribute :anon
57
- # @!attribute [rw] pass
58
- # What to do when the session passes, typically a redirect uri
59
- # @return [String]
60
- attribute :pass, default: "/"
61
- # @!attribute [rw] fail
62
- # What to do when the session is failed by checks, credentials, or another error
63
- # @return [String|Boolean]
64
- attribute :fail, default: true
65
- # @!attribute [rw] backup
66
- # Whether or not to save results of masks
67
- # @return [Boolean]
68
- attribute :backup, default: true
69
- # @!attribute [rw] extras
70
- # Extra attributes and configuration accessible on the mask
71
- # @return [Hash]
72
- attribute :extras, default: -> { {} }
73
-
74
- # @visibility private
75
- attribute :config
76
-
77
- # @visibility private
78
- def initialize(attrs = {})
79
- if attrs[:type]
80
- type = attrs[:config].mask(attrs[:type])
81
- attrs = type.deep_merge(**attrs)
82
- end
83
-
84
- # required for `attribute_names` to work
85
- super({})
86
-
87
- extras = attrs.except(*attribute_names.map(&:to_sym))
88
- attrs = attrs.slice(*attribute_names.map(&:to_sym))
89
- attrs[:extras] = extras.deep_symbolize_keys
90
-
91
- assign_attributes(**attrs)
92
- end
93
-
94
- # Returns the class name expected for any actor attached to this session.
95
- #
96
- # @return [String]
97
- def actor
98
- super || config.models[:actor]
99
- end
100
-
101
- # Returns the constantized version of +#actor+.
102
- #
103
- # @return [String]
104
- def actor_scope
105
- (actor.constantize unless skip?)
106
- end
107
-
108
- # Whether or not sessions matching this mask should skip all work.
109
- #
110
- # @return [Boolean]
111
- def skip?
112
- !!skip
113
- end
114
-
115
- # Whether or not anonymous actors are allowed in the session.
116
- #
117
- # @return [Boolean]
118
- def allow_anonymous?
119
- anon
120
- end
121
-
122
- # Whether or not sessions using this mask should be saved.
123
- #
124
- # Some masks definitely want this enabled, as it is what stores the results
125
- # of masks, credentials, and checks in the rails session. In other cases, it
126
- # is not necessary, for example when verifying an API key.
127
- def backup?
128
- return false if skip?
129
-
130
- !!backup
131
- end
132
-
133
- # A hash of check configuration, keyed by their name.
134
- #
135
- # @return [Hash]
136
- def checks
137
- return {} if skip?
138
-
139
- (type_config&.fetch(:checks, {}) || {}).merge(super || {})
140
- end
141
-
142
- # Converts credentials to classes before returning the list.
143
- #
144
- # @return [Array<Class>]
145
- # @raise Masks::Error::InvalidConfiguration
146
- def credentials
147
- return [] if skip?
148
-
149
- merged = [
150
- *(type_config&.fetch(:credentials, []) || []),
151
- *(super || [])
152
- ].uniq
153
- merged.map do |cls|
154
- case cls
155
- when Class
156
- cls
157
- when /:+/
158
- cls.constantize
159
- when String
160
- "Masks::Credentials::#{cls}".constantize
161
- else
162
- raise Masks::Error::InvalidConfiguration, cls
163
- end
164
- end
165
- end
166
-
167
- # Returns whether or not the mask matches the passed session.
168
- #
169
- # The behaviour of this method depends on the mask's configuration. For
170
- # example, a session with an anonymous actor will return true only if the
171
- # mask's +#allow_anonymous?+ method returns true.
172
- #
173
- # @param [Masks::Session] session
174
- # @return [Boolean]
175
- def matches_session?(session)
176
- actor = session.actor
177
-
178
- return false unless actor
179
- return true if actor.anonymous? && allow_anonymous?
180
-
181
- case self.actor
182
- when String
183
- return false unless actor.is_a?(self.actor.constantize)
184
- when Class
185
- return false unless actor.is_a?(self.actor)
186
- else
187
- return false unless actor.is_a?(config.model(:actor))
188
- end
189
-
190
- matches_scopes?(session.scopes)
191
- end
192
-
193
- # Returns whether or not the mask's request confiig matches the request passed.
194
- #
195
- # The following parameters are supported in the +request+ hash:
196
- #
197
- # - +path+ - if specified, the request path must be in this list.
198
- # - +method+ - if specified, the request method must be in this list.
199
- # - +param+ - if specified, the key must exist in the session params.
200
- # - +header+ - if specified, the header must be present in the request.
201
- #
202
- # @return [Boolean]
203
- def matches_request?(request)
204
- return false unless self.request
205
- return false unless matches_path?(request)
206
- return false unless matches_method?(request)
207
-
208
- param = self.request.fetch(:param, nil)
209
-
210
- if param && param != "*" && !request.params&.fetch(param.to_sym, nil)
211
- return false
212
- end
213
-
214
- header = self.request.fetch(:header, nil)
215
-
216
- return false if header && !request.headers[header]
217
-
218
- true
219
- end
220
-
221
- private
222
-
223
- # Returns whether or not the mask's allowed scopes includes one of the scopes passed.
224
- #
225
- # @return [Boolean]
226
- def matches_scopes?(scopes)
227
- return true if self.scopes.blank?
228
-
229
- # the mask scopes and scopes passed should have at least one match
230
- # otherwise this will return false because the intersection is blank
231
- (Array.wrap(self.scopes) & (scopes || [])).present?
232
- end
233
-
234
- def matches_path?(request)
235
- return true unless (paths = self.request.fetch(:path, nil))
236
-
237
- Array
238
- .wrap(paths)
239
- .any? do |path|
240
- case path
241
- when /^r.+/
242
- Regexp.new(path.slice(1..)).match?(request.url)
243
- else
244
- Fuzzyurl.matches?(Fuzzyurl.mask(path:), request.url)
245
- end
246
- end
247
- end
248
-
249
- def matches_method?(request)
250
- return true unless (methods = self.request&.fetch(:method, nil))
251
- return true if !methods || methods == "*"
252
-
253
- Array
254
- .wrap(methods)
255
- .map(&:upcase)
256
- .any? do |m|
257
- m == request.method ||
258
- (request.method == "POST" && request.params[:_method]&.upcase == m)
259
- end
260
- end
261
-
262
- def type_config
263
- @type_config ||= config.mask(type) if type
264
- end
265
- end
266
- end