summaryrefslogtreecommitdiff
path: root/lib/api/api.rb
blob: 94dfb7f598c23c5dd6cb746c6c40d630afa739d5 (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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# frozen_string_literal: true

module API
  class API < ::API::Base
    include APIGuard
    include Helpers::OpenApi

    LOG_FILENAME = Rails.root.join("log", "api_json.log")

    NO_SLASH_URL_PART_REGEX = %r{[^/]+}.freeze
    NAMESPACE_OR_PROJECT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
    COMMIT_ENDPOINT_REQUIREMENTS = NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze
    USER_REQUIREMENTS = { user_id: NO_SLASH_URL_PART_REGEX }.freeze
    LOG_FILTERS = ::Rails.application.config.filter_parameters + [/^output$/]
    LOG_FORMATTER = Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new
    LOGGER = Logger.new(LOG_FILENAME)

    insert_before Grape::Middleware::Error,
                  GrapeLogging::Middleware::RequestLogger,
                  logger: LOGGER,
                  formatter: LOG_FORMATTER,
                  include: [
                    Gitlab::GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS),
                    Gitlab::GrapeLogging::Loggers::ClientEnvLogger.new,
                    Gitlab::GrapeLogging::Loggers::RouteLogger.new,
                    Gitlab::GrapeLogging::Loggers::UserLogger.new,
                    Gitlab::GrapeLogging::Loggers::TokenLogger.new,
                    Gitlab::GrapeLogging::Loggers::ExceptionLogger.new,
                    Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
                    Gitlab::GrapeLogging::Loggers::PerfLogger.new,
                    Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new,
                    Gitlab::GrapeLogging::Loggers::ContextLogger.new,
                    Gitlab::GrapeLogging::Loggers::ContentLogger.new,
                    Gitlab::GrapeLogging::Loggers::UrgencyLogger.new,
                    Gitlab::GrapeLogging::Loggers::ResponseLogger.new
                  ]

    allow_access_with_scope :api
    allow_access_with_scope :read_api, if: -> (request) { request.get? || request.head? }
    prefix :api

    version 'v3', using: :path do
      route :any, '*path' do
        error!('API V3 is no longer supported. Use API V4 instead.', 410)
      end
    end

    version 'v4', using: :path

    before do
      header['X-Frame-Options'] = 'SAMEORIGIN'
      header['X-Content-Type-Options'] = 'nosniff'

      if Rails.application.config.content_security_policy && !Rails.application.config.content_security_policy_report_only
        policy = ActionDispatch::ContentSecurityPolicy.new { |p| p.default_src :none }
      end

      request.env[ActionDispatch::ContentSecurityPolicy::Request::POLICY] = policy
    end

    before do
      coerce_nil_params_to_array!

      api_endpoint = request.env[Grape::Env::API_ENDPOINT]
      feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s

      # remote_ip is added here and the ContextLogger so that the
      # client_id field is set correctly, as the user object does not
      # survive between multiple context pushes.
      Gitlab::ApplicationContext.push(
        user: -> { @current_user },
        project: -> { @project },
        namespace: -> { @group },
        runner: -> { @current_runner || @runner },
        remote_ip: request.ip,
        caller_id: api_endpoint.endpoint_id,
        feature_category: feature_category
      )
    end

    before do
      set_peek_enabled_for_current_request
    end

    after do
      Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
    end

    after do
      Gitlab::UsageDataCounters::JetBrainsPluginActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
    end

    after do
      Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
    end

    # The locale is set to the current user's locale when `current_user` is loaded
    after { Gitlab::I18n.use_default_locale }

    rescue_from Gitlab::Access::AccessDeniedError do
      rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
    end

    rescue_from ActiveRecord::RecordNotFound do
      rack_response({ 'message' => '404 Not found' }.to_json, 404)
    end

    rescue_from(
      ::ActiveRecord::StaleObjectError,
      ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
    ) do
      rack_response({ 'message' => '409 Conflict: Resource lock' }.to_json, 409)
    end

    rescue_from UploadedFile::InvalidPathError do |e|
      rack_response({ 'message' => e.message }.to_json, 400)
    end

    rescue_from ObjectStorage::RemoteStoreError do |e|
      rack_response({ 'message' => e.message }.to_json, 500)
    end

    # Retain 405 error rather than a 500 error for Grape 0.15.0+.
    # https://github.com/ruby-grape/grape/blob/a3a28f5b5dfbb2797442e006dbffd750b27f2a76/UPGRADING.md#changes-to-method-not-allowed-routes
    rescue_from Grape::Exceptions::MethodNotAllowed do |e|
      error! e.message, e.status, e.headers
    end

    rescue_from Grape::Exceptions::Base do |e|
      error! e.message, e.status, e.headers
    end

    rescue_from Gitlab::Auth::TooManyIps do |e|
      rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
    end

    rescue_from :all do |exception|
      handle_api_exception(exception)
    end

    # This is a specific exception raised by `rack-timeout` gem when Puma
    # requests surpass its timeout. Given it inherits from Exception, we
    # should rescue it separately. For more info, see:
    # - https://github.com/zombocom/rack-timeout/blob/master/doc/exceptions.md
    # - https://github.com/ruby-grape/grape#exception-handling
    rescue_from Rack::Timeout::RequestTimeoutException do |exception|
      handle_api_exception(exception)
    end

    rescue_from RateLimitedService::RateLimitedError do |exception|
      exception.log_request(context.request, context.current_user)
      rack_response({ 'message' => { 'error' => exception.message } }.to_json, 429, exception.headers)
    end

    format :json
    formatter :json, Gitlab::Json::GrapeFormatter
    content_type :json, 'application/json'

    # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
    helpers ::API::Helpers
    helpers ::API::Helpers::CommonHelpers
    helpers ::API::Helpers::PerformanceBarHelpers
    helpers ::API::Helpers::RateLimiter

    namespace do
      after do
        ::Users::ActivityService.new(@current_user).execute
      end

      # Mount endpoints to include in the OpenAPI V2 documentation here
      namespace do
        # Keep in alphabetical order
        mount ::API::AccessRequests
        mount ::API::Admin::Ci::Variables
        mount ::API::Admin::InstanceClusters
        mount ::API::Admin::PlanLimits
        mount ::API::Appearance
        mount ::API::Applications
        mount ::API::Avatar
        mount ::API::Badges
        mount ::API::Branches
        mount ::API::BroadcastMessages
        mount ::API::BulkImports
        mount ::API::Ci::Jobs
        mount ::API::Ci::ResourceGroups
        mount ::API::Ci::Runner
        mount ::API::Ci::Runners
        mount ::API::Ci::Pipelines
        mount ::API::Ci::PipelineSchedules
        mount ::API::Ci::Triggers
        mount ::API::Ci::Variables
        mount ::API::Clusters::AgentTokens
        mount ::API::Clusters::Agents
        mount ::API::Commits
        mount ::API::CommitStatuses
        mount ::API::DependencyProxy
        mount ::API::DeployKeys
        mount ::API::DeployTokens
        mount ::API::Deployments
        mount ::API::Environments
        mount ::API::ErrorTracking::ClientKeys
        mount ::API::ErrorTracking::ProjectSettings
        mount ::API::FeatureFlags
        mount ::API::FeatureFlagsUserLists
        mount ::API::Features
        mount ::API::Files
        mount ::API::FreezePeriods
        mount ::API::Geo
        mount ::API::GoProxy
        mount ::API::GroupAvatar
        mount ::API::GroupClusters
        mount ::API::GroupContainerRepositories
        mount ::API::GroupExport
        mount ::API::GroupImport
        mount ::API::GroupPackages
        mount ::API::GroupVariables
        mount ::API::ImportBitbucketServer
        mount ::API::ImportGithub
        mount ::API::Integrations
        mount ::API::Invitations
        mount ::API::Keys
        mount ::API::Lint
        mount ::API::Markdown
        mount ::API::MergeRequestApprovals
        mount ::API::MergeRequestDiffs
        mount ::API::Metadata
        mount ::API::Metrics::Dashboard::Annotations
        mount ::API::Metrics::UserStarredDashboards
        mount ::API::PackageFiles
        mount ::API::PersonalAccessTokens::SelfInformation
        mount ::API::PersonalAccessTokens
        mount ::API::ProjectClusters
        mount ::API::ProjectEvents
        mount ::API::ProjectExport
        mount ::API::ProjectHooks
        mount ::API::ProjectImport
        mount ::API::ProjectRepositoryStorageMoves
        mount ::API::ProjectSnippets
        mount ::API::ProjectSnapshots
        mount ::API::ProjectStatistics
        mount ::API::ProjectTemplates
        mount ::API::ProtectedBranches
        mount ::API::ProtectedTags
        mount ::API::Releases
        mount ::API::Release::Links
        mount ::API::RemoteMirrors
        mount ::API::Repositories
        mount ::API::ResourceAccessTokens
        mount ::API::ResourceMilestoneEvents
        mount ::API::Snippets
        mount ::API::SnippetRepositoryStorageMoves
        mount ::API::Statistics
        mount ::API::Submodules
        mount ::API::Suggestions
        mount ::API::SystemHooks
        mount ::API::Tags
        mount ::API::Terraform::Modules::V1::Packages
        mount ::API::Terraform::State
        mount ::API::Terraform::StateVersion
        mount ::API::Topics
        mount ::API::Unleash
        mount ::API::UserCounts
        mount ::API::Wikis

        add_open_api_documentation!
      end

      # Keep in alphabetical order
      mount ::API::Admin::BatchedBackgroundMigrations
      mount ::API::Admin::Sidekiq
      mount ::API::AlertManagementAlerts
      mount ::API::AwardEmoji
      mount ::API::Boards
      mount ::API::Ci::JobArtifacts
      mount ::API::Ci::SecureFiles
      mount ::API::ComposerPackages
      mount ::API::ConanInstancePackages
      mount ::API::ConanProjectPackages
      mount ::API::ContainerRegistryEvent
      mount ::API::ContainerRepositories
      mount ::API::DebianGroupPackages
      mount ::API::DebianProjectPackages
      mount ::API::Discussions
      mount ::API::ErrorTracking::Collector
      mount ::API::Events
      mount ::API::GenericPackages
      mount ::API::GroupBoards
      mount ::API::GroupDebianDistributions
      mount ::API::GroupLabels
      mount ::API::GroupMilestones
      mount ::API::Groups
      mount ::API::HelmPackages
      mount ::API::Integrations::JiraConnect::Subscriptions
      mount ::API::IssueLinks
      mount ::API::Issues
      mount ::API::Labels
      mount ::API::MavenPackages
      mount ::API::Members
      mount ::API::MergeRequests
      mount ::API::Namespaces
      mount ::API::Notes
      mount ::API::NotificationSettings
      mount ::API::NpmInstancePackages
      mount ::API::NpmProjectPackages
      mount ::API::NugetGroupPackages
      mount ::API::NugetProjectPackages
      mount ::API::Pages
      mount ::API::PagesDomains
      mount ::API::ProjectContainerRepositories
      mount ::API::ProjectDebianDistributions
      mount ::API::ProjectEvents
      mount ::API::ProjectMilestones
      mount ::API::ProjectPackages
      mount ::API::Projects
      mount ::API::ProtectedTags
      mount ::API::PypiPackages
      mount ::API::ResourceLabelEvents
      mount ::API::ResourceStateEvents
      mount ::API::RpmProjectPackages
      mount ::API::RubygemPackages
      mount ::API::Search
      mount ::API::Settings
      mount ::API::SidekiqMetrics
      mount ::API::Subscriptions
      mount ::API::Tags
      mount ::API::Templates
      mount ::API::Todos
      mount ::API::UsageData
      mount ::API::UsageDataNonSqlMetrics
      mount ::API::UsageDataQueries
      mount ::API::Users
      mount ::API::Ml::Mlflow
    end

    mount ::API::Internal::Base
    mount ::API::Internal::Lfs
    mount ::API::Internal::Pages
    mount ::API::Internal::Kubernetes
    mount ::API::Internal::ErrorTracking
    mount ::API::Internal::MailRoom
    mount ::API::Internal::ContainerRegistry::Migration
    mount ::API::Internal::Workhorse

    version 'v3', using: :path do
      # Although the following endpoints are kept behind V3 namespace,
      # they're not deprecated neither should be removed when V3 get
      # removed.  They're needed as a layer to integrate with Jira
      # Development Panel.
      namespace '/', requirements: ::API::V3::Github::ENDPOINT_REQUIREMENTS do
        mount ::API::V3::Github
      end
    end

    route :any, '*path', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
      error!('404 Not Found', 404)
    end
  end
end

API::API.prepend_mod