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

class Import::BitbucketServerController < Import::BaseController
  extend ::Gitlab::Utils::Override

  include ActionView::Helpers::SanitizeHelper

  before_action :verify_bitbucket_server_import_enabled
  before_action :bitbucket_auth, except: [:new, :configure]
  before_action :normalize_import_params, only: [:create]
  before_action :validate_import_params, only: [:create]

  rescue_from BitbucketServer::Connection::ConnectionError, with: :bitbucket_connection_error

  # 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)
  #
  # Bitbucket Server starts personal project names with a tilde.
  VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/.freeze
  VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/.freeze

  def new
  end

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

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

    result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials)

    if result[:status] == :success
      render json: ProjectSerializer.new.represent(result[:project], serializer: :import)
    else
      render json: { errors: result[:message] }, status: result[:http_status]
    end
  end

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

    redirect_to status_import_bitbucket_server_path(namespace_id: params[:namespace_id])
  end

  # We need to re-expose controller's internal method 'status' as action.
  # rubocop:disable Lint/UselessMethodDefinition
  def status
    super
  end
  # rubocop:enable Lint/UselessMethodDefinition

  protected

  override :importable_repos
  def importable_repos
    bitbucket_repos.filter(&:valid?)
  end

  override :incompatible_repos
  def incompatible_repos
    bitbucket_repos.reject(&:valid?)
  end

  override :provider_name
  def provider_name
    :bitbucket_server
  end

  override :provider_url
  def provider_url
    session[bitbucket_server_url_key]
  end

  private

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

  def bitbucket_repos
    @bitbucket_repos ||= client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param).to_a
  end

  def normalize_import_params
    project_key, repo_slug = params[:repo_id].split('/')
    params[:bitbucket_server_project] = project_key
    params[:bitbucket_server_repo] = repo_slug
  end

  def validate_import_params
    @project_key = params[:bitbucket_server_project]
    @repo_slug = params[:bitbucket_server_repo]

    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_PROJECT_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(namespace_id: params[:namespace_id])
    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

  def bitbucket_connection_error(error)
    flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
    clear_session_data

    respond_to do |format|
      format.json do
        render json: {
          error: {
            message: _("Unable to connect to server: %{error}") % { error: error },
            redirect: new_import_bitbucket_server_path
          }
        }, status: :unprocessable_entity
      end
    end
  end
end