booth 0.0.7 → 0.0.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d547a4c717dfffb45aefa8f29c31635c2f626b5387d722f79d0cf1a91941edff
4
- data.tar.gz: a75ac248690b901a84a14ae6d7a7ad6109aae727d6df12bfadcd151c6e37f076
3
+ metadata.gz: 88710c6c007c94f19ab956a66dc1592665f9de10f786ef5cd5eb8fb2b386fbee
4
+ data.tar.gz: 70f45c4a38f515c779ebda2705c142df7ca8ffb1279d5c5f49c2cf90fdacebfb
5
5
  SHA512:
6
- metadata.gz: 735a909c2ff361d73d9325c5ee3db38e47085db47c510ab56658f92cc93ab9d74b812de893b714e0829340f1ae2364211eb409f109ae282007c39196f2e40f64
7
- data.tar.gz: 9479f3920d36131ddfeda314ad3f90a2099f91975a350792cd99483f0608d8b913a82cde64e670c87e79fc69b6155452eca05aad226e9f1edc0bf9d7b920e470
6
+ metadata.gz: 1d08a6b4a1fa1f7b520952777509041f46232b31152c281d0851dbbbf32296fb84b8f4b62883ecb0e657800edbaec3e7a9fd12af09d753e65090e1dd505d355a
7
+ data.tar.gz: f5273c88aab4cb7425755b58f2e0eade47307748ae9fbbeae0e0fa0ab2ea8cff24e668dc3278bb83adf701ee1fa865780bd08e284c7a34366d079b745c4b6dea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # main
2
2
 
3
+ # 0.0.9
4
+
5
+ - Implement Adminland.start_incognito
6
+
7
+ # 0.0.8
8
+
9
+ - Add a screenshot after every partial test
10
+
3
11
  # 0.0.7
4
12
 
5
13
  - Ensure authenticators#provide_icon_dark is always defined
@@ -21,6 +21,7 @@ de:
21
21
  domain_mismatch: Dieser Benutzername kann sich nicht hier einloggen.
22
22
  email_too_long: The provided email is too long. It must be less at most %{maximum} characters long.
23
23
  email_too_short: The provided email is too short. It should be at least %{minimum} characters long.
24
+ incognito_started: Sie sind jetzt im Namen eines anderen Kontos angemeldet.
24
25
  incompatible_webauth_device: Sorry, this device cannot be used. Please try another webauth device.
25
26
  invalid_domain: Die Domain "%{domain}" ist keine gültige Domain.
26
27
  invalid_email_characters: The provided email address contains invalid characters.
@@ -33,6 +34,7 @@ de:
33
34
  missing_secret_key: Missing the secret key parameter. Is the URL correct?
34
35
  mode_username_and_webauth_description: Auch bekannt als WebAuthentication (WebAuthn), FIDO2, CTAP2, Apple Passkey (Touch ID, Face ID).
35
36
  mode_username_and_webauth_title: Hardwareschlüssel (empfohlen)
37
+ not_logged_in_as_impersonator: Sie müssen angemeldet sein, um sich als jemand anderen auszugeben.
36
38
  onboarding_timeout: Sie hatten %{lifespan_hours} Stunden to access this onboarding link, but it took too long.
37
39
  permanently_blocked: You failed too many times for this account. Please contact customer service.
38
40
  remote_login_path: fernlogin
@@ -41,6 +43,7 @@ de:
41
43
  session_revoked: Das Gerät mit der IP %{ip} wurde erfolgreich ausgeloggt.
42
44
  some_authenticator_removed: Hardware key successfully deleted.
43
45
  successfully_logged_out: Sie haben sich erfolgreich ausgeloggt.
46
+ target_credential_not_found: Das Zielkonto wurde nicht gefunden.
44
47
  try_again_cooldown: Sie können es es in %{distance_of_time_until_cooldown} erneut probieren.
45
48
  uninitialized_credential: This account has not been initialized yet. Please contact support.
46
49
  unknown_email: We don't know that email.
@@ -21,6 +21,7 @@ en:
21
21
  domain_mismatch: This username cannot log in here.
22
22
  email_too_long: The provided email is too long. It must be less at most %{maximum} characters long.
23
23
  email_too_short: The provided email is too short. It should be at least %{minimum} characters long.
