From 4f2c4411615a7c870fd1fa54525079f7ea97d6e5 Mon Sep 17 00:00:00 2001 From: Borja Aparicio Date: Tue, 25 Oct 2016 15:22:12 +0200 Subject: Added API endpoint groups/owned --- lib/api/groups.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib/api') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index a13e353b7f5..40644fc2adf 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -26,6 +26,16 @@ module API present @groups, with: Entities::Group end + # Get list of owned groups for authenticated user + # + # Example Request: + # GET /groups/owned + get '/owned' do + @groups = current_user.owned_groups + @groups = paginate @groups + present @groups, with: Entities::Group, user: current_user + end + # Create group. Available only for users who can create groups. # # Parameters: -- cgit v1.2.1 From a0aaf93fe591215a7fc29a52ff6cbd38604c8dcb Mon Sep 17 00:00:00 2001 From: Yatish Mehta Date: Tue, 25 Oct 2016 14:08:53 -0700 Subject: Add query param to filter users on 'external' & 'blocked' type on API --- lib/api/users.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/users.rb b/lib/api/users.rb index c28e07a76b7..298c401a816 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -10,6 +10,9 @@ module API # GET /users # GET /users?search=Admin # GET /users?username=root + # GET /users?active=true + # GET /users?external=true + # GET /users?blocked=true get do unless can?(current_user, :read_users_list, nil) render_api_error!("Not authorized.", 403) @@ -19,8 +22,10 @@ module API @users = User.where(username: params[:username]) else @users = User.all - @users = @users.active if params[:active].present? + @users = @users.active if to_boolean(params[:active]) @users = @users.search(params[:search]) if params[:search].present? + @users = @users.blocked if to_boolean(params[:blocked]) + @users = @users.external if to_boolean(params[:external]) && current_user.is_admin? @users = paginate @users end -- cgit v1.2.1 From 603ebe55f0232f16b5f1db95d2962a4cf5cdcc1b Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 17:36:35 +0100 Subject: Grapify the session API --- lib/api/session.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'lib/api') diff --git a/lib/api/session.rb b/lib/api/session.rb index 55ec66a6d67..d09400b81f5 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -1,15 +1,14 @@ module API - # Users API class Session < Grape::API - # Login to get token - # - # Parameters: - # login (*required) - user login - # email (*required) - user email - # password (required) - user password - # - # Example Request: - # POST /session + desc 'Login to get token' do + success Entities::UserLogin + end + params do + optional :login, type: String, desc: 'The username' + optional :email, type: String, desc: 'The email of the user' + requires :password, type: String, desc: 'The password of the user' + at_least_one_of :login, :email + end post "/session" do user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password]) -- cgit v1.2.1 From 1afab9eb79c87f32c7b899e58bc9a0ea8a113594 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Wed, 21 Sep 2016 16:15:12 +0200 Subject: Add button to delete all merged branches It adds a button to the branches page that the user can use to delete all the branches that are already merged. This can be used to clean up all the branches that were forgotten to delete while merging MRs. Fixes #21076. --- lib/api/branches.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'lib/api') diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 21a106387f0..73aed624ea7 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -128,6 +128,18 @@ module API render_api_error!(result[:message], result[:return_code]) end end + + # Delete all merged branches + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # DELETE /projects/:id/repository/branches/delete_merged + delete ":id/repository/merged_branches" do + DeleteMergedBranchesService.new(user_project, current_user).async_execute + + status(200) + end end end end -- cgit v1.2.1 From 3378642504a0644411797f7d843bcb411c85582f Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 16:29:07 +0100 Subject: Grapify runners API --- lib/api/runners.rb | 117 +++++++++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 53 deletions(-) (limited to 'lib/api') diff --git a/lib/api/runners.rb b/lib/api/runners.rb index ecc8f2fc5a2..84c19c432b0 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -1,34 +1,39 @@ module API - # Runners API class Runners < Grape::API before { authenticate! } resource :runners do - # Get runners available for user - # - # Example Request: - # GET /runners + desc 'Get runners available for user' do + success Entities::Runner + end + params do + optional :scope, type: String, values: %w[active paused online], + desc: 'The scope of specific runners to show' + end get do runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: ['specific', 'shared']) present paginate(runners), with: Entities::Runner end - # Get all runners - shared and specific - # - # Example Request: - # GET /runners/all + desc 'Get all runners - shared and specific' do + success Entities::Runner + end + params do + optional :scope, type: String, values: %w[active paused online specific shared], + desc: 'The scope of specific runners to show' + end get 'all' do authenticated_as_admin! runners = filter_runners(Ci::Runner.all, params[:scope]) present paginate(runners), with: Entities::Runner end - # Get runner's details - # - # Parameters: - # id (required) - The ID of ther runner - # Example Request: - # GET /runners/:id + desc "Get runner's details" do + success Entities::RunnerDetails + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + end get ':id' do runner = get_runner(params[:id]) authenticate_show_runner!(runner) @@ -36,33 +41,37 @@ module API present runner, with: Entities::RunnerDetails, current_user: current_user end - # Update runner's details - # - # Parameters: - # id (required) - The ID of ther runner - # description (optional) - Runner's description - # active (optional) - Runner's status - # tag_list (optional) - Array of tags for runner - # Example Request: - # PUT /runners/:id + desc "Update runner's details" do + success Entities::RunnerDetails + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + optional :description, type: String, desc: 'The description of the runner' + optional :active, type: Boolean, desc: 'The state of a runner' + optional :tag_list, type: Array[String], desc: 'The list of tags for a runner' + optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs' + optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' + at_least_one_of :description, :active, :tag_list, :run_untagged, :locked + end put ':id' do - runner = get_runner(params[:id]) + runner = get_runner(params.delete(:id)) authenticate_update_runner!(runner) - attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked] - if runner.update(attrs) + runner_params = declared(params, include_missing: false) + + if runner.update(runner_params) present runner, with: Entities::RunnerDetails, current_user: current_user else render_validation_error!(runner) end end - # Remove runner - # - # Parameters: - # id (required) - The ID of ther runner - # Example Request: - # DELETE /runners/:id + desc 'Remove a runner' do + success Entities::Runner + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + end delete ':id' do runner = get_runner(params[:id]) authenticate_delete_runner!(runner) @@ -72,28 +81,31 @@ module API end end + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do before { authorize_admin_project } - # Get runners available for project - # - # Example Request: - # GET /projects/:id/runners + desc 'Get runners available for project' do + success Entities::Runner + end + params do + optional :scope, type: String, values: %w[active paused online specific shared], + desc: 'The scope of specific runners to show' + end get ':id/runners' do runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope]) present paginate(runners), with: Entities::Runner end - # Enable runner for project - # - # Parameters: - # id (required) - The ID of the project - # runner_id (required) - The ID of the runner - # Example Request: - # POST /projects/:id/runners/:runner_id + desc 'Enable a runner for a project' do + success Entities::Runner + end + params do + requires :runner_id, type: Integer, desc: 'The ID of the runner' + end post ':id/runners' do - required_attributes! [:runner_id] - runner = get_runner(params[:runner_id]) authenticate_enable_runner!(runner) @@ -106,13 +118,12 @@ module API end end - # Disable project's runner - # - # Parameters: - # id (required) - The ID of the project - # runner_id (required) - The ID of the runner - # Example Request: - # DELETE /projects/:id/runners/:runner_id + desc "Disable project's runner" do + success Entities::Runner + end + params do + requires :runner_id, type: Integer, desc: 'The ID of the runner' + end delete ':id/runners/:runner_id' do runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id]) not_found!('Runner') unless runner_project -- cgit v1.2.1 From f27e972e84b488fb96202772872379113f72c789 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 15:14:02 +0100 Subject: Grapify milestones API --- lib/api/milestones.rb | 111 ++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 54 deletions(-) (limited to 'lib/api') diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 9b73f6826cf..8984cf8cdcd 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -11,19 +11,25 @@ module API else milestones end end + + params :optional_params do + optional :description, type: String, desc: 'The description of the milestone' + optional :due_date, type: String, desc: 'The due date of the milestone' + end end + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Get a list of project milestones - # - # Parameters: - # id (required) - The ID of a project - # state (optional) - Return "active" or "closed" milestones - # Example Request: - # GET /projects/:id/milestones - # GET /projects/:id/milestones?iid=42 - # GET /projects/:id/milestones?state=active - # GET /projects/:id/milestones?state=closed + desc 'Get a list of project milestones' do + success Entities::Milestone + end + params do + optional :state, type: String, values: %w[active closed all], default: 'all', + desc: 'Return "active", "closed", or "all" milestones' + optional :iid, type: Integer, desc: 'The IID of the milestone' + end get ":id/milestones" do authorize! :read_milestone, user_project @@ -34,34 +40,31 @@ module API present paginate(milestones), with: Entities::Milestone end - # Get a single project milestone - # - # Parameters: - # id (required) - The ID of a project - # milestone_id (required) - The ID of a project milestone - # Example Request: - # GET /projects/:id/milestones/:milestone_id + desc 'Get a single project milestone' do + success Entities::Milestone + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + end get ":id/milestones/:milestone_id" do authorize! :read_milestone, user_project - @milestone = user_project.milestones.find(params[:milestone_id]) - present @milestone, with: Entities::Milestone + milestone = user_project.milestones.find(params[:milestone_id]) + present milestone, with: Entities::Milestone end - # Create a new project milestone - # - # Parameters: - # id (required) - The ID of the project - # title (required) - The title of the milestone - # description (optional) - The description of the milestone - # due_date (optional) - The due date of the milestone - # Example Request: - # POST /projects/:id/milestones + desc 'Create a new project milestone' do + success Entities::Milestone + end + params do + requires :title, type: String, desc: 'The title of the milestone' + use :optional_params + end post ":id/milestones" do authorize! :admin_milestone, user_project - required_attributes! [:title] - attrs = attributes_for_keys [:title, :description, :due_date] - milestone = ::Milestones::CreateService.new(user_project, current_user, attrs).execute + milestone_params = declared(params, include_parent_namespaces: false) + + milestone = ::Milestones::CreateService.new(user_project, current_user, milestone_params).execute if milestone.valid? present milestone, with: Entities::Milestone @@ -70,22 +73,23 @@ module API end end - # Update an existing project milestone - # - # Parameters: - # id (required) - The ID of a project - # milestone_id (required) - The ID of a project milestone - # title (optional) - The title of a milestone - # description (optional) - The description of a milestone - # due_date (optional) - The due date of a milestone - # state_event (optional) - The state event of the milestone (close|activate) - # Example Request: - # PUT /projects/:id/milestones/:milestone_id + desc 'Update an existing project milestone' do + success Entities::Milestone + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + optional :title, type: String, desc: 'The title of the milestone' + optional :state_event, type: String, values: %w[close activate], + desc: 'The state event of the milestone ' + use :optional_params + at_least_one_of :title, :description, :due_date, :state_event + end put ":id/milestones/:milestone_id" do authorize! :admin_milestone, user_project - attrs = attributes_for_keys [:title, :description, :due_date, :state_event] - milestone = user_project.milestones.find(params[:milestone_id]) - milestone = ::Milestones::UpdateService.new(user_project, current_user, attrs).execute(milestone) + milestone_params = declared(params, include_parent_namespaces: false, include_missing: false) + + milestone = user_project.milestones.find(milestone_params.delete(:milestone_id)) + milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone) if milestone.valid? present milestone, with: Entities::Milestone @@ -94,21 +98,20 @@ module API end end - # Get all issues for a single project milestone - # - # Parameters: - # id (required) - The ID of a project - # milestone_id (required) - The ID of a project milestone - # Example Request: - # GET /projects/:id/milestones/:milestone_id/issues + desc 'Get all issues for a single project milestone' do + success Entities::Issue + end + params do + requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + end get ":id/milestones/:milestone_id/issues" do authorize! :read_milestone, user_project - @milestone = user_project.milestones.find(params[:milestone_id]) + milestone = user_project.milestones.find(params[:milestone_id]) finder_params = { project_id: user_project.id, - milestone_title: @milestone.title + milestone_title: milestone.title } issues = IssuesFinder.new(current_user, finder_params).execute -- cgit v1.2.1 From 2965883e60ce215343bc8d2e160f0a889baa02ac Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 16:47:39 +0100 Subject: Grapify token API --- lib/api/triggers.rb | 76 +++++++++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 46 deletions(-) (limited to 'lib/api') diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index d1d07394e92..9a4f1cd342f 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -1,19 +1,18 @@ module API - # Triggers API class Triggers < Grape::API + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Trigger a GitLab project build - # - # Parameters: - # id (required) - The ID of a CI project - # ref (required) - The name of project's branch or tag - # token (required) - The uniq token of trigger - # variables (optional) - The list of variables to be injected into build - # Example Request: - # POST /projects/:id/trigger/builds + desc 'Trigger a GitLab project build' do + success Entities::TriggerRequest + end + params do + requires :ref, type: String, desc: 'The commit sha or name of a branch or tag' + requires :token, type: String, desc: 'The unique token of trigger' + optional :variables, type: Hash, desc: 'The list of variables to be injected into build' + end post ":id/trigger/builds" do - required_attributes! [:ref, :token] - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger @@ -22,10 +21,6 @@ module API # validate variables variables = params[:variables] if variables - unless variables.is_a?(Hash) - render_api_error!('variables needs to be a hash', 400) - end - unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) } render_api_error!('variables needs to be a map of key-valued strings', 400) end @@ -44,31 +39,24 @@ module API end end - # Get triggers list - # - # Parameters: - # id (required) - The ID of a project - # page (optional) - The page number for pagination - # per_page (optional) - The value of items per page to show - # Example Request: - # GET /projects/:id/triggers + desc 'Get triggers list' do + success Entities::Trigger + end get ':id/triggers' do authenticate! authorize! :admin_build, user_project triggers = user_project.triggers.includes(:trigger_requests) - triggers = paginate(triggers) - present triggers, with: Entities::Trigger + present paginate(triggers), with: Entities::Trigger end - # Get specific trigger of a project - # - # Parameters: - # id (required) - The ID of a project - # token (required) - The `token` of a trigger - # Example Request: - # GET /projects/:id/triggers/:token + desc 'Get specific trigger of a project' do + success Entities::Trigger + end + params do + requires :token, type: String, desc: 'The unique token of trigger' + end get ':id/triggers/:token' do authenticate! authorize! :admin_build, user_project @@ -79,12 +67,9 @@ module API present trigger, with: Entities::Trigger end - # Create trigger - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # POST /projects/:id/triggers + desc 'Create a trigger' do + success Entities::Trigger + end post ':id/triggers' do authenticate! authorize! :admin_build, user_project @@ -94,13 +79,12 @@ module API present trigger, with: Entities::Trigger end - # Delete trigger - # - # Parameters: - # id (required) - The ID of a project - # token (required) - The `token` of a trigger - # Example Request: - # DELETE /projects/:id/triggers/:token + desc 'Delete a trigger' do + success Entities::Trigger + end + params do + requires :token, type: String, desc: 'The unique token of trigger' + end delete ':id/triggers/:token' do authenticate! authorize! :admin_build, user_project -- cgit v1.2.1 From 5c966f70fb218d6f4de0f888733604293f36c33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rebeca=20M=C3=A9ndez?= Date: Mon, 29 Aug 2016 17:23:40 +0200 Subject: Issue #4270: Recursive option for files through API --- lib/api/entities.rb | 2 +- lib/api/repositories.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1942aeea656..8f1aaaaaaa0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -159,7 +159,7 @@ module API end class RepoTreeObject < Grape::Entity - expose :id, :name, :type + expose :id, :name, :type, :path expose :mode do |obj, options| filemode = obj.mode.to_s(8) diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index f55aceed92c..0bb2f74809a 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -21,16 +21,18 @@ module API # Parameters: # id (required) - The ID of a project # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used + # recursive (optional) - Used to get a recursive tree # Example Request: # GET /projects/:id/repository/tree get ':id/repository/tree' do ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil + recursive = to_boolean(params[:recursive]) commit = user_project.commit(ref) not_found!('Tree') unless commit - tree = user_project.repository.tree(commit.id, path) + tree = user_project.repository.tree(commit.id, path, recursive: recursive) present tree.sorted_entries, with: Entities::RepoTreeObject end -- cgit v1.2.1 From f0b42c1079341ed0245f5e3971dd4da0f8760c38 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 16:57:14 +0100 Subject: Grapify subscription API --- lib/api/subscriptions.rb | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) (limited to 'lib/api') diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index c49e2a21b82..00a79c24f96 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -9,23 +9,20 @@ module API 'labels' => proc { |id| find_project_label(id) }, } + params do + requires :id, type: String, desc: 'The ID of a project' + requires :subscribable_id, type: String, desc: 'The ID of a resource' + end resource :projects do subscribable_types.each do |type, finder| type_singularized = type.singularize - type_id_str = :"#{type_singularized}_id" entity_class = Entities.const_get(type_singularized.camelcase) - # Subscribe to a resource - # - # Parameters: - # id (required) - The ID of a project - # subscribable_id (required) - The ID of a resource - # Example Request: - # POST /projects/:id/labels/:subscribable_id/subscription - # POST /projects/:id/issues/:subscribable_id/subscription - # POST /projects/:id/merge_requests/:subscribable_id/subscription - post ":id/#{type}/:#{type_id_str}/subscription" do - resource = instance_exec(params[type_id_str], &finder) + desc 'Subscribe to a resource' do + success entity_class + end + post ":id/#{type}/:subscribable_id/subscription" do + resource = instance_exec(params[:subscribable_id], &finder) if resource.subscribed?(current_user) not_modified! @@ -35,17 +32,11 @@ module API end end - # Unsubscribe from a resource - # - # Parameters: - # id (required) - The ID of a project - # subscribable_id (required) - The ID of a resource - # Example Request: - # DELETE /projects/:id/labels/:subscribable_id/subscription - # DELETE /projects/:id/issues/:subscribable_id/subscription - # DELETE /projects/:id/merge_requests/:subscribable_id/subscription - delete ":id/#{type}/:#{type_id_str}/subscription" do - resource = instance_exec(params[type_id_str], &finder) + desc 'Unsubscribe from a resource' do + success entity_class + end + delete ":id/#{type}/:subscribable_id/subscription" do + resource = instance_exec(params[:subscribable_id], &finder) if !resource.subscribed?(current_user) not_modified! -- cgit v1.2.1 From 510092c83ad6b35dc4cc0ac1f15c643952f519b5 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 10 Nov 2016 16:32:59 +0100 Subject: Use #to_h to convert params to a hash --- lib/api/helpers.rb | 5 +++++ lib/api/milestones.rb | 7 +++---- lib/api/runners.rb | 4 +--- 3 files changed, 9 insertions(+), 7 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3c9d7b1aaef..6998b6dc039 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -23,6 +23,11 @@ module API warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) end + def declared_params(options = {}) + options = { include_parent_namespaces: false }.merge(options) + declared(params, options).to_h.symbolize_keys + end + def find_user_by_private_token token = private_token return nil unless token.present? diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 8984cf8cdcd..ba4a84275bc 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -62,9 +62,8 @@ module API end post ":id/milestones" do authorize! :admin_milestone, user_project - milestone_params = declared(params, include_parent_namespaces: false) - milestone = ::Milestones::CreateService.new(user_project, current_user, milestone_params).execute + milestone = ::Milestones::CreateService.new(user_project, current_user, declared_params).execute if milestone.valid? present milestone, with: Entities::Milestone @@ -86,9 +85,9 @@ module API end put ":id/milestones/:milestone_id" do authorize! :admin_milestone, user_project - milestone_params = declared(params, include_parent_namespaces: false, include_missing: false) + milestone = user_project.milestones.find(params.delete(:milestone_id)) - milestone = user_project.milestones.find(milestone_params.delete(:milestone_id)) + milestone_params = declared_params(include_missing: false) milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone) if milestone.valid? diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 84c19c432b0..b145cce7e3e 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -57,9 +57,7 @@ module API runner = get_runner(params.delete(:id)) authenticate_update_runner!(runner) - runner_params = declared(params, include_missing: false) - - if runner.update(runner_params) + if runner.update(declared_params(include_missing: false)) present runner, with: Entities::RunnerDetails, current_user: current_user else render_validation_error!(runner) -- cgit v1.2.1 From 76bd09326fe070d4c865b6fbc1718673d096c178 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 14 Nov 2016 14:44:27 +0100 Subject: Use declared_params helper in API --- lib/api/access_requests.rb | 2 +- lib/api/broadcast_messages.rb | 6 ++---- lib/api/commits.rb | 2 +- lib/api/deploy_keys.rb | 2 +- lib/api/environments.rb | 7 +++---- lib/api/labels.rb | 11 +++-------- lib/api/members.rb | 2 +- lib/api/notification_settings.rb | 7 ++----- lib/api/project_hooks.rb | 10 +++------- lib/api/system_hooks.rb | 2 +- lib/api/tags.rb | 3 +-- lib/api/users.rb | 2 +- 12 files changed, 20 insertions(+), 36 deletions(-) (limited to 'lib/api') diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 87915b19480..ed723b94cfd 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -48,7 +48,7 @@ module API put ':id/access_requests/:user_id/approve' do source = find_source(source_type, params[:id]) - member = ::Members::ApproveAccessRequestService.new(source, current_user, declared(params)).execute + member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute status :created present member.user, with: Entities::Member, member: member diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index fb2a4148011..b6281a7f0ac 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -36,8 +36,7 @@ module API optional :font, type: String, desc: 'Foreground color' end post do - create_params = declared(params, include_missing: false).to_h - message = BroadcastMessage.create(create_params) + message = BroadcastMessage.create(declared_params(include_missing: false)) if message.persisted? present message, with: Entities::BroadcastMessage @@ -73,9 +72,8 @@ module API end put ':id' do message = find_message - update_params = declared(params, include_missing: false).to_h - if message.update(update_params) + if message.update(declared_params(include_missing: false)) present message, with: Entities::BroadcastMessage else render_validation_error!(message) diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 2f2cf769481..f412e1da1bf 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -53,7 +53,7 @@ module API post ":id/repository/commits" do authorize! :push_code, user_project - attrs = declared(params) + attrs = declared_params attrs[:source_branch] = attrs[:branch_name] attrs[:target_branch] = attrs[:branch_name] attrs[:actions].map! do |action| diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 425df2c176a..85360730841 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -82,7 +82,7 @@ module API end post ":id/#{path}/:key_id/enable" do key = ::Projects::EnableDeployKeyService.new(user_project, - current_user, declared(params)).execute + current_user, declared_params).execute if key present key, with: Entities::SSHKey diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 819f80d8365..00c901937b1 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -32,8 +32,7 @@ module API post ':id/environments' do authorize! :create_environment, user_project - create_params = declared(params, include_parent_namespaces: false).to_h - environment = user_project.environments.create(create_params) + environment = user_project.environments.create(declared_params) if environment.persisted? present environment, with: Entities::Environment @@ -55,8 +54,8 @@ module API authorize! :update_environment, user_project environment = user_project.environments.find(params[:environment_id]) - - update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h + + update_params = declared_params(include_missing: false).extract!(:name, :external_url) if environment.update(update_params) present environment, with: Entities::Environment else diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 97218054f37..652786d4e3e 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -30,10 +30,7 @@ module API conflict!('Label already exists') if label priority = params.delete(:priority) - label_params = declared(params, - include_parent_namespaces: false, - include_missing: false).to_h - label = user_project.labels.create(label_params) + label = user_project.labels.create(declared_params(include_missing: false)) if label.valid? label.prioritize!(user_project, priority) if priority @@ -77,11 +74,9 @@ module API update_priority = params.key?(:priority) priority = params.delete(:priority) - label_params = declared(params, - include_parent_namespaces: false, - include_missing: false).to_h + label_params = declared_params(include_missing: false) # Rename new name to the actual label attribute name - label_params[:name] = label_params.delete('new_name') if label_params.key?('new_name') + label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name) render_validation_error!(label) unless label.update(label_params) diff --git a/lib/api/members.rb b/lib/api/members.rb index b80818f0eb6..2d4d5cedf20 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -120,7 +120,7 @@ module API if member.nil? { message: "Access revoked", id: params[:user_id].to_i } else - ::Members::DestroyService.new(source, current_user, declared(params)).execute + ::Members::DestroyService.new(source, current_user, declared_params).execute present member.user, with: Entities::Member, member: member end diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index a70a7e71073..c5e9b3ad69b 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -33,10 +33,9 @@ module API begin notification_setting.transaction do new_notification_email = params.delete(:notification_email) - declared_params = declared(params, include_missing: false).to_h current_user.update(notification_email: new_notification_email) if new_notification_email - notification_setting.update(declared_params) + notification_setting.update(declared_params(include_missing: false)) end rescue ArgumentError => e # catch level enum error render_api_error! e.to_s, 400 @@ -81,9 +80,7 @@ module API notification_setting = current_user.notification_settings_for(source) begin - declared_params = declared(params, include_missing: false).to_h - - notification_setting.update(declared_params) + notification_setting.update(declared_params(include_missing: false)) rescue ArgumentError => e # catch level enum error render_api_error! e.to_s, 400 end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index eef343c2ac6..2b36ef7c426 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -51,8 +51,7 @@ module API use :project_hook_properties end post ":id/hooks" do - new_hook_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h - hook = user_project.hooks.new(new_hook_params) + hook = user_project.hooks.new(declared_params(include_missing: false)) if hook.save present hook, with: Entities::ProjectHook @@ -71,12 +70,9 @@ module API use :project_hook_properties end put ":id/hooks/:hook_id" do - hook = user_project.hooks.find(params[:hook_id]) - - new_params = declared(params, include_missing: false, include_parent_namespaces: false).to_h - new_params.delete('hook_id') + hook = user_project.hooks.find(params.delete(:hook_id)) - if hook.update_attributes(new_params) + if hook.update_attributes(declared_params(include_missing: false)) present hook, with: Entities::ProjectHook else error!("Invalid url given", 422) if hook.errors[:url].present? diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index b6bfff9f20f..708ec8cfe70 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -27,7 +27,7 @@ module API optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" end post do - hook = SystemHook.new declared(params, include_missing: false).to_h + hook = SystemHook.new(declared_params(include_missing: false)) if hook.save present hook, with: Entities::Hook diff --git a/lib/api/tags.rb b/lib/api/tags.rb index bf2a199ce21..cd33f9a9903 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -40,10 +40,9 @@ module API end post ':id/repository/tags' do authorize_push_project - create_params = declared(params) result = CreateTagService.new(user_project, current_user). - execute(create_params[:tag_name], create_params[:ref], create_params[:message], create_params[:release_description]) + execute(params[:tag_name], params[:ref], params[:message], params[:release_description]) if result[:status] == :success present result[:tag], diff --git a/lib/api/users.rb b/lib/api/users.rb index 298c401a816..aea328d2f8f 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -335,7 +335,7 @@ module API requires :id, type: String, desc: 'The user ID' end get ':id/events' do - user = User.find_by(id: declared(params).id) + user = User.find_by(id: params[:id]) not_found!('User') unless user events = user.events. -- cgit v1.2.1 From 84ac742fa6caf6e525e690d6f929f8931902fb9b Mon Sep 17 00:00:00 2001 From: Francesco Coda Zabetta Date: Mon, 14 Nov 2016 15:52:43 +0100 Subject: fix labels API adding missing parameter (current_user) --- lib/api/entities.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1942aeea656..a186cfd94c1 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -437,7 +437,18 @@ module API end class Label < LabelBasic - expose :open_issues_count, :closed_issues_count, :open_merge_requests_count + expose :open_issues_count do |label, options| + label.open_issues_count(options[:current_user]) + end + + expose :closed_issues_count do |label, options| + label.closed_issues_count(options[:current_user]) + end + + expose :open_merge_requests_count do |label, options| + label.open_merge_requests_count(options[:current_user]) + end + expose :priority do |label, options| label.priority(options[:project]) end -- cgit v1.2.1 From 471ef6cb2760717b917227ae545cc8ec8efa0aa2 Mon Sep 17 00:00:00 2001 From: Francesco Coda Zabetta Date: Mon, 14 Nov 2016 16:04:07 +0100 Subject: fix indentation --- lib/api/entities.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a186cfd94c1..e5d641adccc 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -438,15 +438,15 @@ module API class Label < LabelBasic expose :open_issues_count do |label, options| - label.open_issues_count(options[:current_user]) - end + label.open_issues_count(options[:current_user]) + end - expose :closed_issues_count do |label, options| - label.closed_issues_count(options[:current_user]) - end + expose :closed_issues_count do |label, options| + label.closed_issues_count(options[:current_user]) + end - expose :open_merge_requests_count do |label, options| - label.open_merge_requests_count(options[:current_user]) + expose :open_merge_requests_count do |label, options| + label.open_merge_requests_count(options[:current_user]) end expose :priority do |label, options| -- cgit v1.2.1 From 9dbb0417ba683ff220ba62ecb79e6375496ad79d Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 8 Nov 2016 09:28:52 +0100 Subject: Grapify the merge request API --- lib/api/merge_requests.rb | 272 +++++++++++++++++++--------------------------- 1 file changed, 110 insertions(+), 162 deletions(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index bf8504e1101..f9720786e63 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -1,8 +1,12 @@ module API - # MergeRequest API class MergeRequests < Grape::API + DEPRECATION_MESSAGE = 'This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze + before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do helpers do def handle_merge_request_errors!(errors) @@ -18,27 +22,27 @@ module API render_api_error!(errors, 400) end + + params :optional_params do + optional :description, type: String, desc: 'The description of the merge request' + optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' + optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' + optional :labels, type: String, desc: 'Comma-separated list of label names' + end end - # List merge requests - # - # Parameters: - # id (required) - The ID of a project - # iid (optional) - Return the project MR having the given `iid` - # state (optional) - Return requests "merged", "opened" or "closed" - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example: - # GET /projects/:id/merge_requests - # GET /projects/:id/merge_requests?state=opened - # GET /projects/:id/merge_requests?state=closed - # GET /projects/:id/merge_requests?order_by=created_at - # GET /projects/:id/merge_requests?order_by=updated_at - # GET /projects/:id/merge_requests?sort=desc - # GET /projects/:id/merge_requests?sort=asc - # GET /projects/:id/merge_requests?iid=42 - # + desc 'List merge requests' do + success Entities::MergeRequest + end + params do + optional :state, type: String, values: %w[opened closed merged all], default: 'all', + desc: 'Return opened, closed, merged, or all merge requests' + optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', + desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return merge requests sorted in `asc` or `desc` order.' + optional :iid, type: Integer, desc: 'The IID of the merge requests' + end get ":id/merge_requests" do authorize! :read_merge_request, user_project merge_requests = user_project.merge_requests.inc_notes_with_associations @@ -48,10 +52,10 @@ module API end merge_requests = - case params["state"] - when "opened" then merge_requests.opened - when "closed" then merge_requests.closed - when "merged" then merge_requests.merged + case params[:state] + when 'opened' then merge_requests.opened + when 'closed' then merge_requests.closed + when 'merged' then merge_requests.merged else merge_requests end @@ -59,36 +63,28 @@ module API present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user end - # Create MR - # - # Parameters: - # - # id (required) - The ID of a project - this will be the source of the merge request - # source_branch (required) - The source branch - # target_branch (required) - The target branch - # target_project_id - The target project of the merge request defaults to the :id of the project - # assignee_id - Assignee user ID - # title (required) - Title of MR - # description - Description of MR - # labels (optional) - Labels for MR as a comma-separated list - # milestone_id (optional) - Milestone ID - # - # Example: - # POST /projects/:id/merge_requests - # + desc 'Create a merge request' do + success Entities::MergeRequest + end + params do + requires :title, type: String, desc: 'The title of the merge request' + requires :source_branch, type: String, desc: 'The source branch' + requires :target_branch, type: String, desc: 'The target branch' + optional :target_project_id, type: Integer, + desc: 'The target project of the merge request defaults to the :id of the project' + use :optional_params + end post ":id/merge_requests" do authorize! :create_merge_request, user_project - required_attributes! [:source_branch, :target_branch, :title] - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description, :milestone_id] + + mr_params = declared_params # Validate label names in advance - if (errors = validate_label_params(params)).any? + if (errors = validate_label_params(mr_params)).any? render_api_error!({ labels: errors }, 400) end - attrs[:labels] = params[:labels] if params[:labels] - - merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute + merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? present merge_request, with: Entities::MergeRequest, current_user: current_user @@ -97,11 +93,10 @@ module API end end - # Delete a MR - # - # Parameters: - # id (required) - The ID of the project - # merge_request_id (required) - The MR id + desc 'Delete a merge request' + params do + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + end delete ":id/merge_requests/:merge_request_id" do merge_request = user_project.merge_requests.find_by(id: params[:merge_request_id]) @@ -112,89 +107,64 @@ module API # Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0 # Use "merge_requests/:merge_request_id/..." instead. # - [":id/merge_request/:merge_request_id", ":id/merge_requests/:merge_request_id"].each do |path| - # Show MR - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id - # + params do + requires :merge_request_id, type: Integer, desc: 'The ID of a merge request' + end + { ":id/merge_request/:merge_request_id" => :deprecated, ":id/merge_requests/:merge_request_id" => :ok }.each do |path, status| + desc 'Get a single merge request' do + if status == :deprecated + detail DEPRECATION_MESSAGE + end + success Entities::MergeRequest + end get path do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :read_merge_request, merge_request - present merge_request, with: Entities::MergeRequest, current_user: current_user end - # Show MR commits - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id/commits - # + desc 'Get the commits of a merge request' do + success Entities::RepoCommit + end get "#{path}/commits" do - merge_request = user_project.merge_requests. - find(params[:merge_request_id]) + merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :read_merge_request, merge_request present merge_request.commits, with: Entities::RepoCommit end - # Show MR changes - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of MR - # - # Example: - # GET /projects/:id/merge_requests/:merge_request_id/changes - # + desc 'Show the merge request changes' do + success Entities::MergeRequestChanges + end get "#{path}/changes" do - merge_request = user_project.merge_requests. - find(params[:merge_request_id]) + merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :read_merge_request, merge_request present merge_request, with: Entities::MergeRequestChanges, current_user: current_user end - # Update MR - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # target_branch - The target branch - # assignee_id - Assignee user ID - # title - Title of MR - # state_event - Status of MR. (close|reopen|merge) - # description - Description of MR - # labels (optional) - Labels for a MR as a comma-separated list - # milestone_id (optional) - Milestone ID - # Example: - # PUT /projects/:id/merge_requests/:merge_request_id - # + desc 'Update a merge request' do + success Entities::MergeRequest + end + params do + optional :title, type: String, desc: 'The title of the merge request' + optional :target_branch, type: String, desc: 'The target branch' + optional :state_event, type: String, values: %w[close reopen merge], + desc: 'Status of the merge request' + use :optional_params + at_least_one_of :title, :target_branch, :description, :assignee_id, + :milestone_id, :labels, :state_event + end put path do - attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description, :milestone_id] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) + merge_request = user_project.merge_requests.find(params.delete(:merge_request_id)) authorize! :update_merge_request, merge_request - # Ensure source_branch is not specified - if params[:source_branch].present? - render_api_error!('Source branch cannot be changed', 400) - end + mr_params = declared_params(include_missing: false) # Validate label names in advance - if (errors = validate_label_params(params)).any? + if (errors = validate_label_params(mr_params)).any? render_api_error!({ labels: errors }, 400) end - attrs[:labels] = params[:labels] if params[:labels] - - merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) + merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? present merge_request, with: Entities::MergeRequest, current_user: current_user @@ -203,18 +173,17 @@ module API end end - # Merge MR - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # merge_commit_message (optional) - Custom merge commit message - # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible - # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds - # sha (optional) - When present, must have the HEAD SHA of the source branch - # Example: - # PUT /projects/:id/merge_requests/:merge_request_id/merge - # + desc 'Merge a merge request' do + success Entities::MergeRequest + end + params do + optional :merge_commit_message, type: String, desc: 'Custom merge commit message' + optional :should_remove_source_branch, type: Boolean, + desc: 'When true, the source branch will be deleted if possible' + optional :merge_when_build_succeeds, type: Boolean, + desc: 'When true, this merge request will be merged when the build succeeds' + optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' + end put "#{path}/merge" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -235,7 +204,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if to_boolean(params[:merge_when_build_succeeds]) && merge_request.pipeline && merge_request.pipeline.active? + if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else @@ -246,11 +215,9 @@ module API present merge_request, with: Entities::MergeRequest, current_user: current_user end - # Cancel Merge if Merge When build succeeds is enabled - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # + desc 'Cancel merge if "Merge when build succeeds" is enabled' do + success Entities::MergeRequest + end post "#{path}/cancel_merge_when_build_succeeds" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -259,17 +226,10 @@ module API ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) end - # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0. - # Use GET "/projects/:id/merge_requests/:merge_request_id/notes" instead - # - # Get a merge request's comments - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # Examples: - # GET /projects/:id/merge_requests/:merge_request_id/comments - # + desc 'Get the comments of a merge request' do + detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' + success Entities::MRNote + end get "#{path}/comments" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -278,23 +238,15 @@ module API present paginate(merge_request.notes.fresh), with: Entities::MRNote end - # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0. - # Use POST "/projects/:id/merge_requests/:merge_request_id/notes" instead - # - # Post comment to merge request - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # note (required) - Text of comment - # Examples: - # POST /projects/:id/merge_requests/:merge_request_id/comments - # + desc 'Post a comment to a merge request' do + detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' + success Entities::MRNote + end + params do + requires :note, type: String, desc: 'The text of the comment' + end post "#{path}/comments" do - required_attributes! [:note] - merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :create_note, merge_request opts = { @@ -312,13 +264,9 @@ module API end end - # List issues that will close on merge - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # Examples: - # GET /projects/:id/merge_requests/:merge_request_id/closes_issues + desc 'List issues that will be closed on merge' do + success Entities::MRNote + end get "#{path}/closes_issues" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) -- cgit v1.2.1 From ac12c1d271f8ba365f3b38baad3940e7244bbb96 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 7 Nov 2016 16:54:39 +0100 Subject: Grapify the group API --- lib/api/groups.rb | 156 ++++++++++++++++++++++++++---------------------------- 1 file changed, 74 insertions(+), 82 deletions(-) (limited to 'lib/api') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 40644fc2adf..3f57b9ab5bc 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,118 +1,111 @@ module API - # groups API class Groups < Grape::API before { authenticate! } + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the group' + optional :visibility_level, type: Integer, desc: 'The visibility level of the group' + 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' + end + end + resource :groups do - # Get a groups list - # - # Parameters: - # skip_groups (optional) - Array of group ids to exclude from list - # all_available (optional, boolean) - Show all group that you have access to - # Example Request: - # GET /groups + desc 'Get a groups list' do + success Entities::Group + end + params do + optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list' + optional :all_available, type: Boolean, desc: 'Show all group that you have access to' + optional :search, type: String, desc: 'Search for a specific group' + end get do - @groups = if current_user.admin - Group.all - elsif params[:all_available] - GroupsFinder.new.execute(current_user) - else - current_user.groups - end + groups = if current_user.admin + Group.all + elsif params[:all_available] + GroupsFinder.new.execute(current_user) + else + current_user.groups + end - @groups = @groups.search(params[:search]) if params[:search].present? - @groups = @groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? - @groups = paginate @groups - present @groups, with: Entities::Group + groups = groups.search(params[:search]) if params[:search].present? + groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? + present paginate(groups), with: Entities::Group end - # Get list of owned groups for authenticated user - # - # Example Request: - # GET /groups/owned + desc 'Get list of owned groups for authenticated user' do + success Entities::Group + end get '/owned' do - @groups = current_user.owned_groups - @groups = paginate @groups - present @groups, with: Entities::Group, user: current_user + groups = current_user.owned_groups + present paginate(groups), with: Entities::Group, user: current_user end - # Create group. Available only for users who can create groups. - # - # Parameters: - # name (required) - The name of the group - # path (required) - The path of the group - # description (optional) - The description of the group - # visibility_level (optional) - The visibility level of the group - # lfs_enabled (optional) - Enable/disable LFS for the projects in this group - # request_access_enabled (optional) - Allow users to request member access - # Example Request: - # POST /groups + desc 'Create a group. Available only for users who can create groups.' do + success Entities::Group + end + params do + requires :name, type: String, desc: 'The name of the group' + requires :path, type: String, desc: 'The path of the group' + use :optional_params + end post do authorize! :create_group - required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] - @group = Group.new(attrs) + group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute - if @group.save - @group.add_owner(current_user) - present @group, with: Entities::Group + if group.persisted? + present group, with: Entities::Group else - render_api_error!("Failed to save group #{@group.errors.messages}", 400) + render_api_error!("Failed to save group #{group.errors.messages}", 400) end end + end - # Update group. Available only for users who can administrate groups. - # - # Parameters: - # id (required) - The ID of a group - # path (optional) - The path of the group - # description (optional) - The description of the group - # visibility_level (optional) - The visibility level of the group - # lfs_enabled (optional) - Enable/disable LFS for the projects in this group - # request_access_enabled (optional) - Allow users to request member access - # Example Request: - # PUT /groups/:id + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups do + desc 'Update a group. Available only for users who can administrate groups.' do + success Entities::Group + end + params do + optional :name, type: String, desc: 'The name of the group' + optional :path, type: String, desc: 'The path of the group' + use :optional_params + at_least_one_of :name, :path, :description, :visibility_level, + :lfs_enabled, :request_access_enabled + end put ':id' do group = find_group(params[:id]) authorize! :admin_group, group - attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] - - if ::Groups::UpdateService.new(group, current_user, attrs).execute + if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute present group, with: Entities::GroupDetail else render_validation_error!(group) end end - # Get a single group, with containing projects - # - # Parameters: - # id (required) - The ID of a group - # Example Request: - # GET /groups/:id + desc 'Get a single group, with containing projects.' do + success Entities::GroupDetail + end get ":id" do group = find_group(params[:id]) present group, with: Entities::GroupDetail end - # Remove group - # - # Parameters: - # id (required) - The ID of a group - # Example Request: - # DELETE /groups/:id + desc 'Remove a group.' delete ":id" do group = find_group(params[:id]) authorize! :admin_group, group DestroyGroupService.new(group, current_user).execute end - # Get a list of projects in this group - # - # Example Request: - # GET /groups/:id/projects + desc 'Get a list of projects in this group.' do + success Entities::Project + end get ":id/projects" do group = find_group(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) @@ -120,13 +113,12 @@ module API present projects, with: Entities::Project, user: current_user end - # Transfer a project to the Group namespace - # - # Parameters: - # id - group id - # project_id - project id - # Example Request: - # POST /groups/:id/projects/:project_id + desc 'Transfer a project to the group namespace. Available only for admin.' do + success Entities::GroupDetail + end + params do + requires :project_id, type: String, desc: 'The ID of the project' + end post ":id/projects/:project_id" do authenticated_as_admin! group = Group.find_by(id: params[:id]) @@ -134,7 +126,7 @@ module API result = ::Projects::TransferService.new(project, current_user).execute(group) if result - present group + present group, with: Entities::GroupDetail else render_api_error!("Failed to transfer project #{project.errors.messages}", 400) end -- cgit v1.2.1 From ff8194e0ec16092419862011d7cc048baa149c42 Mon Sep 17 00:00:00 2001 From: Dmitry Poray Date: Mon, 7 Nov 2016 20:11:54 +0300 Subject: Add ref parameter for triggerring builds with gitlab webhook from other project. --- lib/api/triggers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 9a4f1cd342f..569598fbd2c 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -12,7 +12,7 @@ module API requires :token, type: String, desc: 'The unique token of trigger' optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end - post ":id/trigger/builds" do + post ":id/(ref/:ref/)trigger/builds" do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger -- cgit v1.2.1 From 5703d6afeee043b633f05c46e9152d21c7b50d4f Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 15:26:27 +0100 Subject: Grapify the notes API --- lib/api/notes.rb | 124 +++++++++++++++++++++++++------------------------------ 1 file changed, 57 insertions(+), 67 deletions(-) (limited to 'lib/api') diff --git a/lib/api/notes.rb b/lib/api/notes.rb index c5c214d4d13..b255b47742b 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -5,23 +5,23 @@ module API NOTEABLE_TYPES = [Issue, MergeRequest, Snippet] + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do NOTEABLE_TYPES.each do |noteable_type| noteables_str = noteable_type.to_s.underscore.pluralize - noteable_id_str = "#{noteable_type.to_s.underscore}_id" - - # Get a list of project +noteable+ notes - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # Example Request: - # GET /projects/:id/issues/:noteable_id/notes - # GET /projects/:id/snippets/:noteable_id/notes - get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do - @noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym]) - - if can?(current_user, noteable_read_ability_name(@noteable), @noteable) + + desc 'Get a list of project +noteable+ notes' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + end + get ":id/#{noteables_str}/:noteable_id/notes" do + noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + + if can?(current_user, noteable_read_ability_name(noteable), noteable) # We exclude notes that are cross-references and that cannot be viewed # by the current user. By doing this exclusion at this level and not # at the DB query level (which we cannot in that case), the current @@ -31,7 +31,7 @@ module API # 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(noteable.notes). reject { |n| n.cross_reference_not_visible_for?(current_user) } present notes, with: Entities::Note else @@ -39,44 +39,40 @@ module API end end - # Get a single +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # note_id (required) - The ID of a note - # Example Request: - # GET /projects/:id/issues/:noteable_id/notes/:note_id - # GET /projects/:id/snippets/:noteable_id/notes/:note_id - get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do - @noteable = user_project.send(noteables_str.to_sym).find(params[noteable_id_str.to_sym]) - @note = @noteable.notes.find(params[:note_id]) - can_read_note = can?(current_user, noteable_read_ability_name(@noteable), @noteable) && !@note.cross_reference_not_visible_for?(current_user) + desc 'Get a single +noteable+ note' do + success Entities::Note + end + params do + requires :note_id, type: Integer, desc: 'The ID of a note' + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + end + get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do + noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + note = noteable.notes.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 - present @note, with: Entities::Note + present note, with: Entities::Note else not_found!("Note") end end - # Create a new +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # body (required) - The content of a note - # created_at (optional) - The date - # Example Request: - # POST /projects/:id/issues/:noteable_id/notes - # POST /projects/:id/snippets/:noteable_id/notes - post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + desc 'Create a new +noteable+ note' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :body, type: String, desc: 'The content of a note' + optional :created_at, type: String, desc: 'The creation date of the note' + end + post ":id/#{noteables_str}/:noteable_id/notes" do required_attributes! [:body] opts = { note: params[:body], noteable_type: noteables_str.classify, - noteable_id: params[noteable_id_str] + noteable_id: params[:noteable_id] } if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) @@ -92,19 +88,15 @@ module API end end - # Modify existing +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue or snippet - # node_id (required) - The ID of a note - # body (required) - New content of a note - # Example Request: - # PUT /projects/:id/issues/:noteable_id/notes/:note_id - # PUT /projects/:id/snippets/:noteable_id/notes/:node_id - put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do - required_attributes! [:body] - + desc 'Update an existing +noteable+ note' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :note_id, type: Integer, desc: 'The ID of a note' + requires :body, type: String, desc: 'The content of a note' + end + put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do note = user_project.notes.find(params[:note_id]) authorize! :admin_note, note @@ -113,25 +105,23 @@ module API note: params[:body] } - @note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note) + note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note) - if @note.valid? - present @note, with: Entities::Note + if note.valid? + present note, with: Entities::Note else render_api_error!("Failed to save note #{note.errors.messages}", 400) end end - # Delete a +noteable+ note - # - # Parameters: - # id (required) - The ID of a project - # noteable_id (required) - The ID of an issue, MR, or snippet - # node_id (required) - The ID of a note - # Example Request: - # DELETE /projects/:id/issues/:noteable_id/notes/:note_id - # DELETE /projects/:id/snippets/:noteable_id/notes/:node_id - delete ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + desc 'Delete a +noteable+ note' do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :note_id, type: Integer, desc: 'The ID of a note' + end + delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do note = user_project.notes.find(params[:note_id]) authorize! :admin_note, note -- cgit v1.2.1 From ef3be00a0297dfa31002616df6ee49a0e2132cb7 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 16 Nov 2016 12:46:07 +0100 Subject: Defer saving project services to the database if there are no user changes --- lib/api/helpers.rb | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3c9d7b1aaef..b4e0457f773 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -81,25 +81,10 @@ module API end def project_service - @project_service ||= begin - underscored_service = params[:service_slug].underscore - - if Service.available_services_names.include?(underscored_service) - user_project.build_missing_services - - service_method = "#{underscored_service}_service" - - send_service(service_method) - end - end - + @project_service ||= user_project.find_or_initialize_service(params[:service_slug].underscore) @project_service || not_found!("Service") end - def send_service(service_method) - user_project.send(service_method) - end - def service_attributes @service_attributes ||= project_service.fields.inject([]) do |arr, hash| arr << hash[:name].to_sym -- cgit v1.2.1 From 1c994dbc05c147714479288126742f3fee158fd8 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 15 Nov 2016 15:02:44 +0000 Subject: Fix POST /internal/allowed to cope with gitlab-shell v4.0.0 project paths gitlab-shell v3.6.6 would give project paths like so: * namespace/project gitlab-shell v4.0.0 can give project paths like so: * /namespace1/namespace2/project * /namespace/project * /path/to/repository/storage/namespace1/namespace2/project * /path/to/repository/storage/namespace/project --- lib/api/helpers/internal_helpers.rb | 57 +++++++++++++++++++++++++++++++++++++ lib/api/internal.rb | 38 ++----------------------- 2 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 lib/api/helpers/internal_helpers.rb (limited to 'lib/api') diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb new file mode 100644 index 00000000000..eb223c1101d --- /dev/null +++ b/lib/api/helpers/internal_helpers.rb @@ -0,0 +1,57 @@ +module API + module Helpers + module InternalHelpers + # Project paths may be any of the following: + # * /repository/storage/path/namespace/project + # * /namespace/project + # * namespace/project + # + # In addition, they may have a '.git' extension and multiple namespaces + # + # Transform all these cases to 'namespace/project' + def clean_project_path(project_path, storage_paths = Repository.storages.values) + project_path = project_path.sub(/\.git\z/, '') + + storage_paths.each do |storage_path| + storage_path = File.expand_path(storage_path) + + if project_path.start_with?(storage_path) + project_path = project_path.sub(storage_path, '') + break + end + end + + project_path.sub(/\A\//, '') + end + + def project_path + @project_path ||= clean_project_path(params[:project]) + end + + def wiki? + @wiki ||= project_path.end_with?('.wiki') && + !Project.find_with_namespace(project_path) + end + + def project + @project ||= begin + # Check for *.wiki repositories. + # Strip out the .wiki from the pathname before finding the + # project. This applies the correct project permissions to + # the wiki repository as well. + project_path.chomp!('.wiki') if wiki? + + Project.find_with_namespace(project_path) + end + end + + def ssh_authentication_abilities + [ + :read_project, + :download_code, + :push_code + ] + end + end + end +end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ccf181402f9..7087ce11401 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -3,6 +3,8 @@ module API class Internal < Grape::API before { authenticate_by_gitlab_shell_token! } + helpers ::API::Helpers::InternalHelpers + namespace 'internal' do # Check if git command is allowed to project # @@ -14,42 +16,6 @@ module API # ref - branch name # forced_push - forced_push # protocol - Git access protocol being used, e.g. HTTP or SSH - # - - helpers do - def project_path - @project_path ||= begin - project_path = params[:project].sub(/\.git\z/, '') - Repository.remove_storage_from_path(project_path) - end - end - - def wiki? - @wiki ||= project_path.end_with?('.wiki') && - !Project.find_with_namespace(project_path) - end - - def project - @project ||= begin - # Check for *.wiki repositories. - # Strip out the .wiki from the pathname before finding the - # project. This applies the correct project permissions to - # the wiki repository as well. - project_path.chomp!('.wiki') if wiki? - - Project.find_with_namespace(project_path) - end - end - - def ssh_authentication_abilities - [ - :read_project, - :download_code, - :push_code - ] - end - end - post "/allowed" do status 200 -- cgit v1.2.1 From b34c063ec428f1fd890a357a3f8ac7c129ee4c46 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 1 Nov 2016 14:06:05 -0200 Subject: Pass project to Entities::Label to check if user is subscribed --- lib/api/entities.rb | 2 +- lib/api/subscriptions.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 6e370e961c4..e7042677635 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -454,7 +454,7 @@ module API end expose :subscribed do |label, options| - label.subscribed?(options[:current_user]) + label.subscribed?(options[:current_user], options[:project]) end end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 00a79c24f96..10749b34004 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -24,11 +24,11 @@ module API post ":id/#{type}/:subscribable_id/subscription" do resource = instance_exec(params[:subscribable_id], &finder) - if resource.subscribed?(current_user) + if resource.subscribed?(current_user, user_project) not_modified! else - resource.subscribe(current_user) - present resource, with: entity_class, current_user: current_user + resource.subscribe(current_user, user_project) + present resource, with: entity_class, current_user: current_user, project: user_project end end @@ -38,11 +38,11 @@ module API delete ":id/#{type}/:subscribable_id/subscription" do resource = instance_exec(params[:subscribable_id], &finder) - if !resource.subscribed?(current_user) + if !resource.subscribed?(current_user, user_project) not_modified! else - resource.unsubscribe(current_user) - present resource, with: entity_class, current_user: current_user + resource.unsubscribe(current_user, user_project) + present resource, with: entity_class, current_user: current_user, project: user_project end end end -- cgit v1.2.1 From 0c052f116c9e093936847280e833ca8985d2d94c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 4 Nov 2016 16:19:08 -0200 Subject: Remove default value for `project` argument on subscribable concern --- lib/api/entities.rb | 4 ++-- lib/api/issues.rb | 10 +++++----- lib/api/merge_requests.rb | 10 +++++----- lib/api/milestones.rb | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e7042677635..33cb6fd3704 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -218,7 +218,7 @@ module API expose :assignee, :author, using: Entities::UserBasic expose :subscribed do |issue, options| - issue.subscribed?(options[:current_user]) + issue.subscribed?(options[:current_user], options[:project] || issue.project) end expose :user_notes_count expose :upvotes, :downvotes @@ -248,7 +248,7 @@ module API expose :diff_head_sha, as: :sha expose :merge_commit_sha expose :subscribed do |merge_request, options| - merge_request.subscribed?(options[:current_user]) + merge_request.subscribed?(options[:current_user], options[:project]) end expose :user_notes_count expose :should_remove_source_branch?, as: :should_remove_source_branch diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c9689e6f8ef..eea5b91d4f9 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -120,7 +120,7 @@ module API issues = issues.reorder(issuable_order_by => issuable_sort) - present paginate(issues), with: Entities::Issue, current_user: current_user + present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project end # Get a single project issue @@ -132,7 +132,7 @@ module API # GET /projects/:id/issues/:issue_id get ":id/issues/:issue_id" do @issue = find_project_issue(params[:issue_id]) - present @issue, with: Entities::Issue, current_user: current_user + present @issue, with: Entities::Issue, current_user: current_user, project: user_project end # Create a new project issue @@ -174,7 +174,7 @@ module API end if issue.valid? - present issue, with: Entities::Issue, current_user: current_user + present issue, with: Entities::Issue, current_user: current_user, project: user_project else render_validation_error!(issue) end @@ -217,7 +217,7 @@ module API issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) if issue.valid? - present issue, with: Entities::Issue, current_user: current_user + present issue, with: Entities::Issue, current_user: current_user, project: user_project else render_validation_error!(issue) end @@ -239,7 +239,7 @@ module API begin issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) - present issue, with: Entities::Issue, current_user: current_user + present issue, with: Entities::Issue, current_user: current_user, project: user_project rescue ::Issues::MoveService::MoveError => error render_api_error!(error.message, 400) end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index f9720786e63..4176c7eec06 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -60,7 +60,7 @@ module API end merge_requests = merge_requests.reorder(issuable_order_by => issuable_sort) - present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user + present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Create a merge request' do @@ -87,7 +87,7 @@ module API merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project else handle_merge_request_errors! merge_request.errors end @@ -120,7 +120,7 @@ module API get path do merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :read_merge_request, merge_request - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Get the commits of a merge request' do @@ -167,7 +167,7 @@ module API merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project else handle_merge_request_errors! merge_request.errors end @@ -212,7 +212,7 @@ module API execute(merge_request) end - present merge_request, with: Entities::MergeRequest, current_user: current_user + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end desc 'Cancel merge if "Merge when build succeeds" is enabled' do diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index ba4a84275bc..937c118779d 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -114,7 +114,7 @@ module API } issues = IssuesFinder.new(current_user, finder_params).execute - present paginate(issues), with: Entities::Issue, current_user: current_user + present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project end end end -- cgit v1.2.1 From 79122896318ae50c835418888f7f781fa2e463f4 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 17 Nov 2016 12:41:48 +0000 Subject: Allow sorting groups in API Allow `order_by` and `sort` parameters to `/api/v3/groups.json`. At present, only ordering by name and path is supported, and the default sort is name ascending (alphabetical order). --- lib/api/groups.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/api') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 3f57b9ab5bc..48ad3b80ae0 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -19,6 +19,8 @@ module API optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list' optional :all_available, type: Boolean, desc: 'Show all group that you have access to' optional :search, type: String, desc: 'Search for a specific group' + optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path' + optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' end get do groups = if current_user.admin @@ -31,6 +33,8 @@ module API groups = groups.search(params[:search]) if params[:search].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? + groups = groups.reorder(params[:order_by] => params[:sort].to_sym) + present paginate(groups), with: Entities::Group end -- cgit v1.2.1 From 53271b486d296fae2e290d6948a05aeb47dbea89 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 14 Nov 2016 15:10:35 +0100 Subject: Make chat authorization to work [ci skip] --- lib/api/services.rb | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index fc8598daa32..b4b3bb6e41a 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -1,10 +1,10 @@ module API # Projects API class Services < Grape::API - before { authenticate! } - before { authorize_admin_project } - resource :projects do + before { authenticate! } + before { authorize_admin_project } + # Set service for project # # Example Request: @@ -59,5 +59,28 @@ module API present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin? end end + + resource :projects do + post ':id/services/:service_slug/trigger' do + project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + + underscored_service = params[:service_slug].underscore + + not_found!('Service') unless Service.available_services_names.include?(underscored_service) + service_method = "#{underscored_service}_service" + + service = project.public_send(service_method) + + result = if service.try(:active?) && service.respond_to?(:trigger) + service.trigger(params) + end + + if result + present result, status: result[:status] || 200 + else + not_found!('Service') + end + end + end end end -- cgit v1.2.1 From d4def9cbcd664b7067e7f9f4ea8be54463bd1d50 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 17 Nov 2016 12:06:45 +0100 Subject: Incorporate feedback, improve presenter class [ci skip] --- lib/api/services.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index b4b3bb6e41a..094fca49c28 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -61,6 +61,10 @@ module API end resource :projects do + + desc 'Trigger a slash command' do + detail 'Added in GitLab 8.13' + end post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) @@ -71,9 +75,7 @@ module API service = project.public_send(service_method) - result = if service.try(:active?) && service.respond_to?(:trigger) - service.trigger(params) - end + result = service.try(:active?) && service.try(:trigger, params) if result present result, status: result[:status] || 200 -- cgit v1.2.1 From 166ee0965bacc20e2ad1187321654499a9b0f825 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 17 Nov 2016 21:27:12 +0100 Subject: More refactoring, push present to base command --- lib/api/services.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index 094fca49c28..b0a94508d10 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -61,7 +61,6 @@ module API end resource :projects do - desc 'Trigger a slash command' do detail 'Added in GitLab 8.13' end @@ -78,7 +77,8 @@ module API result = service.try(:active?) && service.try(:trigger, params) if result - present result, status: result[:status] || 200 + status result[:status] || 200 + present result else not_found!('Service') end -- cgit v1.2.1 From 0d04724fa1cd670124b8ad9a3860bfa476c50f99 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 18 Nov 2016 10:00:40 +0100 Subject: More coverage on service level --- lib/api/helpers.rb | 9 +++++++++ lib/api/services.rb | 7 +------ 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 84cc9200d1b..d6526ec4fdc 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -90,6 +90,15 @@ module API @project_service || not_found!("Service") end + def service_by_slug(project, slug) + underscored_service = slug.underscore + + not_found!('Service') unless Service.available_services_names.include?(underscored_service) + service_method = "#{underscored_service}_service" + + service = project.public_send(service_method) + end + def service_attributes @service_attributes ||= project_service.fields.inject([]) do |arr, hash| arr << hash[:name].to_sym diff --git a/lib/api/services.rb b/lib/api/services.rb index b0a94508d10..163187d450d 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -67,12 +67,7 @@ module API post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) - underscored_service = params[:service_slug].underscore - - not_found!('Service') unless Service.available_services_names.include?(underscored_service) - service_method = "#{underscored_service}_service" - - service = project.public_send(service_method) + service = service_by_slug(project, params[:service_slug]) result = service.try(:active?) && service.try(:trigger, params) -- cgit v1.2.1 From e971009923b59f1d93a085eb0993e6ebabe03c19 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 17 Nov 2016 16:05:57 +0100 Subject: Grapify the repository API --- lib/api/repositories.rb | 97 ++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 58 deletions(-) (limited to 'lib/api') diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 0bb2f74809a..c287ee34a68 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -1,11 +1,13 @@ require 'mime/types' module API - # Projects API class Repositories < Grape::API before { authenticate! } before { authorize! :download_code, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do helpers do def handle_project_member_errors(errors) @@ -16,43 +18,35 @@ module API end end - # Get a project repository tree - # - # Parameters: - # id (required) - The ID of a project - # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used - # recursive (optional) - Used to get a recursive tree - # Example Request: - # GET /projects/:id/repository/tree + desc 'Get a project repository tree' do + success Entities::RepoTreeObject + end + params do + optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' + optional :path, type: String, desc: 'The path of the tree' + optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree' + end get ':id/repository/tree' do ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil - recursive = to_boolean(params[:recursive]) commit = user_project.commit(ref) not_found!('Tree') unless commit - tree = user_project.repository.tree(commit.id, path, recursive: recursive) + tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive]) present tree.sorted_entries, with: Entities::RepoTreeObject end - # Get a raw file contents - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit or branch name - # filepath (required) - The path to the file to display - # Example Request: - # GET /projects/:id/repository/blobs/:sha + desc 'Get a raw file contents' + params do + requires :sha, type: String, desc: 'The commit, branch name, or tag name' + requires :filepath, type: String, desc: 'The path to the file to display' + end get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do - required_attributes! [:filepath] - - ref = params[:sha] - repo = user_project.repository - commit = repo.commit(ref) + commit = repo.commit(params[:sha]) not_found! "Commit" unless commit blob = Gitlab::Git::Blob.find(repo, commit.id, params[:filepath]) @@ -61,20 +55,15 @@ module API send_git_blob repo, blob end - # Get a raw blob contents by blob sha - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The blob's sha - # Example Request: - # GET /projects/:id/repository/raw_blobs/:sha + desc 'Get a raw blob contents by blob sha' + params do + requires :sha, type: String, desc: 'The commit, branch name, or tag name' + end get ':id/repository/raw_blobs/:sha' do - ref = params[:sha] - repo = user_project.repository begin - blob = Gitlab::Git::Blob.raw(repo, ref) + blob = Gitlab::Git::Blob.raw(repo, params[:sha]) rescue not_found! 'Blob' end @@ -84,15 +73,12 @@ module API send_git_blob repo, blob end - # Get a an archive of the repository - # - # Parameters: - # id (required) - The ID of a project - # sha (optional) - the commit sha to download defaults to the tip of the default branch - # Example Request: - # GET /projects/:id/repository/archive - get ':id/repository/archive', - requirements: { format: Gitlab::Regex.archive_formats_regex } do + desc 'Get an archive of the repository' + params do + optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' + optional :format, type: String, desc: 'The archive format' + end + get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do authorize! :download_code, user_project begin @@ -102,27 +88,22 @@ module API end end - # Compare two branches, tags or commits - # - # Parameters: - # id (required) - The ID of a project - # from (required) - the commit sha or branch name - # to (required) - the commit sha or branch name - # Example Request: - # GET /projects/:id/repository/compare?from=master&to=feature + desc 'Compare two branches, tags, or commits' do + success Entities::Compare + end + params do + requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison' + requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison' + end get ':id/repository/compare' do authorize! :download_code, user_project - required_attributes! [:from, :to] compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to]) present compare, with: Entities::Compare end - # Get repository contributors - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/contributors + desc 'Get repository contributors' do + success Entities::Contributor + end get ':id/repository/contributors' do authorize! :download_code, user_project -- cgit v1.2.1 From f749fb7fe0574d07eeb38561b9af62754e518281 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 18 Nov 2016 11:38:54 +0100 Subject: Improve style, add more tests --- lib/api/helpers.rb | 13 ++----------- lib/api/services.rb | 6 ++++-- 2 files changed, 6 insertions(+), 13 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index d6526ec4fdc..2c593dbb4ea 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -85,20 +85,11 @@ module API end end - def project_service - @project_service ||= user_project.find_or_initialize_service(params[:service_slug].underscore) + def project_service(project = user_project) + @project_service ||= project.find_or_initialize_service(params[:service_slug].underscore) @project_service || not_found!("Service") end - def service_by_slug(project, slug) - underscored_service = slug.underscore - - not_found!('Service') unless Service.available_services_names.include?(underscored_service) - service_method = "#{underscored_service}_service" - - service = project.public_send(service_method) - end - def service_attributes @service_attributes ||= project_service.fields.inject([]) do |arr, hash| arr << hash[:name].to_sym diff --git a/lib/api/services.rb b/lib/api/services.rb index 163187d450d..e3c6a998631 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -67,7 +67,9 @@ module API post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) - service = service_by_slug(project, params[:service_slug]) + not_found! unless project + + service = project_service(project) result = service.try(:active?) && service.try(:trigger, params) @@ -75,7 +77,7 @@ module API status result[:status] || 200 present result else - not_found!('Service') + not_found! end end end -- cgit v1.2.1 From dd826a5f20837f33263c658e41a4def0fc932069 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 18 Nov 2016 12:08:30 +0100 Subject: Return a consistent not found message This prevents leakage of project names on an endpoint which is unauthenticated and thus open to the world. --- lib/api/services.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index e3c6a998631..4d23499aa39 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -67,7 +67,8 @@ module API post ':id/services/:service_slug/trigger' do project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) - not_found! unless project + # This is not accurate, but done to prevent leakage of the project names + not_found!('Service') unless project service = project_service(project) @@ -77,7 +78,7 @@ module API status result[:status] || 200 present result else - not_found! + not_found!('Service') end end end -- cgit v1.2.1 From fbfc7523cb0119ac3a0d02cd04dc12e453ad3ad6 Mon Sep 17 00:00:00 2001 From: Ido Leibovich Date: Mon, 31 Oct 2016 22:38:24 +0200 Subject: Add api endpoint for creating a pipeline Add a new endpoint in the new API for creating a new pipeline, and return the details of that pipeline. --- lib/api/pipelines.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'lib/api') diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 2a0c8e1f2c0..e69b0569612 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -22,6 +22,27 @@ module API pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope]) present paginate(pipelines), with: Entities::Pipeline end + + desc 'Create a new pipeline' do + detail 'This feature was introduced in GitLab 8.14' + success Entities::Pipeline + end + params do + requires :ref, type: String, desc: 'Reference' + end + post ':id/pipeline' do + authorize! :create_pipeline, user_project + + new_pipeline = Ci::CreatePipelineService.new(user_project, + current_user, + declared_params(include_missing: false)) + .execute(ignore_skip_ci: true, save_on_errors: false) + if new_pipeline.persisted? + present new_pipeline, with: Entities::Pipeline + else + render_validation_error!(new_pipeline) + end + end desc 'Gets a specific pipeline for the project' do detail 'This feature was introduced in GitLab 8.11' -- cgit v1.2.1 From 01f238893aaf8f5aa24eec7e33edc479d4e1315d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 27 Oct 2016 14:04:43 +0200 Subject: Rename MWBS service to Merge When Pipeline Succeeds --- lib/api/merge_requests.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4176c7eec06..8c55f09a9ce 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -205,11 +205,13 @@ module API } if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? - ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) + ::MergeRequests::MergeWhenPipelineSucceedsService + .new(merge_request.target_project, current_user, merge_params) + .execute(merge_request) else - ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). - execute(merge_request) + ::MergeRequests::MergeService + .new(merge_request.target_project, current_user, merge_params) + .execute(merge_request) end present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project @@ -223,7 +225,9 @@ module API unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) - ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) + ::MergeRequest::MergeWhenPipelineSucceedsService + .new(merge_request.target_project, current_user) + .cancel(merge_request) end desc 'Get the comments of a merge request' do -- cgit v1.2.1 From 4cb3c0b404f256be8bc80430eaf25b468e30ee44 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 27 Oct 2016 10:20:06 +0200 Subject: Grapify the users API --- lib/api/users.rb | 508 +++++++++++++++++++++++++++---------------------------- 1 file changed, 250 insertions(+), 258 deletions(-) (limited to 'lib/api') diff --git a/lib/api/users.rb b/lib/api/users.rb index 298c401a816..c07539194ed 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -4,89 +4,93 @@ module API before { authenticate! } resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do - # Get a users list - # - # Example Request: - # GET /users - # GET /users?search=Admin - # GET /users?username=root - # GET /users?active=true - # GET /users?external=true - # GET /users?blocked=true + helpers do + params :optional_attributes do + optional :skype, type: String, desc: 'The Skype username' + optional :linkedin, type: String, desc: 'The LinkedIn username' + optional :twitter, type: String, desc: 'The Twitter username' + optional :website_url, type: String, desc: 'The website of the user' + optional :organization, type: String, desc: 'The organization of the user' + optional :projects_limit, type: Integer, desc: 'The number of projects a user can create' + optional :extern_uid, type: Integer, desc: 'The external authentication provider UID' + optional :provider, type: String, desc: 'The external provider' + optional :bio, type: String, desc: 'The biography of the user' + 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 :confirm, type: Boolean, desc: 'Flag indicating the account needs to be confirmed' + optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' + all_or_none_of :extern_uid, :provider + end + end + + desc 'Get the list of users' do + success Entities::UserBasic + end + params do + optional :username, type: String, desc: 'Get a single user with a specific username' + optional :search, type: String, desc: 'Search for a username' + optional :active, type: Boolean, default: false, desc: 'Filters only active users' + optional :external, type: Boolean, default: false, desc: 'Filters only external users' + optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' + end get do unless can?(current_user, :read_users_list, nil) render_api_error!("Not authorized.", 403) end if params[:username].present? - @users = User.where(username: params[:username]) + users = User.where(username: params[:username]) else - @users = User.all - @users = @users.active if to_boolean(params[:active]) - @users = @users.search(params[:search]) if params[:search].present? - @users = @users.blocked if to_boolean(params[:blocked]) - @users = @users.external if to_boolean(params[:external]) && current_user.is_admin? - @users = paginate @users + users = User.all + users = users.active if params[:active] + users = users.search(params[:search]) if params[:search].present? + users = users.blocked if params[:blocked] + users = users.external if params[:external] && current_user.is_admin? end - if current_user.is_admin? - present @users, with: Entities::UserFull - else - present @users, with: Entities::UserBasic - end + entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic + present paginate(users), with: entity end - # Get a single user - # - # Parameters: - # id (required) - The ID of a user - # Example Request: - # GET /users/:id + desc 'Get a single user' do + success Entities::UserBasic + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end get ":id" do - @user = User.find(params[:id]) + user = User.find_by(id: params[:id]) + not_found!('User') unless user if current_user && current_user.is_admin? - present @user, with: Entities::UserFull - elsif can?(current_user, :read_user, @user) - present @user, with: Entities::User + present user, with: Entities::UserFull + elsif can?(current_user, :read_user, user) + present user, with: Entities::User else render_api_error!("User not found.", 404) end end - # Create user. Available only for admin - # - # Parameters: - # email (required) - Email - # password (required) - Password - # name (required) - Name - # username (required) - Name - # skype - Skype ID - # linkedin - Linkedin - # twitter - Twitter account - # website_url - Website url - # organization - Organization - # projects_limit - Number of projects user can create - # extern_uid - External authentication provider UID - # provider - External provider - # bio - Bio - # location - Location of the user - # admin - User is admin - true or false (default) - # can_create_group - User can create groups - true or false - # confirm - Require user confirmation - true (default) or false - # external - Flags the user as external - true or false(default) - # Example Request: - # POST /users + desc 'Create a user. Available only for admins.' do + success Entities::UserFull + end + params do + requires :email, type: String, desc: 'The email of the user' + requires :password, type: String, desc: 'The password of the new user' + requires :name, type: String, desc: 'The name of the user' + requires :username, type: String, desc: 'The username of the user' + use :optional_attributes + end post do authenticated_as_admin! - required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external, :organization] - admin = attrs.delete(:admin) - confirm = !(attrs.delete(:confirm) =~ /(false|f|no|0)$/i) - user = User.build_user(attrs) - user.admin = admin unless admin.nil? + + # Filter out params which are used later + identity_attrs = params.slice(:provider, :extern_uid) + confirm = params.delete(:confirm) + + user = User.build_user(declared_params(include_missing: false)) user.skip_confirmation! unless confirm - identity_attrs = attributes_for_keys [:provider, :extern_uid] if identity_attrs.any? user.identities.build(identity_attrs) @@ -107,46 +111,40 @@ module API end end - # Update user. Available only for admin - # - # Parameters: - # email - Email - # name - Name - # password - Password - # skype - Skype ID - # linkedin - Linkedin - # twitter - Twitter account - # website_url - Website url - # organization - Organization - # projects_limit - Limit projects each user can create - # bio - Bio - # location - Location of the user - # admin - User is admin - true or false (default) - # can_create_group - User can create groups - true or false - # external - Flags the user as external - true or false(default) - # Example Request: - # PUT /users/:id + desc 'Update a user. Available only for admins.' do + success Entities::UserFull + end + params do + 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 :name, type: String, desc: 'The name of the user' + optional :username, type: String, desc: 'The username of the user' + use :optional_attributes + at_least_one_of :email, :password, :name, :username, :skype, :linkedin, + :twitter, :website_url, :organization, :projects_limit, + :extern_uid, :provider, :bio, :location, :admin, + :can_create_group, :confirm, :external + end put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external, :organization] - user = User.find(params[:id]) + user = User.find_by(id: params.delete(:id)) not_found!('User') unless user - admin = attrs.delete(:admin) - user.admin = admin unless admin.nil? - - conflict!('Email has already been taken') if attrs[:email] && - User.where(email: attrs[:email]). + conflict!('Email has already been taken') if params[:email] && + User.where(email: params[:email]). where.not(id: user.id).count > 0 - conflict!('Username has already been taken') if attrs[:username] && - User.where(username: attrs[:username]). + conflict!('Username has already been taken') if params[:username] && + User.where(username: params[:username]). where.not(id: user.id).count > 0 - identity_attrs = attributes_for_keys [:provider, :extern_uid] + identity_attrs = params.slice(:provider, :extern_uid) + if identity_attrs.any? identity = user.identities.find_by(provider: identity_attrs[:provider]) + if identity identity.update_attributes(identity_attrs) else @@ -155,28 +153,33 @@ module API end end - if user.update_attributes(attrs) + # Delete already handled parameters + params.delete(:extern_uid) + params.delete(:provider) + + if user.update_attributes(declared_params(include_missing: false)) present user, with: Entities::UserFull else render_validation_error!(user) end end - # Add ssh key to a specified user. Only available to admin users. - # - # Parameters: - # id (required) - The ID of a user - # key (required) - New SSH Key - # title (required) - New SSH Key's title - # Example Request: - # POST /users/:id/keys + desc 'Add an SSH key to a specified user. Available only for admins.' do + success Entities::SSHKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key, type: String, desc: 'The new SSH key' + requires :title, type: String, desc: 'The title of the new SSH key' + end post ":id/keys" do authenticated_as_admin! - required_attributes! [:title, :key] - user = User.find(params[:id]) - attrs = attributes_for_keys [:title, :key] - key = user.keys.new attrs + user = User.find_by(id: params.delete(:id)) + not_found!('User') unless user + + key = user.keys.new(declared_params(include_missing: false)) + if key.save present key, with: Entities::SSHKey else @@ -184,55 +187,55 @@ module API end end - # Get ssh keys of a specified user. Only available to admin users. - # - # Parameters: - # uid (required) - The ID of a user - # Example Request: - # GET /users/:uid/keys - get ':uid/keys' do + desc 'Get the SSH keys of a specified user. Available only for admins.' do + success Entities::SSHKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + get ':id/keys' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + + user = User.find_by(id: params[:id]) not_found!('User') unless user present user.keys, with: Entities::SSHKey end - # Delete existing ssh key of a specified user. Only available to admin - # users. - # - # Parameters: - # uid (required) - The ID of a user - # id (required) - SSH Key ID - # Example Request: - # DELETE /users/:uid/keys/:id - delete ':uid/keys/:id' do + desc 'Delete an existing SSH key from a specified user. Available only for admins.' do + success Entities::SSHKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + delete ':id/keys/:key_id' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + + user = User.find_by(id: params[:id]) not_found!('User') unless user - begin - key = user.keys.find params[:id] - key.destroy - rescue ActiveRecord::RecordNotFound - not_found!('Key') - end + key = user.keys.find_by(id: params[:key_id]) + not_found!('Key') unless key + + present key.destroy, with: Entities::SSHKey end - # Add email to a specified user. Only available to admin users. - # - # Parameters: - # id (required) - The ID of a user - # email (required) - Email address - # Example Request: - # POST /users/:id/emails + desc 'Add an email address to a specified user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :email, type: String, desc: 'The email of the user' + end post ":id/emails" do authenticated_as_admin! - required_attributes! [:email] - user = User.find(params[:id]) - attrs = attributes_for_keys [:email] - email = user.emails.new attrs + user = User.find_by(id: params.delete(:id)) + not_found!('User') unless user + + email = user.emails.new(declared_params(include_missing: false)) + if email.save NotificationService.new.new_email(email) present email, with: Entities::Email @@ -241,101 +244,94 @@ module API end end - # Get emails of a specified user. Only available to admin users. - # - # Parameters: - # uid (required) - The ID of a user - # Example Request: - # GET /users/:uid/emails - get ':uid/emails' do + desc 'Get the emails addresses of a specified user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + get ':id/emails' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + user = User.find_by(id: params[:id]) not_found!('User') unless user present user.emails, with: Entities::Email end - # Delete existing email of a specified user. Only available to admin - # users. - # - # Parameters: - # uid (required) - The ID of a user - # id (required) - Email ID - # Example Request: - # DELETE /users/:uid/emails/:id - delete ':uid/emails/:id' do + desc 'Delete an email address of a specified user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :email_id, type: Integer, desc: 'The ID of the email' + end + delete ':id/emails/:email_id' do authenticated_as_admin! - user = User.find_by(id: params[:uid]) + user = User.find_by(id: params[:id]) not_found!('User') unless user - begin - email = user.emails.find params[:id] - email.destroy + email = user.emails.find_by(id: params[:email_id]) + not_found!('Email') unless email - user.update_secondary_emails! - rescue ActiveRecord::RecordNotFound - not_found!('Email') - end + email.destroy + user.update_secondary_emails! end - # Delete user. Available only for admin - # - # Example Request: - # DELETE /users/:id + desc 'Delete a user. Available only for admins.' do + success Entities::Email + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end delete ":id" do authenticated_as_admin! user = User.find_by(id: params[:id]) + not_found!('User') unless user - if user - DeleteUserService.new(current_user).execute(user) - else - not_found!('User') - end + DeleteUserService.new(current_user).execute(user) end - # Block user. Available only for admin - # - # Example Request: - # PUT /users/:id/block + desc 'Block a user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end put ':id/block' do authenticated_as_admin! user = User.find_by(id: params[:id]) + not_found!('User') unless user - if !user - not_found!('User') - elsif !user.ldap_blocked? + if !user.ldap_blocked? user.block else forbidden!('LDAP blocked users cannot be modified by the API') end end - # Unblock user. Available only for admin - # - # Example Request: - # PUT /users/:id/unblock + desc 'Unblock a user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end put ':id/unblock' do authenticated_as_admin! user = User.find_by(id: params[:id]) + not_found!('User') unless user - if !user - not_found!('User') - elsif user.ldap_blocked? + if user.ldap_blocked? forbidden!('LDAP blocked users cannot be unblocked by the API') else user.activate end end - desc 'Get contribution events of a specified user' do + desc 'Get the contribution events of a specified user' do detail 'This feature was introduced in GitLab 8.13.' success Entities::Event end params do - requires :id, type: String, desc: 'The user ID' + requires :id, type: Integer, desc: 'The ID of the user' end get ':id/events' do - user = User.find_by(id: declared(params).id) + user = User.find_by(id: params[:id]) not_found!('User') unless user events = user.events. @@ -349,43 +345,43 @@ module API end resource :user do - # Get currently authenticated user - # - # Example Request: - # GET /user + desc 'Get the currently authenticated user' do + success Entities::UserFull + end get do - present @current_user, with: Entities::UserFull + present current_user, with: Entities::UserFull end - # Get currently authenticated user's keys - # - # Example Request: - # GET /user/keys + desc "Get the currently authenticated user's SSH keys" do + success Entities::SSHKey + end get "keys" do present current_user.keys, with: Entities::SSHKey end - # Get single key owned by currently authenticated user - # - # Example Request: - # GET /user/keys/:id - get "keys/:id" do - key = current_user.keys.find params[:id] + desc 'Get a single key owned by currently authenticated user' do + success Entities::SSHKey + end + params do + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + get "keys/:key_id" do + key = current_user.keys.find_by(id: params[:key_id]) + not_found!('Key') unless key + present key, with: Entities::SSHKey end - # Add new ssh key to currently authenticated user - # - # Parameters: - # key (required) - New SSH Key - # title (required) - New SSH Key's title - # Example Request: - # POST /user/keys + desc 'Add a new SSH key to the currently authenticated user' do + success Entities::SSHKey + end + params do + requires :key, type: String, desc: 'The new SSH key' + requires :title, type: String, desc: 'The title of the new SSH key' + end post "keys" do - required_attributes! [:title, :key] + key = current_user.keys.new(declared_params) - attrs = attributes_for_keys [:title, :key] - key = current_user.keys.new attrs if key.save present key, with: Entities::SSHKey else @@ -393,48 +389,48 @@ module API end end - # Delete existing ssh key of currently authenticated user - # - # Parameters: - # id (required) - SSH Key ID - # Example Request: - # DELETE /user/keys/:id - delete "keys/:id" do - begin - key = current_user.keys.find params[:id] - key.destroy - rescue - end + desc 'Delete an SSH key from the currently authenticated user' do + success Entities::SSHKey + end + params do + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + delete "keys/:key_id" do + key = current_user.keys.find_by(id: params[:key_id]) + not_found!('Key') unless key + + present key.destroy, with: Entities::SSHKey end - # Get currently authenticated user's emails - # - # Example Request: - # GET /user/emails + desc "Get the currently authenticated user's email addresses" do + success Entities::Email + end get "emails" do present current_user.emails, with: Entities::Email end - # Get single email owned by currently authenticated user - # - # Example Request: - # GET /user/emails/:id - get "emails/:id" do - email = current_user.emails.find params[:id] + desc 'Get a single email address owned by the currently authenticated user' do + success Entities::Email + end + params do + requires :email_id, type: Integer, desc: 'The ID of the email' + end + get "emails/:email_id" do + email = current_user.emails.find_by(id: params[:email_id]) + not_found!('Email') unless email + present email, with: Entities::Email end - # Add new email to currently authenticated user - # - # Parameters: - # email (required) - Email address - # Example Request: - # POST /user/emails + desc 'Add new email address to the currently authenticated user' do + success Entities::Email + end + params do + requires :email, type: String, desc: 'The new email' + end post "emails" do - required_attributes! [:email] + email = current_user.emails.new(declared_params) - attrs = attributes_for_keys [:email] - email = current_user.emails.new attrs if email.save NotificationService.new.new_email(email) present email, with: Entities::Email @@ -443,20 +439,16 @@ module API end end - # Delete existing email of currently authenticated user - # - # Parameters: - # id (required) - EMail ID - # Example Request: - # DELETE /user/emails/:id - delete "emails/:id" do - begin - email = current_user.emails.find params[:id] - email.destroy + desc 'Delete an email address from the currently authenticated user' + params do + requires :email_id, type: Integer, desc: 'The ID of the email' + end + delete "emails/:email_id" do + email = current_user.emails.find_by(id: params[:email_id]) + not_found!('Email') unless email - current_user.update_secondary_emails! - rescue - end + email.destroy + current_user.update_secondary_emails! end end end -- cgit v1.2.1 From c6a4f9fc5b98024d4af872b0009b90c36bc2e595 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 21 Nov 2016 11:27:28 +0100 Subject: Update some docs to reflect MWPS name change --- lib/api/merge_requests.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 8c55f09a9ce..19f93c1c892 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -181,7 +181,7 @@ module API optional :should_remove_source_branch, type: Boolean, desc: 'When true, the source branch will be deleted if possible' optional :merge_when_build_succeeds, type: Boolean, - desc: 'When true, this merge request will be merged when the build succeeds' + desc: 'When true, this merge request will be merged when the pipeline succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' end put "#{path}/merge" do @@ -217,7 +217,7 @@ module API present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end - desc 'Cancel merge if "Merge when build succeeds" is enabled' do + desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do success Entities::MergeRequest end post "#{path}/cancel_merge_when_build_succeeds" do -- cgit v1.2.1 From b7bf1f3a9d8c406024a212e6098f049334cda8e3 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 21 Nov 2016 15:06:32 +0100 Subject: Grapify the projects snippet API --- lib/api/project_snippets.rb | 156 ++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 78 deletions(-) (limited to 'lib/api') diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index ce1bf0d26d2..d0ee9c9a5b2 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -3,6 +3,9 @@ module API class ProjectSnippets < Grape::API before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do helpers do def handle_project_member_errors(errors) @@ -18,111 +21,108 @@ module API end end - # Get a project snippets - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/snippets + desc 'Get all project snippets' do + success Entities::ProjectSnippet + end get ":id/snippets" do present paginate(snippets_for_current_user), with: Entities::ProjectSnippet end - # Get a project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # Example Request: - # GET /projects/:id/snippets/:snippet_id + desc 'Get a single project snippet' do + success Entities::ProjectSnippet + end + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + end get ":id/snippets/:snippet_id" do - @snippet = snippets_for_current_user.find(params[:snippet_id]) - present @snippet, with: Entities::ProjectSnippet - end - - # Create a new project snippet - # - # Parameters: - # id (required) - The ID of a project - # title (required) - The title of a snippet - # file_name (required) - The name of a snippet file - # code (required) - The content of a snippet - # visibility_level (required) - The snippet's visibility - # Example Request: - # POST /projects/:id/snippets + snippet = snippets_for_current_user.find(params[:snippet_id]) + present snippet, with: Entities::ProjectSnippet + end + + desc 'Create a new project snippet' do + success Entities::ProjectSnippet + end + params do + requires :title, type: String, desc: 'The title of the snippet' + requires :file_name, type: String, desc: 'The file name of the snippet' + requires :code, type: String, desc: 'The content of the snippet' + requires :visibility_level, type: Integer, + values: [Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC], + desc: 'The visibility level of the snippet' + end post ":id/snippets" do authorize! :create_project_snippet, user_project - required_attributes! [:title, :file_name, :code, :visibility_level] + snippet_params = declared_params + snippet_params[:content] = snippet_params.delete(:code) - attrs = attributes_for_keys [:title, :file_name, :visibility_level] - attrs[:content] = params[:code] if params[:code].present? - @snippet = CreateSnippetService.new(user_project, current_user, - attrs).execute + snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute - if @snippet.errors.any? - render_validation_error!(@snippet) + if snippet.persisted? + present snippet, with: Entities::ProjectSnippet else - present @snippet, with: Entities::ProjectSnippet + render_validation_error!(snippet) end end - # Update an existing project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # title (optional) - The title of a snippet - # file_name (optional) - The name of a snippet file - # code (optional) - The content of a snippet - # visibility_level (optional) - The snippet's visibility - # Example Request: - # PUT /projects/:id/snippets/:snippet_id + desc 'Update an existing project snippet' do + success Entities::ProjectSnippet + end + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + optional :title, type: String, desc: 'The title of the snippet' + optional :file_name, type: String, desc: 'The file name of the snippet' + optional :code, type: String, desc: 'The content of the snippet' + optional :visibility_level, type: Integer, + values: [Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC], + desc: 'The visibility level of the snippet' + at_least_one_of :title, :file_name, :code, :visibility_level + end put ":id/snippets/:snippet_id" do - @snippet = snippets_for_current_user.find(params[:snippet_id]) - authorize! :update_project_snippet, @snippet + snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id)) + not_found!('Snippet') unless snippet + + authorize! :update_project_snippet, snippet + + snippet_params = declared_params(include_missing: false) + snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present? - attrs = attributes_for_keys [:title, :file_name, :visibility_level] - attrs[:content] = params[:code] if params[:code].present? + UpdateSnippetService.new(user_project, current_user, snippet, + snippet_params).execute - UpdateSnippetService.new(user_project, current_user, @snippet, - attrs).execute - if @snippet.errors.any? - render_validation_error!(@snippet) + if snippet.persisted? + present snippet, with: Entities::ProjectSnippet else - present @snippet, with: Entities::ProjectSnippet + render_validation_error!(snippet) end end - # Delete a project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # Example Request: - # DELETE /projects/:id/snippets/:snippet_id + desc 'Delete a project snippet' + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + end delete ":id/snippets/:snippet_id" do - begin - @snippet = snippets_for_current_user.find(params[:snippet_id]) - authorize! :update_project_snippet, @snippet - @snippet.destroy - rescue - not_found!('Snippet') - end + snippet = snippets_for_current_user.find_by(id: params[:snippet_id]) + not_found!('Snippet') unless snippet + + authorize! :admin_project_snippet, snippet + snippet.destroy end - # Get a raw project snippet - # - # Parameters: - # id (required) - The ID of a project - # snippet_id (required) - The ID of a project snippet - # Example Request: - # GET /projects/:id/snippets/:snippet_id/raw + desc 'Get a raw project snippet' + params do + requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' + end get ":id/snippets/:snippet_id/raw" do - @snippet = snippets_for_current_user.find(params[:snippet_id]) + snippet = snippets_for_current_user.find_by(id: params[:snippet_id]) + not_found!('Snippet') unless snippet env['api.format'] = :txt content_type 'text/plain' - present @snippet.content + present snippet.content end end end -- cgit v1.2.1 From 039d4a1cbff5b54dc60363ff5f244e84cb54aacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 21 Nov 2016 17:44:24 +0100 Subject: Fix StrongAttibutes error with Ruby 2.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/users.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'lib/api') diff --git a/lib/api/users.rb b/lib/api/users.rb index c07539194ed..a73650dc361 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -140,7 +140,8 @@ module API User.where(username: params[:username]). where.not(id: user.id).count > 0 - identity_attrs = params.slice(:provider, :extern_uid) + user_params = declared_params(include_missing: false) + identity_attrs = user_params.slice(:provider, :extern_uid) if identity_attrs.any? identity = user.identities.find_by(provider: identity_attrs[:provider]) @@ -154,10 +155,10 @@ module API end # Delete already handled parameters - params.delete(:extern_uid) - params.delete(:provider) + user_params.delete(:extern_uid) + user_params.delete(:provider) - if user.update_attributes(declared_params(include_missing: false)) + if user.update_attributes(user_params) present user, with: Entities::UserFull else render_validation_error!(user) -- cgit v1.2.1 From 77cf855bb995eb8875b41d5683c30bae2d17f2f4 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 22 Nov 2016 00:15:46 +0500 Subject: Define common helper for describe pagination params in api --- lib/api/broadcast_messages.rb | 5 +++-- lib/api/commits.rb | 5 +++-- lib/api/deployments.rb | 5 +++-- lib/api/environments.rb | 5 +++-- lib/api/pagination_params.rb | 24 ++++++++++++++++++++++++ lib/api/pipelines.rb | 5 +++-- lib/api/variables.rb | 5 +++-- 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 lib/api/pagination_params.rb (limited to 'lib/api') diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index b6281a7f0ac..1217002bf8e 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -1,5 +1,7 @@ module API class BroadcastMessages < Grape::API + include PaginationParams + before { authenticate! } before { authenticated_as_admin! } @@ -15,8 +17,7 @@ module API success Entities::BroadcastMessage end params do - optional :page, type: Integer, desc: 'Current page number' - optional :per_page, type: Integer, desc: 'Number of messages per page' + use :pagination end get do messages = BroadcastMessage.all diff --git a/lib/api/commits.rb b/lib/api/commits.rb index f412e1da1bf..0319d076ecb 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -3,6 +3,8 @@ require 'mime/types' module API # Projects commits API class Commits < Grape::API + include PaginationParams + before { authenticate! } before { authorize! :download_code, user_project } @@ -107,9 +109,8 @@ module API failure [[404, 'Not Found']] end params do + use :pagination requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' - optional :per_page, type: Integer, desc: 'The amount of items per page for paginaion' - optional :page, type: Integer, desc: 'The page number for pagination' end get ':id/repository/commits/:sha/comments' do commit = user_project.commit(params[:sha]) diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index f782bcaf7e9..c5feb49b22f 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -1,6 +1,8 @@ module API # Deployments RESTfull API endpoints class Deployments < Grape::API + include PaginationParams + before { authenticate! } params do @@ -12,8 +14,7 @@ module API success Entities::Deployment end params do - optional :page, type: Integer, desc: 'Page number of the current request' - optional :per_page, type: Integer, desc: 'Number of items per page' + use :pagination end get ':id/deployments' do authorize! :read_deployment, user_project diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 00c901937b1..80bbd9bb6e4 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -1,6 +1,8 @@ module API # Environments RESTfull API endpoints class Environments < Grape::API + include PaginationParams + before { authenticate! } params do @@ -12,8 +14,7 @@ module API success Entities::Environment end params do - optional :page, type: Integer, desc: 'Page number of the current request' - optional :per_page, type: Integer, desc: 'Number of items per page' + use :pagination end get ':id/environments' do authorize! :read_environment, user_project diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb new file mode 100644 index 00000000000..8c1e4381a74 --- /dev/null +++ b/lib/api/pagination_params.rb @@ -0,0 +1,24 @@ +module API + # Concern for declare pagination params. + # + # @example + # class CustomApiResource < Grape::API + # include PaginationParams + # + # params do + # use :pagination + # end + # end + module PaginationParams + extend ActiveSupport::Concern + + included do + helpers do + params :pagination do + optional :page, type: Integer, desc: 'Current page number' + optional :per_page, type: Integer, desc: 'Number of items per page' + end + end + end + end +end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 2a0c8e1f2c0..24cc0932181 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -1,5 +1,7 @@ module API class Pipelines < Grape::API + include PaginationParams + before { authenticate! } params do @@ -11,8 +13,7 @@ module API success Entities::Pipeline end params do - optional :page, type: Integer, desc: 'Page number of the current request' - optional :per_page, type: Integer, desc: 'Number of items per page' + use :pagination optional :scope, type: String, values: ['running', 'branches', 'tags'], desc: 'Either running, branches, or tags' end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index b9fb3c21dbb..90f904b8a12 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -1,6 +1,8 @@ module API # Projects variables API class Variables < Grape::API + include PaginationParams + before { authenticate! } before { authorize! :admin_build, user_project } @@ -13,8 +15,7 @@ module API success Entities::Variable end params do - optional :page, type: Integer, desc: 'The page number for pagination' - optional :per_page, type: Integer, desc: 'The value of items per page to show' + use :pagination end get ':id/variables' do variables = user_project.variables -- cgit v1.2.1 From cb11d3521ce99a5ce796e2fdf9e6d96a3e36791d Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 22 Nov 2016 08:23:43 +0100 Subject: Fix IID filter for merge requests and milestones --- lib/api/merge_requests.rb | 8 +++----- lib/api/milestones.rb | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4176c7eec06..009913c6242 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -41,15 +41,13 @@ module API desc: 'Return merge requests ordered by `created_at` or `updated_at` fields.' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return merge requests sorted in `asc` or `desc` order.' - optional :iid, type: Integer, desc: 'The IID of the merge requests' + optional :iid, type: Array[Integer], desc: 'The IID of the merge requests' end get ":id/merge_requests" do authorize! :read_merge_request, user_project - merge_requests = user_project.merge_requests.inc_notes_with_associations - unless params[:iid].nil? - merge_requests = filter_by_iid(merge_requests, params[:iid]) - end + merge_requests = user_project.merge_requests.inc_notes_with_associations + merge_requests = filter_by_iid(merge_requests, params[:iid]) if params[:iid].present? merge_requests = case params[:state] diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 937c118779d..29bf73934d2 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -28,7 +28,7 @@ module API params do optional :state, type: String, values: %w[active closed all], default: 'all', desc: 'Return "active", "closed", or "all" milestones' - optional :iid, type: Integer, desc: 'The IID of the milestone' + optional :iid, type: Array[Integer], desc: 'The IID of the milestone' end get ":id/milestones" do authorize! :read_milestone, user_project -- cgit v1.2.1 From 9e608b41a13a148f74fc2ab153bf5b97047fe1bc Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 22 Nov 2016 08:27:21 +0100 Subject: Avoid helper call with default parameters --- lib/api/merge_requests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4176c7eec06..15488b33f31 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -59,7 +59,7 @@ module API else merge_requests end - merge_requests = merge_requests.reorder(issuable_order_by => issuable_sort) + merge_requests = merge_requests.reorder(params[:order_by] => params[:sort]) present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project end -- cgit v1.2.1 From 3789cfe056c1d8a5fb91267cc2b1dd0f9f5902a9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 15 Nov 2016 19:48:30 +0200 Subject: Add a starting date to milestones --- lib/api/entities.rb | 1 + lib/api/milestones.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 33cb6fd3704..7a724487e02 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -210,6 +210,7 @@ module API class Milestone < ProjectEntity expose :due_date + expose :start_date end class Issue < ProjectEntity diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 29bf73934d2..50d6109be3d 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -14,7 +14,8 @@ module API params :optional_params do optional :description, type: String, desc: 'The description of the milestone' - optional :due_date, type: String, desc: 'The due date of the milestone' + optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)' + optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)' end end -- cgit v1.2.1 From eff1b05ab1d50895be668be12de8239def648d97 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 22 Nov 2016 11:23:41 +0100 Subject: API: Add endpoint to delete a group share --- lib/api/projects.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib/api') diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 6b856128c2e..ddfde178d30 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -438,6 +438,19 @@ module API end end + params do + requires :group_id, type: Integer, desc: 'The ID of the group' + end + delete ":id/share/:group_id" do + authorize! :admin_project, user_project + + link = user_project.project_group_links.find_by(group_id: params[:group_id]) + not_found!('Group Link') unless link + + link.destroy + no_content! + end + # Upload a file # # Parameters: -- cgit v1.2.1 From d2985eb57201a98b3bb9c1abebdabd2061eabd10 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 23 Nov 2016 13:34:08 +0100 Subject: Grapify the sidekiq metrics API --- lib/api/sidekiq_metrics.rb | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) (limited to 'lib/api') diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb index d3d6827dc54..11f2b40269a 100644 --- a/lib/api/sidekiq_metrics.rb +++ b/lib/api/sidekiq_metrics.rb @@ -39,50 +39,22 @@ module API end end - # Get Sidekiq Queue metrics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/queue_metrics - # + desc 'Get the Sidekiq queue metrics' get 'sidekiq/queue_metrics' do { queues: queue_metrics } end - # Get Sidekiq Process metrics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/process_metrics - # + desc 'Get the Sidekiq process metrics' get 'sidekiq/process_metrics' do { processes: process_metrics } end - # Get Sidekiq Job statistics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/job_stats - # + desc 'Get the Sidekiq job statistics' get 'sidekiq/job_stats' do { jobs: job_stats } end - # Get Sidekiq Compound metrics. Includes all previous metrics - # - # Parameters: - # None - # - # Example: - # GET /sidekiq/compound_metrics - # + desc 'Get the Sidekiq Compound metrics. Includes queue, process, and job statistics' get 'sidekiq/compound_metrics' do { queues: queue_metrics, processes: process_metrics, jobs: job_stats } end -- cgit v1.2.1 From 4d2e7894efa36cc1b5de9432e25fcf22b6cf1d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 23 Nov 2016 19:18:19 +0100 Subject: Make API::Helpers find a project with only one query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/helpers.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2c593dbb4ea..60067758e95 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -76,7 +76,12 @@ module API end def find_project(id) - project = Project.find_with_namespace(id) || Project.find_by(id: id) + project = + if id =~ /^\d+$/ + Project.find_by(id: id) + else + Project.find_with_namespace(id) + end if can?(current_user, :read_project, project) project -- cgit v1.2.1 From 304163becba3610a99dfff644c13972a2f54ed3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 13:22:38 +0100 Subject: API: Use `#find_project` in API::Triggers and API::Services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/services.rb | 2 +- lib/api/triggers.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index 4d23499aa39..bc427705777 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -65,7 +65,7 @@ module API detail 'Added in GitLab 8.13' end post ':id/services/:service_slug/trigger' do - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + project = find_project(params[:id]) # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 569598fbd2c..bb4de39def1 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -13,7 +13,7 @@ module API optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end post ":id/(ref/:ref/)trigger/builds" do - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + project = find_project(params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger unauthorized! unless trigger.project == project -- cgit v1.2.1 From 9dfbfbb2d10dfc6297acff1b59dfbaf43b848d96 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 24 Nov 2016 13:30:53 +0100 Subject: Don't convert data which already is the target type --- lib/api/commit_statuses.rb | 2 +- lib/api/commits.rb | 2 +- lib/api/groups.rb | 2 +- lib/api/variables.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/api') diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index f54d4f06627..492884d162b 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -77,7 +77,7 @@ module API ) begin - case params[:state].to_s + case params[:state] when 'pending' status.enqueue! when 'running' diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 0319d076ecb..2670a2d413a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -48,7 +48,7 @@ module API requires :id, type: Integer, desc: 'The project ID' requires :branch_name, type: String, desc: 'The name of branch' requires :commit_message, type: String, desc: 'Commit message' - requires :actions, type: Array, desc: 'Actions to perform in commit' + requires :actions, type: Array[Hash], desc: 'Actions to perform in commit' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 48ad3b80ae0..fc39fdf4b67 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -33,7 +33,7 @@ module API groups = groups.search(params[:search]) if params[:search].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? - groups = groups.reorder(params[:order_by] => params[:sort].to_sym) + groups = groups.reorder(params[:order_by] => params[:sort]) present paginate(groups), with: Entities::Group end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 90f904b8a12..f623b1dfe9f 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -30,7 +30,7 @@ module API end get ':id/variables/:key' do key = params[:key] - variable = user_project.variables.find_by(key: key.to_s) + variable = user_project.variables.find_by(key: key) return not_found!('Variable') unless variable -- cgit v1.2.1 From 4f5ed812325845f263fc9b566651c1179b5c24bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 14:40:35 +0100 Subject: API: Introduce `#find_project!` which also check access permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/helpers.rb | 17 ++++++++++------- lib/api/projects.rb | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 60067758e95..42f4c2ccf9d 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -68,7 +68,7 @@ module API end def user_project - @project ||= find_project(params[:id]) + @project ||= find_project!(params[:id]) end def available_labels @@ -76,12 +76,15 @@ module API end def find_project(id) - project = - if id =~ /^\d+$/ - Project.find_by(id: id) - else - Project.find_with_namespace(id) - end + if id =~ /^\d+$/ + Project.find_by(id: id) + else + Project.find_with_namespace(id) + end + end + + def find_project!(id) + project = find_project(id) if can?(current_user, :read_project, project) project diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ddfde178d30..2ea3c433ae2 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -379,7 +379,7 @@ module API # POST /projects/:id/fork/:forked_from_id post ":id/fork/:forked_from_id" do authenticated_as_admin! - forked_from_project = find_project(params[:forked_from_id]) + forked_from_project = find_project!(params[:forked_from_id]) unless forked_from_project.nil? if user_project.forked_from_project.nil? user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) -- cgit v1.2.1 From 81ba3f9177fcfd76f6b3b715c572ce4920398345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 24 Nov 2016 16:58:32 +0100 Subject: API: Introduce `#find_group!` which also check access permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/groups.rb | 8 ++++---- lib/api/helpers.rb | 10 +++++++++- lib/api/helpers/members_helpers.rb | 2 +- lib/api/issues.rb | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) (limited to 'lib/api') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 48ad3b80ae0..a3489c4eb92 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -82,7 +82,7 @@ module API :lfs_enabled, :request_access_enabled end put ':id' do - group = find_group(params[:id]) + group = find_group!(params[:id]) authorize! :admin_group, group if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute @@ -96,13 +96,13 @@ module API success Entities::GroupDetail end get ":id" do - group = find_group(params[:id]) + group = find_group!(params[:id]) present group, with: Entities::GroupDetail end desc 'Remove a group.' delete ":id" do - group = find_group(params[:id]) + group = find_group!(params[:id]) authorize! :admin_group, group DestroyGroupService.new(group, current_user).execute end @@ -111,7 +111,7 @@ module API success Entities::Project end get ":id/projects" do - group = find_group(params[:id]) + group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) projects = paginate projects present projects, with: Entities::Project, user: current_user diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 42f4c2ccf9d..0d3ddb89dc3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -105,7 +105,15 @@ module API end def find_group(id) - group = Group.find_by(path: id) || Group.find_by(id: id) + if id =~ /^\d+$/ + Group.find_by(id: id) + else + Group.find_by(path: id) + end + end + + def find_group!(id) + group = find_group(id) if can?(current_user, :read_group, group) group diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 90114f6f667..d9cae1501f8 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -2,7 +2,7 @@ module API module Helpers module MembersHelpers def find_source(source_type, id) - public_send("find_#{source_type}", id) + public_send("find_#{source_type}!", id) end def authorize_admin_source!(source_type, source) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index eea5b91d4f9..2fea71870b8 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -68,7 +68,7 @@ module API # GET /groups/:id/issues?milestone=1.0.0 # GET /groups/:id/issues?milestone=1.0.0&state=closed get ":id/issues" do - group = find_group(params[:id]) + group = find_group!(params[:id]) params[:state] ||= 'opened' params[:group_id] = group.id -- cgit v1.2.1 From 40e8185b64a04f719c85a793d0fdd5438a129975 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 22 Nov 2016 17:28:58 +0100 Subject: Expose coverage on GET pipelines/:id The coverage wasn't exposed yet, now it is but only for detailed requests to save queries on the database. --- lib/api/entities.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 33cb6fd3704..7dfaf2e8eb7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -606,6 +606,7 @@ module API expose :user, with: Entities::UserBasic expose :created_at, :updated_at, :started_at, :finished_at, :committed_at expose :duration + expose :coverage end class EnvironmentBasic < Grape::Entity -- cgit v1.2.1 From 7c607a55ab339293b0e67eeb33439d5407e22aad Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 9 Nov 2016 15:51:27 +0100 Subject: Grapify the projects API --- lib/api/helpers.rb | 26 +-- lib/api/projects.rb | 578 ++++++++++++++++++++++++---------------------------- 2 files changed, 267 insertions(+), 337 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0d3ddb89dc3..2fd50143e91 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -324,11 +324,6 @@ module API # Projects helpers def filter_projects(projects) - # If the archived parameter is passed, limit results accordingly - if params[:archived].present? - projects = projects.where(archived: to_boolean(params[:archived])) - end - if params[:search].present? projects = projects.search(params[:search]) end @@ -337,25 +332,8 @@ module API projects = projects.search_by_visibility(params[:visibility]) end - projects.reorder(project_order_by => project_sort) - end - - def project_order_by - order_fields = %w(id name path created_at updated_at last_activity_at) - - if order_fields.include?(params['order_by']) - params['order_by'] - else - 'created_at' - end - end - - def project_sort - if params["sort"] == 'asc' - :asc - else - :desc - end + projects = projects.where(archived: params[:archived]) + projects.reorder(params[:order_by] => params[:sort]) end # file helpers diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2ea3c433ae2..8975b1a751c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,293 +1,287 @@ module API # Projects API class Projects < Grape::API + include PaginationParams + before { authenticate! } - resource :projects, requirements: { id: /[^\/]+/ } do + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the project' + optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' + optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' + optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' + optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled' + optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' + optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' + optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' + optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' + optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.' + optional :visibility_level, type: Integer, values: [ + Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.' + optional :public_builds, type: Boolean, desc: 'Perform public builds' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' + end + + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + if !publik.nil? && !attrs[:visibility_level].present? + # Since setting the public attribute to private could mean either + # private or internal, use the more conservative option, private. + attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE + end + attrs + end + end + + resource :projects do helpers do - def map_public_to_visibility_level(attrs) - publik = attrs.delete(:public) - if publik.present? && !attrs[:visibility_level].present? - publik = to_boolean(publik) - # Since setting the public attribute to private could mean either - # private or internal, use the more conservative option, private. - attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE - end - attrs + params :sort_params do + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' + end + + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + use :sort_params + end + + params :create_params do + optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' + optional :import_url, type: String, desc: 'URL from which the project is imported' end end - # Get a projects list for authenticated user - # - # Example Request: - # GET /projects + desc 'Get a projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get do projects = current_user.authorized_projects projects = filter_projects(projects) - projects = paginate projects entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get a list of visible projects for authenticated user - # - # Example Request: - # GET /projects/visible + desc 'Get a list of visible projects for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get '/visible' do projects = ProjectsFinder.new.execute(current_user) projects = filter_projects(projects) - projects = paginate projects entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get an owned projects list for authenticated user - # - # Example Request: - # GET /projects/owned + desc 'Get an owned projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/owned' do projects = current_user.owned_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Gets starred project for the authenticated user - # - # Example Request: - # GET /projects/starred + desc 'Gets starred project for the authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/starred' do projects = current_user.viewable_starred_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::Project, user: current_user + + present paginate(projects), with: Entities::Project, user: current_user end - # Get all projects for admin user - # - # Example Request: - # GET /projects/all + desc 'Get all projects for admin user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/all' do authenticated_as_admin! projects = Project.all projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Get a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id - get ":id" do - present user_project, with: Entities::ProjectWithAccess, user: current_user, - user_can_admin_project: can?(current_user, :admin_project, user_project) + desc 'Search for projects the current user has access to' do + success Entities::Project + end + params do + requires :query, type: String, desc: 'The project name to be searched' + use :sort_params + use :pagination end + get "/search/:query" do + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(params[:order_by] => params[:sort]) - # Get events for a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/events - get ":id/events" do - events = paginate user_project.events.recent - present events, with: Entities::Event - end - - # Create new project - # - # Parameters: - # name (required) - name for new project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # namespace_id (optional) - defaults to user namespace - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - 0 by default - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects + present paginate(projects), with: Entities::Project + end + + desc 'Create new project' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + use :create_params + end post do - required_attributes! [:name] - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :namespace_id, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(current_user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(current_user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - if @project.errors[:limit_reached].present? - error!(@project.errors[:limit_reached], 403) + if project.errors[:limit_reached].present? + error!(project.errors[:limit_reached], 403) end - render_validation_error!(@project) + render_validation_error!(project) end end - # Create new project for a specified user. Only available to admin users. - # - # Parameters: - # user_id (required) - The ID of a user - # name (required) - name for new project - # description (optional) - short project description - # default_branch (optional) - 'master' by default - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects/user/:user_id + desc 'Create new project for a specified user. Only available to admin users.' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + requires :user_id, type: Integer, desc: 'The ID of a user' + optional :default_branch, type: String, desc: 'The default branch of the project' + use :optional_params + use :create_params + end post "user/:user_id" do authenticated_as_admin! - user = User.find(params[:user_id]) - attrs = attributes_for_keys [:builds_enabled, - :default_branch, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + user = User.find_by(id: params.delete(:user_id)) + not_found!('User') unless user + + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - render_validation_error!(@project) + render_validation_error!(project) end end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: /[^\/]+/ } do + desc 'Get a single project' do + success Entities::ProjectWithAccess + end + get ":id" do + present user_project, with: Entities::ProjectWithAccess, user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project) + end + + desc 'Get events for a single project' do + success Entities::Event + end + params do + use :pagination + end + get ":id/events" do + present paginate(user_project.events.recent), with: Entities::Event + end - # Fork new project for the current user or provided namespace. - # - # Parameters: - # id (required) - The ID of a project - # namespace (optional) - The ID or name of the namespace that the project will be forked into. - # Example Request - # POST /projects/fork/:id + desc 'Fork new project for the current user or provided namespace.' do + success Entities::Project + end + params do + optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + end post 'fork/:id' do - attrs = {} - namespace_id = params[:namespace] + fork_params = declared_params(include_missing: false) + namespace_id = fork_params[:namespace] if namespace_id.present? - namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id) + fork_params[:namespace] = if namespace_id =~ /^\d+$/ + Namespace.find_by(id: namespace_id) + else + Namespace.find_by_path_or_name(namespace_id) + end - unless namespace && can?(current_user, :create_projects, namespace) + unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) not_found!('Target Namespace') end - - attrs[:namespace] = namespace end - @forked_project = - ::Projects::ForkService.new(user_project, - current_user, - attrs).execute + forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute - if @forked_project.errors.any? - conflict!(@forked_project.errors.messages) + if forked_project.errors.any? + conflict!(forked_project.errors.messages) else - present @forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @forked_project) + present forked_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project) end end - # Update an existing project - # - # Parameters: - # id (required) - the id of a project - # name (optional) - name of a project - # path (optional) - path of a project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - visibility level of a project - # public_builds (optional) - # lfs_enabled (optional) - # Example Request - # PUT /projects/:id + desc 'Update an existing project' do + success Entities::Project + end + params do + optional :name, type: String, desc: 'The name of the project' + optional :default_branch, type: String, desc: 'The default branch of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, + :wiki_enabled, :builds_enabled, :snippets_enabled, + :shared_runners_enabled, :container_registry_enabled, + :lfs_enabled, :public, :visibility_level, :public_builds, + :request_access_enabled, :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_all_discussions_are_resolved, :path, + :default_branch + end put ':id' do - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :default_branch, - :description, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) authorize_admin_project + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) authorize! :rename_project, user_project if attrs[:name].present? - if attrs[:visibility_level].present? - authorize! :change_visibility_level, user_project - end + authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? - ::Projects::UpdateService.new(user_project, - current_user, attrs).execute + ::Projects::UpdateService.new(user_project, current_user, attrs).execute if user_project.errors.any? render_validation_error!(user_project) @@ -297,12 +291,9 @@ module API end end - # Archive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/archive + desc 'Archive a project' do + success Entities::Project + end post ':id/archive' do authorize!(:archive_project, user_project) @@ -311,12 +302,9 @@ module API present user_project, with: Entities::Project end - # Unarchive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/unarchive + desc 'Unarchive a project' do + success Entities::Project + end post ':id/unarchive' do authorize!(:archive_project, user_project) @@ -325,12 +313,9 @@ module API present user_project, with: Entities::Project end - # Star project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # POST /projects/:id/star + desc 'Star a project' do + success Entities::Project + end post ':id/star' do if current_user.starred?(user_project) not_modified! @@ -342,12 +327,9 @@ module API end end - # Unstar project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/star + desc 'Unstar a project' do + success Entities::Project + end delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) @@ -359,67 +341,51 @@ module API end end - # Remove project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id + desc 'Remove a project' delete ":id" do authorize! :remove_project, user_project ::Projects::DestroyService.new(user_project, current_user, {}).async_execute end - # Mark this project as forked from another - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # forked_from_id: (required) - The ID of the project it was forked from - # Example Request: - # POST /projects/:id/fork/:forked_from_id + desc 'Mark this project as forked from another' + params do + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + end post ":id/fork/:forked_from_id" do authenticated_as_admin! + forked_from_project = find_project!(params[:forked_from_id]) - unless forked_from_project.nil? - if user_project.forked_from_project.nil? - user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) - else - render_api_error!("Project already forked", 409) - end + not_found!("Source Project") unless forked_from_project + + if user_project.forked_from_project.nil? + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) else - not_found!("Source Project") + render_api_error!("Project already forked", 409) end end - # Remove a forked_from relationship - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # Example Request: - # DELETE /projects/:id/fork + desc 'Remove a forked_from relationship' delete ":id/fork" do authorize! :remove_fork_project, user_project + if user_project.forked? user_project.forked_project_link.destroy + else + not_modified! end end - # Share project with group - # - # Parameters: - # id (required) - The ID of a project - # group_id (required) - The ID of a group - # group_access (required) - Level of permissions for sharing - # expires_at (optional) - Share expiration date - # - # Example Request: - # POST /projects/:id/share + desc 'Share the project with a group' do + success Entities::ProjectGroupLink + end + params do + requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end post ":id/share" do authorize! :admin_project, user_project - required_attributes! [:group_id, :group_access] - attrs = attributes_for_keys [:group_id, :group_access, :expires_at] - - group = Group.find_by_id(attrs[:group_id]) + group = Group.find_by_id(params[:group_id]) unless group && can?(current_user, :read_group, group) not_found!('Group') @@ -429,7 +395,7 @@ module API return render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new(attrs) + link = user_project.project_group_links.new(declared_params(include_missing: false)) if link.save present link, with: Entities::ProjectGroupLink @@ -451,40 +417,26 @@ module API no_content! end - # Upload a file - # - # Parameters: - # id: (required) - The ID of the project - # file: (required) - The file to be uploaded + desc 'Upload a file' + params do + requires :file, type: File, desc: 'The file to be uploaded' + end post ":id/uploads" do ::Projects::UploadService.new(user_project, params[:file]).execute end - # search for projects current_user has access to - # - # Parameters: - # query (required) - A string contained in the project name - # per_page (optional) - number of projects to return per page - # page (optional) - the page to retrieve - # Example Request: - # GET /projects/search/:query - get "/search/:query" do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) - projects = projects.reorder(project_order_by => project_sort) - - present paginate(projects), with: Entities::Project + desc 'Get the users list of a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination end - - # Get a users list - # - # Example Request: - # GET /users get ':id/users' do - @users = User.where(id: user_project.team.users.map(&:id)) - @users = @users.search(params[:search]) if params[:search].present? - @users = paginate @users - present @users, with: Entities::UserBasic + users = User.where(id: user_project.team.users.map(&:id)) + users = users.search(params[:search]) if params[:search].present? + + present paginate(users), with: Entities::UserBasic end end end -- cgit v1.2.1 From 3d7704ae5f62446b8b399c796c64d1f527666376 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 10 Nov 2016 10:23:44 +0000 Subject: Merge branch 'zj-fix-label-creation-non-members' into 'security' Fix label creation non members Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/23416 See merge request !2006 --- lib/api/helpers.rb | 14 --------- lib/api/issues.rb | 74 +++++++++++++++++++++++------------------------ lib/api/merge_requests.rb | 10 ------- 3 files changed, 37 insertions(+), 61 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0d3ddb89dc3..79a83496eee 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -198,20 +198,6 @@ module API ActionController::Parameters.new(attrs).permit! end - # Helper method for validating all labels against its names - def validate_label_params(params) - errors = {} - - params[:labels].to_s.split(',').each do |label_name| - label = available_labels.find_or_initialize_by(title: label_name.strip) - next if label.valid? - - errors[label.title] = label.errors - end - - errors - end - # Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601 # format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked. # diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 2fea71870b8..029be7519f5 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -19,6 +19,15 @@ module API def filter_issues_milestone(issues, milestone) issues.includes(:milestone).where('milestones.title' => milestone) end + + def issue_params + new_params = declared(params, include_parent_namespace: false, include_missing: false).to_h + new_params = new_params.with_indifferent_access + new_params.delete(:id) + new_params.delete(:issue_id) + + new_params + end end resource :issues do @@ -86,6 +95,10 @@ module API end end + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects do # Get a list of project issues # @@ -152,17 +165,10 @@ module API post ':id/issues' do required_attributes! [:title] - keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential] + keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels] keys << :created_at if current_user.admin? || user_project.owner == current_user attrs = attributes_for_keys(keys) - # Validate label names in advance - if (errors = validate_label_params(params)).any? - render_api_error!({ labels: errors }, 400) - end - - attrs[:labels] = params[:labels] if params[:labels] - # Convert and filter out invalid confidential flags attrs['confidential'] = to_boolean(attrs['confidential']) attrs.delete('confidential') if attrs['confidential'].nil? @@ -180,41 +186,35 @@ module API end end - # Update an existing issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # title (optional) - The title of an issue - # description (optional) - The description of an issue - # assignee_id (optional) - The ID of a user to assign issue - # milestone_id (optional) - The ID of a milestone to assign issue - # labels (optional) - The labels of an issue - # state_event (optional) - The state event of an issue (close|reopen) - # updated_at (optional) - Date time string, ISO 8601 formatted - # due_date (optional) - Date time string in the format YEAR-MONTH-DAY - # confidential (optional) - Boolean parameter if the issue should be confidential - # Example Request: - # PUT /projects/:id/issues/:issue_id + desc 'Update an existing issue' do + success Entities::Issue + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :issue_id, type: Integer, desc: "The ID of a project issue" + optional :title, type: String, desc: 'The new title of the issue' + optional :description, type: String, desc: 'The description of an issue' + optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' + optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' + optional :labels, type: String, desc: 'The labels of an issue' + optional :state_event, type: String, values: ['close', 'reopen'], desc: 'The state event of an issue' + # TODO 9.0, use the Grape DateTime type here + optional :updated_at, type: String, desc: 'Date time string, ISO 8601 formatted' + optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' + # TODO 9.0, use the Grape boolean type here + optional :confidential, type: String, desc: 'Boolean parameter if the issue should be confidential' + end put ':id/issues/:issue_id' do issue = user_project.issues.find(params[:issue_id]) authorize! :update_issue, issue - keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential] - keys << :updated_at if current_user.admin? || user_project.owner == current_user - attrs = attributes_for_keys(keys) - - # Validate label names in advance - if (errors = validate_label_params(params)).any? - render_api_error!({ labels: errors }, 400) - end - - attrs[:labels] = params[:labels] if params[:labels] # Convert and filter out invalid confidential flags - attrs['confidential'] = to_boolean(attrs['confidential']) - attrs.delete('confidential') if attrs['confidential'].nil? + params[:confidential] = to_boolean(params[:confidential]) + params.delete(:confidential) if params[:confidential].nil? + + params.delete(:updated_at) unless current_user.admin? || user_project.owner == current_user - issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) + issue = ::Issues::UpdateService.new(user_project, current_user, issue_params).execute(issue) if issue.valid? present issue, with: Entities::Issue, current_user: current_user, project: user_project diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index e82651a1578..90fa588b455 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -77,11 +77,6 @@ module API mr_params = declared_params - # Validate label names in advance - if (errors = validate_label_params(mr_params)).any? - render_api_error!({ labels: errors }, 400) - end - merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute if merge_request.valid? @@ -157,11 +152,6 @@ module API mr_params = declared_params(include_missing: false) - # Validate label names in advance - if (errors = validate_label_params(mr_params)).any? - render_api_error!({ labels: errors }, 400) - end - merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) if merge_request.valid? -- cgit v1.2.1 From 3bf34face4cacf07ca973705c261369b1f596626 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 22 Nov 2016 10:25:04 +0000 Subject: Merge branch 'jej-use-issuable-finder-instead-of-access-check' into 'security' Replace issue access checks with use of IssuableFinder Split from !2024 to partially solve https://gitlab.com/gitlab-org/gitlab-ce/issues/23867 ## Which fixes are in this MR? :warning: - Potentially untested :bomb: - No test coverage :traffic_light: - Test coverage of some sort exists (a test failed when error raised) :vertical_traffic_light: - Test coverage of return value (a test failed when nil used) :white_check_mark: - Permissions check tested ### Issue lookup with access check Using `visible_to_user` likely makes these security issues too. See [Code smells](#code-smells). - [x] :vertical_traffic_light: app/finders/notes_finder.rb:15 [`visible_to_user`] - [x] :traffic_light: app/views/layouts/nav/_project.html.haml:73 [`visible_to_user`] [`.count`] - [x] :white_check_mark: app/services/merge_requests/build_service.rb:84 [`issue.try(:confidential?)`] - [x] :white_check_mark: lib/api/issues.rb:112 [`visible_to_user`] - CHANGELOG: Prevented API returning issues set to 'Only team members' to everyone - [x] :white_check_mark: lib/api/helpers.rb:126 [`can?(current_user, :read_issue, issue)`] Maybe here too? - [x] :white_check_mark: lib/gitlab/search_results.rb:53 [`visible_to_user`] ### Previous discussions - [ ] https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2024/diffs#b2ff264eddf9819d7693c14ae213d941494fe2b3_128_126 - [ ] https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2024/diffs#7b6375270d22f880bdcb085e47b519b426a5c6c7_87_87 See merge request !2031 --- lib/api/helpers.rb | 4 +--- lib/api/issues.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 79a83496eee..34d9c3c6932 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -128,9 +128,7 @@ module API end def find_project_issue(id) - issue = user_project.issues.find(id) - not_found! unless can?(current_user, :read_issue, issue) - issue + IssuesFinder.new(current_user, project_id: user_project.id).find(id) end def paginate(relation) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 029be7519f5..049b4fb214c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -122,7 +122,7 @@ module API # GET /projects/:id/issues?milestone=1.0.0&state=closed # GET /issues?iid=42 get ":id/issues" do - issues = user_project.issues.inc_notes_with_associations.visible_to_user(current_user) + issues = IssuesFinder.new(current_user, project_id: user_project.id).execute.inc_notes_with_associations issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil? -- cgit v1.2.1 From a49e9949c6bc474c8bfd4016d9c6c3b59776772f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 28 Nov 2016 11:13:32 +0100 Subject: Rename `MergeRequest#pipeline` to `head_pipeline` --- lib/api/merge_requests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 90fa588b455..97baebc1d27 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -192,7 +192,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? + if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else -- cgit v1.2.1 From dd5f71138ce98522b1324319fbd60f665b3d1337 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 28 Nov 2016 22:15:12 +0100 Subject: Grapify the files API --- lib/api/files.rb | 153 ++++++++++++++++++++++--------------------------------- 1 file changed, 60 insertions(+), 93 deletions(-) (limited to 'lib/api') diff --git a/lib/api/files.rb b/lib/api/files.rb index 96510e651a3..28f306e45f3 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -23,140 +23,107 @@ module API branch_name: attrs[:branch_name] } end + + params :simple_file_params do + requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb' + requires :branch_name, type: String, desc: 'The name of branch' + requires :commit_message, type: String, desc: 'Commit Message' + optional :author_email, type: String, desc: 'The email of the author' + optional :author_name, type: String, desc: 'The name of the author' + end + + params :extended_file_params do + use :simple_file_params + requires :content, type: String, desc: 'File content' + optional :encoding, type: String, values: %w[base64], desc: 'File encoding' + end end + params do + requires :id, type: String, desc: 'The project ID' + end resource :projects do - # Get file from repository - # File content is Base64 encoded - # - # Parameters: - # file_path (required) - The path to the file. Ex. lib/class.rb - # ref (required) - The name of branch, tag or commit - # - # Example Request: - # GET /projects/:id/repository/files - # - # Example response: - # { - # "file_name": "key.rb", - # "file_path": "app/models/key.rb", - # "size": 1476, - # "encoding": "base64", - # "content": "IyA9PSBTY2hlbWEgSW5mb3...", - # "ref": "master", - # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", - # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50", - # "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - # } - # + desc 'Get a file from repository' + params do + requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb' + requires :ref, type: String, desc: 'The name of branch, tag, or commit' + end get ":id/repository/files" do authorize! :download_code, user_project - required_attributes! [:file_path, :ref] - attrs = attributes_for_keys [:file_path, :ref] - ref = attrs.delete(:ref) - file_path = attrs.delete(:file_path) - - commit = user_project.commit(ref) - not_found! 'Commit' unless commit + commit = user_project.commit(params[:ref]) + not_found!('Commit') unless commit repo = user_project.repository - blob = repo.blob_at(commit.sha, file_path) + blob = repo.blob_at(commit.sha, params[:file_path]) + not_found!('File') unless blob - if blob - blob.load_all_data!(repo) - status(200) + blob.load_all_data!(repo) + status(200) - { - file_name: blob.name, - file_path: blob.path, - size: blob.size, - encoding: "base64", - content: Base64.strict_encode64(blob.data), - ref: ref, - blob_id: blob.id, - commit_id: commit.id, - last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id - } - else - not_found! 'File' - end + { + file_name: blob.name, + file_path: blob.path, + size: blob.size, + encoding: "base64", + content: Base64.strict_encode64(blob.data), + ref: params[:ref], + blob_id: blob.id, + commit_id: commit.id, + last_commit_id: repo.last_commit_for_path(commit.sha, params[:file_path]).id + } end - # Create new file in repository - # - # Parameters: - # file_path (required) - The path to new file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # POST /projects/:id/repository/files - # + desc 'Create new file in repository' + params do + use :extended_file_params + end post ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(201) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end end - # Update existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # PUT /projects/:id/repository/files - # + desc 'Update existing file in repository' + params do + use :extended_file_params + end put ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else http_status = result[:http_status] || 400 render_api_error!(result[:message], http_status) end end - # Delete existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # DELETE /projects/:id/repository/files - # + desc 'Delete an existing file in repository' + params do + use :simple_file_params + end delete ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name] - result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::DeleteService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end -- cgit v1.2.1 From 2ce66c071fc7ab2b8ca881223321a3927ec7d61e Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 28 Nov 2016 19:16:15 +0100 Subject: API: Expose branch status --- lib/api/entities.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fdb19558c1c..d5dfb8d00be 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -141,8 +141,12 @@ module API options[:project].repository.commit(repo_branch.dereferenced_target) end + expose :merged do |repo_branch, options| + options[:project].repository.merged_to_root_ref?(repo_branch.name) + end + expose :protected do |repo_branch, options| - options[:project].protected_branch? repo_branch.name + options[:project].protected_branch?(repo_branch.name) end expose :developers_can_push do |repo_branch, options| -- cgit v1.2.1 From 79d99d470faf5cf088a0b76ae6bb2ec280c4c4a8 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 30 Nov 2016 18:02:58 +0100 Subject: API: Expose committer details for a commit --- lib/api/entities.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d5dfb8d00be..899d68bc6c7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -174,6 +174,7 @@ module API class RepoCommit < Grape::Entity expose :id, :short_id, :title, :author_name, :author_email, :created_at + expose :committer_name, :committer_email expose :safe_message, as: :message end -- cgit v1.2.1 From d757247247ea6015d560eacd29ec7be564e332bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 30 Nov 2016 15:48:19 +0100 Subject: Allow public access to some Project API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/helpers.rb | 5 +++++ lib/api/projects.rb | 28 ++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index cbafa952ef6..7f94ede7940 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -141,6 +141,10 @@ module API unauthorized! unless current_user end + def authenticate_non_get! + authenticate! unless %w[GET HEAD].include?(route.route_method) + end + def authenticate_by_gitlab_shell_token! input = params['secret_token'].try(:chomp) unless Devise.secure_compare(secret_token, input) @@ -149,6 +153,7 @@ module API end def authenticated_as_admin! + authenticate! forbidden! unless current_user.is_admin? end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 8975b1a751c..2929d2157dc 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -3,7 +3,7 @@ module API class Projects < Grape::API include PaginationParams - before { authenticate! } + before { authenticate_non_get! } helpers do params :optional_params do @@ -61,7 +61,7 @@ module API end end - desc 'Get a projects list for authenticated user' do + desc 'Get a list of visible projects for authenticated user' do success Entities::BasicProjectDetails end params do @@ -70,15 +70,15 @@ module API use :filter_params use :pagination end - get do - projects = current_user.authorized_projects + get '/visible' do + projects = ProjectsFinder.new.execute(current_user) projects = filter_projects(projects) - entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess + entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess present paginate(projects), with: entity, user: current_user end - desc 'Get a list of visible projects for authenticated user' do + desc 'Get a projects list for authenticated user' do success Entities::BasicProjectDetails end params do @@ -87,8 +87,10 @@ module API use :filter_params use :pagination end - get '/visible' do - projects = ProjectsFinder.new.execute(current_user) + get do + authenticate! + + projects = current_user.authorized_projects projects = filter_projects(projects) entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess @@ -103,6 +105,8 @@ module API use :pagination end get '/owned' do + authenticate! + projects = current_user.owned_projects projects = filter_projects(projects) @@ -117,6 +121,8 @@ module API use :pagination end get '/starred' do + authenticate! + projects = current_user.viewable_starred_projects projects = filter_projects(projects) @@ -132,6 +138,7 @@ module API end get '/all' do authenticated_as_admin! + projects = Project.all projects = filter_projects(projects) @@ -213,7 +220,8 @@ module API success Entities::ProjectWithAccess end get ":id" do - present user_project, with: Entities::ProjectWithAccess, user: current_user, + entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails + present user_project, with: entity, user: current_user, user_can_admin_project: can?(current_user, :admin_project, user_project) end @@ -433,7 +441,7 @@ module API use :pagination end get ':id/users' do - users = User.where(id: user_project.team.users.map(&:id)) + users = user_project.team.users users = users.search(params[:search]) if params[:search].present? present paginate(users), with: Entities::UserBasic -- cgit v1.2.1 From fde754e2676e40dcf2600190983ef54030c5d5a5 Mon Sep 17 00:00:00 2001 From: Guyzmo Date: Sat, 26 Nov 2016 16:37:26 +0100 Subject: API: Endpoint to expose personal snippets as /snippets Adding the necessary API for the new /snippets Restful resource added with this commit. Added a new Grape class `Snippets`, as well as a `PersonalSnippet` entity. Issue: #20042 Merge-Request: !6373 Signed-off-by: Guyzmo --- lib/api/api.rb | 1 + lib/api/entities.rb | 13 +++++ lib/api/snippets.rb | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 lib/api/snippets.rb (limited to 'lib/api') diff --git a/lib/api/api.rb b/lib/api/api.rb index 67109ceeef9..cec2702e44d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -64,6 +64,7 @@ module API mount ::API::Session mount ::API::Settings mount ::API::SidekiqMetrics + mount ::API::Snippets mount ::API::Subscriptions mount ::API::SystemHooks mount ::API::Tags diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d5dfb8d00be..dc6d085925d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -200,6 +200,19 @@ module API end end + class PersonalSnippet < Grape::Entity + expose :id, :title, :file_name + expose :author, using: Entities::UserBasic + expose :updated_at, :created_at + + expose :web_url do |snippet| + Gitlab::UrlBuilder.build(snippet) + end + expose :raw_url do |snippet| + Gitlab::UrlBuilder.build(snippet) + "/raw" + end + end + class ProjectEntity < Grape::Entity expose :id, :iid expose(:project_id) { |entity| entity.project.id } diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb new file mode 100644 index 00000000000..e096e636806 --- /dev/null +++ b/lib/api/snippets.rb @@ -0,0 +1,137 @@ +module API + # Snippets API + class Snippets < Grape::API + include PaginationParams + + before { authenticate! } + + resource :snippets do + helpers do + def snippets_for_current_user + SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user) + end + + def public_snippets + SnippetsFinder.new.execute(current_user, filter: :public) + end + end + + desc 'Get a snippets list for authenticated user' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + use :pagination + end + get do + present paginate(snippets_for_current_user), with: Entities::PersonalSnippet + end + + desc 'List all public snippets current_user has access to' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + use :pagination + end + get 'public' do + present paginate(public_snippets), with: Entities::PersonalSnippet + end + + desc 'Get a single snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + get ':id' do + snippet = snippets_for_current_user.find(params[:id]) + present snippet, with: Entities::PersonalSnippet + end + + desc 'Create new snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :title, type: String, desc: 'The title of a snippet' + requires :file_name, type: String, desc: 'The name of a snippet file' + requires :content, type: String, desc: 'The content of a snippet' + optional :visibility_level, type: Integer, + values: Gitlab::VisibilityLevel.values, + default: Gitlab::VisibilityLevel::INTERNAL, + desc: 'The visibility level of the snippet' + end + post do + attrs = declared_params(include_missing: false) + snippet = CreateSnippetService.new(nil, current_user, attrs).execute + + if snippet.persisted? + present snippet, with: Entities::PersonalSnippet + else + render_validation_error!(snippet) + end + end + + desc 'Update an existing snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + optional :title, type: String, desc: 'The title of a snippet' + optional :file_name, type: String, desc: 'The name of a snippet file' + optional :content, type: String, desc: 'The content of a snippet' + optional :visibility_level, type: Integer, + values: Gitlab::VisibilityLevel.values, + desc: 'The visibility level of the snippet' + at_least_one_of :title, :file_name, :content, :visibility_level + end + 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) + + UpdateSnippetService.new(nil, current_user, snippet, attrs).execute + if snippet.persisted? + present snippet, with: Entities::PersonalSnippet + else + render_validation_error!(snippet) + end + end + + desc 'Remove snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + 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! + end + + desc 'Get a raw snippet' do + detail 'This feature was introduced in GitLab 8.15.' + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + get ":id/raw" do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + + env['api.format'] = :txt + content_type 'text/plain' + present snippet.content + end + end + end +end -- cgit v1.2.1 From 8b5c16e4b1d54745ba6ca65ecfbf14c1683db3b4 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 28 Nov 2016 21:33:12 +0100 Subject: API: Ability to remove source branch --- lib/api/merge_requests.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 97baebc1d27..5535c807d21 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -28,6 +28,7 @@ module API optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' end end @@ -75,7 +76,8 @@ module API post ":id/merge_requests" do authorize! :create_merge_request, user_project - mr_params = declared_params + mr_params = declared_params(include_missing: false) + mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute @@ -144,13 +146,15 @@ module API desc: 'Status of the merge request' use :optional_params at_least_one_of :title, :target_branch, :description, :assignee_id, - :milestone_id, :labels, :state_event + :milestone_id, :labels, :state_event, + :remove_source_branch end put path do merge_request = user_project.merge_requests.find(params.delete(:merge_request_id)) authorize! :update_merge_request, merge_request mr_params = declared_params(include_missing: false) + mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request) -- cgit v1.2.1 From 74c8669b0a96b6afcb41ce5e09b147c7309ecbeb Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sun, 4 Dec 2016 18:11:19 +0100 Subject: Use the pagination helper in the API --- lib/api/access_requests.rb | 5 +++++ lib/api/award_emoji.rb | 5 +++++ lib/api/builds.rb | 7 +++++-- lib/api/commit_statuses.rb | 4 +++- lib/api/groups.rb | 12 ++++++++++-- lib/api/members.rb | 6 ++++-- lib/api/merge_requests.rb | 9 +++++++++ lib/api/milestones.rb | 5 ++++- lib/api/namespaces.rb | 4 +++- lib/api/notes.rb | 4 +++- lib/api/project_hooks.rb | 12 ++++++++---- lib/api/project_snippets.rb | 6 +++++- lib/api/runners.rb | 5 +++++ lib/api/todos.rb | 10 ++++++---- lib/api/triggers.rb | 5 +++++ lib/api/users.rb | 5 ++++- 16 files changed, 84 insertions(+), 20 deletions(-) (limited to 'lib/api') diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index ed723b94cfd..789f45489eb 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -1,5 +1,7 @@ module API class AccessRequests < Grape::API + include PaginationParams + before { authenticate! } helpers ::API::Helpers::MembersHelpers @@ -13,6 +15,9 @@ module API detail 'This feature was introduced in GitLab 8.11.' success Entities::AccessRequester end + params do + use :pagination + end get ":id/access_requests" do source = find_source(source_type, params[:id]) diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index e9ccba3b465..58a4df54bea 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -1,5 +1,7 @@ module API class AwardEmoji < Grape::API + include PaginationParams + before { authenticate! } AWARDABLES = %w[issue merge_request snippet] @@ -21,6 +23,9 @@ module API detail 'This feature was introduced in 8.9' success Entities::AwardEmoji end + params do + use :pagination + end get endpoint do if can_read_awardable? awards = paginate(awardable.award_emoji) diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 67adca6605f..af61be343be 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -1,6 +1,7 @@ module API - # Projects builds API class Builds < Grape::API + include PaginationParams + before { authenticate! } params do @@ -28,6 +29,7 @@ module API end params do use :optional_scope + use :pagination end get ':id/builds' do builds = user_project.builds.order('id DESC') @@ -41,8 +43,9 @@ module API success Entities::Build end params do - requires :sha, type: String, desc: 'The SHA id of a commit' + requires :sha, type: String, desc: 'The SHA id of a commit' use :optional_scope + use :pagination end get ':id/repository/commits/:sha/builds' do authorize_read_builds! diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 492884d162b..4bbdf06a49c 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -1,9 +1,10 @@ require 'mime/types' module API - # Project commit statuses API class CommitStatuses < Grape::API resource :projects do + include PaginationParams + before { authenticate! } desc "Get a commit's statuses" do @@ -16,6 +17,7 @@ module API optional :stage, type: String, desc: 'The stage' optional :name, type: String, desc: 'The name' optional :all, type: String, desc: 'Show all statuses, default: false' + use :pagination end get ':id/repository/commits/:sha/statuses' do authorize!(:read_commit_status, user_project) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 5315c22e1e4..fbf7513302b 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,5 +1,7 @@ module API class Groups < Grape::API + include PaginationParams + before { authenticate! } helpers do @@ -21,6 +23,7 @@ module API optional :search, type: String, desc: 'Search for a specific group' optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' + use :pagination end get do groups = if current_user.admin @@ -41,6 +44,9 @@ module API desc 'Get list of owned groups for authenticated user' do success Entities::Group end + params do + use :pagination + end get '/owned' do groups = current_user.owned_groups present paginate(groups), with: Entities::Group, user: current_user @@ -110,11 +116,13 @@ module API desc 'Get a list of projects in this group.' do success Entities::Project end + params do + use :pagination + end get ":id/projects" do group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) - projects = paginate projects - present projects, with: Entities::Project, user: current_user + present paginate(projects), with: Entities::Project, user: current_user end desc 'Transfer a project to the group namespace. Available only for admin.' do diff --git a/lib/api/members.rb b/lib/api/members.rb index 2d4d5cedf20..d85f1f78cd6 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -1,5 +1,7 @@ module API class Members < Grape::API + include PaginationParams + before { authenticate! } helpers ::API::Helpers::MembersHelpers @@ -14,15 +16,15 @@ module API end params do optional :query, type: String, desc: 'A query string to search for members' + use :pagination end get ":id/members" do source = find_source(source_type, params[:id]) users = source.users users = users.merge(User.search(params[:query])) if params[:query] - users = paginate(users) - present users, with: Entities::Member, source: source + present paginate(users), with: Entities::Member, source: source end desc 'Gets a member of a group or project.' do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 97baebc1d27..752c105ff7c 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -1,5 +1,7 @@ module API class MergeRequests < Grape::API + include PaginationParams + DEPRECATION_MESSAGE = 'This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze before { authenticate! } @@ -42,6 +44,7 @@ module API optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return merge requests sorted in `asc` or `desc` order.' optional :iid, type: Array[Integer], desc: 'The IID of the merge requests' + use :pagination end get ":id/merge_requests" do authorize! :read_merge_request, user_project @@ -218,6 +221,9 @@ module API detail 'Duplicate. DEPRECATED and WILL BE REMOVED in 9.0' success Entities::MRNote end + params do + use :pagination + end get "#{path}/comments" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) @@ -255,6 +261,9 @@ module API desc 'List issues that will be closed on merge' do success Entities::MRNote end + params do + use :pagination + end get "#{path}/closes_issues" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 50d6109be3d..3c373a84ec5 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -1,6 +1,7 @@ module API - # Milestones API class Milestones < Grape::API + include PaginationParams + before { authenticate! } helpers do @@ -30,6 +31,7 @@ module API optional :state, type: String, values: %w[active closed all], default: 'all', desc: 'Return "active", "closed", or "all" milestones' optional :iid, type: Array[Integer], desc: 'The IID of the milestone' + use :pagination end get ":id/milestones" do authorize! :read_milestone, user_project @@ -103,6 +105,7 @@ module API end params do requires :milestone_id, type: Integer, desc: 'The ID of a project milestone' + use :pagination end get ":id/milestones/:milestone_id/issues" do authorize! :read_milestone, user_project diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index fe981d7b9fa..30761cb9b55 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -1,6 +1,7 @@ module API - # namespaces API class Namespaces < Grape::API + include PaginationParams + before { authenticate! } resource :namespaces do @@ -9,6 +10,7 @@ module API end params do optional :search, type: String, desc: "Search query for namespaces" + use :pagination end get do namespaces = current_user.admin ? Namespace.all : current_user.namespaces diff --git a/lib/api/notes.rb b/lib/api/notes.rb index b255b47742b..d0faf17714b 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -1,6 +1,7 @@ module API - # Notes API class Notes < Grape::API + include PaginationParams + before { authenticate! } NOTEABLE_TYPES = [Issue, MergeRequest, Snippet] @@ -17,6 +18,7 @@ module API end params do requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + use :pagination end get ":id/#{noteables_str}/:noteable_id/notes" do noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 2b36ef7c426..dcc0fb7a911 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -1,6 +1,10 @@ module API - # Projects API class ProjectHooks < Grape::API + include PaginationParams + + before { authenticate! } + before { authorize_admin_project } + helpers do params :project_hook_properties do requires :url, type: String, desc: "The URL to send the request to" @@ -17,9 +21,6 @@ module API end end - before { authenticate! } - before { authorize_admin_project } - params do requires :id, type: String, desc: 'The ID of a project' end @@ -27,6 +28,9 @@ module API desc 'Get project hooks' do success Entities::ProjectHook end + params do + use :pagination + end get ":id/hooks" do hooks = paginate user_project.hooks diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index d0ee9c9a5b2..9d8c5b63685 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -1,6 +1,7 @@ module API - # Projects API class ProjectSnippets < Grape::API + include PaginationParams + before { authenticate! } params do @@ -24,6 +25,9 @@ module API desc 'Get all project snippets' do success Entities::ProjectSnippet end + params do + use :pagination + end get ":id/snippets" do present paginate(snippets_for_current_user), with: Entities::ProjectSnippet end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index b145cce7e3e..4816b5ed1b7 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -1,5 +1,7 @@ module API class Runners < Grape::API + include PaginationParams + before { authenticate! } resource :runners do @@ -9,6 +11,7 @@ module API params do optional :scope, type: String, values: %w[active paused online], desc: 'The scope of specific runners to show' + use :pagination end get do runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: ['specific', 'shared']) @@ -21,6 +24,7 @@ module API params do optional :scope, type: String, values: %w[active paused online specific shared], desc: 'The scope of specific runners to show' + use :pagination end get 'all' do authenticated_as_admin! @@ -91,6 +95,7 @@ module API params do optional :scope, type: String, values: %w[active paused online specific shared], desc: 'The scope of specific runners to show' + use :pagination end get ':id/runners' do runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope]) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 832b04a3bb1..ed8f48aa1e3 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -1,6 +1,7 @@ module API - # Todos API class Todos < Grape::API + include PaginationParams + before { authenticate! } ISSUABLE_TYPES = { @@ -44,10 +45,11 @@ module API desc 'Get a todo list' do success Entities::Todo end + params do + use :pagination + end get do - todos = find_todos - - present paginate(todos), with: Entities::Todo, current_user: current_user + present paginate(find_todos), with: Entities::Todo, current_user: current_user end desc 'Mark a todo as done' do diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index bb4de39def1..87a717ba751 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -1,5 +1,7 @@ module API class Triggers < Grape::API + include PaginationParams + params do requires :id, type: String, desc: 'The ID of a project' end @@ -42,6 +44,9 @@ module API desc 'Get triggers list' do success Entities::Trigger end + params do + use :pagination + end get ':id/triggers' do authenticate! authorize! :admin_build, user_project diff --git a/lib/api/users.rb b/lib/api/users.rb index a73650dc361..bc2362aa72e 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1,6 +1,7 @@ module API - # Users API class Users < Grape::API + include PaginationParams + before { authenticate! } resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do @@ -33,6 +34,7 @@ module API optional :active, type: Boolean, default: false, desc: 'Filters only active users' optional :external, type: Boolean, default: false, desc: 'Filters only external users' optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' + use :pagination end get do unless can?(current_user, :read_users_list, nil) @@ -330,6 +332,7 @@ module API end params do requires :id, type: Integer, desc: 'The ID of the user' + use :pagination end get ':id/events' do user = User.find_by(id: params[:id]) -- cgit v1.2.1 From 1123057ab792ac73b1611f4d3a9faf79369dd6da Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 26 Oct 2016 23:21:50 +0200 Subject: Feature: delegate all open discussions to Issue When a merge request can only be merged when all discussions are resolved. This feature allows to easily delegate those discussions to a new issue, while marking them as resolved in the merge request. The user is presented with a new issue, prepared with mentions of all unresolved discussions, including the first unresolved note of the discussion, time and link to the note. When the issue is created, the discussions in the merge request will get a system note directing the user to the newly created issue. --- lib/api/issues.rb | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) (limited to 'lib/api') diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 049b4fb214c..cfb7c45de8e 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -28,6 +28,14 @@ module API new_params end + + def merge_request_for_resolving_discussions + return unless merge_request_iid = params[:merge_request_for_resolving_discussions] + + @merge_request_for_resolving_discussions ||= MergeRequestsFinder.new(current_user, project_id: user_project.id). + execute. + find_by(iid: merge_request_iid) + end end resource :issues do @@ -151,24 +159,28 @@ module API # Create a new project issue # # Parameters: - # id (required) - The ID of a project - # title (required) - The title of an issue - # description (optional) - The description of an issue - # assignee_id (optional) - The ID of a user to assign issue - # milestone_id (optional) - The ID of a milestone to assign issue - # labels (optional) - The labels of an issue - # created_at (optional) - Date time string, ISO 8601 formatted - # due_date (optional) - Date time string in the format YEAR-MONTH-DAY - # confidential (optional) - Boolean parameter if the issue should be confidential + # id (required) - The ID of a project + # title (required) - The title of an issue + # description (optional) - The description of an issue + # assignee_id (optional) - The ID of a user to assign issue + # milestone_id (optional) - The ID of a milestone to assign issue + # labels (optional) - The labels of an issue + # created_at (optional) - Date time string, ISO 8601 formatted + # due_date (optional) - Date time string in the format YEAR-MONTH-DAY + # confidential (optional) - Boolean parameter if the issue should be confidential + # merge_request_for_resolving_discussions (optional) - The IID of a merge request for which to resolve discussions # Example Request: # POST /projects/:id/issues post ':id/issues' do required_attributes! [:title] - keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels] + keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels, :merge_request_for_resolving_discussions] keys << :created_at if current_user.admin? || user_project.owner == current_user attrs = attributes_for_keys(keys) + attrs[:labels] = params[:labels] if params[:labels] + attrs[:merge_request_for_resolving_discussions] = merge_request_for_resolving_discussions if params[:merge_request_for_resolving_discussions] + # Convert and filter out invalid confidential flags attrs['confidential'] = to_boolean(attrs['confidential']) attrs.delete('confidential') if attrs['confidential'].nil? -- cgit v1.2.1 From 51a921baf90be2a6654990b9b7d062f4c613a64b Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Tue, 6 Dec 2016 17:13:14 +0100 Subject: A simpler implementation of finding a merge request Following a discussion in !7180 --- lib/api/issues.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'lib/api') diff --git a/lib/api/issues.rb b/lib/api/issues.rb index cfb7c45de8e..26c8f2fecd0 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -28,14 +28,6 @@ module API new_params end - - def merge_request_for_resolving_discussions - return unless merge_request_iid = params[:merge_request_for_resolving_discussions] - - @merge_request_for_resolving_discussions ||= MergeRequestsFinder.new(current_user, project_id: user_project.id). - execute. - find_by(iid: merge_request_iid) - end end resource :issues do @@ -179,7 +171,12 @@ module API attrs = attributes_for_keys(keys) attrs[:labels] = params[:labels] if params[:labels] - attrs[:merge_request_for_resolving_discussions] = merge_request_for_resolving_discussions if params[:merge_request_for_resolving_discussions] + + if merge_request_iid = params[:merge_request_for_resolving_discussions] + attrs[:merge_request_for_resolving_discussions] = MergeRequestsFinder.new(current_user, project_id: user_project.id). + execute. + find_by(iid: merge_request_iid) + end # Convert and filter out invalid confidential flags attrs['confidential'] = to_boolean(attrs['confidential']) -- cgit v1.2.1 From 3e7818e93a69d2f628c864e40b00ab0871bff3dc Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 7 Nov 2016 15:15:14 +0100 Subject: Grapify the issues API --- lib/api/helpers.rb | 16 ---- lib/api/issues.rb | 263 +++++++++++++++++++++++------------------------------ 2 files changed, 112 insertions(+), 167 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..898ca470a30 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -217,22 +217,6 @@ module API end end - def issuable_order_by - if params["order_by"] == 'updated_at' - 'updated_at' - else - 'created_at' - end - end - - def issuable_sort - if params["sort"] == 'asc' - :asc - else - :desc - end - end - def filter_by_iid(items, iid) items.where(iid: iid) end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 26c8f2fecd0..c9124649cbb 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -1,6 +1,7 @@ module API - # Issues API class Issues < Grape::API + include PaginationParams + before { authenticate! } helpers do @@ -20,77 +21,68 @@ module API issues.includes(:milestone).where('milestones.title' => milestone) end - def issue_params - new_params = declared(params, include_parent_namespace: false, include_missing: false).to_h - new_params = new_params.with_indifferent_access - new_params.delete(:id) - new_params.delete(:issue_id) + params :issues_params do + optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at', + desc: 'Return issues ordered by `created_at` or `updated_at` fields.' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return issues sorted in `asc` or `desc` order.' + use :pagination + end - new_params + params :issue_params do + optional :description, type: String, desc: 'The description of an issue' + optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' + optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' + optional :labels, type: String, desc: 'Comma-separated list of label names' + optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' + optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' + optional :state_event, type: String, values: %w[open close], + desc: 'State of the issue' end end resource :issues do - # Get currently authenticated user's issues - # - # Parameters: - # state (optional) - Return "opened" or "closed" issues - # labels (optional) - Comma-separated list of label names - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example Requests: - # GET /issues - # GET /issues?state=opened - # GET /issues?state=closed - # GET /issues?labels=foo - # GET /issues?labels=foo,bar - # GET /issues?labels=foo,bar&state=opened + desc "Get currently authenticated user's issues" do + success Entities::Issue + end + params do + optional :state, type: String, values: %w[opened closed all], default: 'all', + desc: 'Return opened, closed, or all issues' + use :issues_params + end get do issues = current_user.issues.inc_notes_with_associations - issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_state(issues, params[:state]) issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? - issues = issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(params[:order_by] => params[:sort]) present paginate(issues), with: Entities::Issue, current_user: current_user end end + params do + requires :id, type: String, desc: 'The ID of a group' + end resource :groups do - # Get a list of group issues - # - # Parameters: - # id (required) - The ID of a group - # state (optional) - Return "opened" or "closed" issues - # labels (optional) - Comma-separated list of label names - # milestone (optional) - Milestone title - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example Requests: - # GET /groups/:id/issues - # GET /groups/:id/issues?state=opened - # GET /groups/:id/issues?state=closed - # GET /groups/:id/issues?labels=foo - # GET /groups/:id/issues?labels=foo,bar - # GET /groups/:id/issues?labels=foo,bar&state=opened - # GET /groups/:id/issues?milestone=1.0.0 - # GET /groups/:id/issues?milestone=1.0.0&state=closed + desc 'Get a list of group issues' do + success Entities::Issue + end + params do + optional :state, type: String, values: %w[opened closed all], default: 'opened', + desc: 'Return opened, closed, or all issues' + use :issues_params + end get ":id/issues" do - group = find_group!(params[:id]) + group = find_group!(params.delete(:id)) - params[:state] ||= 'opened' params[:group_id] = group.id params[:milestone_title] = params.delete(:milestone) params[:label_name] = params.delete(:labels) - if params[:order_by] || params[:sort] - # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example) - params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}" - end - issues = IssuesFinder.new(current_user, params).execute + issues = issues.reorder(params[:order_by] => params[:sort]) present paginate(issues), with: Entities::Issue, current_user: current_user end end @@ -98,32 +90,19 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects do - # Get a list of project issues - # - # Parameters: - # id (required) - The ID of a project - # iid (optional) - Return the project issue having the given `iid` - # state (optional) - Return "opened" or "closed" issues - # labels (optional) - Comma-separated list of label names - # milestone (optional) - Milestone title - # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` - # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - # - # Example Requests: - # GET /projects/:id/issues - # GET /projects/:id/issues?state=opened - # GET /projects/:id/issues?state=closed - # GET /projects/:id/issues?labels=foo - # GET /projects/:id/issues?labels=foo,bar - # GET /projects/:id/issues?labels=foo,bar&state=opened - # GET /projects/:id/issues?milestone=1.0.0 - # GET /projects/:id/issues?milestone=1.0.0&state=closed - # GET /issues?iid=42 + desc 'Get a list of project issues' do + success Entities::Issue + end + params do + optional :state, type: String, values: %w[opened closed all], default: 'all', + desc: 'Return opened, closed, or all issues' + optional :iid, type: Integer, desc: 'The IID of the issue' + use :issues_params + end get ":id/issues" do issues = IssuesFinder.new(current_user, project_id: user_project.id).execute.inc_notes_with_associations - issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_state(issues, params[:state]) issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil? @@ -131,59 +110,49 @@ module API issues = filter_issues_milestone(issues, params[:milestone]) end - issues = issues.reorder(issuable_order_by => issuable_sort) - + issues = issues.reorder(params[:order_by] => params[:sort]) present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project end - # Get a single project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # GET /projects/:id/issues/:issue_id + desc 'Get a single project issue' do + success Entities::Issue + end + params do + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + end get ":id/issues/:issue_id" do - @issue = find_project_issue(params[:issue_id]) - present @issue, with: Entities::Issue, current_user: current_user, project: user_project + issue = find_project_issue(params[:issue_id]) + present issue, with: Entities::Issue, current_user: current_user, project: user_project end - # Create a new project issue - # - # Parameters: - # id (required) - The ID of a project - # title (required) - The title of an issue - # description (optional) - The description of an issue - # assignee_id (optional) - The ID of a user to assign issue - # milestone_id (optional) - The ID of a milestone to assign issue - # labels (optional) - The labels of an issue - # created_at (optional) - Date time string, ISO 8601 formatted - # due_date (optional) - Date time string in the format YEAR-MONTH-DAY - # confidential (optional) - Boolean parameter if the issue should be confidential - # merge_request_for_resolving_discussions (optional) - The IID of a merge request for which to resolve discussions - # Example Request: - # POST /projects/:id/issues + desc 'Create a new project issue' do + success Entities::Issue + end + params do + requires :title, type: String, desc: 'The title of an issue' + optional :created_at, type: DateTime, + desc: 'Date time when the issue was created. Available only for admins and project owners.' + optional :merge_request_for_resolving_discussions, type: Integer, + desc: 'The IID of a merge request for which to resolve discussions' + use :issue_params + end post ':id/issues' do - required_attributes! [:title] - - keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential, :labels, :merge_request_for_resolving_discussions] - keys << :created_at if current_user.admin? || user_project.owner == current_user - attrs = attributes_for_keys(keys) + # Setting created_at time only allowed for admins and project owners + unless current_user.admin? || user_project.owner == current_user + params.delete(:created_at) + end - attrs[:labels] = params[:labels] if params[:labels] + issue_params = declared_params(include_missing: false) if merge_request_iid = params[:merge_request_for_resolving_discussions] - attrs[:merge_request_for_resolving_discussions] = MergeRequestsFinder.new(current_user, project_id: user_project.id). + issue_params[:merge_request_for_resolving_discussions] = MergeRequestsFinder.new(current_user, project_id: user_project.id). execute. find_by(iid: merge_request_iid) end - # Convert and filter out invalid confidential flags - attrs['confidential'] = to_boolean(attrs['confidential']) - attrs.delete('confidential') if attrs['confidential'].nil? - - issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute - + issue = ::Issues::CreateService.new(user_project, + current_user, + issue_params.merge(request: request, api: true)).execute if issue.spam? render_api_error!({ error: 'Spam detected' }, 400) end @@ -199,31 +168,26 @@ module API success Entities::Issue end params do - requires :id, type: String, desc: 'The ID of a project' - requires :issue_id, type: Integer, desc: "The ID of a project issue" - optional :title, type: String, desc: 'The new title of the issue' - optional :description, type: String, desc: 'The description of an issue' - optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue' - optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' - optional :labels, type: String, desc: 'The labels of an issue' - optional :state_event, type: String, values: ['close', 'reopen'], desc: 'The state event of an issue' - # TODO 9.0, use the Grape DateTime type here - optional :updated_at, type: String, desc: 'Date time string, ISO 8601 formatted' - optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY' - # TODO 9.0, use the Grape boolean type here - optional :confidential, type: String, desc: 'Boolean parameter if the issue should be confidential' + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + optional :title, type: String, desc: 'The title of an issue' + optional :updated_at, type: DateTime, + desc: 'Date time when the issue was updated. Available only for admins and project owners.' + use :issue_params + at_least_one_of :title, :description, :assignee_id, :milestone_id, + :labels, :created_at, :due_date, :confidential, :state_event end put ':id/issues/:issue_id' do - issue = user_project.issues.find(params[:issue_id]) + issue = user_project.issues.find(params.delete(:issue_id)) authorize! :update_issue, issue - # Convert and filter out invalid confidential flags - params[:confidential] = to_boolean(params[:confidential]) - params.delete(:confidential) if params[:confidential].nil? - - params.delete(:updated_at) unless current_user.admin? || user_project.owner == current_user + # Setting created_at time only allowed for admins and project owners + unless current_user.admin? || user_project.owner == current_user + params.delete(:updated_at) + end - issue = ::Issues::UpdateService.new(user_project, current_user, issue_params).execute(issue) + issue = ::Issues::UpdateService.new(user_project, + current_user, + declared_params(include_missing: false)).execute(issue) if issue.valid? present issue, with: Entities::Issue, current_user: current_user, project: user_project @@ -232,19 +196,19 @@ module API end end - # Move an existing issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # to_project_id (required) - The ID of the new project - # Example Request: - # POST /projects/:id/issues/:issue_id/move + desc 'Move an existing issue' do + success Entities::Issue + end + params do + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + requires :to_project_id, type: Integer, desc: 'The ID of the new project' + end post ':id/issues/:issue_id/move' do - required_attributes! [:to_project_id] + issue = user_project.issues.find_by(id: params[:issue_id]) + not_found!('Issue') unless issue - issue = user_project.issues.find(params[:issue_id]) - new_project = Project.find(params[:to_project_id]) + new_project = Project.find_by(id: params[:to_project_id]) + not_found!('Project') unless new_project begin issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) @@ -254,16 +218,13 @@ module API end end - # - # Delete a project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # DELETE /projects/:id/issues/:issue_id + desc 'Delete a project issue' + params do + requires :issue_id, type: Integer, desc: 'The ID of a project issue' + end delete ":id/issues/:issue_id" do issue = user_project.issues.find_by(id: params[:issue_id]) + not_found!('Issue') unless issue authorize!(:destroy_issue, issue) issue.destroy -- cgit v1.2.1 From 9e307b93b7c57a14de6e425566f88511024859a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 2 Dec 2016 14:33:29 +0100 Subject: Allow public access to some Tag API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/tags.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/tags.rb b/lib/api/tags.rb index cd33f9a9903..5b345db3a41 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -1,7 +1,6 @@ module API # Git Tags API class Tags < Grape::API - before { authenticate! } before { authorize! :download_code, user_project } params do -- cgit v1.2.1 From 8b379465a5be48c8062379a3dea8e58110c52d87 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 21 Nov 2016 11:42:10 +0000 Subject: Reenables /user API request to return private-token if user is admin and requested with sudo --- lib/api/entities.rb | 1 + lib/api/users.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 899d68bc6c7..1dd191161ef 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -32,6 +32,7 @@ module API expose :can_create_project?, as: :can_create_project expose :two_factor_enabled?, as: :two_factor_enabled expose :external + expose :private_token, if: lambda { |user, options| user.is_admin? && options[:sudo_identifier] } end class UserLogin < UserFull diff --git a/lib/api/users.rb b/lib/api/users.rb index bc2362aa72e..b3870e0c7c8 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -353,7 +353,7 @@ module API success Entities::UserFull end get do - present current_user, with: Entities::UserFull + present current_user, with: Entities::UserFull, sudo_identifier: sudo_identifier end desc "Get the currently authenticated user's SSH keys" do -- cgit v1.2.1 From 3ed96afc47c481db4f8c0a6581602abaee920808 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 21 Nov 2016 12:59:37 +0000 Subject: adds impersonator variable and makes sudo usage overall more clear --- lib/api/entities.rb | 7 +++---- lib/api/helpers.rb | 13 ++++++++++--- lib/api/session.rb | 4 ++-- lib/api/users.rb | 16 ++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1dd191161ef..006d5f9f44e 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -22,7 +22,7 @@ module API expose :provider, :extern_uid end - class UserFull < User + class UserPublic < User expose :last_sign_in_at expose :confirmed_at expose :email @@ -32,10 +32,9 @@ module API expose :can_create_project?, as: :can_create_project expose :two_factor_enabled?, as: :two_factor_enabled expose :external - expose :private_token, if: lambda { |user, options| user.is_admin? && options[:sudo_identifier] } end - class UserLogin < UserFull + class UserWithPrivateToken < UserPublic expose :private_token end @@ -290,7 +289,7 @@ module API end class SSHKeyWithUser < SSHKey - expose :user, using: Entities::UserFull + expose :user, using: Entities::UserPublic end class Note < Grape::Entity diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..6a47efc74f0 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -44,11 +44,14 @@ module API return nil end - identifier = sudo_identifier() + identifier = sudo_identifier - # If the sudo is the current user do nothing - if identifier && !(@current_user.id == identifier || @current_user.username == identifier) + if identifier + # We check for private_token because we cannot allow PAT to be used forbidden!('Must be admin to use sudo') unless @current_user.is_admin? + forbidden!('Private token must be specified in order to use sudo') unless private_token_used? + + @impersonator = @current_user @current_user = User.by_username_or_id(identifier) not_found!("No user id or username for: #{identifier}") if @current_user.nil? end @@ -399,6 +402,10 @@ module API links.join(', ') end + def private_token_used? + private_token == @current_user.private_token + end + def secret_token Gitlab::Shell.secret_token end diff --git a/lib/api/session.rb b/lib/api/session.rb index d09400b81f5..002ffd1d154 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -1,7 +1,7 @@ module API class Session < Grape::API desc 'Login to get token' do - success Entities::UserLogin + success Entities::UserWithPrivateToken end params do optional :login, type: String, desc: 'The username' @@ -14,7 +14,7 @@ module API return unauthorized! unless user return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled? - present user, with: Entities::UserLogin + present user, with: Entities::UserWithPrivateToken end end end diff --git a/lib/api/users.rb b/lib/api/users.rb index b3870e0c7c8..1dab799dd61 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -51,7 +51,7 @@ module API users = users.external if params[:external] && current_user.is_admin? end - entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic + entity = current_user.is_admin? ? Entities::UserPublic : Entities::UserBasic present paginate(users), with: entity end @@ -66,7 +66,7 @@ module API not_found!('User') unless user if current_user && current_user.is_admin? - present user, with: Entities::UserFull + present user, with: Entities::UserPublic elsif can?(current_user, :read_user, user) present user, with: Entities::User else @@ -75,7 +75,7 @@ module API end desc 'Create a user. Available only for admins.' do - success Entities::UserFull + success Entities::UserPublic end params do requires :email, type: String, desc: 'The email of the user' @@ -99,7 +99,7 @@ module API end if user.save - present user, with: Entities::UserFull + present user, with: Entities::UserPublic else conflict!('Email has already been taken') if User. where(email: user.email). @@ -114,7 +114,7 @@ module API end desc 'Update a user. Available only for admins.' do - success Entities::UserFull + success Entities::UserPublic end params do requires :id, type: Integer, desc: 'The ID of the user' @@ -161,7 +161,7 @@ module API user_params.delete(:provider) if user.update_attributes(user_params) - present user, with: Entities::UserFull + present user, with: Entities::UserPublic else render_validation_error!(user) end @@ -350,10 +350,10 @@ module API resource :user do desc 'Get the currently authenticated user' do - success Entities::UserFull + success Entities::UserPublic end get do - present current_user, with: Entities::UserFull, sudo_identifier: sudo_identifier + present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic end desc "Get the currently authenticated user's SSH keys" do -- cgit v1.2.1 From 83232be0e14cc8b35bf74532203a6e4371c15e70 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 31 Oct 2016 13:00:53 +0200 Subject: Add nested groups support on data level * add parent_id field to namespaces table to store relation with nested groups * create routes table to keep information about full path of every group and project * project/group lookup by full path from routes table Signed-off-by: Dmitriy Zaporozhets --- lib/api/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..96ccde760db 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -108,7 +108,7 @@ module API if id =~ /^\d+$/ Group.find_by(id: id) else - Group.find_by(path: id) + Group.find_by_full_path(id) end end -- cgit v1.2.1 From 4a0ca85834b6cf7decb58857986e535ba4e37c53 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 6 Dec 2016 18:56:59 -0200 Subject: Allow branch names with dots on API endpoint --- lib/api/branches.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'lib/api') diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 73aed624ea7..0950c3d2e88 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -23,9 +23,9 @@ module API success Entities::RepoBranch end params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' end - get ':id/repository/branches/:branch' do + get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do branch = user_project.repository.find_branch(params[:branch]) not_found!("Branch") unless branch @@ -39,11 +39,11 @@ module API success Entities::RepoBranch end params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch' optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch' end - put ':id/repository/branches/:branch/protect' do + put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do authorize_admin_project branch = user_project.repository.find_branch(params[:branch]) @@ -76,9 +76,9 @@ module API success Entities::RepoBranch end params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' end - put ':id/repository/branches/:branch/unprotect' do + put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do authorize_admin_project branch = user_project.repository.find_branch(params[:branch]) @@ -112,9 +112,9 @@ module API desc 'Delete a branch' params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' end - delete ":id/repository/branches/:branch" do + delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do authorize_push_project result = DeleteBranchService.new(user_project, current_user). -- cgit v1.2.1 From 593c912151eaef865d31fc2a8307ef6d337c2349 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 5 Dec 2016 15:40:53 +0100 Subject: Grapify the service API --- lib/api/helpers.rb | 11 - lib/api/services.rb | 626 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 572 insertions(+), 65 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..16ac40b7142 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -93,17 +93,6 @@ module API end end - def project_service(project = user_project) - @project_service ||= project.find_or_initialize_service(params[:service_slug].underscore) - @project_service || not_found!("Service") - end - - def service_attributes - @service_attributes ||= project_service.fields.inject([]) do |arr, hash| - arr << hash[:name].to_sym - end - end - def find_group(id) if id =~ /^\d+$/ Group.find_by(id: id) diff --git a/lib/api/services.rb b/lib/api/services.rb index bc427705777..fde2e2746f1 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -1,84 +1,602 @@ module API - # Projects API class Services < Grape::API + services = { + 'asana' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'User API token' + }, + { + required: false, + name: :restrict_to_branch, + type: String, + desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches' + } + ], + 'assembla' => [ + { + required: true, + name: :token, + type: String, + desc: 'The authentication token' + }, + { + required: false, + name: :subdomain, + type: String, + desc: 'Subdomain setting' + } + ], + 'bamboo' => [ + { + required: true, + name: :bamboo_url, + type: String, + desc: 'Bamboo root URL like https://bamboo.example.com' + }, + { + required: true, + name: :build_key, + type: String, + desc: 'Bamboo build plan key like' + }, + { + required: true, + name: :username, + type: String, + desc: 'A user with API access, if applicable' + }, + { + required: true, + name: :password, + type: String, + desc: 'Passord of the user' + } + ], + 'bugzilla' => [ + { + required: true, + name: :new_issue_url, + type: String, + desc: 'New issue URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'Issues URL' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'Project URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'Description' + }, + { + required: false, + name: :title, + type: String, + desc: 'Title' + } + ], + 'buildkite' => [ + { + required: true, + name: :token, + type: String, + desc: 'Buildkite project GitLab token' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'The buildkite project URL' + }, + { + required: false, + name: :enable_ssl_verification, + type: Boolean, + desc: 'Enable SSL verification for communication' + } + ], + 'builds-email' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Comma-separated list of recipient email addresses' + }, + { + required: false, + name: :add_pusher, + type: Boolean, + desc: 'Add pusher to recipients list' + }, + { + required: false, + name: :notify_only_broken_builds, + type: Boolean, + desc: 'Notify only broken builds' + } + ], + 'campfire' => [ + { + required: true, + name: :token, + type: String, + desc: 'Campfire token' + }, + { + required: false, + name: :subdomain, + type: String, + desc: 'Campfire subdomain' + }, + { + required: false, + name: :room, + type: String, + desc: 'Campfire room' + }, + ], + 'custom-issue-tracker' => [ + { + required: true, + name: :new_issue_url, + type: String, + desc: 'New issue URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'Issues URL' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'Project URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'Description' + }, + { + required: false, + name: :title, + type: String, + desc: 'Title' + } + ], + 'drone-ci' => [ + { + required: true, + name: :token, + type: String, + desc: 'Drone CI token' + }, + { + required: true, + name: :drone_url, + type: String, + desc: 'Drone CI URL' + }, + { + required: false, + name: :enable_ssl_verification, + type: Boolean, + desc: 'Enable SSL verification for communication' + } + ], + 'emails-on-push' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Comma-separated list of recipient email addresses' + }, + { + required: false, + name: :disable_diffs, + type: Boolean, + desc: 'Disable code diffs' + }, + { + required: false, + name: :send_from_committer_email, + type: Boolean, + desc: 'Send from committer' + } + ], + 'external-wiki' => [ + { + required: true, + name: :external_wiki_url, + type: String, + desc: 'The URL of the external Wiki' + } + ], + 'flowdock' => [ + { + required: true, + name: :token, + type: String, + desc: 'Flowdock token' + } + ], + 'gemnasium' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'Your personal API key on gemnasium.com' + }, + { + required: true, + name: :token, + type: String, + desc: "The project's slug on gemnasium.com" + } + ], + 'hipchat' => [ + { + required: true, + name: :token, + type: String, + desc: 'The room token' + }, + { + required: false, + name: :room, + type: String, + desc: 'The room name or ID' + }, + { + required: false, + name: :color, + type: String, + desc: 'The room color' + }, + { + required: false, + name: :notify, + type: Boolean, + desc: 'Enable notifications' + }, + { + required: false, + name: :api_version, + type: String, + desc: 'Leave blank for default (v2)' + }, + { + required: false, + name: :server, + type: String, + desc: 'Leave blank for default. https://hipchat.example.com' + } + ], + 'irker' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Recipients/channels separated by whitespaces' + }, + { + required: false, + name: :default_irc_uri, + type: String, + desc: 'Default: irc://irc.network.net:6697' + }, + { + required: false, + name: :server_host, + type: String, + desc: 'Server host. Default localhost' + }, + { + required: false, + name: :server_port, + type: Integer, + desc: 'Server port. Default 6659' + }, + { + required: false, + name: :colorize_messages, + type: Boolean, + desc: 'Colorize messages' + } + ], + 'jira' => [ + { + required: true, + name: :url, + type: String, + desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com' + }, + { + required: true, + name: :project_key, + type: String, + desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ' + }, + { + required: false, + name: :username, + type: String, + desc: 'The username of the user created to be used with GitLab/JIRA' + }, + { + required: false, + name: :password, + type: String, + desc: 'The password of the user created to be used with GitLab/JIRA' + }, + { + required: false, + name: :jira_issue_transition_id, + type: Integer, + desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`' + } + ], + 'mattermost-slash-commands' => [ + { + required: true, + name: :token, + type: String, + desc: 'The Mattermost token' + } + ], + 'pipelines-email' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Comma-separated list of recipient email addresses' + }, + { + required: false, + name: :notify_only_broken_builds, + type: Boolean, + desc: 'Notify only broken builds' + } + ], + 'pivotaltracker' => [ + { + required: true, + name: :token, + type: String, + desc: 'The Pivotaltracker token' + }, + { + required: false, + name: :restrict_to_branch, + type: String, + desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.' + } + ], + 'pushover' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'The application key' + }, + { + required: true, + name: :user_key, + type: String, + desc: 'The user key' + }, + { + required: true, + name: :priority, + type: String, + desc: 'The priority' + }, + { + required: true, + name: :device, + type: String, + desc: 'Leave blank for all active devices' + }, + { + required: true, + name: :sound, + type: String, + desc: 'The sound of the notification' + } + ], + 'redmine' => [ + { + required: true, + name: :new_issue_url, + type: String, + desc: 'The new issue URL' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'The project URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'The issues URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'The description of the tracker' + } + ], + 'slack' => [ + { + required: true, + name: :webhook, + type: String, + desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...' + }, + { + required: false, + name: :new_issue_url, + type: String, + desc: 'The user name' + }, + { + required: false, + name: :channel, + type: String, + desc: 'The channel name' + } + ], + 'teamcity' => [ + { + required: true, + name: :teamcity_url, + type: String, + desc: 'TeamCity root URL like https://teamcity.example.com' + }, + { + required: true, + name: :build_type, + type: String, + desc: 'Build configuration ID' + }, + { + required: true, + name: :username, + type: String, + desc: 'A user with permissions to trigger a manual build' + }, + { + required: true, + name: :password, + type: String, + desc: 'The password of the user' + } + ] + }.freeze + + trigger_services = { + 'mattermost-slash-commands' => [ + { + name: :token, + type: String, + desc: 'The Mattermost token' + } + ] + }.freeze + resource :projects do before { authenticate! } before { authorize_admin_project } - # Set service for project - # - # Example Request: - # - # PUT /projects/:id/services/gitlab-ci - # - put ':id/services/:service_slug' do - if project_service - validators = project_service.class.validators.select do |s| - s.class == ActiveRecord::Validations::PresenceValidator && - s.attributes != [:project_id] + helpers do + def service_attributes(service) + service.fields.inject([]) do |arr, hash| + arr << hash[:name].to_sym end + end + end - required_attributes! validators.map(&:attributes).flatten.uniq - attrs = attributes_for_keys service_attributes + services.each do |service_slug, settings| + desc "Set #{service_slug} service for project" + params do + settings.each do |setting| + if setting[:required] + requires setting[:name], type: setting[:type], desc: setting[:desc] + else + optional setting[:name], type: setting[:type], desc: setting[:desc] + end + end + end + put ":id/services/#{service_slug}" do + service = user_project.find_or_initialize_service(service_slug.underscore) + service_params = declared_params(include_missing: false).merge(active: true) - if project_service.update_attributes(attrs.merge(active: true)) + if service.update_attributes(service_params) true else - not_found! + render_api_error!('400 Bad Request', 400) end end end - # Delete service for project - # - # Example Request: - # - # DELETE /project/:id/services/gitlab-ci - # - delete ':id/services/:service_slug' do - if project_service - attrs = service_attributes.inject({}) do |hash, key| - hash.merge!(key => nil) - end + desc "Delete a service for project" + params do + requires :service_slug, type: String, values: services.keys, desc: 'The name of the service' + end + delete ":id/services/:service_slug" do + service = user_project.find_or_initialize_service(params[:service_slug].underscore) - if project_service.update_attributes(attrs.merge(active: false)) - true - else - not_found! - end + attrs = service_attributes(service).inject({}) do |hash, key| + hash.merge!(key => nil) + end + + if service.update_attributes(attrs.merge(active: false)) + true + else + render_api_error!('400 Bad Request', 400) end end - # Get service settings for project - # - # Example Request: - # - # GET /project/:id/services/gitlab-ci - # - get ':id/services/:service_slug' do - present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin? + desc 'Get the service settings for project' do + success Entities::ProjectService + end + params do + requires :service_slug, type: String, values: services.keys, desc: 'The name of the service' + end + get ":id/services/:service_slug" do + service = user_project.find_or_initialize_service(params[:service_slug].underscore) + present service, with: Entities::ProjectService, include_passwords: current_user.is_admin? end end - resource :projects do - desc 'Trigger a slash command' do - detail 'Added in GitLab 8.13' + trigger_services.each do |service_slug, settings| + params do + requires :id, type: String, desc: 'The ID of a project' end - post ':id/services/:service_slug/trigger' do - project = find_project(params[:id]) + resource :projects do + desc "Trigger a slash command for #{service_slug}" do + detail 'Added in GitLab 8.13' + end + params do + settings.each do |setting| + requires setting[:name], type: setting[:type], desc: setting[:desc] + end + end + post ":id/services/#{service_slug.underscore}/trigger" do + project = find_project(params[:id]) - # This is not accurate, but done to prevent leakage of the project names - not_found!('Service') unless project + # This is not accurate, but done to prevent leakage of the project names + not_found!('Service') unless project - service = project_service(project) + service = project.find_or_initialize_service(service_slug.underscore) - result = service.try(:active?) && service.try(:trigger, params) + result = service.try(:active?) && service.try(:trigger, params) - if result - status result[:status] || 200 - present result - else - not_found!('Service') + if result + status result[:status] || 200 + present result + else + not_found!('Service') + end end end end -- cgit v1.2.1 From 81a12c10fe7ba6f58ab4d1ae57861aa8899d545f Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 12 Dec 2016 09:55:29 +0100 Subject: API: Fix groups filter --- lib/api/groups.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index fbf7513302b..105d3ee342e 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,7 +1,7 @@ module API class Groups < Grape::API include PaginationParams - + before { authenticate! } helpers do @@ -117,11 +117,20 @@ module API success Entities::Project end params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' use :pagination end get ":id/projects" do group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) + projects = filter_projects(projects) present paginate(projects), with: Entities::Project, user: current_user end -- cgit v1.2.1 From 2f45d3bcf0f28d4cd4124b4c9722edc1d3085201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 9 Dec 2016 18:48:20 +0100 Subject: API: Memoize the current_user so that the sudo can work properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The issue was arising when `#current_user` was called a second time after a user was impersonated: the `User#is_admin?` check would be performed on it and it would fail. Signed-off-by: Rémy Coutable --- lib/api/helpers.rb | 131 +++++++++++++++++++++++++++++++---------------------- lib/api/users.rb | 2 +- 2 files changed, 79 insertions(+), 54 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 8b0f8deadfa..2041f0dac6b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -7,67 +7,23 @@ module API SUDO_HEADER = "HTTP_SUDO" SUDO_PARAM = :sudo - def private_token - params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] - end - - def warden - env['warden'] - end - - # Check the Rails session for valid authentication details - # - # Until CSRF protection is added to the API, disallow this method for - # state-changing endpoints - def find_user_from_warden - warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) - end - def declared_params(options = {}) options = { include_parent_namespaces: false }.merge(options) declared(params, options).to_h.symbolize_keys end - def find_user_by_private_token - token = private_token - return nil unless token.present? - - User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) - end - def current_user - @current_user ||= find_user_by_private_token - @current_user ||= doorkeeper_guard - @current_user ||= find_user_from_warden - - unless @current_user && Gitlab::UserAccess.new(@current_user).allowed? - return nil - end - - identifier = sudo_identifier + return @current_user if defined?(@current_user) - if identifier - # We check for private_token because we cannot allow PAT to be used - forbidden!('Must be admin to use sudo') unless @current_user.is_admin? - forbidden!('Private token must be specified in order to use sudo') unless private_token_used? + @current_user = initial_current_user - @impersonator = @current_user - @current_user = User.by_username_or_id(identifier) - not_found!("No user id or username for: #{identifier}") if @current_user.nil? - end + sudo! @current_user end - def sudo_identifier - identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] - - # Regex for integers - if !!(identifier =~ /\A[0-9]+\z/) - identifier.to_i - else - identifier - end + def sudo? + initial_current_user != current_user end def user_project @@ -354,6 +310,79 @@ module API private + def private_token + params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] + end + + def warden + env['warden'] + end + + # Check the Rails session for valid authentication details + # + # Until CSRF protection is added to the API, disallow this method for + # state-changing endpoints + def find_user_from_warden + warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) + end + + def find_user_by_private_token + token = private_token + return nil unless token.present? + + User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) + end + + def initial_current_user + return @initial_current_user if defined?(@initial_current_user) + + @initial_current_user ||= find_user_by_private_token + @initial_current_user ||= doorkeeper_guard + @initial_current_user ||= find_user_from_warden + + unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed? + @initial_current_user = nil + end + + @initial_current_user + end + + def sudo! + return unless sudo_identifier + return unless initial_current_user.is_a?(User) + + unless initial_current_user.is_admin? + forbidden!('Must be admin to use sudo') + end + + # Only private tokens should be used for the SUDO feature + unless private_token == initial_current_user.private_token + forbidden!('Private token must be specified in order to use sudo') + end + + sudoed_user = User.by_username_or_id(sudo_identifier) + + if sudoed_user + @current_user = sudoed_user + else + not_found!("No user id or username for: #{sudo_identifier}") + end + end + + def sudo_identifier + return @sudo_identifier if defined?(@sudo_identifier) + + identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] + + # Regex for integers + @sudo_identifier = + if !!(identifier =~ /\A[0-9]+\z/) + identifier.to_i + else + identifier + end + end + def add_pagination_headers(paginated_data) header 'X-Total', paginated_data.total_count.to_s header 'X-Total-Pages', paginated_data.total_pages.to_s @@ -386,10 +415,6 @@ module API links.join(', ') end - def private_token_used? - private_token == @current_user.private_token - end - def secret_token Gitlab::Shell.secret_token end diff --git a/lib/api/users.rb b/lib/api/users.rb index 1dab799dd61..c7db2d71017 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -353,7 +353,7 @@ module API success Entities::UserPublic end get do - present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic + present current_user, with: sudo? ? Entities::UserWithPrivateToken : Entities::UserPublic end desc "Get the currently authenticated user's SSH keys" do -- cgit v1.2.1 From 2cbd07e69c647726cfb927bf35a594850d4f22fa Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 30 Nov 2016 17:12:43 +0100 Subject: Don't allow blank MR titles in API --- lib/api/merge_requests.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 55bdbc6a47c..5d1fe22f2df 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -143,8 +143,8 @@ module API success Entities::MergeRequest end params do - optional :title, type: String, desc: 'The title of the merge request' - optional :target_branch, type: String, desc: 'The target branch' + optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' + optional :target_branch, type: String, allow_blank: false, desc: 'The target branch' optional :state_event, type: String, values: %w[close reopen merge], desc: 'Status of the merge request' use :optional_params -- cgit v1.2.1 From 7841be243e5f28828cea95500b554d7f5cc039eb Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 13 Dec 2016 13:51:30 +0100 Subject: API: Ability to get group's project in simple representation --- lib/api/groups.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 105d3ee342e..9b9d3df7435 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -125,13 +125,16 @@ module API default: 'created_at', desc: 'Return projects ordered by field' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return projects sorted in ascending and descending order' + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' use :pagination end get ":id/projects" do group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) projects = filter_projects(projects) - present paginate(projects), with: Entities::Project, user: current_user + entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project + present paginate(projects), with: entity, user: current_user end desc 'Transfer a project to the group namespace. Available only for admin.' do -- cgit v1.2.1 From d95b709a66a5597dced25a2b9df9a1e24fc6d49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 13 Dec 2016 15:53:00 +0100 Subject: Be smarter when finding a sudoed user in API::Helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/helpers.rb | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'lib/api') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2041f0dac6b..8260fcab80a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -34,6 +34,14 @@ module API @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute end + def find_user(id) + if id =~ /^\d+$/ + User.find_by(id: id) + else + User.find_by(username: id) + end + end + def find_project(id) if id =~ /^\d+$/ Project.find_by(id: id) @@ -349,7 +357,7 @@ module API def sudo! return unless sudo_identifier - return unless initial_current_user.is_a?(User) + return unless initial_current_user unless initial_current_user.is_admin? forbidden!('Must be admin to use sudo') @@ -360,7 +368,7 @@ module API forbidden!('Private token must be specified in order to use sudo') end - sudoed_user = User.by_username_or_id(sudo_identifier) + sudoed_user = find_user(sudo_identifier) if sudoed_user @current_user = sudoed_user @@ -370,17 +378,7 @@ module API end def sudo_identifier - return @sudo_identifier if defined?(@sudo_identifier) - - identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] - - # Regex for integers - @sudo_identifier = - if !!(identifier =~ /\A[0-9]+\z/) - identifier.to_i - else - identifier - end + @sudo_identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] end def add_pagination_headers(paginated_data) -- cgit v1.2.1 From 1a81fcfbd8261862c9614f0ea317af02ae20b24a Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 12 Dec 2016 13:04:13 +0100 Subject: API: Ability to cherry-pick a commit --- lib/api/commits.rb | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 2670a2d413a..cf2489dbb67 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -1,7 +1,6 @@ require 'mime/types' module API - # Projects commits API class Commits < Grape::API include PaginationParams @@ -121,6 +120,41 @@ module API present paginate(notes), with: Entities::CommitNote end + desc 'Cherry pick commit into a branch' do + detail 'This feature was introduced in GitLab 8.15' + success Entities::RepoCommit + end + params do + requires :sha, type: String, desc: 'A commit sha to be cherry picked' + requires :branch, type: String, desc: 'The name of the branch' + end + post ':id/repository/commits/:sha/cherry_pick' do + authorize! :push_code, user_project + + commit = user_project.commit(params[:sha]) + not_found!('Commit') unless commit + + branch = user_project.repository.find_branch(params[:branch]) + not_found!('Branch') unless branch + + commit_params = { + commit: commit, + create_merge_request: false, + source_project: user_project, + source_branch: commit.cherry_pick_branch_name, + target_branch: params[:branch] + } + + result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute + + if result[:status] == :success + branch = user_project.repository.find_branch(params[:branch]) + present user_project.repository.commit(branch.dereferenced_target), with: Entities::RepoCommit + else + render_api_error!(result[:message], 400) + end + end + desc 'Post comment to commit' do success Entities::CommitNote end -- cgit v1.2.1 From b7b83fe0c9368fa6f04dcb6eb8cd247978bba76b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 8 Dec 2016 16:36:26 +0000 Subject: Introduce deployment services, starting with a KubernetesService --- lib/api/services.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index fde2e2746f1..b1e072b4f47 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -351,6 +351,34 @@ module API desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`' } ], + + 'kubernetes' => [ + { + required: true, + name: :namespace, + type: String, + desc: 'The Kubernetes namespace to use' + }, + { + required: true, + name: :api_url, + type: String, + desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com' + }, + { + required: true, + name: :token, + type: String, + desc: 'The service token to authenticate against the Kubernetes cluster with' + }, + { + required: false, + name: :ca_pem, + type: String, + desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)' + }, + ], + 'mattermost-slash-commands' => [ { required: true, -- cgit v1.2.1 From 93a03cd92f6418fbeaf126c30c161ab40d377e94 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 8 Dec 2016 01:09:18 +0000 Subject: Add an environment slug --- lib/api/entities.rb | 2 +- lib/api/environments.rb | 3 +++ lib/api/helpers/custom_validators.rb | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 lib/api/helpers/custom_validators.rb (limited to 'lib/api') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 01c0f5072ba..dfbb3ab86dd 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -629,7 +629,7 @@ module API end class EnvironmentBasic < Grape::Entity - expose :id, :name, :external_url + expose :id, :name, :slug, :external_url end class Environment < EnvironmentBasic diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 80bbd9bb6e4..1a7e68f0528 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -1,6 +1,7 @@ module API # Environments RESTfull API endpoints class Environments < Grape::API + include ::API::Helpers::CustomValidators include PaginationParams before { authenticate! } @@ -29,6 +30,7 @@ module API params do requires :name, type: String, desc: 'The name of the environment to be created' optional :external_url, type: String, desc: 'URL on which this deployment is viewable' + optional :slug, absence: { message: "is automatically generated and cannot be changed" } end post ':id/environments' do authorize! :create_environment, user_project @@ -50,6 +52,7 @@ module API requires :environment_id, type: Integer, desc: 'The environment ID' optional :name, type: String, desc: 'The new environment name' optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable' + optional :slug, absence: { message: "is automatically generated and cannot be changed" } end put ':id/environments/:environment_id' do authorize! :update_environment, user_project diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb new file mode 100644 index 00000000000..0a8f3073a50 --- /dev/null +++ b/lib/api/helpers/custom_validators.rb @@ -0,0 +1,14 @@ +module API + module Helpers + module CustomValidators + 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 + end + end +end + +Grape::Validations.register_validator(:absence, ::API::Helpers::CustomValidators::Absence) -- cgit v1.2.1 From a5ccaded656fb215f1f8d503b88c8f28bf90ce68 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 6 Dec 2016 15:59:03 -0200 Subject: Change SlackService to SlackNotificationsService --- lib/api/services.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'lib/api') diff --git a/lib/api/services.rb b/lib/api/services.rb index b1e072b4f47..59232c84c24 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -473,7 +473,7 @@ module API desc: 'The description of the tracker' } ], - 'slack' => [ + 'slack-notification' => [ { required: true, name: :webhook, @@ -493,6 +493,14 @@ module API desc: 'The channel name' } ], + 'mattermost-notification' => [ + { + required: true, + name: :webhook, + type: String, + desc: 'The Mattermost webhook. e.g. http://mattermost_host/hooks/...' + } + ], 'teamcity' => [ { required: true, -- cgit v1.2.1 From 7fa06ed55d18af4d055041eb27d38fecf9b5548f Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 22 Nov 2016 14:34:23 +0530 Subject: Calls to the API are checked for scope. - Move the `Oauth2::AccessTokenValidationService` class to `AccessTokenValidationService`, since it is now being used for personal access token validation as well. - Each API endpoint declares the scopes it accepts (if any). Currently, the top level API module declares the `api` scope, and the `Users` API module declares the `read_user` scope (for GET requests). - Move the `find_user_by_private_token` from the API `Helpers` module to the `APIGuard` module, to avoid littering `Helpers` with more auth-related methods to support `find_user_by_private_token` --- lib/api/api.rb | 2 ++ lib/api/api_guard.rb | 62 ++++++++++++++++++++++++++++++++++++++-------------- lib/api/helpers.rb | 15 +++---------- lib/api/users.rb | 5 ++++- 4 files changed, 54 insertions(+), 30 deletions(-) (limited to 'lib/api') diff --git a/lib/api/api.rb b/lib/api/api.rb index cec2702e44d..9d5adffd8f4 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -3,6 +3,8 @@ module API include APIGuard version 'v3', using: :path + before { allow_access_with_scope :api } + rescue_from Gitlab::Access::AccessDeniedError do rack_response({ 'message' => '403 Forbidden' }.to_json, 403) end diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 8cc7a26f1fa..cd266669b1e 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -6,6 +6,9 @@ module API module APIGuard extend ActiveSupport::Concern + PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" + PRIVATE_TOKEN_PARAM = :private_token + included do |base| # OAuth2 Resource Server Authentication use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request| @@ -41,30 +44,59 @@ module API # Defaults to empty array. # def doorkeeper_guard(scopes: []) - access_token = find_access_token - return nil unless access_token - - case validate_access_token(access_token, scopes) - when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE - raise InsufficientScopeError.new(scopes) + if access_token = find_access_token + case AccessTokenValidationService.validate(access_token, scopes: scopes) + when AccessTokenValidationService::INSUFFICIENT_SCOPE + raise InsufficientScopeError.new(scopes) + when AccessTokenValidationService::EXPIRED + raise ExpiredError + when AccessTokenValidationService::REVOKED + raise RevokedError + when AccessTokenValidationService::VALID + @current_user = User.find(access_token.resource_owner_id) + end + end + end - when Oauth2::AccessTokenValidationService::EXPIRED - raise ExpiredError + def find_user_by_private_token(scopes: []) + token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s - when Oauth2::AccessTokenValidationService::REVOKED - raise RevokedError + return nil unless token_string.present? - when Oauth2::AccessTokenValidationService::VALID - @current_user = User.find(access_token.resource_owner_id) - end + find_user_by_authentication_token(token_string) || find_user_by_personal_access_token(token_string, scopes) end def current_user @current_user end + # Set the authorization scope(s) allowed for the current request. + # + # Note: A call to this method adds to any previous scopes in place. This is done because + # `Grape` callbacks run from the outside-in: the top-level callback (API::API) runs first, then + # the next-level callback (API::API::Users, for example) runs. All these scopes are valid for the + # given endpoint (GET `/api/users` is accessible by the `api` and `read_user` scopes), and so they + # need to be stored. + def allow_access_with_scope(*scopes) + @scopes ||= [] + @scopes.concat(scopes.map(&:to_s)) + end + private + def find_user_by_authentication_token(token_string) + User.find_by_authentication_token(token_string) + end + + def find_user_by_personal_access_token(token_string, scopes) + access_token = PersonalAccessToken.active.find_by_token(token_string) + return unless access_token + + if AccessTokenValidationService.sufficient_scope?(access_token, scopes) + User.find(access_token.user_id) + end + end + def find_access_token @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) end @@ -72,10 +104,6 @@ module API def doorkeeper_request @doorkeeper_request ||= ActionDispatch::Request.new(env) end - - def validate_access_token(access_token, scopes) - Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes) - end end module ClassMethods diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 746849ef4c0..4be659fc20b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -2,8 +2,6 @@ module API module Helpers include Gitlab::Utils - PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" - PRIVATE_TOKEN_PARAM = :private_token SUDO_HEADER = "HTTP_SUDO" SUDO_PARAM = :sudo @@ -308,7 +306,7 @@ module API private def private_token - params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] + params[APIGuard::PRIVATE_TOKEN_PARAM] || env[APIGuard::PRIVATE_TOKEN_HEADER] end def warden @@ -323,18 +321,11 @@ module API warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) end - def find_user_by_private_token - token = private_token - return nil unless token.present? - - User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) - end - def initial_current_user return @initial_current_user if defined?(@initial_current_user) - @initial_current_user ||= find_user_by_private_token - @initial_current_user ||= doorkeeper_guard + @initial_current_user ||= find_user_by_private_token(scopes: @scopes) + @initial_current_user ||= doorkeeper_guard(scopes: @scopes) @initial_current_user ||= find_user_from_warden unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed? diff --git a/lib/api/users.rb b/lib/api/users.rb index c7db2d71017..0842c3874c5 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -2,7 +2,10 @@ module API class Users < Grape::API include PaginationParams - before { authenticate! } + before do + allow_access_with_scope :read_user if request.get? + authenticate! + end resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do helpers do -- cgit v1.2.1 From 4d6da770de94f4bf140507cdf43461b67269ce28 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 24 Nov 2016 13:07:22 +0530 Subject: Implement minor changes from @dbalexandre's review. - Mainly whitespace changes. - Require the migration adding the `scope` column to the `personal_access_tokens` table to have downtime, since API calls will fail if the new code is in place, but the migration hasn't run. - Minor refactoring - load `@scopes` in a `before_action`, since we're doing it in three different places. --- lib/api/api_guard.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'lib/api') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index cd266669b1e..563224a580f 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -44,17 +44,21 @@ module API # Defaults to empty array. # def doorkeeper_guard(scopes: []) - if access_token = find_access_token - case AccessTokenValidationService.validate(access_token, scopes: scopes) - when AccessTokenValidationService::INSUFFICIENT_SCOPE - raise InsufficientScopeError.new(scopes) - when AccessTokenValidationService::EXPIRED - raise ExpiredError - when AccessTokenValidationService::REVOKED - raise RevokedError - when AccessTokenValidationService::VALID - @current_user = User.find(access_token.resource_owner_id) - end + access_token = find_access_token + return nil unless access_token + + case AccessTokenValidationService.validate(access_token, scopes: scopes) + when AccessTokenValidationService::INSUFFICIENT_SCOPE + raise InsufficientScopeError.new(scopes) + + when AccessTokenValidationService::EXPIRED + raise ExpiredError + + when AccessTokenValidationService::REVOKED + raise RevokedError + + when AccessTokenValidationService::VALID + @current_user = User.find(access_token.resource_owner_id) end end -- cgit v1.2.1 From b303948ff549ce57d3b6985c2c366dfcdc5a2ca3 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 5 Dec 2016 22:55:53 +0530 Subject: Convert AccessTokenValidationService into a class. - Previously, AccessTokenValidationService was a module, and all its public methods accepted a token. It makes sense to convert it to a class which accepts a token during initialization. - Also rename the `sufficient_scope?` method to `include_any_scope?` - Based on feedback from @rymai --- lib/api/api_guard.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 563224a580f..df6db140d0e 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -47,7 +47,7 @@ module API access_token = find_access_token return nil unless access_token - case AccessTokenValidationService.validate(access_token, scopes: scopes) + case AccessTokenValidationService.new(access_token).validate(scopes: scopes) when AccessTokenValidationService::INSUFFICIENT_SCOPE raise InsufficientScopeError.new(scopes) @@ -96,7 +96,7 @@ module API access_token = PersonalAccessToken.active.find_by_token(token_string) return unless access_token - if AccessTokenValidationService.sufficient_scope?(access_token, scopes) + if AccessTokenValidationService.new(access_token).include_any_scope?(scopes) User.find(access_token.user_id) end end -- cgit v1.2.1