summaryrefslogtreecommitdiff
path: root/app/controllers/import/bitbucket_server_controller.rb
blob: 6b7cc2d8ad7adbd2a792fc227681de1c62f273b8 (plain)
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
# frozen_string_literal: true

class Import::BitbucketServerController < Import::BaseController
  include PaginationHelper

  before_action :verify_bitbucket_server_import_enabled
  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
  # repository 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 = /\A[\w\-_\.\s]+\z/

  def new
  end

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

    unless repo
      return render json: { errors: "Project #{@project_key}/#{@repo_slug} could not be found" }, status: :unprocessable_entity
    end

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

    if current_user.can?(:create_projects, target_namespace)
      project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute

      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
  rescue BitbucketServer::Client::ServerError => e
    render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
  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

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

    # 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))
    already_added_projects_names = @already_added_projects.pluck(:import_source)

    @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
  rescue BitbucketServer::Connection::ConnectionError, BitbucketServer::Client::ServerError => e
    flash[:alert] = "Unable to connect to server: #{e}"
    clear_session_data
    redirect_to new_import_bitbucket_server_path
  end
  # rubocop: enable CodeReuse/ActiveRecord

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

  private

  # 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

  def bitbucket_client
    @bitbucket_client ||= BitbucketServer::Client.new(credentials)
  end

  def validate_import_params
    @project_key = params[:project]
    @repo_slug = params[:repository]

    return render_validation_error('Missing project key') unless @project_key.present? && @repo_slug.present?
    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)
    render json: { errors: message }, status: :unprocessable_entity
  end

  def bitbucket_auth
    unless session[bitbucket_server_url_key].present? &&
        session[bitbucket_server_username_key].present? &&
        session[personal_access_token_key].present?
      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

  def clear_session_data
    session[bitbucket_server_url_key] = nil
    session[bitbucket_server_username_key] = nil
    session[personal_access_token_key] = nil
  end

  def credentials
    {
      base_uri: session[bitbucket_server_url_key],
      user: session[bitbucket_server_username_key],
      password: session[personal_access_token_key]
    }
  end

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

  def limit_per_page
    BitbucketServer::Paginator::PAGE_LENGTH
  end
end