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
|
# frozen_string_literal: true
module Import
class GithubService < Import::BaseService
include ActiveSupport::NumberHelper
include Gitlab::Utils::StrongMemoize
include SafeFormatHelper
attr_accessor :client
attr_reader :params, :current_user
def execute(access_params, provider)
context_error = validate_context
return context_error if context_error
if provider == :github # we skip access token validation for Gitea importer calls
access_token_error = validate_access_token
return access_token_error if access_token_error
end
project = create_project(access_params, provider)
track_access_level(provider.to_s) # provider may be :gitea
if project.persisted?
store_import_settings(project)
success(project)
elsif project.errors[:import_source_disabled].present?
error(project.errors[:import_source_disabled], :forbidden)
else
error(project_save_error(project), :unprocessable_entity)
end
rescue Octokit::Error => e
log_error(e)
end
def create_project(access_params, provider)
Gitlab::LegacyGithubImport::ProjectCreator.new(
repo,
project_name,
target_namespace,
current_user,
type: provider,
**access_params
).execute(extra_project_attrs)
end
def repo
@repo ||= client.repository(params[:repo_id].to_i)
end
def project_name
@project_name ||= params[:new_name].presence || repo[:name]
end
def target_namespace
@target_namespace ||= Namespace.find_by_full_path(target_namespace_path)
end
def extra_project_attrs
{}
end
def oversized?
repository_size_limit > 0 && repo[:size] > repository_size_limit
end
def oversize_error_message
s_('GithubImport|"%{repository_name}" size (%{repository_size}) is larger than the limit of %{limit}.') % {
repository_name: repo[:name],
repository_size: number_to_human_size(repo[:size]),
limit: number_to_human_size(repository_size_limit)
}
end
def repository_size_limit
strong_memoize :repository_size_limit do
namespace_limit = target_namespace.repository_size_limit.to_i
if namespace_limit > 0
namespace_limit
else
Gitlab::CurrentSettings.repository_size_limit.to_i
end
end
end
def url
@url ||= params[:github_hostname]
end
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
def blocked_url?
Gitlab::HTTP_V2::UrlBlocker.blocked_url?(
url,
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
schemes: %w[http https],
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?,
outbound_local_requests_allowlist: Gitlab::CurrentSettings.outbound_local_requests_whitelist # rubocop:disable Naming/InclusiveLanguage -- existing setting
)
end
private
def validate_access_token
begin
client.octokit.repository(params[:repo_id].to_i)
rescue Octokit::Forbidden, Octokit::Unauthorized
return error(repository_access_error_message, :unprocessable_entity)
end
return unless Gitlab::Utils.to_boolean(params.dig(:optional_stages, :collaborators_import))
begin
client.octokit.collaborators(params[:repo_id].to_i)
rescue Octokit::Forbidden, Octokit::Unauthorized
return error(collaborators_access_error_message, :unprocessable_entity)
end
nil # we intentionally return nil if we don't raise an error
end
def repository_access_error_message
s_("GithubImport|Your GitHub personal access token does not have read access to the repository. " \
"Please use a classic GitHub personal access token with the `repo` scope. Fine-grained tokens are not supported.")
end
def collaborators_access_error_message
s_("GithubImport|Your GitHub personal access token does not have read access to collaborators. " \
"Please use a classic GitHub personal access token with the `read:org` scope. Fine-grained tokens are not supported.")
end
def validate_context
if blocked_url?
log_and_return_error("Invalid URL: #{url}", _("Invalid URL: %{url}") % { url: url }, :bad_request)
elsif target_namespace.nil?
error(s_('GithubImport|Namespace or group to import repository into does not exist.'), :unprocessable_entity)
elsif !authorized?
error(s_('GithubImport|You are not allowed to import projects in this namespace.'), :unprocessable_entity)
elsif oversized?
error(oversize_error_message, :unprocessable_entity)
end
end
def target_namespace_path
raise ArgumentError, s_('GithubImport|Target namespace is required') if params[:target_namespace].blank?
params[:target_namespace]
end
def log_error(exception)
Gitlab::GithubImport::Logger.error(
message: 'Import failed because of a GitHub error',
status: exception.response_status,
error: exception.response_body
)
error(s_('GithubImport|Import failed because of a GitHub error: %{original} (HTTP %{code})') % { original: exception.response_body, code: exception.response_status }, :unprocessable_entity)
end
def log_and_return_error(message, translated_message, http_status)
Gitlab::GithubImport::Logger.error(
message: 'Error while attempting to import from GitHub',
error: message
)
error(translated_message, http_status)
end
def store_import_settings(project)
Gitlab::GithubImport::Settings
.new(project)
.write(
timeout_strategy: params[:timeout_strategy] || ProjectImportData::PESSIMISTIC_TIMEOUT,
optional_stages: params[:optional_stages]
)
end
end
end
Import::GithubService.prepend_mod_with('Import::GithubService')
|