summaryrefslogtreecommitdiff
path: root/lib/api/api.rb
blob: f83a36068ddaa16524121b2475e5d41571c04f13 (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
# frozen_string_literal: true

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

    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$/]

    insert_before Grape::Middleware::Error,
                  GrapeLogging::Middleware::RequestLogger,
                  logger: Logger.new(LOG_FILENAME),
                  formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
                  include: [
                    GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS),
                    Gitlab::GrapeLogging::Loggers::ClientEnvLogger.new,
                    Gitlab::GrapeLogging::Loggers::RouteLogger.new,
                    Gitlab::GrapeLogging::Loggers::UserLogger.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
                  ]

    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'
    end

    before do
      coerce_nil_params_to_array!

      api_endpoint = env['api.endpoint']
      feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s

      header[Gitlab::Metrics::RequestsRackMiddleware::FEATURE_CATEGORY_HEADER] = feature_category

      Gitlab::ApplicationContext.push(
        user: -> { @current_user },
        project: -> { @project },
        namespace: -> { @group },
        runner: -> { @current_runner || @runner },
        caller_id: route.origin,
        remote_ip: request.ip,
        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

    # 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/sharpstone/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

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

    # Remove the `text/plain+deprecated` with `api_always_use_application_json` feature flag
    # There is a small chance some users depend on the old behavior.
    # We this change under a feature flag to see if affects GitLab.com users.
    # The `+deprecated` is added to distinguish content type
    # as defined by `API::API` vs ex. `API::Repositories`
    content_type :txt, 'text/plain+deprecated'

    before do
      # the feature flag workaround is only for `.txt`
      api_format = env[Grape::Env::API_FORMAT]
      next unless api_format == :txt

      # get all defined content-types for the endpoint
      api_endpoint = env[Grape::Env::API_ENDPOINT]
      content_types = api_endpoint&.namespace_stackable_with_hash(:content_types).to_h

      # Only overwrite `text/plain+deprecated`
      if content_types[api_format] == 'text/plain+deprecated'
        if Feature.enabled?(:api_always_use_application_json, default_enabled: :yaml)
          content_type 'application/json'
        else
          content_type 'text/plain'
        end
      end
    end

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

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

      # Keep in alphabetical order
      mount ::API::AccessRequests
      mount ::API::Admin::Ci::Variables
      mount ::API::Admin::InstanceClusters
      mount ::API::Admin::PlanLimits
      mount ::API::Admin::Sidekiq
      mount ::API::Appearance
      mount ::API::Applications
      mount ::API::Avatar
      mount ::API::AwardEmoji
      mount ::API::Badges
      mount ::API::Boards
      mount ::API::Branches
      mount ::API::BroadcastMessages
      mount ::API::Ci::Pipelines
      mount ::API::Ci::PipelineSchedules
      mount ::API::Ci::Runner
      mount ::API::Ci::Runners
      mount ::API::Commits
      mount ::API::CommitStatuses
      mount ::API::ContainerRegistryEvent
      mount ::API::ContainerRepositories
      mount ::API::DependencyProxy
      mount ::API::DeployKeys
      mount ::API::DeployTokens
      mount ::API::Deployments
      mount ::API::Environments
      mount ::API::ErrorTracking
      mount ::API::Events
      mount ::API::FeatureFlags
      mount ::API::FeatureFlagScopes
      mount ::API::FeatureFlagsUserLists
      mount ::API::Features
      mount ::API::Files
      mount ::API::FreezePeriods
      mount ::API::GroupBoards
      mount ::API::GroupClusters
      mount ::API::GroupExport
      mount ::API::GroupImport
      mount ::API::GroupLabels
      mount ::API::GroupMilestones
      mount ::API::Groups
      mount ::API::GroupContainerRepositories
      mount ::API::GroupVariables
      mount ::API::ImportBitbucketServer
      mount ::API::ImportGithub
      mount ::API::IssueLinks
      mount ::API::Invitations
      mount ::API::Issues
      mount ::API::JobArtifacts
      mount ::API::Jobs
      mount ::API::Keys
      mount ::API::Labels
      mount ::API::Lint
      mount ::API::Markdown
      mount ::API::Members
      mount ::API::MergeRequestDiffs
      mount ::API::MergeRequests
      mount ::API::MergeRequestApprovals
      mount ::API::Metrics::Dashboard::Annotations
      mount ::API::Metrics::UserStarredDashboards
      mount ::API::Namespaces
      mount ::API::Notes
      mount ::API::Discussions
      mount ::API::ResourceLabelEvents
      mount ::API::ResourceMilestoneEvents
      mount ::API::ResourceStateEvents
      mount ::API::NotificationSettings
      mount ::API::ProjectPackages
      mount ::API::GroupPackages
      mount ::API::PackageFiles
      mount ::API::NugetProjectPackages
      mount ::API::NugetGroupPackages
      mount ::API::PypiPackages
      mount ::API::ComposerPackages
      mount ::API::ConanProjectPackages
      mount ::API::ConanInstancePackages
      mount ::API::DebianGroupPackages
      mount ::API::DebianProjectPackages
      mount ::API::MavenPackages
      mount ::API::NpmProjectPackages
      mount ::API::NpmInstancePackages
      mount ::API::GenericPackages
      mount ::API::GoProxy
      mount ::API::Pages
      mount ::API::PagesDomains
      mount ::API::ProjectClusters
      mount ::API::ProjectContainerRepositories
      mount ::API::ProjectEvents
      mount ::API::ProjectExport
      mount ::API::ProjectImport
      mount ::API::ProjectHooks
      mount ::API::ProjectMilestones
      mount ::API::ProjectRepositoryStorageMoves
      mount ::API::Projects
      mount ::API::ProjectSnapshots
      mount ::API::ProjectSnippets
      mount ::API::ProjectStatistics
      mount ::API::ProjectTemplates
      mount ::API::Terraform::State
      mount ::API::Terraform::StateVersion
      mount ::API::PersonalAccessTokens
      mount ::API::ProtectedBranches
      mount ::API::ProtectedTags
      mount ::API::Releases
      mount ::API::Release::Links
      mount ::API::RemoteMirrors
      mount ::API::Repositories
      mount ::API::ResourceAccessTokens
      mount ::API::RubygemPackages
      mount ::API::Search
      mount ::API::Services
      mount ::API::Settings
      mount ::API::SidekiqMetrics
      mount ::API::SnippetRepositoryStorageMoves
      mount ::API::Snippets
      mount ::API::Statistics
      mount ::API::Submodules
      mount ::API::Subscriptions
      mount ::API::Suggestions
      mount ::API::SystemHooks
      mount ::API::Tags
      mount ::API::Templates
      mount ::API::Todos
      mount ::API::Triggers
      mount ::API::Unleash
      mount ::API::UsageData
      mount ::API::UserCounts
      mount ::API::Users
      mount ::API::Variables
      mount ::API::Version
      mount ::API::Wikis
    end

    mount ::API::Internal::Base
    mount ::API::Internal::Lfs
    mount ::API::Internal::Pages
    mount ::API::Internal::Kubernetes

    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
      error!('404 Not Found', 404)
    end
  end
end

API::API.prepend_ee_mod