File: github_service.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (183 lines) | stat: -rw-r--r-- 6,094 bytes parent folder | download
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')