Commit a78e36ab authored by Stan Hu's avatar Stan Hu

Improve error handling of Bitbucket login errors

parent c7198166
...@@ -36,6 +36,8 @@ class ImporterStatus { ...@@ -36,6 +36,8 @@ class ImporterStatus {
const $targetField = $tr.find('.import-target'); const $targetField = $tr.find('.import-target');
const $namespaceInput = $targetField.find('.js-select-namespace option:selected'); const $namespaceInput = $targetField.find('.js-select-namespace option:selected');
const id = $tr.attr('id').replace('repo_', ''); const id = $tr.attr('id').replace('repo_', '');
const repoData = $tr.data();
let targetNamespace; let targetNamespace;
let newName; let newName;
if ($namespaceInput.length > 0) { if ($namespaceInput.length > 0) {
...@@ -47,12 +49,18 @@ class ImporterStatus { ...@@ -47,12 +49,18 @@ class ImporterStatus {
this.id = id; this.id = id;
return axios.post(this.importUrl, { let attributes = {
repo_id: id, repo_id: id,
target_namespace: targetNamespace, target_namespace: targetNamespace,
new_name: newName, new_name: newName,
ci_cd_only: this.ciCdOnly, ci_cd_only: this.ciCdOnly
}) };
if (repoData) {
attributes = Object.assign(repoData, attributes);
}
return axios.post(this.importUrl, attributes)
.then(({ data }) => { .then(({ data }) => {
const job = $(`tr#repo_${id}`); const job = $(`tr#repo_${id}`);
job.attr('id', `project_${data.id}`); job.attr('id', `project_${data.id}`);
......
class Import::BitbucketServerController < Import::BaseController class Import::BitbucketServerController < Import::BaseController
before_action :verify_bitbucket_server_import_enabled before_action :verify_bitbucket_server_import_enabled
before_action :bitbucket_auth, except: [:new, :configure] before_action :bitbucket_auth, except: [:new, :configure]
before_action :validate_import_params, only: [:create]
# As a basic sanity check to prevent URL injection, restrict project
# repostiory input and repository slugs to allowed characters. For Bitbucket:
#
# Project keys must start with a letter and may only consist of ASCII letters, numbers and underscores (A-Z, a-z, 0-9, _).
#
# Repository names are limited to 128 characters. They must start with a
# letter or number and may contain spaces, hyphens, underscores, and periods.
# (https://community.atlassian.com/t5/Answers-Developer-Questions/stash-repository-names/qaq-p/499054)
VALID_BITBUCKET_CHARS = %r(\A[a-zA-z0-9\-_\.\s]*$)
SERVER_ERRORS = [SocketError,
OpenSSL::SSL::SSLError,
Errno::ECONNRESET,
Errno::ECONNREFUSED,
Errno::EHOSTUNREACH,
Net::OpenTimeout,
Net::ReadTimeout,
Gitlab::HTTP::BlockedUrlError,
BitbucketServer::Error::Unauthorized].freeze
def new def new
end end
...@@ -8,10 +29,12 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -8,10 +29,12 @@ class Import::BitbucketServerController < Import::BaseController
def create def create
bitbucket_client = BitbucketServer::Client.new(credentials) bitbucket_client = BitbucketServer::Client.new(credentials)
repo_id = params[:repo_id].to_s repo = bitbucket_client.repo(@project_key, @repo_slug)
# XXX must be a better way
project_slug, repo_slug = repo_id.split("___") unless repo
repo = bitbucket_client.repo(project_slug, repo_slug) return render json: { errors: "Project #{@project_key}/#{repo_slug} could not be found" }
end
project_name = params[:new_name].presence || repo.name project_name = params[:new_name].presence || repo.name
repo_owner = current_user.username repo_owner = current_user.username
...@@ -19,7 +42,7 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -19,7 +42,7 @@ class Import::BitbucketServerController < Import::BaseController
target_namespace = find_or_create_namespace(namespace_path, current_user) target_namespace = find_or_create_namespace(namespace_path, current_user)
if current_user.can?(:create_projects, target_namespace) if current_user.can?(:create_projects, target_namespace)
project = Gitlab::BitbucketServerImport::ProjectCreator.new(project_slug, repo_slug, repo, project_name, target_namespace, current_user, credentials).execute project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute
if project.persisted? if project.persisted?
render json: ProjectSerializer.new.represent(project) render json: ProjectSerializer.new.represent(project)
...@@ -29,6 +52,8 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -29,6 +52,8 @@ class Import::BitbucketServerController < Import::BaseController
else else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
end end
rescue *SERVER_ERRROS => e
render json: { errors: "Unable to connect to server: #{e}" }
end end
def configure def configure
...@@ -49,6 +74,10 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -49,6 +74,10 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) } @repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
rescue *SERVER_ERRORS => e
flash[:alert] = "Unable to connect to server: #{e}"
clear_session_data
redirect_to new_import_bitbucket_server_path
end end
def jobs def jobs
...@@ -57,6 +86,16 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -57,6 +86,16 @@ class Import::BitbucketServerController < Import::BaseController
private private
def validate_import_params
@project_key = params[:project]
@repo_slug = params[:repository]
return render json: { errors: 'Missing project key' } unless @project_key.present? && @repo_slug.present?
return render json: { errors: 'Missing repository slug' } unless @repo_slug.present?
return render json: { errors: 'Invalid project key' } unless @project_key =~ VALID_BITBUCKET_CHARS
return render json: { errors: 'Invalid repository slug' } unless @repo_slug =~ VALID_BITBUCKET_CHARS
end
def bitbucket_auth def bitbucket_auth
unless session[bitbucket_server_url_key].present? && unless session[bitbucket_server_url_key].present? &&
session[bitbucket_server_username_key].present? && session[bitbucket_server_username_key].present? &&
...@@ -81,6 +120,14 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -81,6 +120,14 @@ class Import::BitbucketServerController < Import::BaseController
:bitbucket_server_personal_access_token :bitbucket_server_personal_access_token
end end
def clear_session_data
return unless session
session[bitbucket_server_url_key] = nil
session[bitbucket_server_username_key] = nil
session[personal_access_token_key] = nil
end
def credentials def credentials
{ {
base_uri: session[bitbucket_server_url_key], base_uri: session[bitbucket_server_url_key],
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %tr{ id: "repo_#{repo.owner}___#{repo.slug}", data: { project: repo.project_name, repository: repo.slug } }
%td %td
= link_to repo.full_name, repo.browse_url, target: '_blank', rel: 'noopener noreferrer' = link_to repo.full_name, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'
%td.import-target %td.import-target
......
module BitbucketServer module BitbucketServer
class Connection class Connection
include ActionView::Helpers::SanitizeHelper
DEFAULT_API_VERSION = '1.0'.freeze DEFAULT_API_VERSION = '1.0'.freeze
attr_reader :api_version, :base_uri, :username, :token attr_reader :api_version, :base_uri, :username, :token
...@@ -15,19 +17,36 @@ module BitbucketServer ...@@ -15,19 +17,36 @@ module BitbucketServer
response = Gitlab::HTTP.get(build_url(path), response = Gitlab::HTTP.get(build_url(path),
basic_auth: auth, basic_auth: auth,
params: extra_query) params: extra_query)
## Handle failure
check_errors!(response)
response.parsed_response response.parsed_response
end end
def post(path, body) def post(path, body)
Gitlab::HTTP.post(build_url(path), response = Gitlab::HTTP.post(build_url(path),
basic_auth: auth, basic_auth: auth,
headers: post_headers, headers: post_headers,
body: body) body: body)
check_errors!(response)
response
end end
private private
def check_errors!(response)
if response.code != 200
error =
if response.parsed_response
sanitize(response.parsed_response.dig('errors', 0, 'message'))
end
message = "Error #{response.code}"
message += ": #{error}" if error
raise ::BitbucketServer::Error::Unauthorized, message
end
end
def auth def auth
@auth ||= { username: username, password: token } @auth ||= { username: username, password: token }
end end
......
...@@ -5,6 +5,10 @@ module BitbucketServer ...@@ -5,6 +5,10 @@ module BitbucketServer
super(raw) super(raw)
end end
def project_name
raw.dig('project', 'name')
end
def owner def owner
project['name'] project['name']
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