1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
# frozen_string_literal: true
module API
module Helpers
module InternalHelpers
attr_reader :redirected_path
UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'
delegate :wiki?, to: :repo_type
def actor
@actor ||= Support::GitAccessActor.from_params(params)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def repo_type
parse_repo_path unless defined?(@repo_type)
@repo_type
end
def project
parse_repo_path unless defined?(@project)
@project
end
def container
parse_repo_path unless defined?(@container)
@container
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def access_check_result
with_admin_mode_bypass!(actor.user&.id) do
access_check!(actor, params)
end
rescue Gitlab::GitAccess::ForbiddenError => e
# The return code needs to be 401. If we return 403
# the custom message we return won't be shown to the user
# and, instead, the default message 'GitLab: API is not accessible'
# will be displayed
response_with_status(code: 401, success: false, message: e.message)
rescue Gitlab::GitAccess::TimeoutError => e
response_with_status(code: 503, success: false, message: e.message)
rescue Gitlab::GitAccess::NotFoundError => e
response_with_status(code: 404, success: false, message: e.message)
rescue Gitlab::GitAccessProject::CreationError => e
response_with_status(code: 422, success: false, message: e.message)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def access_check!(actor, params)
access_checker = access_checker_for(actor, params[:protocol])
access_checker.check(params[:action], params[:changes]).tap do |result|
break result if @project || !repo_type.project?
# If we have created a project directly from a git push
# we have to assign its value to both @project and @container
@project = @container = access_checker.container
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def access_checker_for(actor, protocol)
access_checker_klass.new(actor.key_or_user, container, protocol,
authentication_abilities: ssh_authentication_abilities,
repository_path: repository_path,
redirected_path: redirected_path,
push_options: params[:push_options])
end
def access_checker_klass
repo_type.access_checker_class
end
def ssh_authentication_abilities
[
:read_project,
:download_code,
:push_code
]
end
def parse_env
return {} if params[:env].blank?
Gitlab::Json.parse(params[:env])
rescue JSON::ParserError
{}
end
def log_user_activity(actor)
commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS
return unless commands.include?(params[:action])
::Users::ActivityService.new(author: actor, namespace: project&.namespace, project: project).execute
return unless project && actor
Gitlab::EventStore.publish(
::Users::ActivityEvent.new(data: {
user_id: actor.id,
namespace_id: project.root_ancestor.id
})
)
end
def redis_ping
result = Gitlab::Redis::SharedState.with { |redis| redis.ping }
result == 'PONG'
rescue StandardError => e
Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
false
end
def response_with_status(code: 200, success: true, message: nil, **extra_options)
status code
{ status: success, message: message }.merge(extra_options).compact
end
def unsuccessful_response?(response)
response.is_a?(Hash) && response[:status] == false
end
def with_admin_mode_bypass!(actor_id, &block)
return yield unless Gitlab::CurrentSettings.admin_mode
Gitlab::Auth::CurrentUserMode.bypass_session!(actor_id, &block)
end
def send_git_audit_streaming_event(msg)
# Defined in EE
end
def need_git_audit_event?
false
end
private
def repository_path
if container
"#{container.full_path}.git"
elsif params[:project]
# When the project doesn't exist, we still need to pass on the path
# to support auto-creation in `GitAccessProject`.
#
# For consistency with the Git HTTP controllers, we normalize the path
# to remove a leading slash and ensure a trailing `.git`.
#
# NOTE: For GitLab Shell, `params[:project]` is the full repository path
# from the SSH command, with an optional trailing `.git`.
"#{params[:project].delete_prefix('/').delete_suffix('.git')}.git"
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def parse_repo_path
@container, @project, @repo_type, @redirected_path =
if params[:gl_repository]
Gitlab::GlRepository.parse(params[:gl_repository])
elsif params[:project]
Gitlab::RepoPath.parse(params[:project])
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
# Repository id to pass between components that don't share/don't have
# access to the same filesystem mounts
def gl_repository
repo_type.identifier_for_container(container)
end
def gl_repository_path
repository.full_path
end
# Return the repository for the detected type and container
#
# @returns [Repository]
def repository
@repository ||= repo_type.repository_for(container)
end
# Return the Gitaly Address if it is enabled
def gitaly_payload(action)
return unless %w[git-receive-pack git-upload-pack git-upload-archive].include?(action)
{
repository: repository.gitaly_repository.to_h,
address: Gitlab::GitalyClient.address(repository.shard),
token: Gitlab::GitalyClient.token(repository.shard),
features: Feature::Gitaly.server_feature_flags(
user: ::Feature::Gitaly.user_actor(actor.user),
repository: repository,
project: ::Feature::Gitaly.project_actor(repository.container),
group: ::Feature::Gitaly.group_actor(repository.container)
)
}
end
end
end
end
API::Helpers::InternalHelpers.prepend_mod
|