24
+ incognito_started: You are now acting on behalf of another account.
24
25
  incompatible_webauth_device: Sorry, this device cannot be used. Please try another webauth device.
25
26
  invalid_domain: The Domain "%{domain}" is not a valid domain name.
26
27
  invalid_email_characters: The provided email address contains invalid characters.
@@ -33,6 +34,7 @@ en:
33
34
  missing_secret_key: Missing the secret key parameter. Is the URL correct?
34
35
  mode_username_and_webauth_description: Also known as WebAuthentication (WebAuthn), FIDO2, CTAP2, Apple Passkey (Touch ID, Face ID).
35
36
  mode_username_and_webauth_title: Hardware key (recommended)
37
+ not_logged_in_as_impersonator: You must be logged in to impersonate someone.
36
38
  onboarding_timeout: You had %{lifespan_hours} hours to access this onboarding link, but it took too long.
37
39
  permanently_blocked: You failed too many times for this account. Please contact customer service.
38
40
  remote_login_path: remote
@@ -41,6 +43,7 @@ en:
41
43
  session_revoked: The logged in device with the IP %{ip} has successfully been logged out.
42
44
  some_authenticator_removed: Hardware key successfully deleted.
43
45
  successfully_logged_out: Logout successful.
46
+ target_credential_not_found: The target account could not be found.
44
47
  try_again_cooldown: You may try again in %{distance_of_time_until_cooldown}.
45
48
  uninitialized_credential: This account has not been initialized yet. Please contact support.
46
49
  unknown_email: We don't know that email.
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Adminland
5
+ module Incognitos
6
+ # The admin (impersonator) specifies their own +scope+ and a +target_scope+.
7
+ # +request+ is coerced with the impersonator's +scope+ (+request.authentication+ checks the
8
+ # impersonator's scope). We create a new session for the target credential and store the
9
+ # impersonator's credential_id as +incognito_credential_id+. If +target_scope+ matches the
10
+ # impersonator's scope, we revoke the impersonator's session before logging in as the target.
11
+ class Start
12
+ include Calls
13
+ include ::Booth::Logging
14
+
15
+ option :scope, ::Booth::Coercers::Scope
16
+ option :request, ::Booth::Coercers::Request
17
+ option :target_credential_id
18
+ option :target_scope
19
+
20
+ def call
21
+ do_check_impersonator_logged_in
22
+ .on_success { do_find_target_credential }
23
+ .on_success { do_compare_domain }
24
+ .on_success { do_compare_scope }
25
+ .on_success { do_create_session }
26
+ .on_success { do_revoke_impersonator_if_same_scope }
27
+ .on_success { do_login }
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :impersonator_credential_id, :target_credential, :session
33
+
34
+ def do_check_impersonator_logged_in
35
+ unless request.authentication.logged_in?
36
+ log { 'No impersonator logged in' }
37
+ return Tron.failure :not_logged_in_as_impersonator,
38
+ public_message: I18n.t('booth.not_logged_in_as_impersonator')
39
+ end
40
+
41
+ self.impersonator_credential_id = request.authentication.credential_id
42
+ log { "Impersonator credential_id=#{impersonator_credential_id}" }
43
+ Tron.success :impersonator_captured
44
+ end
45
+
46
+ def do_find_target_credential
47
+ self.target_credential = ::Booth::Models::Credential.find_by(id: target_credential_id)
48
+
49
+ if target_credential
50
+ log { "Target credential ##{target_credential.id} found" }
51
+ Tron.success :target_credential_found
52
+ else
53
+ Tron.failure :target_credential_not_found,
54
+ public_message: I18n.t('booth.target_credential_not_found')
55
+ end
56
+ end
57
+
58
+ def do_compare_domain
59
+ ::Booth::Comparisons::Domain.call(
60
+ expected: target_credential.domain,
61
+ actual: request.host,
62
+ )
63
+ end
64
+
65
+ def do_compare_scope
66
+ ::Booth::Comparisons::Scope.call(
67
+ expected: target_credential.scope,
68
+ actual: target_scope,
69
+ )
70
+ end
71
+
72
+ def do_create_session
73
+ self.session = ::Booth::Models::Session.create!(
74
+ credential: target_credential,
75
+ incognito_credential_id: impersonator_credential_id,
76
+ agent: request.agent,
77
+ most_recent_ip: request.ip,
78
+ )
79
+ log { "Created incognito session ##{session.id}" }
80
+ Tron.success :session_created
81
+ end
82
+
83
+ def do_revoke_impersonator_if_same_scope
84
+ return Tron.success(:no_revoke_needed) unless same_scope?
85
+
86
+ impersonator_session = ::Booth::Models::Session.find_by(
87
+ id: request.authentication.session_id,
88
+ )
89
+ return Tron.failure(:impersonator_session_not_found) unless impersonator_session
90
+
91
+ log { "Revoking impersonator session ##{impersonator_session.id}" }
92
+ impersonator_session.update!(revoked_at: Time.current, revoke_reason: :impersonation)
93
+
94
+ Tron.success :impersonator_session_revoked
95
+ end
96
+
97
+ def do_login
98
+ target_auth.login(session:)
99
+ log { "Incognito started: #{target_credential.username}" }
100
+ Tron.success :incognito_started,
101
+ public_message: I18n.t('booth.incognito_started')
102
+ end
103
+
104
+ def same_scope?
105
+ target_scope == request.scope
106
+ end
107
+
108
+ def target_auth
109
+ @target_auth ||= request.authentication(scope: target_scope)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -44,6 +44,14 @@ module Booth
44
44
  ::Booth::Adminland::Onboardings::Destroy.call(...)
