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
be054b4a
Commit
be054b4a
authored
Feb 09, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
98cff07c
36caacb0
Changes
17
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
426 additions
and
335 deletions
+426
-335
app/assets/javascripts/vue_shared/components/actions_button.vue
...sets/javascripts/vue_shared/components/actions_button.vue
+1
-2
app/assets/stylesheets/framework/dropdowns.scss
app/assets/stylesheets/framework/dropdowns.scss
+0
-14
app/views/projects/network/show.html.haml
app/views/projects/network/show.html.haml
+1
-1
changelogs/unreleased/align-graph-page.yml
changelogs/unreleased/align-graph-page.yml
+5
-0
changelogs/unreleased/ps-remove-gl-dropdown-item-deprecated-adapter.yml
...eleased/ps-remove-gl-dropdown-item-deprecated-adapter.yml
+5
-0
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.
app/assets/javascripts/vue_shared/components/actions_button.vue
View file @
be054b4a
...
...
@@ -76,14 +76,13 @@ export default {
<
template
v-for=
"(action, index) in actions"
>
<gl-dropdown-item
:key=
"action.key"
class=
"gl-dropdown-item-deprecated-adapter"
:is-check-item=
"true"
:is-checked=
"action.key === selectedAction.key"
:secondary-text=
"action.secondaryText"
:data-testid=
"`action_$
{action.key}`"
@click="handleItemClick(action)"
>
{{
action
.
text
}}
<span
class=
"gl-font-weight-bold"
>
{{
action
.
text
}}
</span>
</gl-dropdown-item>
<gl-dropdown-divider
v-if=
"index != actions.length - 1"
:key=
"action.key + '_divider'"
/>
</
template
>
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
be054b4a
...
...
@@ -1093,17 +1093,3 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
width
:
$gl-dropdown-width-wide
;
}
}
.gl-dropdown-item-deprecated-adapter
{
.dropdown-item
{
align-items
:
flex-start
;
.gl-new-dropdown-item-text-primary
{
@include
gl-font-weight-bold
;
}
.gl-new-dropdown-item-text-secondary
{
color
:
inherit
;
}
}
}
app/views/projects/network/show.html.haml
View file @
be054b4a
-
breadcrumb_title
_
(
"Graph"
)
-
page_title
_
(
"Graph"
),
@ref
=
render
"head"
%div
{
class:
container_class
}
.gl-mt-5
.project-network.gl-border-1.gl-border-solid.gl-border-gray-300
.controls.gl-bg-gray-50.gl-p-2.gl-font-base.gl-text-gray-400.gl-border-b-1.gl-border-b-solid.gl-border-b-gray-300
=
form_tag
project_network_path
(
@project
,
@id
),
method: :get
,
class:
'form-inline network-form'
do
|
f
|
...
...
changelogs/unreleased/align-graph-page.yml
0 → 100644
View file @
be054b4a
---
title
:
Add margin and remove padding in project graph page
merge_request
:
53557
author
:
Yogi (@yo)
type
:
other
changelogs/unreleased/ps-remove-gl-dropdown-item-deprecated-adapter.yml
0 → 100644
View file @
be054b4a
---
title
:
Change secondary text color on Gitpod editor dropdown button
merge_request
:
53437
author
:
type
:
other
doc/security/two_factor_authentication.md
View file @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
File moved
spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
→
ee/
spec/lib/gitlab/auth/otp/session_enforcer_spec.rb
View file @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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 @
be054b4a
...
...
@@ -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
'returns an error message'
do
it
'is not available'
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
end
end
...
...
spec/support/shared_examples/lib/api/internal_base_shared_examples.rb
0 → 100644
View file @
be054b4a
# 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