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

module API
  class API < Grape::API::Instance
    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
                  ]

    allow_access_with_scope :api
    allow_access_with_scope :read_api, if: -> (request) { request.get? }
    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!

      Gitlab::ApplicationContext.push(
        user: -> { @current_user },
        project: -> { @project },
        namespace: -> { @group },
        caller_id: route.origin
      )
    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 :txt, "text/plain"

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

    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::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::DeployKeys
      mount ::API::DeployTokens
      mount ::API::Deployments
      mount ::API::Environments
      mount ::API::ErrorTracking
      mount ::API::Events
      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::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::NugetPackages
      mount ::API::PypiPackages
      mount ::API::ComposerPackages
      mount ::API::ConanPackages
      mount ::API::MavenPackages
      mount ::API::NpmPackages
      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::ProtectedBranches
      mount ::API::ProtectedTags
      mount ::API::Releases
      mount ::API::Release::Links
      mount ::API::RemoteMirrors
      mount ::API::Repositories
      mount ::API::Search
      mount ::API::Services
      mount ::API::Settings
      mount ::API::SidekiqMetrics
      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::UserCounts
      mount ::API::Users
      mount ::API::Variables
      mount ::API::Version
      mount ::API::Wikis
    end

    mount ::API::Internal::Base
    mount ::API::Internal::Pages

    route :any, '*path' do
      error!('404 Not Found', 404)
    end
  end
end

API::API.prepend_if_ee('::EE::API::API')