45
45
  end
46
46
 
47
+ # -----------------
48
+ # Incognito Helpers
49
+ # -----------------
50
+
51
+ def self.start_incognito(...)
52
+ ::Booth::Adminland::Incognitos::Start.call(...)
53
+ end
54
+
47
55
  # ----------------
48
56
  # Internal Helpers
49
57
  # ----------------
data/lib/booth/request.rb CHANGED
@@ -4,6 +4,7 @@ module Booth
4
4
  # Convenience wrapper for Rack::Request.
5
5
  class Request
6
6
  include ::Booth::Logging
7
+
7
8
  # Initializes a new Booth::Request instance.
8
9
  #
9
10
  # **Parameters:**
@@ -77,7 +78,7 @@ module Booth
77
78
  ::Booth::Core::Geolocation.lookup(ip)
78
79
  end
79
80
 
80
- def authentication
81
+ def authentication(scope: self.scope)
81
82
  ::Booth::Requests::Authentication.new(scope:, warden: request.env['warden'])
82
83
  end
83
84
 
@@ -6,7 +6,7 @@ module Booth
6
6
  class Authentication
7
7
  include ::Booth::Logging
8
8
 
9
- delegate :username, :credential_id, :mode,
9
+ delegate :username, :credential_id, :incognito_credential_id, :mode,
10
10
  to: :passport,
11
11
  allow_nil: true
12
12
 
@@ -33,6 +33,7 @@ module Booth
33
33
 
34
34
  log { "The expected view `#{partial}` was rendered." }
35
35
  self.class.asserted_partials << partial
36
+ take_screenshot
36
37
  return
37
38
  end
38
39
  end
@@ -46,6 +47,21 @@ module Booth
46
47
  def partial
47
48
  "#{namespace}/#{controller}/#{step}"
48
49
  end
50
+
51
+ private
52
+
53
+ # For manual visual inspection of all partial (e.g. CSS glitches).
54
+ # Because doing this manually is very hard (would have to play through every scenario).
55
+ def take_screenshot
56
+ screenshot_dir.mkpath
57
+ path = screenshot_dir.join(partial.gsub(/[^a-zA-Z0-9._-]/, '_'))
58
+ page.save_screenshot(path.to_s) # rubocop:disable Lint/Debugger
59
+ log { "Screenshot saved to `#{path}`" }
60
+ end
61
+
62
+ def screenshot_dir
63
+ ::Rails.root.join('tmp/booth')
64
+ end
49
65
  end
50
66
  end
51
67
  end
data/lib/booth/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Booth
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.9'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: booth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - halo
@@ -230,6 +230,7 @@ files:
230
230
  - lib/booth/adminland/authenticators/exist.rb
231
231
  - lib/booth/adminland/credentials/create.rb
232
232
  - lib/booth/adminland/credentials/index.rb
233
+ - lib/booth/adminland/incognitos/start.rb
233
234
  - lib/booth/adminland/onboardings/create.rb
234
235
  - lib/booth/adminland/onboardings/destroy.rb
235
236
  - lib/booth/adminland/onboardings/find.rb