quo_vadis 2.1.2 → 2.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +2 -4
- data/app/controllers/quo_vadis/password_resets_controller.rb +1 -1
- data/app/controllers/quo_vadis/sessions_controller.rb +3 -3
- data/app/controllers/quo_vadis/twofas_controller.rb +1 -1
- data/app/mailers/quo_vadis/mailer.rb +16 -16
- data/app/models/quo_vadis/session.rb +1 -0
- data/app/views/quo_vadis/sessions/index.html.erb +6 -0
- data/lib/quo_vadis/controller.rb +3 -3
- data/lib/quo_vadis/version.rb +1 -1
- data/lib/quo_vadis.rb +6 -4
- data/quo_vadis.gemspec +1 -1
- data/test/mailers/mailer_test.rb +49 -32
- data/test/models/account_test.rb +8 -2
- data/test/models/model_test.rb +4 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d01da21fdfe58d00e4f073036f53df4e0da212a9b202c31bf9fe9ed162e2e2d5
|
4
|
+
data.tar.gz: f9b7b777065c7b4e1c83ed54330bc08608cc6020f55f1b72d316de5a1e4e06f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3daf72be8a7e1a4545bc8dc7acd77553c62933dd5837e78679a7581cf44356822fbecf4036d5cd8cac5648636a5077e1edd5cb8d6e804ea7c9d55c10b36dc6e
|
7
|
+
data.tar.gz: eee0046f0ae978f537f85946946cb4fe0267fd866216108f226411a3df479cb1b7e9c5c334d120a9107298ce9c30f3b8b6736dcaf6248a9c4a5e23a34962bc2e
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,28 @@
|
|
4
4
|
## HEAD
|
5
5
|
|
6
6
|
|
7
|
+
## 2.1.5 (27 May 2022)
|
8
|
+
|
9
|
+
* Order sessions list and display more information.
|
10
|
+
* Set status 303 See Other on destroy redirects.
|
11
|
+
* Streamline bundler instructions.
|
12
|
+
|
13
|
+
|
14
|
+
## 2.1.4 (2 October 2021)
|
15
|
+
|
16
|
+
* Allow metadata for login log.
|
17
|
+
|
18
|
+
|
19
|
+
## 2.1.3 (30 September 2021)
|
20
|
+
|
21
|
+
* Pass IP and timestamp as paramenters to mailer.
|
22
|
+
|
23
|
+
|
24
|
+
## 2.1.2 (30 September 2021)
|
25
|
+
|
26
|
+
* Delete existing recovery codes when generating new ones.
|
27
|
+
|
28
|
+
|
7
29
|
## 2.1.1 (8 July 2021)
|
8
30
|
|
9
31
|
* Remove unnecessary route names.
|
data/README.md
CHANGED
@@ -37,11 +37,9 @@ Simple to integrate into your application. The main task is customising the exa
|
|
37
37
|
Add the gem to your Gemfile:
|
38
38
|
|
39
39
|
```ruby
|
40
|
-
|
40
|
+
bundle add 'quo_vadis'
|
41
41
|
```
|
42
42
|
|
43
|
-
Then run `bundle install`.
|
44
|
-
|
45
43
|
Next, add the database tables:
|
46
44
|
|
47
45
|
```
|
@@ -119,7 +117,7 @@ end
|
|
119
117
|
|
120
118
|
__`login(model, browser_session = true)`__
|
121
119
|
|
122
|
-
To log in a user who has authenticated with a password, call `#login(model, browser_session = true)`. For the `browser_session` argument, pass `true` to log in for the duration of the browser session, or `false` to log in for `QuoVadis.session_lifetime` (which could be the browser session anyway).
|
120
|
+
To log in a user who has authenticated with a password, call `#login(model, browser_session = true, metadata: {})`. For the `browser_session` argument, optionally pass `true` to log in for the duration of the browser session, or `false` to log in for `QuoVadis.session_lifetime` (which could be the browser session anyway). Any metadata are stored in the log entry for the login.
|
123
121
|
|
124
122
|
__`request_confirmation(model)`__
|
125
123
|
|
@@ -17,7 +17,7 @@ module QuoVadis
|
|
17
17
|
|
18
18
|
if account
|
19
19
|
token = QuoVadis::PasswordResetToken.generate account
|
20
|
-
QuoVadis.deliver :reset_password, email: account.model.email, url: quo_vadis.password_reset_url(token)
|
20
|
+
QuoVadis.deliver :reset_password, {email: account.model.email, url: quo_vadis.password_reset_url(token)}
|
21
21
|
end
|
22
22
|
|
23
23
|
redirect_to password_resets_path, notice: QuoVadis.translate('flash.password_reset.create')
|
@@ -9,7 +9,7 @@ module QuoVadis
|
|
9
9
|
|
10
10
|
def index
|
11
11
|
@qv_session = qv.session
|
12
|
-
@qv_sessions = @qv_session.account.sessions
|
12
|
+
@qv_sessions = @qv_session.account.sessions.new_to_old
|
13
13
|
end
|
14
14
|
|
15
15
|
|
@@ -58,12 +58,12 @@ module QuoVadis
|
|
58
58
|
current_qv_session.account.sessions.destroy params[:id]
|
59
59
|
qv.log current_qv_session.account, Log::LOGOUT_OTHER
|
60
60
|
flash[:notice] = QuoVadis.translate 'flash.logout.other'
|
61
|
-
redirect_to action: :index
|
61
|
+
redirect_to action: :index, status: :see_other
|
62
62
|
else # this session
|
63
63
|
qv.log authenticated_model.qv_account, Log::LOGOUT
|
64
64
|
qv.logout
|
65
65
|
flash[:notice] = QuoVadis.translate 'flash.logout.self'
|
66
|
-
redirect_to main_app.root_path
|
66
|
+
redirect_to main_app.root_path, status: :see_other
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -14,7 +14,7 @@ module QuoVadis
|
|
14
14
|
account.sessions.each &:reset_authenticated_with_second_factor # OWASP ASV v4.0, 2.8.6
|
15
15
|
qv.log account, Log::TWOFA_DEACTIVATED
|
16
16
|
QuoVadis.notify :twofa_deactivated_notification, email: authenticated_model.email
|
17
|
-
redirect_to twofa_path, notice: QuoVadis.translate('flash.2fa.invalidated')
|
17
|
+
redirect_to twofa_path, notice: QuoVadis.translate('flash.2fa.invalidated'), status: :see_other
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -14,52 +14,52 @@ module QuoVadis
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def email_change_notification
|
17
|
-
@timestamp =
|
18
|
-
@ip =
|
17
|
+
@timestamp = params[:timestamp]
|
18
|
+
@ip = params[:ip]
|
19
19
|
_mail params[:email], QuoVadis.translate('mailer.notification.email_change')
|
20
20
|
end
|
21
21
|
|
22
22
|
def identifier_change_notification
|
23
|
-
@timestamp =
|
23
|
+
@timestamp = params[:timestamp]
|
24
24
|
@identifier = params[:identifier]
|
25
|
-
@ip =
|
25
|
+
@ip = params[:ip]
|
26
26
|
_mail params[:email], QuoVadis.translate('mailer.notification.identifier_change',
|
27
27
|
identifier: params[:identifier])
|
28
28
|
end
|
29
29
|
|
30
30
|
def password_change_notification
|
31
|
-
@timestamp =
|
32
|
-
@ip =
|
31
|
+
@timestamp = params[:timestamp]
|
32
|
+
@ip = params[:ip]
|
33
33
|
_mail params[:email], QuoVadis.translate('mailer.notification.password_change')
|
34
34
|
end
|
35
35
|
|
36
36
|
def password_reset_notification
|
37
|
-
@timestamp =
|
38
|
-
@ip =
|
37
|
+
@timestamp = params[:timestamp]
|
38
|
+
@ip = params[:ip]
|
39
39
|
_mail params[:email], QuoVadis.translate('mailer.notification.password_reset')
|
40
40
|
end
|
41
41
|
|
42
42
|
def totp_setup_notification
|
43
|
-
@timestamp =
|
44
|
-
@ip =
|
43
|
+
@timestamp = params[:timestamp]
|
44
|
+
@ip = params[:ip]
|
45
45
|
_mail params[:email], QuoVadis.translate('mailer.notification.totp_setup')
|
46
46
|
end
|
47
47
|
|
48
48
|
def totp_reuse_notification
|
49
|
-
@timestamp =
|
50
|
-
@ip =
|
49
|
+
@timestamp = params[:timestamp]
|
50
|
+
@ip = params[:ip]
|
51
51
|
_mail params[:email], QuoVadis.translate('mailer.notification.totp_reuse')
|
52
52
|
end
|
53
53
|
|
54
54
|
def twofa_deactivated_notification
|
55
|
-
@timestamp =
|
56
|
-
@ip =
|
55
|
+
@timestamp = params[:timestamp]
|
56
|
+
@ip = params[:ip]
|
57
57
|
_mail params[:email], QuoVadis.translate('mailer.notification.twofa_deactivated')
|
58
58
|
end
|
59
59
|
|
60
60
|
def recovery_codes_generation_notification
|
61
|
-
@timestamp =
|
62
|
-
@ip =
|
61
|
+
@timestamp = params[:timestamp]
|
62
|
+
@ip = params[:ip]
|
63
63
|
_mail params[:email], QuoVadis.translate('mailer.notification.recovery_codes_generation')
|
64
64
|
end
|
65
65
|
|
@@ -3,6 +3,9 @@
|
|
3
3
|
<table>
|
4
4
|
<thead>
|
5
5
|
<tr>
|
6
|
+
<th>Signed in</th>
|
7
|
+
<th>Last seen</th>
|
8
|
+
<th>2FA used</th>
|
6
9
|
<th>IP</th>
|
7
10
|
<th>User agent</th>
|
8
11
|
<th></th>
|
@@ -11,6 +14,9 @@
|
|
11
14
|
<tbody>
|
12
15
|
<% @qv_sessions.each do |sess| %>
|
13
16
|
<tr>
|
17
|
+
<td><time datetime="<%= sess.created_at.to_formatted_s(:iso_8601) %>"><%= sess.created_at.to_formatted_s('%-d %B %Y') %></time></td>
|
18
|
+
<td><time datetime="<%= sess.last_seen_at.to_formatted_s(:iso_8601) %>"><%= sess.last_seen_at.to_formatted_s('%-d %B %Y') %></time></td>
|
19
|
+
<td><%= sess.second_factor_authenticated? ? 'Yes' : 'No' %></td>
|
14
20
|
<td><%= sess.ip %></td>
|
15
21
|
<td><%= sess.user_agent %></td>
|
16
22
|
<td>
|
data/lib/quo_vadis/controller.rb
CHANGED
@@ -36,8 +36,8 @@ module QuoVadis
|
|
36
36
|
#
|
37
37
|
# browser_session - true: login only for duration of browser session
|
38
38
|
# false: login for QuoVadis.session_lifetime (which may be browser session anyway)
|
39
|
-
def login(model, browser_session = true)
|
40
|
-
qv.log model.qv_account, Log::LOGIN_SUCCESS
|
39
|
+
def login(model, browser_session = true, metadata: {})
|
40
|
+
qv.log model.qv_account, Log::LOGIN_SUCCESS, metadata
|
41
41
|
|
42
42
|
qv.prevent_rails_session_fixation
|
43
43
|
|
@@ -87,7 +87,7 @@ module QuoVadis
|
|
87
87
|
|
88
88
|
def request_confirmation(model)
|
89
89
|
token = QuoVadis::AccountConfirmationToken.generate model.qv_account
|
90
|
-
QuoVadis.deliver :account_confirmation, email: model.email, url: quo_vadis.confirmation_url(token)
|
90
|
+
QuoVadis.deliver :account_confirmation, {email: model.email, url: quo_vadis.confirmation_url(token)}
|
91
91
|
session[:account_pending_confirmation] = model.qv_account.id
|
92
92
|
|
93
93
|
flash[:notice] = QuoVadis.translate 'flash.confirmation.create'
|
data/lib/quo_vadis/version.rb
CHANGED
data/lib/quo_vadis.rb
CHANGED
@@ -73,12 +73,14 @@ module QuoVadis
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def notify(action, params)
|
76
|
-
|
76
|
+
deliver(action, params, later: true)
|
77
77
|
end
|
78
78
|
|
79
|
-
def deliver(action, params)
|
80
|
-
mail = QuoVadis::Mailer
|
81
|
-
|
79
|
+
def deliver(action, params, later: QuoVadis.enqueue_transactional_emails)
|
80
|
+
mail = QuoVadis::Mailer
|
81
|
+
.with(params.merge(ip: QuoVadis::CurrentRequestDetails.ip, timestamp: Time.now))
|
82
|
+
.send(action)
|
83
|
+
later ?
|
82
84
|
mail.deliver_later :
|
83
85
|
mail.deliver_now
|
84
86
|
end
|
data/quo_vadis.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ['Andy Stewart']
|
9
9
|
spec.email = ['boss@airbladesoftware.com']
|
10
10
|
|
11
|
-
spec.summary = 'Multifactor authentication for Rails 6.'
|
11
|
+
spec.summary = 'Multifactor authentication for Rails 6 and 7.'
|
12
12
|
spec.homepage = 'https://github.com/airblade/quo_vadis'
|
13
13
|
spec.license = 'MIT'
|
14
14
|
|
data/test/mailers/mailer_test.rb
CHANGED
@@ -44,14 +44,16 @@ class MailerTest < ActionMailer::TestCase
|
|
44
44
|
|
45
45
|
|
46
46
|
test 'email change notification' do
|
47
|
-
email = QuoVadis::Mailer.with(
|
47
|
+
email = QuoVadis::Mailer.with(
|
48
|
+
email: 'Foo <foo@example.com>',
|
49
|
+
ip: '1.2.3.4',
|
50
|
+
timestamp: Time.now
|
51
|
+
).email_change_notification
|
48
52
|
|
49
53
|
# freeze_time
|
50
54
|
|
51
55
|
assert_emails 1 do
|
52
|
-
|
53
|
-
email.deliver_now
|
54
|
-
end
|
56
|
+
email.deliver_now
|
55
57
|
end
|
56
58
|
|
57
59
|
assert_equal ['foo@example.com'], email.to
|
@@ -62,14 +64,17 @@ class MailerTest < ActionMailer::TestCase
|
|
62
64
|
|
63
65
|
|
64
66
|
test 'identifier change notification' do
|
65
|
-
email = QuoVadis::Mailer.with(
|
67
|
+
email = QuoVadis::Mailer.with(
|
68
|
+
email: 'Foo <foo@example.com>',
|
69
|
+
identifier: 'email',
|
70
|
+
ip: '1.2.3.4',
|
71
|
+
timestamp: Time.now
|
72
|
+
).identifier_change_notification
|
66
73
|
|
67
74
|
# freeze_time
|
68
75
|
|
69
76
|
assert_emails 1 do
|
70
|
-
|
71
|
-
email.deliver_now
|
72
|
-
end
|
77
|
+
email.deliver_now
|
73
78
|
end
|
74
79
|
|
75
80
|
assert_equal ['foo@example.com'], email.to
|
@@ -80,14 +85,16 @@ class MailerTest < ActionMailer::TestCase
|
|
80
85
|
|
81
86
|
|
82
87
|
test 'password change notification' do
|
83
|
-
email = QuoVadis::Mailer.with(
|
88
|
+
email = QuoVadis::Mailer.with(
|
89
|
+
email: 'Foo <foo@example.com>',
|
90
|
+
ip: '1.2.3.4',
|
91
|
+
timestamp: Time.now
|
92
|
+
).password_change_notification
|
84
93
|
|
85
94
|
# freeze_time
|
86
95
|
|
87
96
|
assert_emails 1 do
|
88
|
-
|
89
|
-
email.deliver_now
|
90
|
-
end
|
97
|
+
email.deliver_now
|
91
98
|
end
|
92
99
|
|
93
100
|
assert_equal ['foo@example.com'], email.to
|
@@ -98,14 +105,16 @@ class MailerTest < ActionMailer::TestCase
|
|
98
105
|
|
99
106
|
|
100
107
|
test 'password reset notification' do
|
101
|
-
email = QuoVadis::Mailer.with(
|
108
|
+
email = QuoVadis::Mailer.with(
|
109
|
+
email: 'Foo <foo@example.com>',
|
110
|
+
ip: '1.2.3.4',
|
111
|
+
timestamp: Time.now
|
112
|
+
).password_reset_notification
|
102
113
|
|
103
114
|
# freeze_time
|
104
115
|
|
105
116
|
assert_emails 1 do
|
106
|
-
|
107
|
-
email.deliver_now
|
108
|
-
end
|
117
|
+
email.deliver_now
|
109
118
|
end
|
110
119
|
|
111
120
|
assert_equal ['foo@example.com'], email.to
|
@@ -116,14 +125,16 @@ class MailerTest < ActionMailer::TestCase
|
|
116
125
|
|
117
126
|
|
118
127
|
test 'totp setup notification' do
|
119
|
-
email = QuoVadis::Mailer.with(
|
128
|
+
email = QuoVadis::Mailer.with(
|
129
|
+
email: 'Foo <foo@example.com>',
|
130
|
+
ip: '1.2.3.4',
|
131
|
+
timestamp: Time.now
|
132
|
+
).totp_setup_notification
|
120
133
|
|
121
134
|
# freeze_time
|
122
135
|
|
123
136
|
assert_emails 1 do
|
124
|
-
|
125
|
-
email.deliver_now
|
126
|
-
end
|
137
|
+
email.deliver_now
|
127
138
|
end
|
128
139
|
|
129
140
|
assert_equal ['foo@example.com'], email.to
|
@@ -134,14 +145,16 @@ class MailerTest < ActionMailer::TestCase
|
|
134
145
|
|
135
146
|
|
136
147
|
test 'totp reuse notification' do
|
137
|
-
email = QuoVadis::Mailer.with(
|
148
|
+
email = QuoVadis::Mailer.with(
|
149
|
+
email: 'Foo <foo@example.com>',
|
150
|
+
ip: '1.2.3.4',
|
151
|
+
timestamp: Time.now
|
152
|
+
).totp_reuse_notification
|
138
153
|
|
139
154
|
# freeze_time
|
140
155
|
|
141
156
|
assert_emails 1 do
|
142
|
-
|
143
|
-
email.deliver_now
|
144
|
-
end
|
157
|
+
email.deliver_now
|
145
158
|
end
|
146
159
|
|
147
160
|
assert_equal ['foo@example.com'], email.to
|
@@ -152,14 +165,16 @@ class MailerTest < ActionMailer::TestCase
|
|
152
165
|
|
153
166
|
|
154
167
|
test '2fa deactivated notification' do
|
155
|
-
email = QuoVadis::Mailer.with(
|
168
|
+
email = QuoVadis::Mailer.with(
|
169
|
+
email: 'Foo <foo@example.com>',
|
170
|
+
ip: '1.2.3.4',
|
171
|
+
timestamp: Time.now
|
172
|
+
).twofa_deactivated_notification
|
156
173
|
|
157
174
|
# freeze_time
|
158
175
|
|
159
176
|
assert_emails 1 do
|
160
|
-
|
161
|
-
email.deliver_now
|
162
|
-
end
|
177
|
+
email.deliver_now
|
163
178
|
end
|
164
179
|
|
165
180
|
assert_equal ['foo@example.com'], email.to
|
@@ -170,14 +185,16 @@ class MailerTest < ActionMailer::TestCase
|
|
170
185
|
|
171
186
|
|
172
187
|
test 'recovery codes generation notification' do
|
173
|
-
email = QuoVadis::Mailer.with(
|
188
|
+
email = QuoVadis::Mailer.with(
|
189
|
+
email: 'Foo <foo@example.com>',
|
190
|
+
ip: '1.2.3.4',
|
191
|
+
timestamp: Time.now
|
192
|
+
).recovery_codes_generation_notification
|
174
193
|
|
175
194
|
# freeze_time
|
176
195
|
|
177
196
|
assert_emails 1 do
|
178
|
-
|
179
|
-
email.deliver_now
|
180
|
-
end
|
197
|
+
email.deliver_now
|
181
198
|
end
|
182
199
|
|
183
200
|
assert_equal ['foo@example.com'], email.to
|
data/test/models/account_test.rb
CHANGED
@@ -13,8 +13,11 @@ class AccountTest < ActiveSupport::TestCase
|
|
13
13
|
|
14
14
|
|
15
15
|
test 'notifies on identifier change when notifier is not email' do
|
16
|
+
freeze_time
|
16
17
|
p = Person.create! username: 'bob', email: 'bob@example.com', password: 'secretsecret'
|
17
|
-
assert_enqueued_email_with QuoVadis::Mailer,
|
18
|
+
assert_enqueued_email_with QuoVadis::Mailer,
|
19
|
+
:identifier_change_notification,
|
20
|
+
args: {email: 'bob@example.com', identifier: 'username', ip: nil, timestamp: Time.now} do
|
18
21
|
assert_enqueued_emails 1 do
|
19
22
|
p.update username: 'robert@example.com'
|
20
23
|
end
|
@@ -23,8 +26,11 @@ class AccountTest < ActiveSupport::TestCase
|
|
23
26
|
|
24
27
|
|
25
28
|
test 'does not notify on identifier change when notifier is email' do
|
29
|
+
freeze_time
|
26
30
|
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
27
|
-
assert_enqueued_email_with QuoVadis::Mailer,
|
31
|
+
assert_enqueued_email_with QuoVadis::Mailer,
|
32
|
+
:email_change_notification,
|
33
|
+
args: {email: 'bob@example.com', ip: nil, timestamp: Time.now} do
|
28
34
|
assert_enqueued_emails 1 do
|
29
35
|
u.update email: 'robert@example.com'
|
30
36
|
end
|
data/test/models/model_test.rb
CHANGED
@@ -58,8 +58,11 @@ class ModelTest < ActiveSupport::TestCase
|
|
58
58
|
|
59
59
|
|
60
60
|
test 'notifies on email change' do
|
61
|
+
freeze_time
|
61
62
|
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
62
|
-
assert_enqueued_email_with QuoVadis::Mailer,
|
63
|
+
assert_enqueued_email_with QuoVadis::Mailer,
|
64
|
+
:email_change_notification,
|
65
|
+
args: {email: 'bob@example.com', ip: nil, timestamp: Time.now} do
|
63
66
|
u.update email: 'robert@example.com'
|
64
67
|
end
|
65
68
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quo_vadis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Stewart
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -224,8 +224,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
224
224
|
- !ruby/object:Gem::Version
|
225
225
|
version: '0'
|
226
226
|
requirements: []
|
227
|
-
rubygems_version: 3.
|
227
|
+
rubygems_version: 3.2.22
|
228
228
|
signing_key:
|
229
229
|
specification_version: 4
|
230
|
-
summary: Multifactor authentication for Rails 6.
|
230
|
+
summary: Multifactor authentication for Rails 6 and 7.
|
231
231
|
test_files: []
|