bitbucket_server_controller.rb 5.19 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5
class Import::BitbucketServerController < Import::BaseController
  before_action :verify_bitbucket_server_import_enabled
  before_action :bitbucket_auth, except: [:new, :configure]
6 7 8
  before_action :validate_import_params, only: [:create]

  # As a basic sanity check to prevent URL injection, restrict project
9
  # repository input and repository slugs to allowed characters. For Bitbucket:
10 11 12 13 14 15
  #
  # 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)
16
  VALID_BITBUCKET_CHARS = /\A[\w\-_\.\s]+\z/
17

18 19 20 21
  def new
  end

  def create
22 23 24
    repo = bitbucket_client.repo(@project_key, @repo_slug)

    unless repo
Stan Hu's avatar
Stan Hu committed
25
      return render json: { errors: "Project #{@project_key}/#{@repo_slug} could not be found" }, status: :unprocessable_entity
Stan Hu's avatar
Stan Hu committed
26
    end
27

28
    project_name = params[:new_name].presence || repo.name
29
    namespace_path = params[:new_namespace].presence || current_user.username
30 31 32
    target_namespace = find_or_create_namespace(namespace_path, current_user)

    if current_user.can?(:create_projects, target_namespace)
33
      project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute
34 35 36 37 38 39 40 41 42

      if project.persisted?
        render json: ProjectSerializer.new.represent(project)
      else
        render json: { errors: project_save_error(project) }, status: :unprocessable_entity
      end
    else
      render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
    end
43
  rescue BitbucketServer::Connection::ConnectionError => e
Stan Hu's avatar
Stan Hu committed
44
    render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
45 46 47 48 49 50 51 52 53 54
  end

  def configure
    session[personal_access_token_key] = params[:personal_access_token]
    session[bitbucket_server_username_key] = params[:bitbucket_username]
    session[bitbucket_server_url_key] = params[:bitbucket_server_url]

    redirect_to status_import_bitbucket_server_path
  end

55
  # rubocop: disable CodeReuse/ActiveRecord
56
  def status
57 58
    @collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page)
    @repos, @incompatible_repos = @collection.partition { |repo| repo.valid? }
59

60 61
    # Use the import URL to filter beyond what BaseService#find_already_added_projects
    @already_added_projects = filter_added_projects('bitbucket_server', @repos.map(&:browse_url))
62 63
    already_added_projects_names = @already_added_projects.pluck(:import_source)

64
    @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
65
  rescue BitbucketServer::Connection::ConnectionError => e
66 67 68
    flash[:alert] = "Unable to connect to server: #{e}"
    clear_session_data
    redirect_to new_import_bitbucket_server_path
69
  end
70
  # rubocop: enable CodeReuse/ActiveRecord
71 72 73 74 75 76 77

  def jobs
    render json: find_jobs('bitbucket_server')
  end

  private

78 79 80 81 82 83
  # rubocop: disable CodeReuse/ActiveRecord
  def filter_added_projects(import_type, import_sources)
    current_user.created_projects.where(import_type: import_type, import_source: import_sources).includes(:import_state)
  end
  # rubocop: enable CodeReuse/ActiveRecord

84 85 86 87
  def bitbucket_client
    @bitbucket_client ||= BitbucketServer::Client.new(credentials)
  end

88 89 90 91
  def validate_import_params
    @project_key = params[:project]
    @repo_slug = params[:repository]

92
    return render_validation_error('Missing project key') unless @project_key.present? && @repo_slug.present?
Stan Hu's avatar
Stan Hu committed
93 94 95 96 97 98
    return render_validation_error('Missing repository slug') unless @repo_slug.present?
    return render_validation_error('Invalid project key') unless @project_key =~ VALID_BITBUCKET_CHARS
    return render_validation_error('Invalid repository slug') unless @repo_slug =~ VALID_BITBUCKET_CHARS
  end

  def render_validation_error(message)
Stan Hu's avatar
Stan Hu committed
99
    render json: { errors: message }, status: :unprocessable_entity
100 101
  end

102 103
  def bitbucket_auth
    unless session[bitbucket_server_url_key].present? &&
Stan Hu's avatar
Stan Hu committed
104 105
        session[bitbucket_server_username_key].present? &&
        session[personal_access_token_key].present?
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
      redirect_to new_import_bitbucket_server_path
    end
  end

  def verify_bitbucket_server_import_enabled
    render_404 unless bitbucket_server_import_enabled?
  end

  def bitbucket_server_url_key
    :bitbucket_server_url
  end

  def bitbucket_server_username_key
    :bitbucket_server_username
  end

  def personal_access_token_key
    :bitbucket_server_personal_access_token
  end

126 127 128 129 130 131
  def clear_session_data
    session[bitbucket_server_url_key] = nil
    session[bitbucket_server_username_key] = nil
    session[personal_access_token_key] = nil
  end

132 133 134
  def credentials
    {
      base_uri: session[bitbucket_server_url_key],
135
      user: session[bitbucket_server_username_key],
136
      password: session[personal_access_token_key]
137 138
    }
  end
139 140 141 142 143 144 145 146

  def page_offset
    [0, params[:page].to_i].max
  end

  def limit_per_page
    BitbucketServer::Paginator::PAGE_LENGTH
  end
147
end