Read about our upcoming Code of Conduct on this issue

Commit e2454afd authored by Tiago Botelho's avatar Tiago Botelho
Browse files

Adds remote messsage when project is created in a push over SSH or HTTP

parent 9dad987328ad
......@@ -11,10 +11,14 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def info_refs
log_user_activity if upload_pack?
if project.blank? && params[:service] == 'git-receive-pack'
if user && project.blank? && receive_pack?
@project = ::Projects::CreateService.new(user, project_params).execute
return render_ok if @project.saved?
if @project.saved?
Gitlab::Checks::NewProject.new(user, @project, 'http').add_new_project_message
else
raise Gitlab::GitAccess::NotFoundError, 'Could not create project'
end
end
render_ok
......@@ -32,15 +36,6 @@ def git_receive_pack
private
def project_params
{
description: "",
path: params[:project_id].gsub("\.git", ''),
namespace_id: namespace.id.to_s,
visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s
}
end
def download_request?
upload_pack?
end
......@@ -49,6 +44,10 @@ def upload_pack?
git_command == 'git-upload-pack'
end
def receive_pack?
git_command == 'git-receive-pack'
end
def git_command
if action_name == 'info_refs'
params[:service]
......@@ -74,10 +73,6 @@ def access
@access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path, target_namespace: namespace)
end
def namespace
@namespace = Namespace.find_by_path_or_name(params[:namespace_id])
end
def access_actor
return user if user
return :ci if ci?
......@@ -93,6 +88,19 @@ def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
end
def project_params
{
description: "",
path: Project.parse_project_id(params[:project_id]),
namespace_id: namespace&.id,
visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s
}
end
def namespace
@namespace ||= Namespace.find_by_path_or_name(params[:namespace_id])
end
def log_user_activity
Users::ActivityService.new(user, 'pull').execute
end
......
......@@ -468,6 +468,10 @@ def cached_count
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
def parse_project_id(project_id)
project_id.gsub("\.git", '')
end
end
# returns all ancestor-groups upto but excluding the given namespace
......
......@@ -29,6 +29,10 @@ def parse_env
{}
end
def receive_pack?
params[:action] == 'git-receive-pack'
end
def fix_git_env_repository_paths(env, repository_path)
if obj_dir_relative = env['GIT_OBJECT_DIRECTORY_RELATIVE'].presence
env['GIT_OBJECT_DIRECTORY'] = File.join(repository_path, obj_dir_relative)
......@@ -62,6 +66,18 @@ def redis_ping
private
def project_path_regex
@project_regex ||= /\A(?<namespace_id>#{Gitlab::PathRegex.full_namespace_route_regex})\/(?<project_id>#{Gitlab::PathRegex.project_git_route_regex})\z/.freeze
end
def project_match
@match ||= params[:project].match(project_path_regex).captures
end
def namespace
@namespace ||= Namespace.find_by_path_or_name(project_match[:namespace_id])
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_project
if params[:gl_repository]
......
......@@ -43,7 +43,7 @@ class Internal < Grape::API
access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
access_checker = access_checker_klass
.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path, target_namespace: user.namespace)
.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path, target_namespace: namespace)
begin
access_checker.check(params[:action], params[:changes])
......@@ -51,17 +51,21 @@ class Internal < Grape::API
return { status: false, message: e.message }
end
if project.blank? && params[:action] == 'git-receive-pack'
if user && project.blank? && receive_pack?
project_params = {
description: "",
path: params[:project].split('/').last.gsub("\.git", ''),
namespace_id: user.namespace.id.to_s,
path: Project.parse_project_id(project_match[:project_name]),
namespace_id: namespace&.id,
visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s
}
@project = ::Projects::CreateService.new(user, project_params).execute
return { status: false, message: "Could not create project" } unless @project.saved?
if @project.saved?
Gitlab::Checks::NewProject.new(user, @project, protocol).add_new_project_message
else
return { status: false, message: "Could not create project" }
end
end
log_user_activity(actor)
......@@ -221,7 +225,10 @@ class Internal < Grape::API
# key could be used
if user
redirect_message = Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id)
new_project_message = Gitlab::Checks::NewProject.fetch_new_project_message(user.id, project.id)
output[:redirected_message] = redirect_message if redirect_message
output[:new_project_message] = new_project_message if new_project_message
end
output
......
module Gitlab
module Checks
class NewProject
NEW_PROJECT = "new_project".freeze
def initialize(user, project, protocol)
@user = user
@project = project
@protocol = protocol
end
def self.fetch_new_project_message(user_id, project_id)
new_project_key = new_project_message_key(user_id, project_id)
Gitlab::Redis::SharedState.with do |redis|
message = redis.get(new_project_key)
redis.del(new_project_key)
message
end
end
def add_new_project_message
Gitlab::Redis::SharedState.with do |redis|
key = self.class.new_project_message_key(user.id, project.id)
redis.setex(key, 5.minutes, new_project_message)
end
end
def new_project_message
<<~MESSAGE.strip_heredoc
The private project #{project.full_path} was created.
To configure the remote, run:
git remote add origin #{git_url}
To view the project, visit:
#{project_url}
MESSAGE
end
private
attr_reader :project, :user, :protocol
def self.new_project_message_key(user_id, project_id)
"#{NEW_PROJECT}:#{user_id}:#{project_id}"
end
def project_url
Gitlab::Routing.url_helpers.project_url(project)
end
def git_url
protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo
end
end
end
end
......@@ -13,6 +13,7 @@ class GitAccess
'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.',
project_not_found: 'The project you were looking for could not be found.',
namespace_not_found: 'The namespace you were looking for could not be found.',
account_blocked: 'Your account has been blocked.',
command_not_allowed: "The command you're trying to execute is not allowed.",
upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
......@@ -41,18 +42,18 @@ def check(cmd, changes)
check_protocol!
check_valid_actor!
check_active_user!
check_project_accessibility!
check_project_accessibility!(cmd)
check_project_moved!
check_command_disabled!(cmd)
check_command_existence!(cmd)
check_repository_existence!
check_repository_creation!
check_repository_existence!(cmd)
case cmd
when *DOWNLOAD_COMMANDS
check_download_access!
when *PUSH_COMMANDS
check_push_access!(changes)
check_push_access!(cmd, changes)
check_repository_creation!(cmd)
end
true
......@@ -98,8 +99,8 @@ def check_active_user!
end
end
def check_project_accessibility!
if (project.blank? || !can_read_project?) && !can_create_project_in_namespace?
def check_project_accessibility!(cmd)
if (project.blank? || !can_read_project?) && !can_create_project_in_namespace?(cmd)
raise NotFoundError, ERROR_MESSAGES[:project_not_found]
end
end
......@@ -142,16 +143,20 @@ def check_command_existence!(cmd)
end
end
def check_repository_existence!
if (project.blank? || !project.repository.exists?) && !can_create_project_in_namespace?
def check_repository_existence!(cmd)
if (project.blank? || !project.repository.exists?) && !can_create_project_in_namespace?(cmd)
raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
end
end
def check_repository_creation!
return unless target_namespace
def check_repository_creation!(cmd)
return unless project.blank?
unless can_create_project_in_namespace?
unless target_namespace
raise NotFoundError, ERROR_MESSAGES[:namespace_not_found]
end
unless can_create_project_in_namespace?(cmd)
raise UnauthorizedError, ERROR_MESSAGES[:create]
end
end
......@@ -168,8 +173,8 @@ def check_download_access!
end
end
def check_push_access!(changes)
return if can_create_project_in_namespace?
def check_push_access!(cmd, changes)
return if project.blank? && can_create_project_in_namespace?(cmd)
if project.repository_read_only?
raise UnauthorizedError, ERROR_MESSAGES[:read_only]
......@@ -247,8 +252,8 @@ def can_read_project?
end || Guest.can?(:read_project, project)
end
def can_create_project_in_namespace?
return unless target_namespace
def can_create_project_in_namespace?(cmd)
return false unless PUSH_COMMANDS.include?(cmd) && target_namespace
user.can?(:create_projects, target_namespace)
end
......
......@@ -25,6 +25,10 @@ def check_single_change_access(change)
true
end
def check_repository_creation!(cmd)
# Method not used in wiki
end
def push_to_read_only_message
ERROR_MESSAGES[:read_only]
end
......
......@@ -501,15 +501,17 @@ def self.run_permission_checks(permissions_matrix)
end
aggregate_failures do
matrix.each do |action, allowed|
check = -> { access.send(:check_push_access!, changes[action]) }
if allowed
expect(&check).not_to raise_error,
-> { "expected #{action} to be allowed" }
else
expect(&check).to raise_error(Gitlab::GitAccess::UnauthorizedError),
-> { "expected #{action} to be disallowed" }
Gitlab::GitAccess::ALL_COMMANDS.each do |cmd|
matrix.each do |action, allowed|
check = -> { access.send(:check_push_access!, cmd, changes[action]) }
if allowed
expect(&check).not_to raise_error,
-> { "expected #{action} to be allowed" }
else
expect(&check).to raise_error(Gitlab::GitAccess::UnauthorizedError),
-> { "expected #{action} to be disallowed" }
end
end
end
end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment