Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
030f4fc1
Commit
030f4fc1
authored
Mar 13, 2020
by
Diego Louzán
Committed by
Kushal Pandya
Mar 13, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
2FA support for admin mode feature
Users that have 2FA setup must use it before activating admin mode
parent
465acee6
Changes
22
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
574 additions
and
29 deletions
+574
-29
app/assets/javascripts/pages/admin/sessions/index.js
app/assets/javascripts/pages/admin/sessions/index.js
+1
-0
app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb
...ollers/admin/concerns/authenticates_2fa_for_admin_mode.rb
+84
-0
app/controllers/admin/sessions_controller.rb
app/controllers/admin/sessions_controller.rb
+17
-1
app/controllers/concerns/authenticates_with_two_factor.rb
app/controllers/concerns/authenticates_with_two_factor.rb
+0
-2
app/controllers/omniauth_callbacks_controller.rb
app/controllers/omniauth_callbacks_controller.rb
+12
-5
app/views/admin/sessions/_new_base.html.haml
app/views/admin/sessions/_new_base.html.haml
+2
-2
app/views/admin/sessions/_tabs_normal.html.haml
app/views/admin/sessions/_tabs_normal.html.haml
+1
-1
app/views/admin/sessions/_two_factor_otp.html.haml
app/views/admin/sessions/_two_factor_otp.html.haml
+9
-0
app/views/admin/sessions/_two_factor_u2f.html.haml
app/views/admin/sessions/_two_factor_u2f.html.haml
+17
-0
app/views/admin/sessions/new.html.haml
app/views/admin/sessions/new.html.haml
+3
-3
app/views/admin/sessions/two_factor.html.haml
app/views/admin/sessions/two_factor.html.haml
+15
-0
app/views/devise/shared/_omniauth_box.html.haml
app/views/devise/shared/_omniauth_box.html.haml
+6
-5
app/views/u2f/_authenticate.html.haml
app/views/u2f/_authenticate.html.haml
+0
-1
changelogs/unreleased/feat-2fa-for-admin-mode.yml
changelogs/unreleased/feat-2fa-for-admin-mode.yml
+5
-0
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/controllers/admin/sessions_controller_spec.rb
spec/controllers/admin/sessions_controller_spec.rb
+111
-5
spec/features/admin/admin_mode/login_spec.rb
spec/features/admin/admin_mode/login_spec.rb
+184
-0
spec/features/admin/admin_mode/logout_spec.rb
spec/features/admin/admin_mode/logout_spec.rb
+42
-0
spec/features/admin/admin_mode_spec.rb
spec/features/admin/admin_mode_spec.rb
+2
-2
spec/support/helpers/login_helpers.rb
spec/support/helpers/login_helpers.rb
+12
-1
spec/views/admin/sessions/new.html.haml_spec.rb
spec/views/admin/sessions/new.html.haml_spec.rb
+1
-1
spec/views/admin/sessions/two_factor.html.haml_spec.rb
spec/views/admin/sessions/two_factor.html.haml_spec.rb
+41
-0
No files found.
app/assets/javascripts/pages/admin/sessions/index.js
0 → 100644
View file @
030f4fc1
import
'
~/pages/sessions/index
'
;
app/controllers/admin/concerns/authenticates_2fa_for_admin_mode.rb
0 → 100644
View file @
030f4fc1
# frozen_string_literal: true
module
Authenticates2FAForAdminMode
extend
ActiveSupport
::
Concern
included
do
include
AuthenticatesWithTwoFactor
end
def
admin_mode_prompt_for_two_factor
(
user
)
return
handle_locked_user
(
user
)
unless
user
.
can?
(
:log_in
)
session
[
:otp_user_id
]
=
user
.
id
setup_u2f_authentication
(
user
)
render
'admin/sessions/two_factor'
,
layout:
'application'
end
def
admin_mode_authenticate_with_two_factor
user
=
current_user
return
handle_locked_user
(
user
)
unless
user
.
can?
(
:log_in
)
if
user_params
[
:otp_attempt
].
present?
&&
session
[
:otp_user_id
]
admin_mode_authenticate_with_two_factor_via_otp
(
user
)
elsif
user_params
[
:device_response
].
present?
&&
session
[
:otp_user_id
]
admin_mode_authenticate_with_two_factor_via_u2f
(
user
)
elsif
user
&&
user
.
valid_password?
(
user_params
[
:password
])
admin_mode_prompt_for_two_factor
(
user
)
else
invalid_login_redirect
end
end
def
admin_mode_authenticate_with_two_factor_via_otp
(
user
)
if
valid_otp_attempt?
(
user
)
# Remove any lingering user data from login
session
.
delete
(
:otp_user_id
)
user
.
save!
# The admin user has successfully passed 2fa, enable admin mode ignoring password
enable_admin_mode
else
user
.
increment_failed_attempts!
Gitlab
::
AppLogger
.
info
(
"Failed Admin Mode Login: user=
#{
user
.
username
}
ip=
#{
request
.
remote_ip
}
method=OTP"
)
flash
.
now
[
:alert
]
=
_
(
'Invalid two-factor code.'
)
admin_mode_prompt_for_two_factor
(
user
)
end
end
def
admin_mode_authenticate_with_two_factor_via_u2f
(
user
)
if
U2fRegistration
.
authenticate
(
user
,
u2f_app_id
,
user_params
[
:device_response
],
session
[
:challenge
])
# Remove any lingering user data from login
session
.
delete
(
:otp_user_id
)
session
.
delete
(
:challenge
)
# The admin user has successfully passed 2fa, enable admin mode ignoring password
enable_admin_mode
else
user
.
increment_failed_attempts!
Gitlab
::
AppLogger
.
info
(
"Failed Admin Mode Login: user=
#{
user
.
username
}
ip=
#{
request
.
remote_ip
}
method=U2F"
)
flash
.
now
[
:alert
]
=
_
(
'Authentication via U2F device failed.'
)
admin_mode_prompt_for_two_factor
(
user
)
end
end
private
def
enable_admin_mode
if
current_user_mode
.
enable_admin_mode!
(
skip_password_validation:
true
)
redirect_to
redirect_path
,
notice:
_
(
'Admin mode enabled'
)
else
invalid_login_redirect
end
end
def
invalid_login_redirect
flash
.
now
[
:alert
]
=
_
(
'Invalid login or password'
)
render
:new
end
end
app/controllers/admin/sessions_controller.rb
View file @
030f4fc1
# frozen_string_literal: true
class
Admin::SessionsController
<
ApplicationController
include
Authenticates2FAForAdminMode
include
InternalRedirect
before_action
:user_is_admin!
...
...
@@ -15,7 +16,9 @@ class Admin::SessionsController < ApplicationController
end
def
create
if
current_user_mode
.
enable_admin_mode!
(
password:
params
[
:password
])
if
two_factor_enabled_for_user?
admin_mode_authenticate_with_two_factor
elsif
current_user_mode
.
enable_admin_mode!
(
password:
user_params
[
:password
])
redirect_to
redirect_path
,
notice:
_
(
'Admin mode enabled'
)
else
flash
.
now
[
:alert
]
=
_
(
'Invalid login or password'
)
...
...
@@ -37,6 +40,10 @@ class Admin::SessionsController < ApplicationController
render_404
unless
current_user
&
.
admin?
end
def
two_factor_enabled_for_user?
current_user
&
.
two_factor_enabled?
end
def
redirect_path
redirect_to_path
=
safe_redirect_path
(
stored_location_for
(
:redirect
))
||
safe_redirect_path_for_url
(
request
.
referer
)
...
...
@@ -51,4 +58,13 @@ class Admin::SessionsController < ApplicationController
def
excluded_redirect_paths
[
new_admin_session_path
,
admin_session_path
]
end
def
user_params
params
.
fetch
(
:user
,
{}).
permit
(
:password
,
:otp_attempt
,
:device_response
)
end
def
valid_otp_attempt?
(
user
)
user
.
validate_and_consume_otp!
(
user_params
[
:otp_attempt
])
||
user
.
invalidate_otp_backup_code!
(
user_params
[
:otp_attempt
])
end
end
app/controllers/concerns/authenticates_with_two_factor.rb
View file @
030f4fc1
...
...
@@ -3,8 +3,6 @@
# == AuthenticatesWithTwoFactor
#
# Controller concern to handle two-factor authentication
#
# Upon inclusion, skips `require_no_authentication` on `:create`.
module
AuthenticatesWithTwoFactor
extend
ActiveSupport
::
Concern
...
...
app/controllers/omniauth_callbacks_controller.rb
View file @
030f4fc1
...
...
@@ -2,6 +2,7 @@
class
OmniauthCallbacksController
<
Devise
::
OmniauthCallbacksController
include
AuthenticatesWithTwoFactor
include
Authenticates2FAForAdminMode
include
Devise
::
Controllers
::
Rememberable
include
AuthHelper
include
InitializesCurrentUserMode
...
...
@@ -97,7 +98,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event
(
current_user
,
with:
oauth
[
'provider'
])
if
Feature
.
enabled?
(
:user_mode_in_session
)
return
admin_mode_flow
if
current_user_mode
.
admin_mode_requested?
return
admin_mode_flow
(
auth_module
::
User
)
if
current_user_mode
.
admin_mode_requested?
end
identity_linker
||=
auth_module
::
IdentityLinker
.
new
(
current_user
,
oauth
,
session
)
...
...
@@ -245,13 +246,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
def
admin_mode_flow
if
omniauth_identity_matches_current_user?
def
admin_mode_flow
(
auth_user_class
)
auth_user
=
build_auth_user
(
auth_user_class
)
return
fail_admin_mode_invalid_credentials
unless
omniauth_identity_matches_current_user?
if
current_user
.
two_factor_enabled?
&&
!
auth_user
.
bypass_two_factor?
admin_mode_prompt_for_two_factor
(
current_user
)
else
# Can only reach here if the omniauth identity matches current user
# and current_user is an admin that requested admin mode
current_user_mode
.
enable_admin_mode!
(
skip_password_validation:
true
)
redirect_to
stored_location_for
(
:redirect
)
||
admin_root_path
,
notice:
_
(
'Admin mode enabled'
)
else
fail_admin_mode_invalid_credentials
end
end
...
...
app/views/admin/sessions/_new_base.html.haml
View file @
030f4fc1
=
form_tag
(
admin_session_path
,
method: :post
,
html:
{
class:
'new_user gl-show-field-errors'
,
'aria-live'
:
'assertive'
})
do
.form-group
=
label_tag
:password
,
_
(
'Password'
),
class:
'label-bold'
=
password_field_tag
:password
,
nil
,
class:
'form-control'
,
required:
true
,
title:
_
(
'This field is required.'
),
data:
{
qa_selector:
'password_field'
}
=
label_tag
:
user_
password
,
_
(
'Password'
),
class:
'label-bold'
=
password_field_tag
'user[password]'
,
nil
,
class:
'form-control'
,
required:
true
,
title:
_
(
'This field is required.'
),
data:
{
qa_selector:
'password_field'
}
.submit-container.move-submit-down
=
submit_tag
_
(
'Enter Admin Mode'
),
class:
'btn btn-success'
,
data:
{
qa_selector:
'enter_admin_mode_button'
}
app/views/admin/sessions/_tabs_normal.html.haml
View file @
030f4fc1
%ul
.nav-links.new-session-tabs.nav-tabs.nav
{
role:
'tablist'
}
%li
.nav-item
{
role:
'presentation'
}
%a
.nav-link.active
{
href:
'#login-pane'
,
data:
{
toggle:
'tab'
,
qa_selector:
'sign_in_tab'
},
role:
'tab'
}=
_
(
'Enter Admin Mode'
)
%a
.nav-link.active
{
href:
'#login-pane'
,
data:
{
toggle:
'tab'
,
qa_selector:
'sign_in_tab'
},
role:
'tab'
}=
tab_title
app/views/admin/sessions/_two_factor_otp.html.haml
0 → 100644
View file @
030f4fc1
=
form_tag
(
admin_session_path
,
{
method: :post
,
class:
"edit_user gl-show-field-errors js-2fa-form
#{
'hidden'
if
current_user
.
two_factor_u2f_enabled?
}
"
})
do
.form-group
=
label_tag
:user_otp_attempt
,
_
(
'Two-Factor Authentication code'
)
=
text_field_tag
'user[otp_attempt]'
,
nil
,
class:
'form-control'
,
required:
true
,
autofocus:
true
,
autocomplete:
'off'
,
title:
_
(
'This field is required.'
)
%p
.form-text.text-muted.hint
=
_
(
"Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
)
.submit-container.move-submit-down
=
submit_tag
'Verify code'
,
class:
'btn btn-success'
app/views/admin/sessions/_two_factor_u2f.html.haml
0 → 100644
View file @
030f4fc1
#js-authenticate-u2f
%a
.btn.btn-block.btn-info
#js-login-2fa-device
{
href:
'#'
}=
_
(
"Sign in via 2FA code"
)
%script
#js-authenticate-u2f-in-progress
{
type:
"text/template"
}
%p
=
_
(
"Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
)
-# haml-lint:disable NoPlainNodes
%script
#js-authenticate-u2f-error
{
type:
"text/template"
}
%div
%p
<
%=
error_message
%
>
(
#{
_
(
"error code:"
)
}
<
%=
error_code
%
>
)
%a
.btn.btn-block.btn-warning
#js-u2f-try-again
=
_
(
"Try again?"
)
%script
#js-authenticate-u2f-authenticated
{
type:
"text/template"
}
%div
%p
=
_
(
"We heard back from your U2F device. You have been authenticated."
)
=
form_tag
(
admin_session_path
,
method: :post
,
id:
'js-login-u2f-form'
)
do
|
f
|
=
hidden_field_tag
'user[device_response]'
,
nil
,
class:
'form-control'
,
required:
true
,
id:
"js-device-response"
app/views/admin/sessions/new.html.haml
View file @
030f4fc1
...
...
@@ -2,10 +2,10 @@
-
page_title
_
(
'Enter Admin Mode'
)
.row.justify-content-center
.col-
6
.new-session-forms-container
.col-
md-5
.new-session-forms-container
.login-page
#signin-container
=
render
'admin/sessions/tabs_normal'
=
render
'admin/sessions/tabs_normal'
,
tab_title:
_
(
'Enter Admin Mode'
)
.tab-content
-
if
!
current_user
.
require_password_creation_for_web?
.login-box.tab-pane.active
{
id:
'login-pane'
,
role:
'tabpanel'
}
...
...
@@ -14,7 +14,7 @@
-
if
omniauth_enabled?
&&
button_based_providers_enabled?
.clearfix
=
render
'devise/shared/omniauth_box'
=
render
'devise/shared/omniauth_box'
,
hide_remember_me:
true
-# Show a message if none of the mechanisms above are enabled
-
if
current_user
.
require_password_creation_for_web?
&&
!
omniauth_enabled?
...
...
app/views/admin/sessions/two_factor.html.haml
0 → 100644
View file @
030f4fc1
-
@hide_breadcrumbs
=
true
-
page_title
_
(
'Enter 2FA for Admin Mode'
)
.row.justify-content-center
.col-md-5.new-session-forms-container
.login-page
#signin-container
=
render
'admin/sessions/tabs_normal'
,
tab_title:
_
(
'Enter Admin Mode'
)
.tab-content
.login-box.tab-pane.active
{
id:
'login-pane'
,
role:
'tabpanel'
}
.login-body
-
if
current_user
.
two_factor_otp_enabled?
=
render
'admin/sessions/two_factor_otp'
-
if
current_user
.
two_factor_u2f_enabled?
=
render
'admin/sessions/two_factor_u2f'
app/views/devise/shared/_omniauth_box.html.haml
View file @
030f4fc1
...
...
@@ -10,6 +10,7 @@
=
provider_image_tag
(
provider
)
%span
=
label_for_provider
(
provider
)
-
unless
defined?
(
hide_remember_me
)
&&
hide_remember_me
%fieldset
.remember-me
%label
=
check_box_tag
:remember_me
,
nil
,
false
,
class:
'remember-me-checkbox'
...
...
app/views/u2f/_authenticate.html.haml
View file @
030f4fc1
#js-authenticate-u2f
%a
.btn.btn-block.btn-info
#js-login-2fa-device
{
href:
'#'
}=
_
(
"Sign in via 2FA code"
)
-# haml-lint:disable InlineJavaScript
%script
#js-authenticate-u2f-in-progress
{
type:
"text/template"
}
%p
=
_
(
"Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
)
...
...
changelogs/unreleased/feat-2fa-for-admin-mode.yml
0 → 100644
View file @
030f4fc1
---
title
:
Add 2FA support to admin mode feature
merge_request
:
22281
author
:
Diego Louzán
type
:
added
locale/gitlab.pot
View file @
030f4fc1
...
...
@@ -7501,6 +7501,9 @@ msgstr ""
msgid "Ensure your %{linkStart}environment is part of the deploy stage%{linkEnd} of your CI pipeline to track deployments to your cluster."
msgstr ""
msgid "Enter 2FA for Admin Mode"
msgstr ""
msgid "Enter Admin Mode"
msgstr ""
...
...
@@ -7540,6 +7543,9 @@ msgstr ""
msgid "Enter one or more user ID separated by commas"
msgstr ""
msgid "Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
msgstr ""
msgid "Enter the issue description"
msgstr ""
...
...
@@ -21055,6 +21061,9 @@ msgstr ""
msgid "Two-Factor Authentication"
msgstr ""
msgid "Two-Factor Authentication code"
msgstr ""
msgid "Two-factor Authentication"
msgstr ""
...
...
spec/controllers/admin/sessions_controller_spec.rb
View file @
030f4fc1
...
...
@@ -68,7 +68,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do
# triggering the auth form will request admin mode
get
:new
post
:create
,
params:
{
password:
user
.
password
}
post
:create
,
params:
{
user:
{
password:
user
.
password
}
}
expect
(
response
).
to
redirect_to
admin_root_path
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
true
)
...
...
@@ -82,7 +82,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do
# triggering the auth form will request admin mode
get
:new
post
:create
,
params:
{
password:
''
}
post
:create
,
params:
{
user:
{
password:
''
}
}
expect
(
response
).
to
render_template
:new
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
...
...
@@ -95,7 +95,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do
# do not trigger the auth form
post
:create
,
params:
{
password:
user
.
password
}
post
:create
,
params:
{
user:
{
password:
user
.
password
}
}
expect
(
response
).
to
redirect_to
(
new_admin_session_path
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
...
...
@@ -110,12 +110,118 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do
get
:new
Timecop
.
freeze
(
Gitlab
::
Auth
::
CurrentUserMode
::
ADMIN_MODE_REQUESTED_GRACE_PERIOD
.
from_now
)
do
post
:create
,
params:
{
password:
user
.
password
}
post
:create
,
params:
{
user:
{
password:
user
.
password
}
}
expect
(
response
).
to
redirect_to
(
new_admin_session_path
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
end
end
context
'when using two-factor authentication via OTP'
do
let
(
:user
)
{
create
(
:admin
,
:two_factor
)
}
def
authenticate_2fa
(
user_params
)
post
(
:create
,
params:
{
user:
user_params
},
session:
{
otp_user_id:
user
.
id
})
end
it
'requests two factor after a valid password is provided'
do
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
# triggering the auth form will request admin mode
get
:new
post
:create
,
params:
{
user:
{
password:
user
.
password
}
}
expect
(
response
).
to
render_template
(
'admin/sessions/two_factor'
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
end
it
'can login with valid otp'
do
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
controller
.
store_location_for
(
:redirect
,
admin_root_path
)
controller
.
current_user_mode
.
request_admin_mode!
authenticate_2fa
(
otp_attempt:
user
.
current_otp
)
expect
(
response
).
to
redirect_to
admin_root_path
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
true
)
end
it
'cannot login with invalid otp'
do
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
controller
.
current_user_mode
.
request_admin_mode!
authenticate_2fa
(
otp_attempt:
'invalid'
)
expect
(
response
).
to
render_template
(
'admin/sessions/two_factor'
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
end
context
'with password authentication disabled'
do
before
do
stub_application_setting
(
password_authentication_enabled_for_web:
false
)
end
it
'allows 2FA stage of non-password login'
do
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
controller
.
store_location_for
(
:redirect
,
admin_root_path
)
controller
.
current_user_mode
.
request_admin_mode!
authenticate_2fa
(
otp_attempt:
user
.
current_otp
)
expect
(
response
).
to
redirect_to
admin_root_path
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
true
)
end
end
end
context
'when using two-factor authentication via U2F'
do
let
(
:user
)
{
create
(
:admin
,
:two_factor_via_u2f
)
}
def
authenticate_2fa_u2f
(
user_params
)
post
(
:create
,
params:
{
user:
user_params
},
session:
{
otp_user_id:
user
.
id
})
end
it
'requests two factor after a valid password is provided'
do
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
# triggering the auth form will request admin mode
get
:new
post
:create
,
params:
{
user:
{
password:
user
.
password
}
}
expect
(
response
).
to
render_template
(
'admin/sessions/two_factor'
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
end
it
'can login with valid auth'
do
allow
(
U2fRegistration
).
to
receive
(
:authenticate
).
and_return
(
true
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
controller
.
store_location_for
(
:redirect
,
admin_root_path
)
controller
.
current_user_mode
.
request_admin_mode!
authenticate_2fa_u2f
(
login:
user
.
username
,
device_response:
'{}'
)
expect
(
response
).
to
redirect_to
admin_root_path
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
true
)
end
it
'cannot login with invalid auth'
do
allow
(
U2fRegistration
).
to
receive
(
:authenticate
).
and_return
(
false
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
controller
.
current_user_mode
.
request_admin_mode!
authenticate_2fa_u2f
(
login:
user
.
username
,
device_response:
'{}'
)
expect
(
response
).
to
render_template
(
'admin/sessions/two_factor'
)
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
end
end
end
end
...
...
@@ -136,7 +242,7 @@ describe Admin::SessionsController, :do_not_mock_admin_mode do
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
false
)
get
:new
post
:create
,
params:
{
password:
user
.
password
}
post
:create
,
params:
{
user:
{
password:
user
.
password
}
}
expect
(
controller
.
current_user_mode
.
admin_mode?
).
to
be
(
true
)
post
:destroy
...
...
spec/features/admin/admin_mode/login_spec.rb
0 → 100644
View file @
030f4fc1
# frozen_string_literal: true
require
'spec_helper'
describe
'Admin Mode Login'
,
:clean_gitlab_redis_shared_state
,
:do_not_mock_admin_mode
do
include
TermsHelper
include
UserLoginHelper
describe
'with two-factor authentication'
,
:js
do
def
enter_code
(
code
)
fill_in
'user_otp_attempt'
,
with:
code
click_button
'Verify code'
end
context
'with valid username/password'
do
let
(
:user
)
{
create
(
:admin
,
:two_factor
)
}
context
'using one-time code'
do
it
'blocks login if we reuse the same code immediately'
do
gitlab_sign_in
(
user
,
remember:
true
)
expect
(
page
).
to
have_content
(
'Two-Factor Authentication'
)
repeated_otp
=
user
.
current_otp
enter_code
(
repeated_otp
)
gitlab_enable_admin_mode_sign_in
(
user
)
expect
(
page
).
to
have_content
(
'Two-Factor Authentication'
)
enter_code
(
repeated_otp
)
expect
(
current_path
).
to
eq
admin_session_path
expect
(
page
).
to
have_content
(
'Invalid two-factor code'
)
end
context
'not re-using codes'
do
before
do
gitlab_sign_in
(
user
,
remember:
true
)
expect
(
page
).
to
have_content
(
'Two-Factor Authentication'
)
enter_code
(
user
.
current_otp
)
gitlab_enable_admin_mode_sign_in
(
user
)
expect
(
page
).
to
have_content
(
'Two-Factor Authentication'
)
end
it
'allows login with valid code'
do
# Cannot reuse the TOTP
Timecop
.
travel
(
30
.
seconds
.
from_now
)
do
enter_code
(
user
.
current_otp
)
expect
(
current_path
).
to
eq
admin_root_path
expect
(
page
).
to
have_content
(
'Admin mode enabled'
)
end
end
it
'blocks login with invalid code'
do
# Cannot reuse the TOTP
Timecop
.
travel
(
30
.
seconds
.
from_now
)
do
enter_code
(
'foo'
)
expect
(
page
).
to
have_content
(
'Invalid two-factor code'
)
end
end
it
'allows login with invalid code, then valid code'
do
# Cannot reuse the TOTP
Timecop
.
travel
(
30
.
seconds
.
from_now
)
do
enter_code
(
'foo'
)
expect
(
page
).
to
have_content
(
'Invalid two-factor code'
)
enter_code
(
user
.
current_otp
)
expect
(
current_path
).
to
eq
admin_root_path
expect
(
page
).
to
have_content
(
'Admin mode enabled'
)
end
end
context
'using backup code'
do
let
(
:codes
)
{
user
.
generate_otp_backup_codes!
}
before
do
expect
(
codes
.
size
).
to
eq
10
# Ensure the generated codes get saved
user
.
save
end
context
'with valid code'
do
it
'allows login'
do
enter_code
(
codes
.
sample
)
expect
(
current_path
).
to
eq
admin_root_path
expect
(
page
).
to
have_content
(
'Admin mode enabled'
)
end
it
'invalidates the used code'
do
expect
{
enter_code
(
codes
.
sample
)
}
.
to
change
{
user
.
reload
.
otp_backup_codes
.
size
}.
by
(
-
1
)
end
end
context
'with invalid code'
do
it
'blocks login'
do
code
=
codes
.
sample
expect
(
user
.
invalidate_otp_backup_code!
(
code
)).
to
eq
true
user
.
save!
expect
(
user
.
reload
.
otp_backup_codes
.
size
).
to
eq
9
enter_code
(
code
)
expect
(
page
).
to
have_content
(
'Invalid two-factor code.'
)
end
end
end
end
end
context
'when logging in via omniauth'
do
let
(
:user
)
{
create
(
:omniauth_user
,
:admin
,
:two_factor
,
extern_uid:
'my-uid'
,
provider:
'saml'
)}
let
(
:mock_saml_response
)
do
File
.
read
(
'spec/fixtures/authentication/saml_response.xml'
)
end
before
do
stub_omniauth_saml_config
(
enabled:
true
,
auto_link_saml_user:
true
,
allow_single_sign_on:
[
'saml'
],
providers:
[
mock_saml_config_with_upstream_two_factor_authn_contexts
])
end
context
'when authn_context is worth two factors'
do
let
(
:mock_saml_response
)
do
File
.
read
(
'spec/fixtures/authentication/saml_response.xml'
)
.
gsub
(
'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
,
'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS'
)
end
it
'signs user in without prompting for second factor'
do
sign_in_using_saml!
expect
(
page
).
not_to
have_content
(
'Two-Factor Authentication'
)
enable_admin_mode_using_saml!
expect
(
page
).
not_to
have_content
(
'Two-Factor Authentication'
)
expect
(
current_path
).
to
eq
admin_root_path
expect
(
page
).
to
have_content
(
'Admin mode enabled'
)
end
end
context
'when two factor authentication is required'
do
it
'shows 2FA prompt after omniauth login'
do
sign_in_using_saml!
expect
(
page
).
to
have_content
(
'Two-Factor Authentication'
)
enter_code
(
user
.
current_otp
)
enable_admin_mode_using_saml!
expect
(
page
).
to
have_content
(
'Two-Factor Authentication'
)
# Cannot reuse the TOTP
Timecop
.
travel
(
30
.
seconds
.
from_now
)
do
enter_code
(
user
.
current_otp
)
expect
(
current_path
).
to
eq
admin_root_path
expect
(
page
).
to
have_content
(
'Admin mode enabled'
)
end
end
end
def
sign_in_using_saml!
gitlab_sign_in_via
(
'saml'
,
user
,
'my-uid'
,
mock_saml_response
)
end
def
enable_admin_mode_using_saml!
gitlab_enable_admin_mode_sign_in_via
(
'saml'
,
user
,
'my-uid'
,
mock_saml_response
)
end
end
end
end
end
spec/features/admin/admin_mode/logout_spec.rb
0 → 100644
View file @
030f4fc1
# frozen_string_literal: true
require
'spec_helper'
describe
'Admin Mode Logout'
,
:js
,
:clean_gitlab_redis_shared_state
,
:do_not_mock_admin_mode
do
include
TermsHelper
include
UserLoginHelper
let
(
:user
)
{
create
(
:admin
)
}
before
do
gitlab_sign_in
(
user
)
gitlab_enable_admin_mode_sign_in
(
user
)
visit
admin_root_path
end
it
'disable removes admin mode and redirects to root page'
do
gitlab_disable_admin_mode
expect
(
current_path
).
to
eq
root_path
expect
(
page
).
to
have_link
(
href:
new_admin_session_path
)
end
it
'disable shows flash notice'
do
gitlab_disable_admin_mode
expect
(
page
).
to
have_selector
(
'.flash-notice'
)
end
context
'on a read-only instance'
do
before
do
allow
(
Gitlab
::
Database
).
to
receive
(
:read_only?
).
and_return
(
true
)
end
it
'disable removes admin mode and redirects to root page'
do
gitlab_disable_admin_mode
expect
(
current_path
).
to
eq
root_path
expect
(
page
).
to
have_link
(
href:
new_admin_session_path
)
end
end
end
spec/features/admin/admin_mode_spec.rb
View file @
030f4fc1
...
...
@@ -45,7 +45,7 @@ describe 'Admin mode', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode
it
'can enter admin mode'
do
visit
new_admin_session_path
fill_in
'password'
,
with:
admin
.
password
fill_in
'
user_
password'
,
with:
admin
.
password
click_button
'Enter Admin Mode'
...
...
@@ -60,7 +60,7 @@ describe 'Admin mode', :clean_gitlab_redis_shared_state, :do_not_mock_admin_mode
it
'can enter admin mode'
do
visit
new_admin_session_path
fill_in
'password'
,
with:
admin
.
password
fill_in
'
user_
password'
,
with:
admin
.
password
click_button
'Enter Admin Mode'
...
...
spec/support/helpers/login_helpers.rb
View file @
030f4fc1
...
...
@@ -51,7 +51,7 @@ module LoginHelpers
def
gitlab_enable_admin_mode_sign_in
(
user
)
visit
new_admin_session_path
fill_in
'password'
,
with:
user
.
password
fill_in
'
user_
password'
,
with:
user
.
password
click_button
'Enter Admin Mode'
end
...
...
@@ -62,6 +62,12 @@ module LoginHelpers
click_link
provider
end
def
gitlab_enable_admin_mode_sign_in_via
(
provider
,
user
,
uid
,
saml_response
=
nil
)
mock_auth_hash_with_saml_xml
(
provider
,
uid
,
user
.
email
,
saml_response
)
visit
new_admin_session_path
click_link
provider
end
# Requires Javascript driver.
def
gitlab_sign_out
find
(
".header-user-dropdown-toggle"
).
click
...
...
@@ -71,6 +77,11 @@ module LoginHelpers
expect
(
page
).
to
have_button
(
'Sign in'
)
end
# Requires Javascript driver.
def
gitlab_disable_admin_mode
click_on
'Leave Admin Mode'
end
private
# Private: Login as the specified user
...
...
spec/views/admin/sessions/new.html.haml_spec.rb
View file @
030f4fc1
...
...
@@ -15,7 +15,7 @@ describe 'admin/sessions/new.html.haml' do
render
expect
(
rendered
).
to
have_css
(
'#login-pane.active'
)
expect
(
rendered
).
to
have_selector
(
'input[name="
password
"]'
)
expect
(
rendered
).
to
have_selector
(
'input[name="
user[password]
"]'
)
end
it
'warns authentication not possible if password not set'
do
...
...
spec/views/admin/sessions/two_factor.html.haml_spec.rb
0 → 100644
View file @
030f4fc1
# frozen_string_literal: true
require
'spec_helper'
describe
'admin/sessions/two_factor.html.haml'
do
before
do
allow
(
view
).
to
receive
(
:current_user
).
and_return
(
user
)
end
context
'user has no two factor auth'
do
let
(
:user
)
{
create
(
:admin
)
}
it
'shows tab'
do
render
expect
(
rendered
).
to
have_no_field
(
'user[otp_attempt]'
)
expect
(
rendered
).
to
have_no_field
(
'user[device_response]'
)
end
end
context
'user has otp active'
do
let
(
:user
)
{
create
(
:admin
,
:two_factor
)
}
it
'shows enter otp form'
do
render
expect
(
rendered
).
to
have_css
(
'#login-pane.active'
)
expect
(
rendered
).
to
have_field
(
'user[otp_attempt]'
)
end
end
context
'user has u2f active'
do
let
(
:user
)
{
create
(
:admin
,
:two_factor_via_u2f
)
}
it
'shows enter u2f form'
do
render
expect
(
rendered
).
to
have_css
(
'#js-login-2fa-device.btn'
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment