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
ffb72bba
Commit
ffb72bba
authored
Feb 02, 2021
by
Imre Farkas
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move OTP for git over SSH to EE
Feature should exist in Premium.
parent
bcb377af
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
414 additions
and
318 deletions
+414
-318
doc/security/two_factor_authentication.md
doc/security/two_factor_authentication.md
+2
-1
ee/lib/ee/api/internal/base.rb
ee/lib/ee/api/internal/base.rb
+27
-0
ee/lib/ee/gitlab/git_access.rb
ee/lib/ee/gitlab/git_access.rb
+27
-0
ee/lib/gitlab/auth/otp/session_enforcer.rb
ee/lib/gitlab/auth/otp/session_enforcer.rb
+0
-0
ee/spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
ee/spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
+22
-1
ee/spec/lib/gitlab/git_access_spec.rb
ee/spec/lib/gitlab/git_access_spec.rb
+170
-0
ee/spec/requests/api/internal/base_spec.rb
ee/spec/requests/api/internal/base_spec.rb
+117
-5
lib/api/internal/base.rb
lib/api/internal/base.rb
+10
-22
lib/gitlab/git_access.rb
lib/gitlab/git_access.rb
+0
-26
spec/lib/gitlab/git_access_spec.rb
spec/lib/gitlab/git_access_spec.rb
+0
-155
spec/requests/api/internal/base_spec.rb
spec/requests/api/internal/base_spec.rb
+3
-108
spec/support/shared_examples/lib/api/internal_base_shared_examples.rb
.../shared_examples/lib/api/internal_base_shared_examples.rb
+36
-0
No files found.
doc/security/two_factor_authentication.md
View file @
ffb72bba
...
...
@@ -110,9 +110,10 @@ Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
## Two-factor Authentication (2FA) for Git over SSH operations
## Two-factor Authentication (2FA) for Git over SSH operations
**(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/270554) in GitLab 13.7.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/299088) from GitLab Free to GitLab Premium in 13.9.
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
...
...
ee/lib/ee/api/internal/base.rb
View file @
ffb72bba
...
...
@@ -22,6 +22,33 @@ module EE
super
end
end
override
:two_factor_otp_check
def
two_factor_otp_check
return
{
success:
false
,
message:
'Feature is not available'
}
unless
::
License
.
feature_available?
(
:git_two_factor_enforcement
)
return
{
success:
false
,
message:
'Feature flag is disabled'
}
unless
::
Feature
.
enabled?
(
:two_factor_for_cli
)
actor
.
update_last_used_at!
user
=
actor
.
user
error_message
=
validate_actor_key
(
actor
,
params
[
:key_id
])
return
{
success:
false
,
message:
error_message
}
if
error_message
return
{
success:
false
,
message:
'Deploy keys cannot be used for Two Factor'
}
if
actor
.
key
.
is_a?
(
DeployKey
)
return
{
success:
false
,
message:
'Two-factor authentication is not enabled for this user'
}
unless
user
.
two_factor_enabled?
otp_validation_result
=
::
Users
::
ValidateOtpService
.
new
(
user
).
execute
(
params
.
fetch
(
:otp_attempt
))
if
otp_validation_result
[
:status
]
==
:success
::
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
.
new
(
actor
.
key
).
update_session
{
success:
true
}
else
{
success:
false
,
message:
'Invalid OTP'
}
end
end
end
end
end
...
...
ee/lib/ee/gitlab/git_access.rb
View file @
ffb72bba
...
...
@@ -13,6 +13,7 @@ module EE
check_maintenance_mode!
(
cmd
)
check_geo_license!
check_smartcard_access!
check_otp_session!
super
end
...
...
@@ -94,6 +95,32 @@ module EE
end
end
def
check_otp_session!
return
unless
::
License
.
feature_available?
(
:git_two_factor_enforcement
)
return
unless
::
Feature
.
enabled?
(
:two_factor_for_cli
)
return
unless
ssh?
return
if
!
key?
||
deploy_key?
return
unless
user
.
two_factor_enabled?
if
::
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
.
new
(
actor
).
access_restricted?
message
=
"OTP verification is required to access the repository.
\n\n
"
\
" Use:
#{
build_ssh_otp_verify_command
}
"
raise
::
Gitlab
::
GitAccess
::
ForbiddenError
,
message
end
end
def
build_ssh_otp_verify_command
user
=
"
#{
::
Gitlab
.
config
.
gitlab_shell
.
ssh_user
}
@"
unless
::
Gitlab
.
config
.
gitlab_shell
.
ssh_user
.
empty?
user_host
=
"
#{
user
}#{
::
Gitlab
.
config
.
gitlab_shell
.
ssh_host
}
"
if
::
Gitlab
.
config
.
gitlab_shell
.
ssh_port
!=
22
"ssh
#{
user_host
}
-p
#{
::
Gitlab
.
config
.
gitlab_shell
.
ssh_port
}
2fa_verify"
else
"ssh
#{
user_host
}
2fa_verify"
end
end
def
check_maintenance_mode!
(
cmd
)
return
unless
cmd
==
'git-receive-pack'
return
unless
::
Gitlab
.
maintenance_mode?
...
...
lib/gitlab/auth/otp/session_enforcer.rb
→
ee/
lib/gitlab/auth/otp/session_enforcer.rb
View file @
ffb72bba
File moved
spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
→
ee/
spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
View file @
ffb72bba
...
...
@@ -6,8 +6,13 @@ RSpec.describe Gitlab::Auth::Otp::SessionEnforcer, :clean_gitlab_redis_shared_st
let_it_be
(
:key
)
{
create
(
:key
)}
describe
'#update_session'
do
let
(
:redis
)
{
double
(
:redis
)
}
before
do
stub_licensed_features
(
git_two_factor_enforcement:
true
)
end
it
'registers a session in Redis'
do
redis
=
double
(
:redis
)
expect
(
Gitlab
::
Redis
::
SharedState
).
to
receive
(
:with
).
and_yield
(
redis
)
session_expiry_in_seconds
=
Gitlab
::
CurrentSettings
.
git_two_factor_session_expiry
.
minutes
.
to_i
...
...
@@ -20,11 +25,27 @@ RSpec.describe Gitlab::Auth::Otp::SessionEnforcer, :clean_gitlab_redis_shared_st
described_class
.
new
(
key
).
update_session
end
context
'when licensed feature is not available'
do
before
do
stub_licensed_features
(
git_two_factor_enforcement:
false
)
end
it
'does not register a session in Redis'
do
expect
(
redis
).
not_to
receive
(
:setex
)
described_class
.
new
(
key
).
update_session
end
end
end
describe
'#access_restricted?'
do
subject
{
described_class
.
new
(
key
).
access_restricted?
}
before
do
stub_licensed_features
(
git_two_factor_enforcement:
true
)
end
context
'with existing session'
do
before
do
Gitlab
::
Redis
::
SharedState
.
with
do
|
redis
|
...
...
ee/spec/lib/gitlab/git_access_spec.rb
View file @
ffb72bba
...
...
@@ -745,6 +745,176 @@ RSpec.describe Gitlab::GitAccess do
end
end
describe
'#check_otp_session!'
do
let_it_be
(
:user
)
{
create
(
:user
,
:two_factor_via_otp
)}
let_it_be
(
:key
)
{
create
(
:key
,
user:
user
)
}
let_it_be
(
:actor
)
{
key
}
let
(
:protocol
)
{
'ssh'
}
before
do
project
.
add_developer
(
user
)
stub_feature_flags
(
two_factor_for_cli:
true
)
stub_licensed_features
(
git_two_factor_enforcement:
true
)
end
context
'with an OTP session'
,
:clean_gitlab_redis_shared_state
do
before
do
Gitlab
::
Redis
::
SharedState
.
with
do
|
redis
|
redis
.
set
(
"
#{
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
::
OTP_SESSIONS_NAMESPACE
}
:
#{
key
.
id
}
"
,
true
)
end
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
context
'based on the duration set by the `git_two_factor_session_expiry` setting'
do
let_it_be
(
:git_two_factor_session_expiry
)
{
20
}
let_it_be
(
:redis_key_expiry_at
)
{
git_two_factor_session_expiry
.
minutes
.
from_now
}
before
do
stub_application_setting
(
git_two_factor_session_expiry:
git_two_factor_session_expiry
)
end
def
value_of_key
key_expired
=
Time
.
current
>
redis_key_expiry_at
return
if
key_expired
true
end
def
stub_redis
redis
=
double
(
:redis
)
expect
(
Gitlab
::
Redis
::
SharedState
).
to
receive
(
:with
).
at_most
(
:twice
).
and_yield
(
redis
)
expect
(
redis
).
to
(
receive
(
:get
)
.
with
(
"
#{
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
::
OTP_SESSIONS_NAMESPACE
}
:
#{
key
.
id
}
"
))
.
at_most
(
:twice
)
.
and_return
(
value_of_key
)
end
context
'at a time before the stipulated expiry'
do
it
'allows push and pull access'
do
travel_to
(
10
.
minutes
.
from_now
)
do
stub_redis
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
end
end
context
'at a time after the stipulated expiry'
do
it
'does not allow push and pull access'
do
travel_to
(
30
.
minutes
.
from_now
)
do
stub_redis
aggregate_failures
do
expect
{
push_changes
}.
to
raise_error
(
::
Gitlab
::
GitAccess
::
ForbiddenError
)
expect
{
pull_changes
}.
to
raise_error
(
::
Gitlab
::
GitAccess
::
ForbiddenError
)
end
end
end
end
end
end
context
'without OTP session'
do
it
'does not allow push or pull access'
do
user
=
'jane.doe'
host
=
'fridge.ssh'
port
=
42
stub_config
(
gitlab_shell:
{
ssh_user:
user
,
ssh_host:
host
,
ssh_port:
port
}
)
error_message
=
"OTP verification is required to access the repository.
\n\n
"
\
" Use: ssh
#{
user
}
@
#{
host
}
-p
#{
port
}
2fa_verify"
aggregate_failures
do
expect
{
push_changes
}.
to
raise_forbidden
(
error_message
)
expect
{
pull_changes
}.
to
raise_forbidden
(
error_message
)
end
end
context
'when protocol is HTTP'
do
let
(
:protocol
)
{
'http'
}
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
end
context
'when actor is not an SSH key'
do
let
(
:deploy_key
)
{
create
(
:deploy_key
,
user:
user
)
}
let
(
:actor
)
{
deploy_key
}
before
do
deploy_key
.
deploy_keys_projects
.
create
(
project:
project
,
can_push:
true
)
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
end
context
'when 2FA is not enabled for the user'
do
let
(
:user
)
{
create
(
:user
)}
let
(
:actor
)
{
create
(
:key
,
user:
user
)
}
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
end
context
'when feature flag is disabled'
do
before
do
stub_feature_flags
(
two_factor_for_cli:
false
)
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
end
context
'when licensed feature is not available'
do
before
do
stub_licensed_features
(
git_two_factor_enforcement:
false
)
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_changes
}.
not_to
raise_error
expect
{
pull_changes
}.
not_to
raise_error
end
end
end
end
end
describe
'#check_maintenance_mode!'
do
let
(
:changes
)
{
Gitlab
::
GitAccess
::
ANY
}
...
...
ee/spec/requests/api/internal/base_spec.rb
View file @
ffb72bba
...
...
@@ -10,11 +10,11 @@ RSpec.describe API::Internal::Base do
let_it_be
(
:primary_node
,
reload:
true
)
{
create
(
:geo_node
,
:primary
,
url:
primary_url
)
}
let_it_be
(
:secondary_node
,
reload:
true
)
{
create
(
:geo_node
,
url:
secondary_url
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let
(
:secret_token
)
{
Gitlab
::
Shell
.
secret_token
}
describe
'POST /internal/post_receive'
,
:geo
do
let
(
:key
)
{
create
(
:key
,
user:
user
)
}
let_it_be
(
:project
,
reload:
true
)
{
create
(
:project
,
:repository
,
:wiki_repo
)
}
let
(
:secret_token
)
{
Gitlab
::
Shell
.
secret_token
}
let
(
:gl_repository
)
{
"project-
#{
project
.
id
}
"
}
let
(
:reference_counter
)
{
double
(
'ReferenceCounter'
)
}
...
...
@@ -74,7 +74,6 @@ RSpec.describe API::Internal::Base do
describe
"POST /internal/allowed"
do
let_it_be
(
:key
)
{
create
(
:key
,
user:
user
)
}
let
(
:secret_token
)
{
Gitlab
::
Shell
.
secret_token
}
context
"project alias"
do
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
...
...
@@ -279,7 +278,6 @@ RSpec.describe API::Internal::Base do
describe
"POST /internal/lfs_authenticate"
,
:geo
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:secret_token
)
{
Gitlab
::
Shell
.
secret_token
}
context
'for a secondary node'
do
before
do
...
...
@@ -312,9 +310,7 @@ RSpec.describe API::Internal::Base do
describe
'POST /internal/personal_access_token'
do
let_it_be
(
:key
)
{
create
(
:key
,
user:
user
)
}
let
(
:instance_level_max_personal_access_token_lifetime
)
{
nil
}
let
(
:secret_token
)
{
Gitlab
::
Shell
.
secret_token
}
before
do
stub_licensed_features
(
personal_access_token_expiration_policy:
!!
instance_level_max_personal_access_token_lifetime
)
...
...
@@ -362,4 +358,120 @@ RSpec.describe API::Internal::Base do
end
end
end
describe
'POST /internal/two_factor_otp_check'
do
let_it_be
(
:key
)
{
create
(
:key
,
user:
user
)
}
let
(
:key_id
)
{
key
.
id
}
let
(
:otp
)
{
'123456'
}
before
do
stub_feature_flags
(
two_factor_for_cli:
true
)
stub_licensed_features
(
git_two_factor_enforcement:
true
)
end
subject
do
post
api
(
'/internal/two_factor_otp_check'
),
params:
{
secret_token:
secret_token
,
key_id:
key_id
,
otp_attempt:
otp
}
end
it_behaves_like
'actor key validations'
context
'when the key is a deploy key'
do
let
(
:key_id
)
{
create
(
:deploy_key
).
id
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Deploy keys cannot be used for Two Factor'
)
end
end
context
'when the two factor is enabled'
do
before
do
allow_any_instance_of
(
User
).
to
receive
(
:two_factor_enabled?
).
and_return
(
true
)
# rubocop:disable RSpec/AnyInstanceOf
end
context
'when the OTP is valid'
do
it
'registers a new OTP session and returns success'
do
allow_next_instance_of
(
Users
::
ValidateOtpService
)
do
|
service
|
allow
(
service
).
to
receive
(
:execute
).
with
(
otp
).
and_return
(
status: :success
)
end
expect_next_instance_of
(
::
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
)
do
|
session_enforcer
|
expect
(
session_enforcer
).
to
receive
(
:update_session
).
once
end
subject
expect
(
json_response
[
'success'
]).
to
be_truthy
end
end
context
'when the OTP is invalid'
do
it
'is not success'
do
allow_next_instance_of
(
Users
::
ValidateOtpService
)
do
|
service
|
allow
(
service
).
to
receive
(
:execute
).
with
(
otp
).
and_return
(
status: :error
)
end
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
end
end
end
context
'when the two factor is disabled'
do
before
do
allow_any_instance_of
(
User
).
to
receive
(
:two_factor_enabled?
).
and_return
(
false
)
# rubocop:disable RSpec/AnyInstanceOf
end
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
'Two-factor authentication is not enabled for this user'
end
end
context
'feature flag is disabled'
do
before
do
stub_feature_flags
(
two_factor_for_cli:
false
)
end
context
'when two-factor is enabled for the user'
do
it
'returns user two factor config'
do
allow_next_instance_of
(
User
)
do
|
instance
|
allow
(
instance
).
to
receive
(
:two_factor_enabled?
).
and_return
(
true
)
end
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
end
end
end
context
'licensed feature is not available'
do
before
do
stub_licensed_features
(
git_two_factor_enforcement:
false
)
end
context
'when two-factor is enabled for the user'
do
it
'returns user two factor config'
do
allow_next_instance_of
(
User
)
do
|
instance
|
allow
(
instance
).
to
receive
(
:two_factor_enabled?
).
and_return
(
true
)
end
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
end
end
end
end
end
lib/api/internal/base.rb
View file @
ffb72bba
...
...
@@ -116,6 +116,10 @@ module API
'Could not find a user for the given key'
unless
actor
.
user
end
def
two_factor_otp_check
{
success:
false
,
message:
'Feature is not available'
}
end
end
namespace
'internal'
do
...
...
@@ -278,6 +282,11 @@ module API
present
response
,
with:
Entities
::
InternalPostReceive
::
Response
end
# This endpoint was added in https://gitlab.com/gitlab-org/gitlab/-/issues/212308
# It was added with the plan to be used by GitLab PAM module but we
# decided to pursue a different approach, so it's currently not used.
# We might revive the PAM module though as it provides better user
# flow.
post
'/two_factor_config'
,
feature_category: :authentication_and_authorization
do
status
200
...
...
@@ -303,28 +312,7 @@ module API
post
'/two_factor_otp_check'
,
feature_category: :authentication_and_authorization
do
status
200
break
{
success:
false
,
message:
'Feature flag is disabled'
}
unless
Feature
.
enabled?
(
:two_factor_for_cli
)
actor
.
update_last_used_at!
user
=
actor
.
user
error_message
=
validate_actor_key
(
actor
,
params
[
:key_id
])
break
{
success:
false
,
message:
error_message
}
if
error_message
break
{
success:
false
,
message:
'Deploy keys cannot be used for Two Factor'
}
if
actor
.
key
.
is_a?
(
DeployKey
)
break
{
success:
false
,
message:
'Two-factor authentication is not enabled for this user'
}
unless
user
.
two_factor_enabled?
otp_validation_result
=
::
Users
::
ValidateOtpService
.
new
(
user
).
execute
(
params
.
fetch
(
:otp_attempt
))
if
otp_validation_result
[
:status
]
==
:success
::
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
.
new
(
actor
.
key
).
update_session
{
success:
true
}
else
{
success:
false
,
message:
'Invalid OTP'
}
end
two_factor_otp_check
end
end
end
...
...
lib/gitlab/git_access.rb
View file @
ffb72bba
...
...
@@ -77,7 +77,6 @@ module Gitlab
check_authentication_abilities!
check_command_disabled!
check_command_existence!
check_otp_session!
custom_action
=
check_custom_action
return
custom_action
if
custom_action
...
...
@@ -255,31 +254,6 @@ module Gitlab
end
end
def
check_otp_session!
return
unless
ssh?
return
if
!
key?
||
deploy_key?
return
unless
Feature
.
enabled?
(
:two_factor_for_cli
)
return
unless
user
.
two_factor_enabled?
if
::
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
.
new
(
actor
).
access_restricted?
message
=
"OTP verification is required to access the repository.
\n\n
"
\
" Use:
#{
build_ssh_otp_verify_command
}
"
raise
ForbiddenError
,
message
end
end
def
build_ssh_otp_verify_command
user
=
"
#{
Gitlab
.
config
.
gitlab_shell
.
ssh_user
}
@"
unless
Gitlab
.
config
.
gitlab_shell
.
ssh_user
.
empty?
user_host
=
"
#{
user
}#{
Gitlab
.
config
.
gitlab_shell
.
ssh_host
}
"
if
Gitlab
.
config
.
gitlab_shell
.
ssh_port
!=
22
"ssh
#{
user_host
}
-p
#{
Gitlab
.
config
.
gitlab_shell
.
ssh_port
}
2fa_verify"
else
"ssh
#{
user_host
}
2fa_verify"
end
end
def
check_db_accessibility!
return
unless
receive_pack?
...
...
spec/lib/gitlab/git_access_spec.rb
View file @
ffb72bba
...
...
@@ -388,161 +388,6 @@ RSpec.describe Gitlab::GitAccess do
end
end
describe
'#check_otp_session!'
do
let_it_be
(
:user
)
{
create
(
:user
,
:two_factor_via_otp
)}
let_it_be
(
:key
)
{
create
(
:key
,
user:
user
)
}
let_it_be
(
:actor
)
{
key
}
before
do
project
.
add_developer
(
user
)
stub_feature_flags
(
two_factor_for_cli:
true
)
end
context
'with an OTP session'
,
:clean_gitlab_redis_shared_state
do
before
do
Gitlab
::
Redis
::
SharedState
.
with
do
|
redis
|
redis
.
set
(
"
#{
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
::
OTP_SESSIONS_NAMESPACE
}
:
#{
key
.
id
}
"
,
true
)
end
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_access_check
}.
not_to
raise_error
expect
{
pull_access_check
}.
not_to
raise_error
end
end
context
'based on the duration set by the `git_two_factor_session_expiry` setting'
do
let_it_be
(
:git_two_factor_session_expiry
)
{
20
}
let_it_be
(
:redis_key_expiry_at
)
{
git_two_factor_session_expiry
.
minutes
.
from_now
}
before
do
stub_application_setting
(
git_two_factor_session_expiry:
git_two_factor_session_expiry
)
end
def
value_of_key
key_expired
=
Time
.
current
>
redis_key_expiry_at
return
if
key_expired
true
end
def
stub_redis
redis
=
double
(
:redis
)
expect
(
Gitlab
::
Redis
::
SharedState
).
to
receive
(
:with
).
at_most
(
:twice
).
and_yield
(
redis
)
expect
(
redis
).
to
(
receive
(
:get
)
.
with
(
"
#{
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
::
OTP_SESSIONS_NAMESPACE
}
:
#{
key
.
id
}
"
))
.
at_most
(
:twice
)
.
and_return
(
value_of_key
)
end
context
'at a time before the stipulated expiry'
do
it
'allows push and pull access'
do
travel_to
(
10
.
minutes
.
from_now
)
do
stub_redis
aggregate_failures
do
expect
{
push_access_check
}.
not_to
raise_error
expect
{
pull_access_check
}.
not_to
raise_error
end
end
end
end
context
'at a time after the stipulated expiry'
do
it
'does not allow push and pull access'
do
travel_to
(
30
.
minutes
.
from_now
)
do
stub_redis
aggregate_failures
do
expect
{
push_access_check
}.
to
raise_error
expect
{
pull_access_check
}.
to
raise_error
end
end
end
end
end
end
context
'without OTP session'
do
it
'does not allow push or pull access'
do
user
=
'jane.doe'
host
=
'fridge.ssh'
port
=
42
stub_config
(
gitlab_shell:
{
ssh_user:
user
,
ssh_host:
host
,
ssh_port:
port
}
)
error_message
=
"OTP verification is required to access the repository.
\n\n
"
\
" Use: ssh
#{
user
}
@
#{
host
}
-p
#{
port
}
2fa_verify"
aggregate_failures
do
expect
{
push_access_check
}.
to
raise_forbidden
(
error_message
)
expect
{
pull_access_check
}.
to
raise_forbidden
(
error_message
)
end
end
context
'when protocol is HTTP'
do
let
(
:protocol
)
{
'http'
}
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_access_check
}.
not_to
raise_error
expect
{
pull_access_check
}.
not_to
raise_error
end
end
end
context
'when actor is not an SSH key'
do
let
(
:deploy_key
)
{
create
(
:deploy_key
,
user:
user
)
}
let
(
:actor
)
{
deploy_key
}
before
do
deploy_key
.
deploy_keys_projects
.
create
(
project:
project
,
can_push:
true
)
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_access_check
}.
not_to
raise_error
expect
{
pull_access_check
}.
not_to
raise_error
end
end
end
context
'when 2FA is not enabled for the user'
do
let
(
:user
)
{
create
(
:user
)}
let
(
:actor
)
{
create
(
:key
,
user:
user
)
}
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_access_check
}.
not_to
raise_error
expect
{
pull_access_check
}.
not_to
raise_error
end
end
end
context
'when feature flag is disabled'
do
before
do
stub_feature_flags
(
two_factor_for_cli:
false
)
end
it
'allows push and pull access'
do
aggregate_failures
do
expect
{
push_access_check
}.
not_to
raise_error
expect
{
pull_access_check
}.
not_to
raise_error
end
end
end
end
end
describe
'#check_db_accessibility!'
do
context
'when in a read-only GitLab instance'
do
before
do
...
...
spec/requests/api/internal/base_spec.rb
View file @
ffb72bba
...
...
@@ -50,41 +50,6 @@ RSpec.describe API::Internal::Base do
end
end
shared_examples
'actor key validations'
do
context
'key id is not provided'
do
let
(
:key_id
)
{
nil
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Could not find a user without a key'
)
end
end
context
'key does not exist'
do
let
(
:key_id
)
{
non_existing_record_id
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Could not find the given key'
)
end
end
context
'key without user'
do
let
(
:key_id
)
{
create
(
:key
,
user:
nil
).
id
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Could not find a user for the given key'
)
end
end
end
describe
'GET /internal/two_factor_recovery_codes'
do
let
(
:key_id
)
{
key
.
id
}
...
...
@@ -1406,10 +1371,6 @@ RSpec.describe API::Internal::Base do
let
(
:key_id
)
{
key
.
id
}
let
(
:otp
)
{
'123456'
}
before
do
stub_feature_flags
(
two_factor_for_cli:
true
)
end
subject
do
post
api
(
'/internal/two_factor_otp_check'
),
params:
{
...
...
@@ -1419,76 +1380,10 @@ RSpec.describe API::Internal::Base do
}
end
it_behaves_like
'actor key validations'
context
'when the key is a deploy key'
do
let
(
:key_id
)
{
create
(
:deploy_key
).
id
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Deploy keys cannot be used for Two Factor'
)
end
end
context
'when the two factor is enabled'
do
before
do
allow_any_instance_of
(
User
).
to
receive
(
:two_factor_enabled?
).
and_return
(
true
)
end
context
'when the OTP is valid'
do
it
'registers a new OTP session and returns success'
do
allow_any_instance_of
(
Users
::
ValidateOtpService
).
to
receive
(
:execute
).
with
(
otp
).
and_return
(
status: :success
)
expect_next_instance_of
(
::
Gitlab
::
Auth
::
Otp
::
SessionEnforcer
)
do
|
session_enforcer
|
expect
(
session_enforcer
).
to
receive
(
:update_session
).
once
end
subject
expect
(
json_response
[
'success'
]).
to
be_truthy
end
end
context
'when the OTP is invalid'
do
it
'is not success'
do
allow_any_instance_of
(
Users
::
ValidateOtpService
).
to
receive
(
:execute
).
with
(
otp
).
and_return
(
status: :error
)
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
end
end
end
context
'when the two factor is disabled'
do
before
do
allow_any_instance_of
(
User
).
to
receive
(
:two_factor_enabled?
).
and_return
(
false
)
end
it
'is not available'
do
subject
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
'Two-factor authentication is not enabled for this user'
end
end
context
'two_factor_for_cli feature is disabled'
do
before
do
stub_feature_flags
(
two_factor_for_cli:
false
)
end
context
'when two-factor is enabled for the user'
do
it
'returns user two factor config'
do
allow_any_instance_of
(
User
).
to
receive
(
:two_factor_enabled?
).
and_return
(
true
)
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
end
end
expect
(
json_response
[
'success'
]).
to
be_falsey
end
end
...
...
spec/support/shared_examples/lib/api/internal_base_shared_examples.rb
0 → 100644
View file @
ffb72bba
# frozen_string_literal: true
RSpec
.
shared_examples
'actor key validations'
do
context
'key id is not provided'
do
let
(
:key_id
)
{
nil
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Could not find a user without a key'
)
end
end
context
'key does not exist'
do
let
(
:key_id
)
{
non_existing_record_id
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Could not find the given key'
)
end
end
context
'key without user'
do
let
(
:key_id
)
{
create
(
:key
,
user:
nil
).
id
}
it
'returns an error message'
do
subject
expect
(
json_response
[
'success'
]).
to
be_falsey
expect
(
json_response
[
'message'
]).
to
eq
(
'Could not find a user for the given key'
)
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