2016-07-18 04:16:56 -04:00
|
|
|
# Check a user's access to perform a git action. All public methods in this
|
|
|
|
# class return an instance of `GitlabAccessStatus`
|
2014-03-19 15:01:00 -04:00
|
|
|
module Gitlab
|
|
|
|
class GitAccess
|
2016-11-02 17:50:44 -04:00
|
|
|
UnauthorizedError = Class.new(StandardError)
|
|
|
|
|
|
|
|
ERROR_MESSAGES = {
|
|
|
|
upload: 'You are not allowed to upload code for this project.',
|
|
|
|
download: 'You are not allowed to download code from this project.',
|
2016-11-18 04:28:05 -05:00
|
|
|
deploy_key_upload:
|
|
|
|
'This deploy key does not have write access to this project.',
|
2016-11-02 17:50:44 -04:00
|
|
|
no_repo: 'A repository for this project does not exist yet.'
|
2017-02-21 18:32:18 -05:00
|
|
|
}.freeze
|
2016-11-02 17:50:44 -04:00
|
|
|
|
2017-02-21 18:32:18 -05:00
|
|
|
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
|
|
|
|
PUSH_COMMANDS = %w{ git-receive-pack }.freeze
|
2016-11-02 17:50:44 -04:00
|
|
|
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
|
2014-03-19 15:01:00 -04:00
|
|
|
|
2016-09-16 03:59:10 -04:00
|
|
|
attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
|
2014-03-19 15:01:00 -04:00
|
|
|
|
Accept environment variables from the `pre-receive` script.
1. Starting version 2.11, git changed the way the pre-receive flow works.
- Previously, the new potential objects would be added to the main repo. If the
pre-receive passes, the new objects stay in the repo but are linked up. If
the pre-receive fails, the new objects stay orphaned in the repo, and are
cleaned up during the next `git gc`.
- In 2.11, the new potential objects are added to a temporary "alternate object
directory", that git creates for this purpose. If the pre-receive passes, the
objects from the alternate object directory are migrated to the main repo. If
the pre-receive fails the alternate object directory is simply deleted.
2. In our workflow, the pre-recieve script (in `gitlab-shell) calls the
`/allowed` endpoint, which calls out directly to git to perform
various checks. These direct calls to git do _not_ have the necessary
environment variables set which allow access to the "alternate object
directory" (explained above). Therefore these calls to git are not able to
access any of the new potential objects to be added during this push.
3. We fix this by accepting the relevant environment variables
(GIT_ALTERNATE_OBJECT_DIRECTORIES, GIT_OBJECT_DIRECTORY) on the
`/allowed` endpoint, and then include these environment variables while
calling out to git.
4. This commit includes (whitelisted) these environment variables while making
the "force push" check. A `Gitlab::Git::RevList` module is extracted to
prevent `ForcePush` from being littered with these checks.
2016-12-07 02:55:49 -05:00
|
|
|
def initialize(actor, project, protocol, authentication_abilities:, env: {})
|
2015-03-24 09:10:55 -04:00
|
|
|
@actor = actor
|
|
|
|
@project = project
|
2016-06-20 21:40:56 -04:00
|
|
|
@protocol = protocol
|
2016-09-16 03:59:10 -04:00
|
|
|
@authentication_abilities = authentication_abilities
|
2016-07-18 04:16:56 -04:00
|
|
|
@user_access = UserAccess.new(user, project: project)
|
Accept environment variables from the `pre-receive` script.
1. Starting version 2.11, git changed the way the pre-receive flow works.
- Previously, the new potential objects would be added to the main repo. If the
pre-receive passes, the new objects stay in the repo but are linked up. If
the pre-receive fails, the new objects stay orphaned in the repo, and are
cleaned up during the next `git gc`.
- In 2.11, the new potential objects are added to a temporary "alternate object
directory", that git creates for this purpose. If the pre-receive passes, the
objects from the alternate object directory are migrated to the main repo. If
the pre-receive fails the alternate object directory is simply deleted.
2. In our workflow, the pre-recieve script (in `gitlab-shell) calls the
`/allowed` endpoint, which calls out directly to git to perform
various checks. These direct calls to git do _not_ have the necessary
environment variables set which allow access to the "alternate object
directory" (explained above). Therefore these calls to git are not able to
access any of the new potential objects to be added during this push.
3. We fix this by accepting the relevant environment variables
(GIT_ALTERNATE_OBJECT_DIRECTORIES, GIT_OBJECT_DIRECTORY) on the
`/allowed` endpoint, and then include these environment variables while
calling out to git.
4. This commit includes (whitelisted) these environment variables while making
the "force push" check. A `Gitlab::Git::RevList` module is extracted to
prevent `ForcePush` from being littered with these checks.
2016-12-07 02:55:49 -05:00
|
|
|
@env = env
|
2015-03-24 09:10:55 -04:00
|
|
|
end
|
|
|
|
|
2016-08-03 08:54:12 -04:00
|
|
|
def check(cmd, changes)
|
2016-11-02 17:50:44 -04:00
|
|
|
check_protocol!
|
2016-12-20 08:19:07 -05:00
|
|
|
check_active_user!
|
2016-11-02 17:50:44 -04:00
|
|
|
check_project_accessibility!
|
|
|
|
check_command_existence!(cmd)
|
2016-12-09 13:04:36 -05:00
|
|
|
check_repository_existence!
|
2015-05-10 17:07:35 -04:00
|
|
|
|
2014-03-19 15:01:00 -04:00
|
|
|
case cmd
|
|
|
|
when *DOWNLOAD_COMMANDS
|
2016-12-20 08:19:07 -05:00
|
|
|
check_download_access!
|
2014-03-19 15:01:00 -04:00
|
|
|
when *PUSH_COMMANDS
|
2016-12-20 08:19:07 -05:00
|
|
|
check_push_access!(changes)
|
2014-03-19 15:01:00 -04:00
|
|
|
end
|
2016-11-02 17:50:44 -04:00
|
|
|
|
|
|
|
build_status_object(true)
|
|
|
|
rescue UnauthorizedError => ex
|
|
|
|
build_status_object(false, ex.message)
|
2014-03-19 15:01:00 -04:00
|
|
|
end
|
|
|
|
|
2016-12-06 08:10:27 -05:00
|
|
|
def guest_can_download_code?
|
2016-11-29 13:59:25 -05:00
|
|
|
Guest.can?(:download_code, project)
|
|
|
|
end
|
|
|
|
|
2016-09-15 04:34:53 -04:00
|
|
|
def user_can_download_code?
|
2016-09-16 03:59:10 -04:00
|
|
|
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
|
2016-08-08 06:01:25 -04:00
|
|
|
end
|
|
|
|
|
2016-09-15 04:34:53 -04:00
|
|
|
def build_can_download_code?
|
2016-09-16 03:59:10 -04:00
|
|
|
authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
|
2015-03-24 09:10:55 -04:00
|
|
|
end
|
|
|
|
|
2016-06-27 12:14:44 -04:00
|
|
|
def protocol_allowed?
|
|
|
|
Gitlab::ProtocolAccess.allowed?(protocol)
|
|
|
|
end
|
|
|
|
|
2014-03-19 15:01:00 -04:00
|
|
|
private
|
|
|
|
|
2016-11-02 17:50:44 -04:00
|
|
|
def check_protocol!
|
|
|
|
unless protocol_allowed?
|
|
|
|
raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_active_user!
|
2016-12-20 08:19:07 -05:00
|
|
|
return if deploy_key?
|
|
|
|
|
2016-11-02 17:50:44 -04:00
|
|
|
if user && !user_access.allowed?
|
|
|
|
raise UnauthorizedError, "Your account has been blocked."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_project_accessibility!
|
|
|
|
if project.blank? || !can_read_project?
|
|
|
|
raise UnauthorizedError, 'The project you were looking for could not be found.'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_command_existence!(cmd)
|
|
|
|
unless ALL_COMMANDS.include?(cmd)
|
|
|
|
raise UnauthorizedError, "The command you're trying to execute is not allowed."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-11 07:40:28 -05:00
|
|
|
def check_repository_existence!
|
|
|
|
unless project.repository.exists?
|
|
|
|
raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-20 08:19:07 -05:00
|
|
|
def check_download_access!
|
|
|
|
return if deploy_key?
|
|
|
|
|
|
|
|
passed = user_can_download_code? ||
|
2016-12-20 10:30:01 -05:00
|
|
|
build_can_download_code? ||
|
|
|
|
guest_can_download_code?
|
2016-12-20 08:19:07 -05:00
|
|
|
|
|
|
|
unless passed
|
|
|
|
raise UnauthorizedError, ERROR_MESSAGES[:download]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_push_access!(changes)
|
|
|
|
if deploy_key
|
|
|
|
check_deploy_key_push_access!
|
|
|
|
elsif user
|
|
|
|
check_user_push_access!
|
|
|
|
else
|
|
|
|
raise UnauthorizedError, ERROR_MESSAGES[:upload]
|
|
|
|
end
|
|
|
|
|
|
|
|
return if changes.blank? # Allow access.
|
|
|
|
|
|
|
|
check_change_access!(changes)
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_user_push_access!
|
|
|
|
unless authentication_abilities.include?(:push_code)
|
|
|
|
raise UnauthorizedError, ERROR_MESSAGES[:upload]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_deploy_key_push_access!
|
|
|
|
unless deploy_key.can_push_to?(project)
|
|
|
|
raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-11 07:40:28 -05:00
|
|
|
def check_change_access!(changes)
|
|
|
|
changes_list = Gitlab::ChangesList.new(changes)
|
|
|
|
|
|
|
|
# Iterate over all changes to find if user allowed all of them to be applied
|
|
|
|
changes_list.each do |change|
|
2016-11-11 09:26:05 -05:00
|
|
|
status = check_single_change_access(change)
|
2016-11-11 07:40:28 -05:00
|
|
|
unless status.allowed?
|
|
|
|
# If user does not have access to make at least one change - cancel all push
|
|
|
|
raise UnauthorizedError, status.message
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-11 09:26:05 -05:00
|
|
|
def check_single_change_access(change)
|
|
|
|
Checks::ChangeAccess.new(
|
2016-11-17 14:48:23 -05:00
|
|
|
change,
|
|
|
|
user_access: user_access,
|
|
|
|
project: project,
|
2016-12-20 07:54:40 -05:00
|
|
|
env: @env,
|
2017-03-13 07:31:27 -04:00
|
|
|
skip_authorization: deploy_key?,
|
|
|
|
protocol: protocol
|
|
|
|
).exec
|
2016-11-11 09:26:05 -05:00
|
|
|
end
|
|
|
|
|
2016-07-18 04:16:56 -04:00
|
|
|
def matching_merge_request?(newrev, branch_name)
|
|
|
|
Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
|
2014-12-26 03:52:39 -05:00
|
|
|
end
|
|
|
|
|
2016-07-18 04:16:56 -04:00
|
|
|
def deploy_key
|
2016-11-17 14:48:23 -05:00
|
|
|
actor if deploy_key?
|
|
|
|
end
|
|
|
|
|
|
|
|
def deploy_key?
|
|
|
|
actor.is_a?(DeployKey)
|
2014-09-23 07:18:36 -04:00
|
|
|
end
|
2014-11-14 11:23:55 -05:00
|
|
|
|
2016-11-02 17:50:44 -04:00
|
|
|
def can_read_project?
|
2016-11-11 07:40:28 -05:00
|
|
|
if deploy_key
|
2016-11-17 14:48:23 -05:00
|
|
|
deploy_key.has_access_to?(project)
|
2016-11-11 07:40:28 -05:00
|
|
|
elsif user
|
2016-11-17 14:48:23 -05:00
|
|
|
user.can?(:read_project, project)
|
|
|
|
end || Guest.can?(:read_project, project)
|
2016-11-02 17:50:44 -04:00
|
|
|
end
|
|
|
|
|
2016-07-13 14:57:30 -04:00
|
|
|
protected
|
|
|
|
|
2016-07-18 04:16:56 -04:00
|
|
|
def user
|
|
|
|
return @user if defined?(@user)
|
|
|
|
|
|
|
|
@user =
|
|
|
|
case actor
|
|
|
|
when User
|
|
|
|
actor
|
2016-12-09 12:45:13 -05:00
|
|
|
when DeployKey
|
|
|
|
nil
|
2016-07-18 04:16:56 -04:00
|
|
|
when Key
|
|
|
|
actor.user
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-14 11:23:55 -05:00
|
|
|
def build_status_object(status, message = '')
|
2016-07-25 17:46:40 -04:00
|
|
|
Gitlab::GitAccessStatus.new(status, message)
|
2014-11-14 11:23:55 -05:00
|
|
|
end
|
2014-03-19 15:01:00 -04:00
|
|
|
end
|
|
|
|
end
|