summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api_guard.rb107
-rw-r--r--lib/api/branches.rb4
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb97
-rw-r--r--lib/api/groups.rb14
-rw-r--r--lib/api/helpers.rb24
-rw-r--r--lib/api/helpers/custom_validators.rb1
-rw-r--r--lib/api/helpers/internal_helpers.rb8
-rw-r--r--lib/api/helpers/pagination.rb10
-rw-r--r--lib/api/helpers/runner.rb1
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/merge_requests.rb6
-rw-r--r--lib/api/namespaces.rb10
-rw-r--r--lib/api/notes.rb9
-rw-r--r--lib/api/projects.rb8
-rw-r--r--lib/api/projects_relation_builder.rb34
-rw-r--r--lib/api/protected_branches.rb4
-rw-r--r--lib/api/runner.rb10
-rw-r--r--lib/api/runners.rb27
-rw-r--r--lib/api/settings.rb16
-rw-r--r--lib/api/snippets.rb1
-rw-r--r--lib/api/users.rb5
-rw-r--r--lib/api/v3/commits.rb2
-rw-r--r--lib/api/v3/entities.rb4
-rw-r--r--lib/api/v3/runners.rb1
-rw-r--r--lib/api/v3/settings.rb8
-rw-r--r--lib/api/v3/snippets.rb2
27 files changed, 268 insertions, 151 deletions
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index c1c0d344917..9aeebc34525 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -6,9 +6,6 @@ module API
module APIGuard
extend ActiveSupport::Concern
- PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN".freeze
- PRIVATE_TOKEN_PARAM = :private_token
-
included do |base|
# OAuth2 Resource Server Authentication
use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
@@ -42,7 +39,7 @@ module API
# Helper Methods for Grape Endpoint
module HelperMethods
- include Gitlab::Utils::StrongMemoize
+ include Gitlab::Auth::UserAuthFinders
def find_current_user!
user = find_user_from_access_token || find_user_from_warden
@@ -53,76 +50,8 @@ module API
user
end
- def access_token
- strong_memoize(:access_token) do
- find_oauth_access_token || find_personal_access_token
- end
- end
-
- def validate_access_token!(scopes: [])
- return unless access_token
-
- case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
- when AccessTokenValidationService::INSUFFICIENT_SCOPE
- raise InsufficientScopeError.new(scopes)
- when AccessTokenValidationService::EXPIRED
- raise ExpiredError
- when AccessTokenValidationService::REVOKED
- raise RevokedError
- end
- end
-
private
- def find_user_from_access_token
- return unless access_token
-
- validate_access_token!
-
- access_token.user || raise(UnauthorizedError)
- end
-
- # Check the Rails session for valid authentication details
- def find_user_from_warden
- warden.try(:authenticate) if verified_request?
- end
-
- def warden
- env['warden']
- end
-
- # Check if the request is GET/HEAD, or if CSRF token is valid.
- def verified_request?
- Gitlab::RequestForgeryProtection.verified?(env)
- end
-
- def find_oauth_access_token
- token = Doorkeeper::OAuth::Token.from_request(doorkeeper_request, *Doorkeeper.configuration.access_token_methods)
- return unless token
-
- # Expiration, revocation and scopes are verified in `find_user_by_access_token`
- access_token = OauthAccessToken.by_token(token)
- raise UnauthorizedError unless access_token
-
- access_token.revoke_previous_refresh_token!
- access_token
- end
-
- def find_personal_access_token
- token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
- return unless token.present?
-
- # Expiration, revocation and scopes are verified in `find_user_by_access_token`
- access_token = PersonalAccessToken.find_by(token: token)
- raise UnauthorizedError unless access_token
-
- access_token
- end
-
- def doorkeeper_request
- @doorkeeper_request ||= ActionDispatch::Request.new(env)
- end
-
# An array of scopes that were registered (using `allow_access_with_scope`)
# for the current endpoint class. It also returns scopes registered on
# `API::API`, since these are meant to apply to all API routes.
@@ -145,8 +74,11 @@ module API
private
def install_error_responders(base)
- error_classes = [MissingTokenError, TokenNotFoundError,
- ExpiredError, RevokedError, InsufficientScopeError]
+ error_classes = [Gitlab::Auth::MissingTokenError,
+ Gitlab::Auth::TokenNotFoundError,
+ Gitlab::Auth::ExpiredError,
+ Gitlab::Auth::RevokedError,
+ Gitlab::Auth::InsufficientScopeError]
base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -155,25 +87,25 @@ module API
proc do |e|
response =
case e
- when MissingTokenError
+ when Gitlab::Auth::MissingTokenError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
- when TokenNotFoundError
+ when Gitlab::Auth::TokenNotFoundError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Bad Access Token.")
- when ExpiredError
+ when Gitlab::Auth::ExpiredError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Token is expired. You can either do re-authorization or token refresh.")
- when RevokedError
+ when Gitlab::Auth::RevokedError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Token was revoked. You have to re-authorize from the user.")
- when InsufficientScopeError
+ when Gitlab::Auth::InsufficientScopeError
# FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
# does not include WWW-Authenticate header, which breaks the standard.
Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
@@ -186,22 +118,5 @@ module API
end
end
end
-
- #
- # Exceptions
- #
-
- MissingTokenError = Class.new(StandardError)
- TokenNotFoundError = Class.new(StandardError)
- ExpiredError = Class.new(StandardError)
- RevokedError = Class.new(StandardError)
- UnauthorizedError = Class.new(StandardError)
-
- class InsufficientScopeError < StandardError
- attr_reader :scopes
- def initialize(scopes)
- @scopes = scopes.map { |s| s.try(:name) || s }
- end
- end
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index cdef1b546a9..0791a110c39 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -81,9 +81,9 @@ module API
service_args = [user_project, current_user, protected_branch_params]
protected_branch = if protected_branch
- ::ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch)
+ ::ProtectedBranches::LegacyApiUpdateService.new(*service_args).execute(protected_branch)
else
- ::ProtectedBranches::ApiCreateService.new(*service_args).execute
+ ::ProtectedBranches::LegacyApiCreateService.new(*service_args).execute
end
if protected_branch.valid?
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 2bc4039b019..38e05074353 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -180,10 +180,12 @@ module API
if params[:path]
commit.raw_diffs(limits: false).each do |diff|
next unless diff.new_path == params[:path]
+
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line|
next unless line.new_pos == params[:line] && line.type == params[:line_type]
+
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 16ae99b5c6c..62ee20bf7de 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -80,16 +80,37 @@ module API
expose :group_access, as: :group_access_level
end
- class BasicProjectDetails < Grape::Entity
- expose :id, :description, :default_branch, :tag_list
- expose :ssh_url_to_repo, :http_url_to_repo, :web_url
+ class ProjectIdentity < Grape::Entity
+ expose :id, :description
expose :name, :name_with_namespace
expose :path, :path_with_namespace
+ expose :created_at
+ end
+
+ class BasicProjectDetails < ProjectIdentity
+ include ::API::ProjectsRelationBuilder
+
+ expose :default_branch
+ # Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
+ expose :tag_list do |project|
+ # project.tags.order(:name).pluck(:name) is the most suitable option
+ # to avoid loading all the ActiveRecord objects but, if we use it here
+ # it override the preloaded associations and makes a query
+ # (fixed in https://github.com/rails/rails/pull/25976).
+ project.tags.map(&:name).sort
+ end
+ expose :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :avatar_url do |project, options|
project.avatar_url(only_path: false)
end
expose :star_count, :forks_count
- expose :created_at, :last_activity_at
+ expose :last_activity_at
+
+ def self.preload_relation(projects_relation, options = {})
+ projects_relation.preload(:project_feature, :route)
+ .preload(namespace: [:route, :owner],
+ tags: :taggings)
+ end
end
class Project < BasicProjectDetails
@@ -141,7 +162,7 @@ module API
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
- expose :namespace, using: 'API::Entities::Namespace'
+ expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
@@ -151,7 +172,7 @@ module API
expose :public_builds, as: :public_jobs
expose :ci_config_path
expose :shared_with_groups do |project, options|
- SharedGroup.represent(project.project_group_links.all, options)
+ SharedGroup.represent(project.project_group_links, options)
end
expose :only_allow_merge_if_pipeline_succeeds
expose :request_access_enabled
@@ -159,6 +180,18 @@ module API
expose :printing_merge_request_link_enabled
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+
+ def self.preload_relation(projects_relation, options = {})
+ super(projects_relation).preload(:group)
+ .preload(project_group_links: :group,
+ fork_network: :root_project,
+ forked_project_link: :forked_from_project,
+ forked_from_project: [:route, :forks, namespace: :route, tags: :taggings])
+ end
+
+ def self.forks_counting_projects(projects_relation)
+ projects_relation + projects_relation.map(&:forked_from_project).compact
+ end
end
class ProjectStatistics < Grape::Entity
@@ -242,7 +275,11 @@ module API
end
expose :merged do |repo_branch, options|
- options[:project].repository.merged_to_root_ref?(repo_branch, options[:merged_branch_names])
+ if options[:merged_branch_names]
+ options[:merged_branch_names].include?(repo_branch.name)
+ else
+ options[:project].repository.merged_to_root_ref?(repo_branch)
+ end
end
expose :protected do |repo_branch, options|
@@ -609,9 +646,11 @@ module API
expose :created_at
end
- class Namespace < Grape::Entity
+ class NamespaceBasic < Grape::Entity
expose :id, :name, :path, :kind, :full_path, :parent_id
+ end
+ class Namespace < NamespaceBasic
expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
namespace.users_with_descendants.count
end
@@ -671,7 +710,7 @@ module API
if options.key?(:project_members)
(options[:project_members] || []).find { |member| member.source_id == project.id }
else
- project.project_members.find_by(user_id: options[:current_user].id)
+ project.project_member(options[:current_user])
end
end
@@ -680,11 +719,25 @@ module API
if options.key?(:group_members)
(options[:group_members] || []).find { |member| member.source_id == project.namespace_id }
else
- project.group.group_members.find_by(user_id: options[:current_user].id)
+ project.group.group_member(options[:current_user])
end
end
end
end
+
+ def self.preload_relation(projects_relation, options = {})
+ relation = super(projects_relation, options)
+
+ unless options.key?(:group_members)
+ relation = relation.preload(group: [group_members: [:source, user: [notification_settings: :source]]])
+ end
+
+ unless options.key?(:project_members)
+ relation = relation.preload(project_members: [:source, user: [notification_settings: :source]])
+ end
+
+ relation
+ end
end
class LabelBasic < Grape::Entity
@@ -763,7 +816,10 @@ module API
expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) }
expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
- expose :password_authentication_enabled, as: :signin_enabled
+
+ # support legacy names, can be removed in v5
+ expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
+ expose :password_authentication_enabled_for_web, as: :signin_enabled
end
class Release < Grape::Entity
@@ -820,17 +876,24 @@ module API
expose :id, :sha, :ref, :status
end
- class Job < Grape::Entity
+ class JobBasic < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
expose :duration
expose :user, with: User
- expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
expose :commit, with: Commit
- expose :runner, with: Runner
expose :pipeline, with: PipelineBasic
end
+ class Job < JobBasic
+ expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
+ expose :runner, with: Runner
+ end
+
+ class JobBasicWithProject < JobBasic
+ expose :project, with: ProjectIdentity
+ end
+
class Trigger < Grape::Entity
expose :id
expose :token, :description
@@ -987,13 +1050,9 @@ module API
expose :type, :url, :username, :password
end
- class ArtifactFile < Grape::Entity
- expose :filename, :size
- end
-
class Dependency < Grape::Entity
expose :id, :name, :token
- expose :artifacts_file, using: ArtifactFile, if: ->(job, _) { job.artifacts? }
+ expose :artifacts_file, using: JobArtifactFile, if: ->(job, _) { job.artifacts? }
end
class Response < Grape::Entity
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index bcf2e6dae1d..b81f07a1770 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -52,6 +52,13 @@ module API
groups
end
+ def find_group_projects(params)
+ group = find_group!(params[:id])
+ projects = GroupProjectsFinder.new(group: group, current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
+ paginate(projects)
+ end
+
def present_groups(params, groups)
options = {
with: Entities::Group,
@@ -170,11 +177,10 @@ module API
use :pagination
end
get ":id/projects" do
- group = find_group!(params[:id])
- projects = GroupProjectsFinder.new(group: group, current_user: current_user, params: project_finder_params).execute
- projects = reorder_projects(projects)
+ projects = find_group_projects(params)
entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project
- present paginate(projects), with: entity, current_user: current_user
+
+ present entity.prepare_relation(projects), with: entity, current_user: current_user
end
desc 'Get a list of subgroups in this group.' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3c8960cb1ab..686bf7a3c2b 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -50,6 +50,10 @@ module API
initial_current_user != current_user
end
+ def user_namespace
+ @user_namespace ||= find_namespace!(params[:id])
+ end
+
def user_group
@group ||= find_group!(params[:id])
end
@@ -112,6 +116,24 @@ module API
end
end
+ def find_namespace(id)
+ if id.to_s =~ /^\d+$/
+ Namespace.find_by(id: id)
+ else
+ Namespace.find_by_full_path(id)
+ end
+ end
+
+ def find_namespace!(id)
+ namespace = find_namespace(id)
+
+ if can?(current_user, :read_namespace, namespace)
+ namespace
+ else
+ not_found!('Namespace')
+ end
+ end
+
def find_project_label(id)
label = available_labels.find_by_id(id) || available_labels.find_by_title(id)
label || not_found!('Label')
@@ -398,7 +420,7 @@ module API
begin
@initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user! }
- rescue APIGuard::UnauthorizedError
+ rescue Gitlab::Auth::UnauthorizedError
unauthorized!
end
end
diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb
index 0a8f3073a50..dd4f6c41131 100644
--- a/lib/api/helpers/custom_validators.rb
+++ b/lib/api/helpers/custom_validators.rb
@@ -4,6 +4,7 @@ module API
class Absence < Grape::Validations::Base
def validate_param!(attr_name, params)
return if params.respond_to?(:key?) && !params.key?(attr_name)
+
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:absence)
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 4b3c473b0bb..d6dea4c30e3 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -2,8 +2,8 @@ module API
module Helpers
module InternalHelpers
SSH_GITALY_FEATURES = {
- 'git-receive-pack' => :ssh_receive_pack,
- 'git-upload-pack' => :ssh_upload_pack
+ 'git-receive-pack' => [:ssh_receive_pack, Gitlab::GitalyClient::MigrationStatus::OPT_IN],
+ 'git-upload-pack' => [:ssh_upload_pack, Gitlab::GitalyClient::MigrationStatus::OPT_OUT]
}.freeze
def wiki?
@@ -102,8 +102,8 @@ module API
# Return the Gitaly Address if it is enabled
def gitaly_payload(action)
- feature = SSH_GITALY_FEATURES[action]
- return unless feature && Gitlab::GitalyClient.feature_enabled?(feature)
+ feature, status = SSH_GITALY_FEATURES[action]
+ return unless feature && Gitlab::GitalyClient.feature_enabled?(feature, status: status)
{
repository: repository.gitaly_repository,
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 95108292aac..bb70370ba77 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -2,6 +2,8 @@ module API
module Helpers
module Pagination
def paginate(relation)
+ relation = add_default_order(relation)
+
relation.page(params[:page]).per(params[:per_page]).tap do |data|
add_pagination_headers(data)
end
@@ -45,6 +47,14 @@ module API
# Ensure there is in total at least 1 page
[paginated_data.total_pages, 1].max
end
+
+ def add_default_order(relation)
+ if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty?
+ relation = relation.order(:id)
+ end
+
+ relation
+ end
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 282af32ca94..2cae53dba53 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -14,6 +14,7 @@ module API
def get_runner_version_from_params
return unless params['info'].present?
+
attributes_for_keys(%w(name version revision platform architecture), params['info'])
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 74dfd9f96de..e60e00d7956 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -255,7 +255,9 @@ module API
authorize!(:destroy_issue, issue)
- destroy_conditionally!(issue)
+ destroy_conditionally!(issue) do |issue|
+ Issuable::DestroyService.new(user_project, current_user).execute(issue)
+ end
end
desc 'List merge requests closing issue' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 726f09e3669..d34886fca2e 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -21,7 +21,7 @@ module API
return merge_requests if args[:view] == 'simple'
merge_requests
- .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels, :timelogs)
+ .preload(:notes, :author, :assignee, :milestone, :latest_merge_request_diff, :labels, :timelogs)
end
params :merge_requests_params do
@@ -167,7 +167,9 @@ module API
authorize!(:destroy_merge_request, merge_request)
- destroy_conditionally!(merge_request)
+ destroy_conditionally!(merge_request) do |merge_request|
+ Issuable::DestroyService.new(user_project, current_user).execute(merge_request)
+ end
end
params do
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index f1eaff6b0eb..32b77aedba8 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -19,6 +19,16 @@ module API
present paginate(namespaces), with: Entities::Namespace, current_user: current_user
end
+
+ desc 'Get a namespace by ID' do
+ success Entities::Namespace
+ end
+ params do
+ requires :id, type: String, desc: "Namespace's ID or path"
+ end
+ get ':id' do
+ present user_namespace, with: Entities::Namespace, current_user: current_user
+ end
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0b9ab4eeb05..3588dc85c9e 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -18,6 +18,10 @@ module API
end
params do
requires :noteable_id, type: Integer, desc: 'The ID of the noteable'
+ optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
+ desc: 'Return notes ordered by `created_at` or `updated_at` fields.'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return notes sorted in `asc` or `desc` order.'
use :pagination
end
get ":id/#{noteables_str}/:noteable_id/notes" do
@@ -29,11 +33,12 @@ module API
# at the DB query level (which we cannot in that case), the current
# page can have less elements than :per_page even if
# there's more than one page.
+ raw_notes = noteable.notes.with_metadata.reorder(params[:order_by] => params[:sort])
notes =
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
# array returned, but this is really a edge-case.
- paginate(noteable.notes)
+ paginate(raw_notes)
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: Entities::Note
else
@@ -50,7 +55,7 @@ module API
end
get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do
noteable = find_project_noteable(noteables_str, params[:noteable_id])
- note = noteable.notes.find(params[:note_id])
+ note = noteable.notes.with_metadata.find(params[:note_id])
can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user)
if can_read_note
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 4cd7e714aa2..14a4fc6f025 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -79,11 +79,11 @@ module API
projects = projects.with_statistics if params[:statistics]
projects = projects.with_issues_enabled if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+ projects = paginate(projects)
if current_user
- projects = projects.includes(:route, :taggings, namespace: :route)
- project_members = current_user.project_members
- group_members = current_user.group_members
+ project_members = current_user.project_members.preload(:source, user: [notification_settings: :source])
+ group_members = current_user.group_members.preload(:source, user: [notification_settings: :source])
end
options = options.reverse_merge(
@@ -95,7 +95,7 @@ module API
)
options[:with] = Entities::BasicProjectDetails if params[:simple]
- present paginate(projects), options
+ present options[:with].prepare_relation(projects, options), options
end
end
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
new file mode 100644
index 00000000000..6482fd94ab8
--- /dev/null
+++ b/lib/api/projects_relation_builder.rb
@@ -0,0 +1,34 @@
+module API
+ module ProjectsRelationBuilder
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def prepare_relation(projects_relation, options = {})
+ projects_relation = preload_relation(projects_relation, options)
+ execute_batch_counting(projects_relation)
+ projects_relation
+ end
+
+ def preload_relation(projects_relation, options = {})
+ projects_relation
+ end
+
+ def forks_counting_projects(projects_relation)
+ projects_relation
+ end
+
+ def batch_forks_counting(projects_relation)
+ ::Projects::BatchForksCountService.new(forks_counting_projects(projects_relation)).refresh_cache
+ end
+
+ def batch_open_issues_counting(projects_relation)
+ ::Projects::BatchOpenIssuesCountService.new(projects_relation).refresh_cache
+ end
+
+ def execute_batch_counting(projects_relation)
+ batch_forks_counting(projects_relation)
+ batch_open_issues_counting(projects_relation)
+ end
+ end
+ end
+end
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 15fcb9e8e27..b5021e8a712 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -40,10 +40,10 @@ module API
params do
requires :name, type: String, desc: 'The name of the protected branch'
optional :push_access_level, type: Integer, default: Gitlab::Access::MASTER,
- values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS,
+ values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to push (defaults: `40`, master access level)'
optional :merge_access_level, type: Integer, default: Gitlab::Access::MASTER,
- values: ProtectedBranchAccess::ALLOWED_ACCESS_LEVELS,
+ values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to merge (defaults: `40`, master access level)'
end
post ':id/protected_branches' do
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index a3987c560dd..80feb629d54 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -215,18 +215,20 @@ module API
job = authenticate_job!
forbidden!('Job is not running!') unless job.running?
- artifacts_upload_path = ArtifactUploader.artifacts_upload_path
+ artifacts_upload_path = JobArtifactUploader.artifacts_upload_path
artifacts = uploaded_file(:file, artifacts_upload_path)
metadata = uploaded_file(:metadata, artifacts_upload_path)
bad_request!('Missing artifacts file!') unless artifacts
file_to_large! unless artifacts.size < max_artifacts_size
- job.artifacts_file = artifacts
- job.artifacts_metadata = metadata
- job.artifacts_expire_in = params['expire_in'] ||
+ expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
+ job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, expire_in: expire_in)
+ job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata
+ job.artifacts_expire_in = expire_in
+
if job.save
present job, with: Entities::JobRequest::Response
else
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index d3559ef71be..996457c5dfe 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -84,6 +84,23 @@ module API
destroy_conditionally!(runner)
end
+
+ desc 'List jobs running on a runner' do
+ success Entities::JobBasicWithProject
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the runner'
+ optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES
+ use :pagination
+ end
+ get ':id/jobs' do
+ runner = get_runner(params[:id])
+ authenticate_list_runners_jobs!(runner)
+
+ jobs = RunnerJobsFinder.new(runner, params).execute
+
+ present paginate(jobs), with: Entities::JobBasicWithProject
+ end
end
params do
@@ -165,17 +182,20 @@ module API
def authenticate_show_runner!(runner)
return if runner.is_shared || current_user.admin?
+
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_update_runner!(runner)
return if current_user.admin?
+
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
def authenticate_delete_runner!(runner)
return if current_user.admin?
+
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
@@ -185,6 +205,13 @@ module API
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner is locked") if runner.locked?
return if current_user.admin?
+
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def authenticate_list_runners_jobs!(runner)
+ return if current_user.admin?
+
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 851b226e9e5..cee4d309816 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -44,9 +44,11 @@ module API
requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
end
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
- mutually_exclusive :password_authentication_enabled, :signin_enabled
+ optional :password_authentication_enabled_for_web, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5
+ mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled
+ optional :password_authentication_enabled_for_git, type: Boolean, desc: 'Flag indicating if password authentication is enabled for Git over HTTP(S)'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
@@ -121,6 +123,9 @@ module API
end
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
+ optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
+ optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
@@ -135,8 +140,11 @@ module API
put "application/settings" do
attrs = declared_params(include_missing: false)
+ # support legacy names, can be removed in v5
if attrs.has_key?(:signin_enabled)
- attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled)
+ elsif attrs.has_key?(:password_authentication_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
if current_settings.update_attributes(attrs)
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 00eb7c60f16..c736cc32021 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -95,6 +95,7 @@ module API
put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :update_personal_snippet, snippet
attrs = declared_params(include_missing: false).merge(request: request, api: true)
diff --git a/lib/api/users.rb b/lib/api/users.rb
index d80b364bd09..e5de31ad51b 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -31,7 +31,6 @@ module API
optional :location, type: String, desc: 'The location of the user'
optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
- optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
optional :avatar, type: File, desc: 'Avatar image for user'
all_or_none_of :extern_uid, :provider
@@ -77,6 +76,8 @@ module API
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
+ users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
+
present paginate(users), with: entity
end
@@ -101,6 +102,7 @@ module API
requires :email, type: String, desc: 'The email of the user'
optional :password, type: String, desc: 'The password of the new user'
optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
+ optional :skip_confirmation, type: Boolean, desc: 'Flag indicating the account is confirmed'
at_least_one_of :password, :reset_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
@@ -134,6 +136,7 @@ module API
requires :id, type: Integer, desc: 'The ID of the user'
optional :email, type: String, desc: 'The email of the user'
optional :password, type: String, desc: 'The password of the new user'
+ optional :skip_reconfirmation, type: Boolean, desc: 'Flag indicating the account skips the confirmation by email'
optional :name, type: String, desc: 'The name of the user'
optional :username, type: String, desc: 'The username of the user'
use :optional_attributes
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index be360fbfc0c..0ef26aa696a 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -169,10 +169,12 @@ module API
if params[:path]
commit.raw_diffs(limits: false).each do |diff|
next unless diff.new_path == params[:path]
+
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line|
next unless line.new_pos == params[:line] && line.type == params[:line_type]
+
break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos)
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index afdd7b83998..c17b6f45ed8 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -172,8 +172,8 @@ module API
expose :id
expose :default_projects_limit
expose :signup_enabled
- expose :password_authentication_enabled
- expose :password_authentication_enabled, as: :signin_enabled
+ expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
+ expose :password_authentication_enabled_for_web, as: :signin_enabled
expose :gravatar_enabled
expose :sign_in_text
expose :after_sign_up_text
diff --git a/lib/api/v3/runners.rb b/lib/api/v3/runners.rb
index faa265f3314..c6d9957d452 100644
--- a/lib/api/v3/runners.rb
+++ b/lib/api/v3/runners.rb
@@ -51,6 +51,7 @@ module API
helpers do
def authenticate_delete_runner!(runner)
return if current_user.admin?
+
forbidden!("Runner is shared") if runner.is_shared?
forbidden!("Runner associated with more than one project") if runner.projects.count > 1
forbidden!("No access granted") unless user_can_access_runner?(runner)
diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb
index 202011cfcbe..9b4ab7630fb 100644
--- a/lib/api/v3/settings.rb
+++ b/lib/api/v3/settings.rb
@@ -44,8 +44,8 @@ module API
requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
end
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface'
mutually_exclusive :password_authentication_enabled, :signin_enabled
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
@@ -131,7 +131,9 @@ module API
attrs = declared_params(include_missing: false)
if attrs.has_key?(:signin_enabled)
- attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled)
+ elsif attrs.has_key?(:password_authentication_enabled)
+ attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
if current_settings.update_attributes(attrs)
diff --git a/lib/api/v3/snippets.rb b/lib/api/v3/snippets.rb
index 0762fc02d70..126ec72248e 100644
--- a/lib/api/v3/snippets.rb
+++ b/lib/api/v3/snippets.rb
@@ -91,6 +91,7 @@ module API
put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :update_personal_snippet, snippet
attrs = declared_params(include_missing: false)
@@ -113,6 +114,7 @@ module API
delete ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
return not_found!('Snippet') unless snippet
+
authorize! :destroy_personal_snippet, snippet
snippet.destroy
no_content!