From 905c1110b08f93a19661cf42a276c7ea90d0a0ff Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 22 Oct 2019 11:31:16 +0000 Subject: Add latest changes from gitlab-org/gitlab@12-4-stable-ee --- lib/api/api_guard.rb | 24 +++++++++ lib/api/commit_statuses.rb | 20 ++++++-- lib/api/commits.rb | 9 ++-- lib/api/deploy_keys.rb | 18 ++++--- lib/api/deployments.rb | 82 ++++++++++++++++++++++++++++++ lib/api/entities.rb | 84 +++++++++++++++++++++++++------ lib/api/group_labels.rb | 52 ++++++++++++++++--- lib/api/helpers.rb | 2 +- lib/api/helpers/graphql_helpers.rb | 2 +- lib/api/helpers/groups_helpers.rb | 10 ++-- lib/api/helpers/label_helpers.rb | 46 ++++++++++++++++- lib/api/helpers/runner.rb | 5 +- lib/api/internal/base.rb | 76 ++++++++++++---------------- lib/api/internal/pages.rb | 9 +++- lib/api/issues.rb | 3 +- lib/api/labels.rb | 70 +++++++++++++++++++------- lib/api/members.rb | 21 ++++++++ lib/api/notes.rb | 2 +- lib/api/project_container_repositories.rb | 12 +++-- lib/api/project_import.rb | 2 + lib/api/protected_branches.rb | 4 +- lib/api/runner.rb | 8 +-- lib/api/settings.rb | 2 + lib/api/todos.rb | 2 +- lib/api/users.rb | 38 ++++++++++++++ lib/api/version.rb | 5 +- 26 files changed, 487 insertions(+), 121 deletions(-) (limited to 'lib/api') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index a3fa7cd5cf9..02ea321df67 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -17,6 +17,8 @@ module API request.access_token end + use AdminModeMiddleware + helpers HelperMethods install_error_responders(base) @@ -52,6 +54,11 @@ module API forbidden!(api_access_denied_message(user)) end + # Set admin mode for API requests (if admin) + if Feature.enabled?(:user_mode_in_session) + Gitlab::Auth::CurrentUserMode.new(user).enable_admin_mode!(skip_password_validation: true) + end + user end @@ -141,5 +148,22 @@ module API end end end + + class AdminModeMiddleware < ::Grape::Middleware::Base + def initialize(app, **options) + super + end + + def call(env) + if Feature.enabled?(:user_mode_in_session) + session = {} + Gitlab::Session.with_session(session) do + app.call(env) + end + else + app.call(env) + end + end + end end end diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index d58a5e214ed..d108c811f4b 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -58,7 +58,6 @@ module API post ':id/statuses/:sha' do authorize! :create_commit_status, user_project - commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit # Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline) @@ -68,14 +67,15 @@ module API # If we don't receive it, we will attach the CommitStatus to # the first found branch on that commit + pipeline = all_matching_pipelines.first + ref = params[:ref] + ref ||= pipeline&.ref ref ||= @project.repository.branch_names_contains(commit.sha).first not_found! 'References for commit' unless ref name = params[:name] || params[:context] || 'default' - pipeline = @project.pipeline_for(ref, commit.sha, params[:pipeline_id]) - unless pipeline pipeline = @project.ci_pipelines.create!( source: :external, @@ -126,6 +126,20 @@ module API end end # rubocop: enable CodeReuse/ActiveRecord + helpers do + def commit + strong_memoize(:commit) do + user_project.commit(params[:sha]) + end + end + + def all_matching_pipelines + pipelines = user_project.ci_pipelines.newest_first(sha: commit.sha) + pipelines = pipelines.for_ref(params[:ref]) if params[:ref] + pipelines = pipelines.for_id(params[:pipeline_id]) if params[:pipeline_id] + pipelines + end + end end end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index a2f3e87ebd2..ffff40141de 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -37,6 +37,7 @@ module API optional :path, type: String, desc: 'The file path' optional :all, type: Boolean, desc: 'Every commit will be returned' optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response' + optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges' use :pagination end get ':id/repository/commits' do @@ -47,6 +48,7 @@ module API offset = (params[:page] - 1) * params[:per_page] all = params[:all] with_stats = params[:with_stats] + first_parent = params[:first_parent] commits = user_project.repository.commits(ref, path: path, @@ -54,11 +56,12 @@ module API offset: offset, before: before, after: after, - all: all) + all: all, + first_parent: first_parent) commit_count = - if all || path || before || after - user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all) + if all || path || before || after || first_parent + user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent) else # Cacheable commit count. user_project.repository.commit_count_for_ref(ref) diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index df6d2721977..e86bcc19b2b 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -115,14 +115,20 @@ module API put ":id/deploy_keys/:key_id" do deploy_keys_project = find_by_deploy_key(user_project, params[:key_id]) - authorize!(:update_deploy_key, deploy_keys_project.deploy_key) + if !can?(current_user, :update_deploy_key, deploy_keys_project.deploy_key) && + !can?(current_user, :update_deploy_keys_project, deploy_keys_project) + forbidden!(nil) + end + + update_params = {} + update_params[:can_push] = params[:can_push] if params.key?(:can_push) + update_params[:deploy_key_attributes] = { id: params[:key_id] } - can_push = params[:can_push].nil? ? deploy_keys_project.can_push : params[:can_push] - title = params[:title] || deploy_keys_project.deploy_key.title + if can?(current_user, :update_deploy_key, deploy_keys_project.deploy_key) + update_params[:deploy_key_attributes][:title] = params[:title] if params.key?(:title) + end - result = deploy_keys_project.update(can_push: can_push, - deploy_key_attributes: { id: params[:key_id], - title: title }) + result = deploy_keys_project.update(update_params) if result present deploy_keys_project, with: Entities::DeployKeysProject diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index eb45df31ff9..da882547071 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -42,6 +42,88 @@ module API present deployment, with: Entities::Deployment end + + desc 'Creates a new deployment' do + detail 'This feature was introduced in GitLab 12.4' + success Entities::Deployment + end + params do + requires :environment, + type: String, + desc: 'The name of the environment to deploy to' + + requires :sha, + type: String, + desc: 'The SHA of the commit that was deployed' + + requires :ref, + type: String, + desc: 'The name of the branch or tag that was deployed' + + requires :tag, + type: Boolean, + desc: 'A boolean indicating if the deployment ran for a tag' + + requires :status, + type: String, + desc: 'The status of the deployment', + values: %w[running success failed canceled] + end + post ':id/deployments' do + authorize!(:create_deployment, user_project) + authorize!(:create_environment, user_project) + + environment = user_project + .environments + .find_or_create_by_name(params[:environment]) + + unless environment.persisted? + render_validation_error!(deployment) + end + + authorize!(:create_deployment, environment) + + service = ::Deployments::CreateService + .new(environment, current_user, declared_params) + + deployment = service.execute + + if deployment.persisted? + present(deployment, with: Entities::Deployment, current_user: current_user) + else + render_validation_error!(deployment) + end + end + + desc 'Updates an existing deployment' do + detail 'This feature was introduced in GitLab 12.4' + success Entities::Deployment + end + params do + requires :status, + type: String, + desc: 'The new status of the deployment', + values: %w[running success failed canceled] + end + put ':id/deployments/:deployment_id' do + authorize!(:read_deployment, user_project) + + deployment = user_project.deployments.find(params[:deployment_id]) + + authorize!(:update_deployment, deployment) + + if deployment.deployable + forbidden!('Deployments created using GitLab CI can not be updated using the API') + end + + service = ::Deployments::UpdateService.new(deployment, declared_params) + + if service.execute + present(deployment, with: Entities::Deployment, current_user: current_user) + else + render_validation_error!(deployment) + end + end end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 89951498489..91811efacd7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -378,6 +378,13 @@ module API class Group < BasicGroupDetails expose :path, :description, :visibility + expose :share_with_group_lock + expose :require_two_factor_authentication + expose :two_factor_grace_period + expose :project_creation_level_str, as: :project_creation_level + expose :auto_devops_enabled + expose :subgroup_creation_level_str, as: :subgroup_creation_level + expose :emails_disabled expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url do |group, options| group.avatar_url(only_path: false) @@ -682,6 +689,7 @@ module API class PipelineBasic < Grape::Entity expose :id, :sha, :ref, :status + expose :created_at, :updated_at expose :web_url do |pipeline, _options| Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline) @@ -771,7 +779,7 @@ module API end class MergeRequest < MergeRequestBasic - expose :subscribed do |merge_request, options| + expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |merge_request, options| merge_request.subscribed?(options[:current_user], options[:project]) end @@ -925,8 +933,8 @@ module API end class PushEventPayload < Grape::Entity - expose :commit_count, :action, :ref_type, :commit_from, :commit_to - expose :ref, :commit_title + expose :commit_count, :action, :ref_type, :commit_from, :commit_to, :ref, + :commit_title, :ref_count end class Event < Grape::Entity @@ -965,13 +973,7 @@ module API end expose :target_url do |todo, options| - target_type = todo.target_type.underscore - target_url = "#{todo.parent.class.to_s.underscore}_#{target_type}_url" - target_anchor = "note_#{todo.note_id}" if todo.note_id? - - Gitlab::Routing - .url_helpers - .public_send(target_url, todo.parent, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend + todo_target_url(todo) end expose :body @@ -983,6 +985,19 @@ module API # see also https://gitlab.com/gitlab-org/gitlab-foss/issues/59719 ::API::Entities.const_get(target_type, false) end + + def todo_target_url(todo) + target_type = todo.target_type.underscore + target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url" + + Gitlab::Routing + .url_helpers + .public_send(target_url, todo.resource_parent, todo.target, anchor: todo_target_anchor(todo)) # rubocop:disable GitlabSecurity/PublicSend + end + + def todo_target_anchor(todo) + "note_#{todo.note_id}" if todo.note_id? + end end class NamespaceBasic < Grape::Entity @@ -1045,7 +1060,7 @@ module API expose :job_events # Expose serialized properties expose :properties do |service, options| - # TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + # TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 if service.data_fields_present? service.data_fields.as_json.slice(*service.api_field_names) else @@ -1276,7 +1291,7 @@ module API class Release < Grape::Entity expose :name - expose :tag, as: :tag_name, if: lambda { |_, _| can_download_code? } + expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? } expose :description expose :description_html do |entity| MarkupHelper.markdown_field(entity, :description) @@ -1284,26 +1299,61 @@ module API expose :created_at expose :released_at expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } - expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? } + expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? } expose :upcoming_release?, as: :upcoming_release expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? } - + expose :commit_path, if: ->(_, _) { can_download_code? } + expose :tag_path, if: ->(_, _) { can_download_code? } expose :assets do expose :assets_count, as: :count do |release, _| assets_to_exclude = can_download_code? ? [] : [:sources] release.assets_count(except: assets_to_exclude) end - expose :sources, using: Entities::Releases::Source, if: lambda { |_, _| can_download_code? } + expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? } expose :links, using: Entities::Releases::Link do |release, options| release.links.sorted end end + expose :_links do + expose :merge_requests_url, if: -> (_) { release_mr_issue_urls_available? } + expose :issues_url, if: -> (_) { release_mr_issue_urls_available? } + end private def can_download_code? Ability.allowed?(options[:current_user], :download_code, object.project) end + + def commit_path + return unless object.commit + + Gitlab::Routing.url_helpers.project_commit_path(project, object.commit.id) + end + + def tag_path + Gitlab::Routing.url_helpers.project_tag_path(project, object.tag) + end + + def merge_requests_url + Gitlab::Routing.url_helpers.project_merge_requests_url(project, params_for_issues_and_mrs) + end + + def issues_url + Gitlab::Routing.url_helpers.project_issues_url(project, params_for_issues_and_mrs) + end + + def params_for_issues_and_mrs + { scope: 'all', state: 'opened', release_tag: object.tag } + end + + def release_mr_issue_urls_available? + ::Feature.enabled?(:release_mr_issue_urls, project) + end + + def project + @project ||= object.project + end end class Tag < Grape::Entity @@ -1448,15 +1498,17 @@ module API end class Deployment < Grape::Entity - expose :id, :iid, :ref, :sha, :created_at + expose :id, :iid, :ref, :sha, :created_at, :updated_at expose :user, using: Entities::UserBasic expose :environment, using: Entities::EnvironmentBasic expose :deployable, using: Entities::Job + expose :status end class Environment < EnvironmentBasic expose :project, using: Entities::BasicProjectDetails expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true } + expose :state end class LicenseBasic < Grape::Entity diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb index 79a44941c81..7585293031f 100644 --- a/lib/api/group_labels.rb +++ b/lib/api/group_labels.rb @@ -18,10 +18,24 @@ module API params do optional :with_counts, type: Boolean, default: false, desc: 'Include issue and merge request counts' + optional :include_ancestor_groups, type: Boolean, default: true, + desc: 'Include ancestor groups' use :pagination end get ':id/labels' do - get_labels(user_group, Entities::GroupLabel) + get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups]) + end + + desc 'Get a single label' do + detail 'This feature was added in GitLab 12.4.' + success Entities::GroupLabel + end + params do + optional :include_ancestor_groups, type: Boolean, default: true, + desc: 'Include ancestor groups' + end + get ':id/labels/:name' do + get_label(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups]) end desc 'Create a new label' do @@ -36,22 +50,21 @@ module API end desc 'Update an existing label. At least one optional parameter is required.' do - detail 'This feature was added in GitLab 11.8' + detail 'This feature was added in GitLab 11.8 and deprecated in GitLab 12.4.' success Entities::GroupLabel end params do - requires :name, type: String, desc: 'The name of the label to be updated' - optional :new_name, type: String, desc: 'The new name of the label' - optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" - optional :description, type: String, desc: 'The new description of label' - at_least_one_of :new_name, :color, :description + optional :label_id, type: Integer, desc: 'The id of the label to be updated' + optional :name, type: String, desc: 'The name of the label to be updated' + use :group_label_update_params + exactly_one_of :label_id, :name end put ':id/labels' do update_label(user_group, Entities::GroupLabel) end desc 'Delete an existing label' do - detail 'This feature was added in GitLab 11.8' + detail 'This feature was added in GitLab 11.8 and deprecated in GitLab 12.4.' success Entities::GroupLabel end params do @@ -60,6 +73,29 @@ module API delete ':id/labels' do delete_label(user_group) end + + desc 'Update an existing label. At least one optional parameter is required.' do + detail 'This feature was added in GitLab 12.4.' + success Entities::GroupLabel + end + params do + requires :name, type: String, desc: 'The name or id of the label to be updated' + use :group_label_update_params + end + put ':id/labels/:name' do + update_label(user_group, Entities::GroupLabel) + end + + desc 'Delete an existing label' do + detail 'This feature was added in GitLab 12.4.' + success Entities::GroupLabel + end + params do + requires :name, type: String, desc: 'The name or id of the label to be deleted' + end + delete ':id/labels/:name' do + delete_label(user_group) + end end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index fad8bb13150..19c29847ce3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -350,7 +350,7 @@ module API render_api_error!(message || '409 Conflict', 409) end - def file_to_large! + def file_too_large! render_api_error!('413 Request Entity Too Large', 413) end diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb index bd60470fbd6..3ddef0c16b3 100644 --- a/lib/api/helpers/graphql_helpers.rb +++ b/lib/api/helpers/graphql_helpers.rb @@ -6,7 +6,7 @@ module API # against the graphql API. Helper code for the graphql server implementation # should be in app/graphql/ or lib/gitlab/graphql/ module GraphqlHelpers - def conditionally_graphql!(fallback:, query:, context: {}, transform: nil) + def run_graphql!(query:, context: {}, transform: nil) result = GitlabSchema.execute(query, context: context) if transform diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb index 585ae1eb5c4..2cc18acb7ec 100644 --- a/lib/api/helpers/groups_helpers.rb +++ b/lib/api/helpers/groups_helpers.rb @@ -10,12 +10,16 @@ module API optional :description, type: String, desc: 'The description of the group' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, - default: Gitlab::VisibilityLevel.string_level( - Gitlab::CurrentSettings.current_application_settings.default_group_visibility), desc: 'The visibility of the group' + optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' + optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users in this group to setup Two-factor authentication' + optional :two_factor_grace_period, type: Integer, desc: 'Time before Two-factor authentication is enforced' + optional :project_creation_level, type: String, values: ::Gitlab::Access.project_creation_string_values, desc: 'Determine if developers can create projects in the group', as: :project_creation_level_str + optional :auto_devops_enabled, type: Boolean, desc: 'Default to Auto DevOps pipeline for all projects within this group' + optional :subgroup_creation_level, type: String, values: ::Gitlab::Access.subgroup_creation_string_values, desc: 'Allowed to create subgroups', as: :subgroup_creation_level_str + optional :emails_disabled, type: Boolean, desc: 'Disable email notifications' optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' - optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' end params :optional_params_ee do diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb index ec5b688dd1c..2fb2d9b79cf 100644 --- a/lib/api/helpers/label_helpers.rb +++ b/lib/api/helpers/label_helpers.rb @@ -11,6 +11,23 @@ module API optional :description, type: String, desc: 'The description of label to be created' end + params :label_update_params do + optional :new_name, type: String, desc: 'The new name of the label' + optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" + optional :description, type: String, desc: 'The new description of label' + end + + params :project_label_update_params do + use :label_update_params + optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true + at_least_one_of :new_name, :color, :description, :priority + end + + params :group_label_update_params do + use :label_update_params + at_least_one_of :new_name, :color, :description + end + def find_label(parent, id_or_title, include_ancestor_groups: true) labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups) label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title) @@ -18,14 +35,20 @@ module API label || not_found!('Label') end - def get_labels(parent, entity) - present paginate(available_labels_for(parent)), + def get_labels(parent, entity, include_ancestor_groups: true) + present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)), with: entity, current_user: current_user, parent: parent, with_counts: params[:with_counts] end + def get_label(parent, entity, include_ancestor_groups: true) + label = find_label(parent, params_id_or_title, include_ancestor_groups: include_ancestor_groups) + + present label, with: entity, current_user: current_user, parent: parent + end + def create_label(parent, entity) authorize! :admin_label, parent @@ -57,6 +80,7 @@ module API # params is used to update the label so we need to remove this field here params.delete(:label_id) + params.delete(:name) label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label) render_validation_error!(label) unless label.valid? @@ -80,6 +104,24 @@ module API destroy_conditionally!(label) end + def promote_label(parent) + authorize! :admin_label, parent + + label = find_label(parent, params[:name], include_ancestor_groups: false) + + begin + group_label = ::Labels::PromoteService.new(parent, current_user).execute(label) + + if group_label + present group_label, with: Entities::GroupLabel, current_user: current_user, parent: parent.group + else + render_api_error!('Failed to promote project label to group label', 400) + end + rescue => error + render_api_error!(error.to_s, 400) + end + end + def params_id_or_title @params_id_or_title ||= params[:label_id] || params[:name] end diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 11631378137..fa8b9ad79bd 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -59,8 +59,9 @@ module API token && job.valid_token?(token) end - def max_artifacts_size - Gitlab::CurrentSettings.max_artifacts_size.megabytes.to_i + def max_artifacts_size(job) + max_size = job.project.closest_setting(:max_artifacts_size) + max_size.megabytes.to_i end def job_forbidden!(job, reason) diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index d5f0ddb0805..d9a22484c1f 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -26,20 +26,11 @@ module API def ee_post_receive_response_hook(response) # Hook for EE to add messages end - end - namespace 'internal' do - # Check if git command is allowed for project - # - # Params: - # key_id - ssh key id for Git over SSH - # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode - # username - user name for Git over SSH in keyless SSH cert mode - # protocol - Git access protocol being used, e.g. HTTP or SSH - # project - project full_path (not path on disk) - # action - git action (git-upload-pack or git-receive-pack) - # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList - post "/allowed" do + def check_allowed(params) + # This is a separate method so that EE can alter its behaviour more + # easily. + # Stores some Git-specific env thread-safely env = parse_env Gitlab::Git::HookEnv.set(gl_repository, env) if project @@ -53,11 +44,11 @@ module API @project ||= access_checker.project result rescue Gitlab::GitAccess::UnauthorizedError => e - break response_with_status(code: 401, success: false, message: e.message) + return response_with_status(code: 401, success: false, message: e.message) rescue Gitlab::GitAccess::TimeoutError => e - break response_with_status(code: 503, success: false, message: e.message) + return response_with_status(code: 503, success: false, message: e.message) rescue Gitlab::GitAccess::NotFoundError => e - break response_with_status(code: 404, success: false, message: e.message) + return response_with_status(code: 404, success: false, message: e.message) end log_user_activity(actor.user) @@ -78,6 +69,10 @@ module API receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i if receive_max_input_size > 0 payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}" + + if Feature.enabled?(:gitaly_upload_pack_filter, project) + payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true" + end end response_with_status(**payload) @@ -87,6 +82,26 @@ module API response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR) end end + end + + namespace 'internal' do + # Check if git command is allowed for project + # + # Params: + # key_id - ssh key id for Git over SSH + # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode + # username - user name for Git over SSH in keyless SSH cert mode + # protocol - Git access protocol being used, e.g. HTTP or SSH + # project - project full_path (not path on disk) + # action - git action (git-upload-pack or git-receive-pack) + # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList + # check_ip - optional, only in EE version, may limit access to + # group resources based on its IP restrictions + post "/allowed" do + # It was moved to a separate method so that EE can alter its behaviour more + # easily. + check_allowed(params) + end # rubocop: disable CodeReuse/ActiveRecord post "/lfs_authenticate" do @@ -108,10 +123,6 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - get "/merge_request_urls" do - merge_request_urls - end - # # Get a ssh key using the fingerprint # @@ -129,20 +140,15 @@ module API # # Discover user by ssh key, user id or username # - # rubocop: disable CodeReuse/ActiveRecord - get "/discover" do + get '/discover' do if params[:key_id] - key = Key.find(params[:key_id]) - user = key.user - elsif params[:user_id] - user = User.find_by(id: params[:user_id]) + user = UserFinder.new(params[:key_id]).find_by_ssh_key_id elsif params[:username] user = UserFinder.new(params[:username]).find_by_username end present user, with: Entities::UserSafe end - # rubocop: enable CodeReuse/ActiveRecord get "/check" do { @@ -153,22 +159,6 @@ module API } end - get "/broadcast_messages" do - if messages = BroadcastMessage.current - present messages, with: Entities::BroadcastMessage - else - [] - end - end - - get "/broadcast_message" do - if message = BroadcastMessage.current&.last - present message, with: Entities::BroadcastMessage - else - {} - end - end - # rubocop: disable CodeReuse/ActiveRecord post '/two_factor_recovery_codes' do status 200 diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb index eaa434cff51..003af7f6dd4 100644 --- a/lib/api/internal/pages.rb +++ b/lib/api/internal/pages.rb @@ -17,11 +17,18 @@ module API namespace 'internal' do namespace 'pages' do + desc 'Get GitLab Pages domain configuration by hostname' do + detail 'This feature was introduced in GitLab 12.3.' + end + params do + requires :host, type: String, desc: 'The host to query for' + end get "/" do - host = PagesDomain.find_by_domain(params[:host]) + host = Namespace.find_by_pages_host(params[:host]) || PagesDomain.find_by_domain(params[:host]) not_found! unless host virtual_domain = host.pages_virtual_domain + no_content! unless virtual_domain present virtual_domain, with: Entities::Internal::Pages::VirtualDomain end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index de6af980896..4208385a48d 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -343,7 +343,8 @@ module API present paginate(::Kaminari.paginate_array(merge_requests)), with: Entities::MergeRequest, current_user: current_user, - project: user_project + project: user_project, + include_subscribed: false end desc 'List merge requests closing issue' do diff --git a/lib/api/labels.rb b/lib/api/labels.rb index de89e94b0c0..2b283d82e4a 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -17,10 +17,24 @@ module API params do optional :with_counts, type: Boolean, default: false, desc: 'Include issue and merge request counts' + optional :include_ancestor_groups, type: Boolean, default: true, + desc: 'Include ancestor groups' use :pagination end get ':id/labels' do - get_labels(user_project, Entities::ProjectLabel) + get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups]) + end + + desc 'Get a single label' do + detail 'This feature was added in GitLab 12.4.' + success Entities::ProjectLabel + end + params do + optional :include_ancestor_groups, type: Boolean, default: true, + desc: 'Include ancestor groups' + end + get ':id/labels/:name' do + get_label(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups]) end desc 'Create a new label' do @@ -35,23 +49,21 @@ module API end desc 'Update an existing label. At least one optional parameter is required.' do + detail 'This feature was deprecated in GitLab 12.4.' success Entities::ProjectLabel end params do optional :label_id, type: Integer, desc: 'The id of the label to be updated' optional :name, type: String, desc: 'The name of the label to be updated' - optional :new_name, type: String, desc: 'The new name of the label' - optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" - optional :description, type: String, desc: 'The new description of label' - optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true + use :project_label_update_params exactly_one_of :label_id, :name - at_least_one_of :new_name, :color, :description, :priority end put ':id/labels' do update_label(user_project, Entities::ProjectLabel) end desc 'Delete an existing label' do + detail 'This feature was deprecated in GitLab 12.4.' success Entities::ProjectLabel end params do @@ -64,28 +76,48 @@ module API end desc 'Promote a label to a group label' do - detail 'This feature was added in GitLab 12.3' + detail 'This feature was added in GitLab 12.3 and deprecated in GitLab 12.4.' success Entities::GroupLabel end params do requires :name, type: String, desc: 'The name of the label to be promoted' end put ':id/labels/promote' do - authorize! :admin_label, user_project + promote_label(user_project) + end - label = find_label(user_project, params[:name], include_ancestor_groups: false) + desc 'Update an existing label. At least one optional parameter is required.' do + detail 'This feature was added in GitLab 12.4.' + success Entities::ProjectLabel + end + params do + requires :name, type: String, desc: 'The name or id of the label to be updated' + use :project_label_update_params + end + put ':id/labels/:name' do + update_label(user_project, Entities::ProjectLabel) + end - begin - group_label = ::Labels::PromoteService.new(user_project, current_user).execute(label) + desc 'Delete an existing label' do + detail 'This feature was added in GitLab 12.4.' + success Entities::ProjectLabel + end + params do + requires :name, type: String, desc: 'The name or id of the label to be deleted' + end + delete ':id/labels/:name' do + delete_label(user_project) + end - if group_label - present group_label, with: Entities::GroupLabel, current_user: current_user, parent: user_project.group - else - render_api_error!('Failed to promote project label to group label', 400) - end - rescue => error - render_api_error!(error.to_s, 400) - end + desc 'Promote a label to a group label' do + detail 'This feature was added in GitLab 12.4.' + success Entities::GroupLabel + end + params do + requires :name, type: String, desc: 'The name or id of the label to be promoted' + end + put ':id/labels/:name/promote' do + promote_label(user_project) end end end diff --git a/lib/api/members.rb b/lib/api/members.rb index 461ffe71a62..1d4616fed52 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -18,6 +18,7 @@ module API end params do optional :query, type: String, desc: 'A query string to search for members' + optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership' use :pagination end # rubocop: disable CodeReuse/ActiveRecord @@ -26,6 +27,7 @@ module API members = source.members.where.not(user_id: nil).includes(:user) members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present? + members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? members = paginate(members) present members, with: Entities::Member @@ -37,6 +39,7 @@ module API end params do optional :query, type: String, desc: 'A query string to search for members' + optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership' use :pagination end # rubocop: disable CodeReuse/ActiveRecord @@ -45,6 +48,7 @@ module API members = find_all_members(source_type, source) members = members.includes(:user).references(:user).merge(User.search(params[:query])) if params[:query].present? + members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? members = paginate(members) present members, with: Entities::Member @@ -68,6 +72,23 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Gets a member of a group or project, including those who gained membership through ancestor group' do + success Entities::Member + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the member' + end + # rubocop: disable CodeReuse/ActiveRecord + get ":id/members/all/:user_id" do + source = find_source(source_type, params[:id]) + + members = find_all_members(source_type, source) + member = members.find_by!(user_id: params[:user_id]) + + present member, with: Entities::Member + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Adds a member to a group or project.' do success Entities::Member end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 16fca9acccb..89e4da5a42e 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -80,7 +80,7 @@ module API note = create_note(noteable, opts) if note.valid? - present note, with: Entities.const_get(note.class.name) + present note, with: Entities.const_get(note.class.name, false) else bad_request!("Note #{note.errors.messages}") end diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index c10ef96922c..2a05974509a 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -106,9 +106,15 @@ module API authorize_destroy_container_image! validate_tag! - tag.delete - - status :ok + result = ::Projects::ContainerRepository::DeleteTagsService + .new(repository.project, current_user, tags: [declared_params[:tag_name]]) + .execute(repository) + + if result[:status] == :success + status :ok + else + status :bad_request + end end end diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 7f1ae5ffbe6..b3f17447ea0 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -29,6 +29,7 @@ module API requires :path, type: String, desc: 'The new project path and name' # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 requires :file, type: File, desc: 'The project export file to be imported' # rubocop:disable Scalability/FileUploads + optional :name, type: String, desc: 'The name of the project to be imported. Defaults to the path of the project if not provided.' optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace." optional :overwrite, type: Boolean, default: false, desc: 'If there is a project in the same namespace and with the same name overwrite it' optional :override_params, @@ -55,6 +56,7 @@ module API project_params = { path: import_params[:path], namespace_id: namespace.id, + name: import_params[:name], file: import_params[:file]['tempfile'], overwrite: import_params[:overwrite] } diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index ca75ee906ce..c7665c20234 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -42,7 +42,7 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Protect a single branch or wildcard' do + desc 'Protect a single branch' do success Entities::ProtectedBranch end params do @@ -93,3 +93,5 @@ module API end end end + +API::ProtectedBranches.prepend_if_ee('EE::API::ProtectedBranches') diff --git a/lib/api/runner.rb b/lib/api/runner.rb index fdf4904e9f5..f383c541f8a 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -221,14 +221,16 @@ module API job = authenticate_job! forbidden!('Job is not running') unless job.running? + max_size = max_artifacts_size(job) + if params[:filesize] file_size = params[:filesize].to_i - file_to_large! unless file_size < max_artifacts_size + file_too_large! unless file_size < max_size end status 200 content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_artifacts_size) + JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size) end desc 'Upload artifacts for job' do @@ -268,7 +270,7 @@ module API metadata = UploadedFile.from_params(params, :metadata, JobArtifactUploader.workhorse_local_upload_path) bad_request!('Missing artifacts file!') unless artifacts - file_to_large! unless artifacts.size < max_artifacts_size + file_too_large! unless artifacts.size < max_artifacts_size(job) expire_in = params['expire_in'] || Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in diff --git a/lib/api/settings.rb b/lib/api/settings.rb index e4ef507228b..c90ba0c9b5d 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -101,6 +101,8 @@ module API optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.' optional :project_export_enabled, type: Boolean, desc: 'Enable project export' optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics' + optional :push_event_hooks_limit, type: Integer, desc: "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value." + optional :push_event_activities_limit, type: Integer, desc: 'Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.' optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts' given recaptcha_enabled: ->(val) { val } do requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha' diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 404675bfaec..e3f3aca27df 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -49,7 +49,7 @@ module API resource :todos do helpers do def issuable_and_awardable?(type) - obj_type = Object.const_get(type) + obj_type = Object.const_get(type, false) (obj_type < Issuable) && (obj_type < Awardable) rescue NameError diff --git a/lib/api/users.rb b/lib/api/users.rb index ff8b82e1898..ff0b1e87b03 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -459,6 +459,42 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Activate a deactivated user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + # rubocop: disable CodeReuse/ActiveRecord + post ':id/activate' do + authenticated_as_admin! + + user = User.find_by(id: params[:id]) + not_found!('User') unless user + forbidden!('A blocked user must be unblocked to be activated') if user.blocked? + + user.activate + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Deactivate an active user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + # rubocop: disable CodeReuse/ActiveRecord + post ':id/deactivate' do + authenticated_as_admin! + user = User.find_by(id: params[:id]) + not_found!('User') unless user + + break if user.deactivated? + + unless user.can_be_deactivated? + forbidden!('A blocked user cannot be deactivated by the API') if user.blocked? + forbidden!("The user you are trying to deactivate has been active in the past #{::User::MINIMUM_INACTIVE_DAYS} days and cannot be deactivated") + end + + user.deactivate + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Block a user. Available only for admins.' params do requires :id, type: Integer, desc: 'The ID of the user' @@ -489,6 +525,8 @@ module API if user.ldap_blocked? forbidden!('LDAP blocked users cannot be unblocked by the API') + elsif user.deactivated? + forbidden!('Deactivated users cannot be unblocked by the API') else user.activate end diff --git a/lib/api/version.rb b/lib/api/version.rb index eca1b529094..f79bb3428f2 100644 --- a/lib/api/version.rb +++ b/lib/api/version.rb @@ -19,11 +19,10 @@ module API detail 'This feature was introduced in GitLab 8.13.' end get '/version' do - conditionally_graphql!( + run_graphql!( query: METADATA_QUERY, context: { current_user: current_user }, - transform: ->(result) { result.dig('data', 'metadata') }, - fallback: -> { { version: Gitlab::VERSION, revision: Gitlab.revision } } + transform: ->(result) { result.dig('data', 'metadata') } ) end end -- cgit v1.2.1