diff options
Diffstat (limited to 'lib/api')
-rw-r--r-- | lib/api/api.rb | 42 | ||||
-rw-r--r-- | lib/api/deploy_keys.rb | 84 | ||||
-rw-r--r-- | lib/api/entities.rb | 66 | ||||
-rw-r--r-- | lib/api/groups.rb | 92 | ||||
-rw-r--r-- | lib/api/helpers.rb | 54 | ||||
-rw-r--r-- | lib/api/internal.rb | 40 | ||||
-rw-r--r-- | lib/api/issues.rb | 10 | ||||
-rw-r--r-- | lib/api/merge_requests.rb | 49 | ||||
-rw-r--r-- | lib/api/milestones.rb | 7 | ||||
-rw-r--r-- | lib/api/notes.rb | 12 | ||||
-rw-r--r-- | lib/api/project_hooks.rb | 108 | ||||
-rw-r--r-- | lib/api/project_snippets.rb | 123 | ||||
-rw-r--r-- | lib/api/projects.rb | 463 | ||||
-rw-r--r-- | lib/api/repositories.rb | 190 | ||||
-rw-r--r-- | lib/api/session.rb | 19 | ||||
-rw-r--r-- | lib/api/system_hooks.rb | 70 | ||||
-rw-r--r-- | lib/api/users.rb | 51 |
17 files changed, 1109 insertions, 371 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb new file mode 100644 index 00000000000..c4c9f166db1 --- /dev/null +++ b/lib/api/api.rb @@ -0,0 +1,42 @@ +Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} + +module API + class API < Grape::API + version 'v3', using: :path + + rescue_from ActiveRecord::RecordNotFound do + rack_response({'message' => '404 Not found'}.to_json, 404) + end + + rescue_from :all do |exception| + # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 + # why is this not wrapped in something reusable? + trace = exception.backtrace + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << trace.join("\n ") + + API.logger.add Logger::FATAL, message + rack_response({'message' => '500 Internal Server Error'}, 500) + end + + format :json + helpers APIHelpers + + mount Groups + mount Users + mount Projects + mount Repositories + mount Issues + mount Milestones + mount Session + mount MergeRequests + mount Notes + mount Internal + mount SystemHooks + mount ProjectSnippets + mount DeployKeys + mount ProjectHooks + end +end diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb new file mode 100644 index 00000000000..55c947eb176 --- /dev/null +++ b/lib/api/deploy_keys.rb @@ -0,0 +1,84 @@ +module API + # Projects API + class DeployKeys < Grape::API + before { authenticate! } + + resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + + + # Get a specific project's keys + # + # Example Request: + # GET /projects/:id/keys + get ":id/keys" do + present user_project.deploy_keys, with: Entities::SSHKey + end + + # Get single key owned by currently authenticated user + # + # Example Request: + # GET /projects/:id/keys/:id + get ":id/keys/:key_id" do + key = user_project.deploy_keys.find params[:key_id] + present key, with: Entities::SSHKey + end + + # Add new ssh key to currently authenticated user + # If deploy key already exists - it will be joined to project + # but only if original one was is accessible by same user + # + # Parameters: + # key (required) - New SSH Key + # title (required) - New SSH Key's title + # Example Request: + # POST /projects/:id/keys + post ":id/keys" do + attrs = attributes_for_keys [:title, :key] + + if attrs[:key].present? + attrs[:key].strip! + + # check if key already exist in project + key = user_project.deploy_keys.find_by_key(attrs[:key]) + if key + present key, with: Entities::SSHKey + return + end + + # Check for available deploy keys in other projects + key = current_user.accessible_deploy_keys.find_by_key(attrs[:key]) + if key + user_project.deploy_keys << key + present key, with: Entities::SSHKey + return + end + end + + key = DeployKey.new attrs + + if key.valid? && user_project.deploy_keys << key + present key, with: Entities::SSHKey + else + not_found! + end + end + + # Delete existed ssh key of currently authenticated user + # + # Example Request: + # DELETE /projects/:id/keys/:id + delete ":id/keys/:key_id" do + key = user_project.deploy_keys.find params[:key_id] + key.destroy + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c1873d87b55..1f35e9ec5fc 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,46 +1,78 @@ -module Gitlab +module API module Entities class User < Grape::Entity expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter, - :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider + :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider + end + + class UserSafe < Grape::Entity + expose :name end class UserBasic < Grape::Entity - expose :id, :username, :email, :name, :blocked, :created_at + expose :id, :username, :email, :name, :state, :created_at end - class UserLogin < UserBasic + class UserLogin < User expose :private_token + expose :is_admin?, as: :is_admin + expose :can_create_group?, as: :can_create_group + expose :can_create_project?, as: :can_create_project + expose :can_create_team?, as: :can_create_team end class Hook < Grape::Entity expose :id, :url, :created_at end + class ForkedFromProject < Grape::Entity + expose :id + expose :name, :name_with_namespace + expose :path, :path_with_namespace + end + class Project < Grape::Entity - expose :id, :name, :description, :default_branch + expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url expose :owner, using: Entities::UserBasic - expose :private_flag, as: :private + expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at + expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public expose :namespace + expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? } end class ProjectMember < UserBasic - expose :project_access, :as => :access_level do |user, options| + expose :project_access, as: :access_level do |user, options| options[:project].users_projects.find_by_user_id(user.id).project_access end end + class TeamMember < UserBasic + expose :permission, as: :access_level do |user, options| + options[:user_team].user_team_user_relationships.find_by_user_id(user.id).permission + end + end + + class TeamProject < Project + expose :greatest_access, as: :greatest_access_level do |project, options| + options[:user_team].user_team_project_relationships.find_by_project_id(project.id).greatest_access + end + end + class Group < Grape::Entity expose :id, :name, :path, :owner_id end - + class GroupDetail < Group expose :projects, using: Entities::Project end - + class GroupMember < UserBasic + expose :group_access, as: :access_level do |user, options| + options[:group].users_groups.find_by_user_id(user.id).group_access + end + end + class RepoObject < Grape::Entity expose :name, :commit expose :protected do |repo, options| @@ -63,7 +95,7 @@ module Gitlab class Milestone < Grape::Entity expose :id expose (:project_id) {|milestone| milestone.project.id} - expose :title, :description, :due_date, :closed, :updated_at, :created_at + expose :title, :description, :due_date, :state, :updated_at, :created_at end class Issue < Grape::Entity @@ -73,7 +105,7 @@ module Gitlab expose :label_list, as: :labels expose :milestone, using: Entities::Milestone expose :assignee, :author, using: Entities::UserBasic - expose :closed, :updated_at, :created_at + expose :state, :updated_at, :created_at end class SSHKey < Grape::Entity @@ -81,13 +113,15 @@ module Gitlab end class MergeRequest < Grape::Entity - expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged + expose :id, :target_branch, :source_branch, :title, :state + expose :target_project_id, as: :project_id expose :author, :assignee, using: Entities::UserBasic end class Note < Grape::Entity expose :id expose :note, as: :body + expose :attachment_identifier, as: :attachment expose :author, using: Entities::UserBasic expose :created_at end @@ -96,5 +130,11 @@ module Gitlab expose :note expose :author, using: Entities::UserBasic end + + class Event < Grape::Entity + expose :title, :project_id, :action_name + expose :target_id, :target_type, :author_id + expose :data, :target_title + end end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index a67caef0bc5..396554404af 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,9 +1,23 @@ -module Gitlab +module API # groups API class Groups < Grape::API before { authenticate! } resource :groups do + helpers do + def find_group(id) + group = Group.find(id) + if current_user.admin or current_user.groups.include? group + group + else + render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403) + end + end + def validate_access_level?(level) + Gitlab::Access.options_with_owner.values.include? level.to_i + end + end + # Get a groups list # # Example Request: @@ -20,12 +34,14 @@ module Gitlab # Create group. Available only for admin # # Parameters: - # name (required) - Name - # path (required) - Path + # name (required) - The name of the group + # path (required) - The path of the group # Example Request: # POST /groups post do authenticated_as_admin! + required_attributes! [:name, :path] + attrs = attributes_for_keys [:name, :path] @group = Group.new(attrs) @group.owner = current_user @@ -44,13 +60,79 @@ module Gitlab # Example Request: # GET /groups/:id get ":id" do + group = find_group(params[:id]) + present group, with: Entities::GroupDetail + end + + # Transfer a project to the Group namespace + # + # Parameters: + # id - group id + # project_id - project id + # Example Request: + # POST /groups/:id/projects/:project_id + post ":id/projects/:project_id" do + authenticated_as_admin! @group = Group.find(params[:id]) - if current_user.admin or current_user.groups.include? @group - present @group, with: Entities::GroupDetail + project = Project.find(params[:project_id]) + if project.transfer(@group) + present @group else not_found! end end + + # Get a list of group members viewable by the authenticated user. + # + # Example Request: + # GET /groups/:id/members + get ":id/members" do + group = find_group(params[:id]) + members = group.users_groups + users = (paginate members).collect(&:user) + present users, with: Entities::GroupMember, group: group + end + + # Add a user to the list of group members + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # access_level (required) - Project access level + # Example Request: + # POST /groups/:id/members + post ":id/members" do + required_attributes! [:user_id, :access_level] + unless validate_access_level?(params[:access_level]) + render_api_error!("Wrong access level", 422) + end + group = find_group(params[:id]) + if group.users_groups.find_by_user_id(params[:user_id]) + render_api_error!("Already exists", 409) + end + group.add_users([params[:user_id]], params[:access_level]) + member = group.users_groups.find_by_user_id(params[:user_id]) + present member.user, with: Entities::GroupMember, group: group + end + + # Remove member. + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # + # Example Request: + # DELETE /groups/:id/members/:user_id + delete ":id/members/:user_id" do + group = find_group(params[:id]) + member = group.users_groups.find_by_user_id(params[:user_id]) + if member.nil? + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) + else + member.destroy + end + end + end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6bd8111c2b2..4f189f35196 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -1,16 +1,43 @@ -module Gitlab +module API module APIHelpers + PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" + PRIVATE_TOKEN_PARAM = :private_token + SUDO_HEADER ="HTTP_SUDO" + SUDO_PARAM = :sudo + def current_user - @current_user ||= User.find_by_authentication_token(params[:private_token] || env["HTTP_PRIVATE_TOKEN"]) + @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]) + identifier = sudo_identifier() + # If the sudo is the current user do nothing + if (identifier && !(@current_user.id == identifier || @current_user.username == identifier)) + render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin? + begin + @current_user = User.by_username_or_id(identifier) + rescue => ex + not_found!("No user id or username for: #{identifier}") + end + not_found!("No user id or username for: #{identifier}") if current_user.nil? + end + @current_user + end + + def sudo_identifier() + identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER] + # Regex for integers + if (!!(identifier =~ /^[0-9]+$/)) + identifier.to_i + else + identifier + end end def user_project - @project ||= find_project + @project ||= find_project(params[:id]) @project || not_found! end - def find_project - project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id]) + def find_project(id) + project = Project.find_by_id(id) || Project.find_with_namespace(id) if project && can?(current_user, :read_project, project) project @@ -41,6 +68,17 @@ module Gitlab abilities.allowed?(object, action, subject) end + # Checks the occurrences of required attributes, each attribute must be present in the params hash + # or a Bad Request error is invoked. + # + # Parameters: + # keys (required) - A hash consisting of keys that must be present + def required_attributes!(keys) + keys.each do |key| + bad_request!(key) unless params[key].present? + end + end + def attributes_for_keys(keys) attrs = {} keys.each do |key| @@ -55,6 +93,12 @@ module Gitlab render_api_error!('403 Forbidden', 403) end + def bad_request!(attribute) + message = ["400 (Bad request)"] + message << "\"" + attribute.to_s + "\" not given" + render_api_error!(message.join(' '), 400) + end + def not_found!(resource = nil) message = ["404"] message << resource if resource diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 3e5e3a478ba..79f8eb3a543 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -1,23 +1,45 @@ -module Gitlab +module API # Internal access API class Internal < Grape::API + + DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } + PUSH_COMMANDS = %w{ git-receive-pack } + namespace 'internal' do # # Check if ssh key has access to project code # + # Params: + # key_id - SSH Key id + # project - project path with namespace + # action - git action (git-upload-pack or git-receive-pack) + # ref - branch name + # get "/allowed" do + # 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 = params[:project] + project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ + key = Key.find(params[:key_id]) - project = Project.find_with_namespace(params[:project]) + project = Project.find_with_namespace(project_path) git_cmd = params[:action] + return false unless project - if key.is_deploy_key - project == key.project && git_cmd == 'git-upload-pack' + + if key.is_a? DeployKey + key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd) else user = key.user + + return false if user.blocked? + action = case git_cmd - when 'git-upload-pack' + when *DOWNLOAD_COMMANDS then :download_code - when 'git-receive-pack' + when *PUSH_COMMANDS then if project.protected_branch?(params[:ref]) :push_code_to_protected_branches @@ -35,12 +57,14 @@ module Gitlab # get "/discover" do key = Key.find(params[:key_id]) - present key.user, with: Entities::User + present key.user, with: Entities::UserSafe end get "/check" do { - api_version: '3' + api_version: API.version, + gitlab_version: Gitlab::VERSION, + gitlab_rev: Gitlab::REVISION, } end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4d832fbe593..a15203d1563 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -1,7 +1,8 @@ -module Gitlab +module API # Issues API class Issues < Grape::API before { authenticate! } + before { Thread.current[:current_user] = current_user } resource :issues do # Get currently authenticated user's issues @@ -48,6 +49,7 @@ module Gitlab # Example Request: # POST /projects/:id/issues post ":id/issues" do + required_attributes! [:title] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] attrs[:label_list] = params[:labels] if params[:labels].present? @issue = user_project.issues.new attrs @@ -69,16 +71,16 @@ module Gitlab # 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 - # closed (optional) - The state of an issue (0 = false, 1 = true) + # state_event (optional) - The state event of an issue (close|reopen) # Example Request: # PUT /projects/:id/issues/:issue_id put ":id/issues/:issue_id" do @issue = user_project.issues.find(params[:issue_id]) authorize! :modify_issue, @issue - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed] + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] attrs[:label_list] = params[:labels] if params[:labels].present? - IssueObserver.current_user = current_user + if @issue.update_attributes attrs present @issue, with: Entities::Issue else diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 470cd1e1c2d..d690f1d07e7 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -1,9 +1,28 @@ -module Gitlab +module API # MergeRequest API class MergeRequests < Grape::API before { authenticate! } + before { Thread.current[:current_user] = current_user } resource :projects do + helpers do + def handle_merge_request_errors!(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + elsif errors[:branch_conflict].any? + error!(errors[:branch_conflict], 422) + end + not_found! + end + + def not_fork?(target_project_id, user_project) + target_project_id.nil? || target_project_id == user_project.id.to_s + end + + def target_matches_fork(target_project_id,user_project) + user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id + end + end # List merge requests # @@ -40,9 +59,10 @@ module Gitlab # # Parameters: # - # id (required) - The ID of a project + # 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 - The target project of the merge request defaults to the :id of the project # assignee_id - Assignee user ID # title (required) - Title of MR # @@ -51,16 +71,27 @@ module Gitlab # post ":id/merge_requests" do authorize! :write_merge_request, user_project - - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] + required_attributes! [:source_branch, :target_branch, :title] + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id] merge_request = user_project.merge_requests.new(attrs) merge_request.author = current_user + merge_request.source_project = user_project + target_project_id = attrs[:target_project_id] + if not_fork?(target_project_id, user_project) + merge_request.target_project = user_project + else + if target_matches_fork(target_project_id,user_project) + merge_request.target_project = Project.find_by_id(attrs[:target_project_id]) + else + render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400) + end + end if merge_request.save merge_request.reload_code present merge_request, with: Entities::MergeRequest else - not_found! + handle_merge_request_errors! merge_request.errors end end @@ -73,12 +104,12 @@ module Gitlab # target_branch - The target branch # assignee_id - Assignee user ID # title - Title of MR - # closed - Status of MR. true - closed + # state_event - Status of MR. (close|reopen|merge) # Example: # PUT /projects/:id/merge_request/:merge_request_id # put ":id/merge_request/:merge_request_id" do - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed] + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event] merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :modify_merge_request, merge_request @@ -88,7 +119,7 @@ module Gitlab merge_request.mark_as_unchecked present merge_request, with: Entities::MergeRequest else - not_found! + handle_merge_request_errors! merge_request.errors end end @@ -102,6 +133,8 @@ module Gitlab # POST /projects/:id/merge_request/:merge_request_id/comments # post ":id/merge_request/:merge_request_id/comments" do + required_attributes! [:note] + merge_request = user_project.merge_requests.find(params[:merge_request_id]) note = merge_request.notes.new(note: params[:note], project_id: user_project.id) note.author = current_user diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 6aca9d01b09..aee12e7dc40 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -1,4 +1,4 @@ -module Gitlab +module API # Milestones API class Milestones < Grape::API before { authenticate! } @@ -41,6 +41,7 @@ module Gitlab # POST /projects/:id/milestones post ":id/milestones" do authorize! :admin_milestone, user_project + required_attributes! [:title] attrs = attributes_for_keys [:title, :description, :due_date] @milestone = user_project.milestones.new attrs @@ -59,14 +60,14 @@ module Gitlab # title (optional) - The title of a milestone # description (optional) - The description of a milestone # due_date (optional) - The due date of a milestone - # closed (optional) - The status of the milestone + # state_event (optional) - The state event of the milestone (close|activate) # Example Request: # PUT /projects/:id/milestones/:milestone_id put ":id/milestones/:milestone_id" do authorize! :admin_milestone, user_project @milestone = user_project.milestones.find(params[:milestone_id]) - attrs = attributes_for_keys [:title, :description, :due_date, :closed] + attrs = attributes_for_keys [:title, :description, :due_date, :state_event] if @milestone.update_attributes attrs present @milestone, with: Entities::Milestone else diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 70344d6e381..cb2bc764476 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -1,4 +1,4 @@ -module Gitlab +module API # Notes API class Notes < Grape::API before { authenticate! } @@ -14,6 +14,10 @@ module Gitlab # GET /projects/:id/notes get ":id/notes" do @notes = user_project.notes.common + + # Get recent notes if recent = true + @notes = @notes.order('id DESC') if params[:recent] + present paginate(@notes), with: Entities::Note end @@ -37,12 +41,16 @@ module Gitlab # Example Request: # POST /projects/:id/notes post ":id/notes" do + required_attributes! [:body] + @note = user_project.notes.new(note: params[:body]) @note.author = current_user if @note.save present @note, with: Entities::Note else + # :note is exposed as :body, but :note is set on error + bad_request!(:note) if @note.errors[:note].any? not_found! end end @@ -89,6 +97,8 @@ module Gitlab # POST /projects/:id/issues/:noteable_id/notes # POST /projects/:id/snippets/:noteable_id/notes post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do + required_attributes! [:body] + @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) @note = @noteable.notes.new(note: params[:body]) @note.author = current_user diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb new file mode 100644 index 00000000000..28501256795 --- /dev/null +++ b/lib/api/project_hooks.rb @@ -0,0 +1,108 @@ +module API + # Projects API + class ProjectHooks < Grape::API + before { authenticate! } + + resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + + # Get project hooks + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/hooks + get ":id/hooks" do + authorize! :admin_project, user_project + @hooks = paginate user_project.hooks + present @hooks, with: Entities::Hook + end + + # Get a project hook + # + # Parameters: + # id (required) - The ID of a project + # hook_id (required) - The ID of a project hook + # Example Request: + # GET /projects/:id/hooks/:hook_id + get ":id/hooks/:hook_id" do + authorize! :admin_project, user_project + @hook = user_project.hooks.find(params[:hook_id]) + present @hook, with: Entities::Hook + end + + + # Add hook to project + # + # Parameters: + # id (required) - The ID of a project + # url (required) - The hook URL + # Example Request: + # POST /projects/:id/hooks + post ":id/hooks" do + authorize! :admin_project, user_project + required_attributes! [:url] + + @hook = user_project.hooks.new({"url" => params[:url]}) + if @hook.save + present @hook, with: Entities::Hook + else + if @hook.errors[:url].present? + error!("Invalid url given", 422) + end + not_found! + end + end + + # Update an existing project hook + # + # Parameters: + # id (required) - The ID of a project + # hook_id (required) - The ID of a project hook + # url (required) - The hook URL + # Example Request: + # PUT /projects/:id/hooks/:hook_id + put ":id/hooks/:hook_id" do + @hook = user_project.hooks.find(params[:hook_id]) + authorize! :admin_project, user_project + required_attributes! [:url] + + attrs = attributes_for_keys [:url] + if @hook.update_attributes attrs + present @hook, with: Entities::Hook + else + if @hook.errors[:url].present? + error!("Invalid url given", 422) + end + not_found! + end + end + + # Deletes project hook. This is an idempotent function. + # + # Parameters: + # id (required) - The ID of a project + # hook_id (required) - The ID of hook to delete + # Example Request: + # DELETE /projects/:id/hooks/:hook_id + delete ":id/hooks/:hook_id" do + authorize! :admin_project, user_project + required_attributes! [:hook_id] + + begin + @hook = ProjectHook.find(params[:hook_id]) + @hook.destroy + rescue + # ProjectHook can raise Error if hook_id not found + end + end + end + end +end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb new file mode 100644 index 00000000000..bee6544ea3d --- /dev/null +++ b/lib/api/project_snippets.rb @@ -0,0 +1,123 @@ +module API + # Projects API + class ProjectSnippets < Grape::API + before { authenticate! } + + resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + + # Get a project snippets + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/snippets + get ":id/snippets" do + present paginate(user_project.snippets), 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 + get ":id/snippets/:snippet_id" do + @snippet = user_project.snippets.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 + # lifetime (optional) - The expiration date of a snippet + # code (required) - The content of a snippet + # Example Request: + # POST /projects/:id/snippets + post ":id/snippets" do + authorize! :write_project_snippet, user_project + required_attributes! [:title, :file_name, :code] + + attrs = attributes_for_keys [:title, :file_name] + attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? + attrs[:content] = params[:code] if params[:code].present? + @snippet = user_project.snippets.new attrs + @snippet.author = current_user + + if @snippet.save + present @snippet, with: Entities::ProjectSnippet + else + not_found! + 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 + # lifetime (optional) - The expiration date of a snippet + # code (optional) - The content of a snippet + # Example Request: + # PUT /projects/:id/snippets/:snippet_id + put ":id/snippets/:snippet_id" do + @snippet = user_project.snippets.find(params[:snippet_id]) + authorize! :modify_project_snippet, @snippet + + attrs = attributes_for_keys [:title, :file_name] + attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? + attrs[:content] = params[:code] if params[:code].present? + + if @snippet.update_attributes attrs + present @snippet, with: Entities::ProjectSnippet + else + not_found! + 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 + delete ":id/snippets/:snippet_id" do + begin + @snippet = user_project.snippets.find(params[:snippet_id]) + authorize! :modify_project_snippet, @snippet + @snippet.destroy + rescue + end + 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 + get ":id/snippets/:snippet_id/raw" do + @snippet = user_project.snippets.find(params[:snippet_id]) + + env['api.format'] = :txt + content_type 'text/plain' + present @snippet.content + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index d416121a78a..cf357b23c40 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,9 +1,18 @@ -module Gitlab +module API # Projects API class Projects < Grape::API before { authenticate! } resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + # Get a projects list for authenticated user # # Example Request: @@ -13,6 +22,15 @@ module Gitlab present @projects, with: Entities::Project end + # Get an owned projects list for authenticated user + # + # Example Request: + # GET /projects/owned + get '/owned' do + @projects = paginate current_user.owned_projects + present @projects, with: Entities::Project + end + # Get a single project # # Parameters: @@ -23,32 +41,128 @@ module Gitlab present user_project, with: Entities::Project end + # Get a single project events + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id + get ":id/events" do + limit = (params[:per_page] || 20).to_i + offset = (params[:page] || 0).to_i * limit + events = user_project.events.recent.limit(limit).offset(offset) + + present events, with: Entities::Event + end + # Create new project # # Parameters: # name (required) - name for new project # description (optional) - short project description # default_branch (optional) - 'master' by default - # issues_enabled (optional) - enabled by default - # wall_enabled (optional) - enabled by default - # merge_requests_enabled (optional) - enabled by default - # wiki_enabled (optional) - enabled by default + # issues_enabled (optional) + # wall_enabled (optional) + # merge_requests_enabled (optional) + # wiki_enabled (optional) + # snippets_enabled (optional) + # namespace_id (optional) - defaults to user namespace + # public (optional) - false by default # Example Request # POST /projects post do + required_attributes! [:name] attrs = attributes_for_keys [:name, - :description, - :default_branch, - :issues_enabled, - :wall_enabled, - :merge_requests_enabled, - :wiki_enabled] + :path, + :description, + :default_branch, + :issues_enabled, + :wall_enabled, + :merge_requests_enabled, + :wiki_enabled, + :snippets_enabled, + :namespace_id, + :public] @project = ::Projects::CreateContext.new(current_user, attrs).execute if @project.saved? present @project, with: Entities::Project else + if @project.errors[:limit_reached].present? + error!(@project.errors[:limit_reached], 403) + end + not_found! + 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) + # wall_enabled (optional) + # merge_requests_enabled (optional) + # wiki_enabled (optional) + # snippets_enabled (optional) + # public (optional) + # Example Request + # POST /projects/user/:user_id + post "user/:user_id" do + authenticated_as_admin! + user = User.find(params[:user_id]) + attrs = attributes_for_keys [:name, + :description, + :default_branch, + :issues_enabled, + :wall_enabled, + :merge_requests_enabled, + :wiki_enabled, + :snippets_enabled, + :public] + @project = ::Projects::CreateContext.new(user, attrs).execute + if @project.saved? + present @project, with: Entities::Project + else + not_found! + end + 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 + 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 + else not_found! 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 + delete ":id/fork" do + authenticated_as_admin! + unless user_project.forked_project_link.nil? + user_project.forked_project_link.destroy + end end # Get a project team members @@ -89,16 +203,22 @@ module Gitlab # POST /projects/:id/members post ":id/members" do authorize! :admin_project, user_project - users_project = user_project.users_projects.new( - user_id: params[:user_id], - project_access: params[:access_level] - ) + required_attributes! [:user_id, :access_level] + + # either the user is already a team member or a new one + team_member = user_project.team_member_by_id(params[:user_id]) + if team_member.nil? + team_member = user_project.users_projects.new( + user_id: params[:user_id], + project_access: params[:access_level] + ) + end - if users_project.save - @member = users_project.user + if team_member.save + @member = team_member.user present @member, with: Entities::ProjectMember, project: user_project else - not_found! + handle_project_member_errors team_member.errors end end @@ -112,13 +232,16 @@ module Gitlab # PUT /projects/:id/members/:user_id put ":id/members/:user_id" do authorize! :admin_project, user_project - users_project = user_project.users_projects.find_by_user_id params[:user_id] + required_attributes! [:access_level] + + team_member = user_project.users_projects.find_by_user_id(params[:user_id]) + not_found!("User can not be found") if team_member.nil? - if users_project.update_attributes(project_access: params[:access_level]) - @member = users_project.user + if team_member.update_attributes(project_access: params[:access_level]) + @member = team_member.user present @member, with: Entities::ProjectMember, project: user_project else - not_found! + handle_project_member_errors team_member.errors end end @@ -131,297 +254,27 @@ module Gitlab # DELETE /projects/:id/members/:user_id delete ":id/members/:user_id" do authorize! :admin_project, user_project - users_project = user_project.users_projects.find_by_user_id params[:user_id] - users_project.destroy - end - - # Get project hooks - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/hooks - get ":id/hooks" do - authorize! :admin_project, user_project - @hooks = paginate user_project.hooks - present @hooks, with: Entities::Hook - end - - # Get a project hook - # - # Parameters: - # id (required) - The ID of a project - # hook_id (required) - The ID of a project hook - # Example Request: - # GET /projects/:id/hooks/:hook_id - get ":id/hooks/:hook_id" do - @hook = user_project.hooks.find(params[:hook_id]) - present @hook, with: Entities::Hook - end - - - # Add hook to project - # - # Parameters: - # id (required) - The ID of a project - # url (required) - The hook URL - # Example Request: - # POST /projects/:id/hooks - post ":id/hooks" do - authorize! :admin_project, user_project - @hook = user_project.hooks.new({"url" => params[:url]}) - if @hook.save - present @hook, with: Entities::Hook + team_member = user_project.users_projects.find_by_user_id(params[:user_id]) + unless team_member.nil? + team_member.destroy else - error!({'message' => '404 Not found'}, 404) + {message: "Access revoked", id: params[:user_id].to_i} end end - # Update an existing project hook + # search for projects current_user has access to # # Parameters: - # id (required) - The ID of a project - # hook_id (required) - The ID of a project hook - # url (required) - The hook URL + # 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: - # PUT /projects/:id/hooks/:hook_id - put ":id/hooks/:hook_id" do - @hook = user_project.hooks.find(params[:hook_id]) - authorize! :admin_project, user_project - - attrs = attributes_for_keys [:url] - - if @hook.update_attributes attrs - present @hook, with: Entities::Hook - else - not_found! - end + # GET /projects/search/:query + get "/search/:query" do + ids = current_user.authorized_projects.map(&:id) + projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%") + present paginate(projects), with: Entities::Project end - - # Delete project hook - # - # Parameters: - # id (required) - The ID of a project - # hook_id (required) - The ID of hook to delete - # Example Request: - # DELETE /projects/:id/hooks - delete ":id/hooks" do - authorize! :admin_project, user_project - @hook = user_project.hooks.find(params[:hook_id]) - @hook.destroy - end - - # Get a project repository branches - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/branches - get ":id/repository/branches" do - present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project - end - - # Get a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # GET /projects/:id/repository/branches/:branch - get ":id/repository/branches/:branch" do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - not_found!("Branch does not exist") if @branch.nil? - present @branch, with: Entities::RepoObject, project: user_project - end - - # Protect a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # PUT /projects/:id/repository/branches/:branch/protect - put ":id/repository/branches/:branch/protect" do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - protected = user_project.protected_branches.find_by_name(@branch.name) - - unless protected - user_project.protected_branches.create(:name => @branch.name) - end - - present @branch, with: Entities::RepoObject, project: user_project - end - - # Unprotect a single branch - # - # Parameters: - # id (required) - The ID of a project - # branch (required) - The name of the branch - # Example Request: - # PUT /projects/:id/repository/branches/:branch/unprotect - put ":id/repository/branches/:branch/unprotect" do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - protected = user_project.protected_branches.find_by_name(@branch.name) - - if protected - protected.destroy - end - - present @branch, with: Entities::RepoObject, project: user_project - end - - # Get a project repository tags - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/tags - get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject - end - - # Get a project repository commits - # - # Parameters: - # id (required) - The ID of a project - # ref_name (optional) - The name of a repository branch or tag - # Example Request: - # GET /projects/:id/repository/commits - get ":id/repository/commits" do - authorize! :download_code, user_project - - page = params[:page] || 0 - per_page = params[:per_page] || 20 - ref = params[:ref_name] || user_project.try(:default_branch) || 'master' - - commits = user_project.repository.commits(ref, nil, per_page, page * per_page) - present CommitDecorator.decorate(commits), with: Entities::RepoCommit - end - - # Get a project snippets - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/snippets - get ":id/snippets" do - present paginate(user_project.snippets), 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 - get ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.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 - # lifetime (optional) - The expiration date of a snippet - # code (required) - The content of a snippet - # Example Request: - # POST /projects/:id/snippets - post ":id/snippets" do - authorize! :write_snippet, user_project - - attrs = attributes_for_keys [:title, :file_name] - attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? - attrs[:content] = params[:code] if params[:code].present? - @snippet = user_project.snippets.new attrs - @snippet.author = current_user - - if @snippet.save - present @snippet, with: Entities::ProjectSnippet - else - not_found! - 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 - # lifetime (optional) - The expiration date of a snippet - # code (optional) - The content of a snippet - # Example Request: - # PUT /projects/:id/snippets/:snippet_id - put ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) - authorize! :modify_snippet, @snippet - - attrs = attributes_for_keys [:title, :file_name] - attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? - attrs[:content] = params[:code] if params[:code].present? - - if @snippet.update_attributes attrs - present @snippet, with: Entities::ProjectSnippet - else - not_found! - 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 - delete ":id/snippets/:snippet_id" do - @snippet = user_project.snippets.find(params[:snippet_id]) - authorize! :modify_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 - get ":id/snippets/:snippet_id/raw" do - @snippet = user_project.snippets.find(params[:snippet_id]) - content_type 'text/plain' - present @snippet.content - 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/commits/:sha/blob - get ":id/repository/commits/:sha/blob" do - authorize! :download_code, user_project - - ref = params[:sha] - - commit = user_project.repository.commit ref - not_found! "Commit" unless commit - - tree = Tree.new commit.tree, ref, params[:filepath] - not_found! "File" unless tree.try(:tree) - - content_type tree.mime_type - present tree.data - end - end end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb new file mode 100644 index 00000000000..fef32d3a2fe --- /dev/null +++ b/lib/api/repositories.rb @@ -0,0 +1,190 @@ +module API + # Projects API + class Repositories < Grape::API + before { authenticate! } + + resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + + # Get a project repository branches + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/branches + get ":id/repository/branches" do + present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project + end + + # Get a single branch + # + # Parameters: + # id (required) - The ID of a project + # branch (required) - The name of the branch + # Example Request: + # GET /projects/:id/repository/branches/:branch + get ":id/repository/branches/:branch" do + @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } + not_found!("Branch does not exist") if @branch.nil? + present @branch, with: Entities::RepoObject, project: user_project + end + + # Protect a single branch + # + # Parameters: + # id (required) - The ID of a project + # branch (required) - The name of the branch + # Example Request: + # PUT /projects/:id/repository/branches/:branch/protect + put ":id/repository/branches/:branch/protect" do + @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } + not_found! unless @branch + protected = user_project.protected_branches.find_by_name(@branch.name) + + unless protected + user_project.protected_branches.create(name: @branch.name) + end + + present @branch, with: Entities::RepoObject, project: user_project + end + + # Unprotect a single branch + # + # Parameters: + # id (required) - The ID of a project + # branch (required) - The name of the branch + # Example Request: + # PUT /projects/:id/repository/branches/:branch/unprotect + put ":id/repository/branches/:branch/unprotect" do + @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } + not_found! unless @branch + protected = user_project.protected_branches.find_by_name(@branch.name) + + if protected + protected.destroy + end + + present @branch, with: Entities::RepoObject, project: user_project + end + + # Get a project repository tags + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/tags + get ":id/repository/tags" do + present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject + end + + # Get a project repository commits + # + # 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 + # Example Request: + # GET /projects/:id/repository/commits + get ":id/repository/commits" do + authorize! :download_code, user_project + + page = (params[:page] || 0).to_i + per_page = (params[:per_page] || 20).to_i + ref = params[:ref_name] || user_project.try(:default_branch) || 'master' + + commits = user_project.repository.commits(ref, nil, per_page, page * per_page) + present commits, with: Entities::RepoCommit + end + + # Get a specific commit of a project + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash or name of a repository branch or tag + # Example Request: + # GET /projects/:id/repository/commits/:sha + get ":id/repository/commits/:sha" do + authorize! :download_code, user_project + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! "Commit" unless commit + present commit, with: Entities::RepoCommit + end + + # Get the diff for a specific commit of a project + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit or branch name + # Example Request: + # GET /projects/:id/repository/commits/:sha/diff + get ":id/repository/commits/:sha/diff" do + authorize! :download_code, user_project + sha = params[:sha] + result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute + not_found! "Commit" unless result[:commit] + result[:commit].diffs + 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 + # Example Request: + # GET /projects/:id/repository/tree + get ":id/repository/tree" do + authorize! :download_code, user_project + + ref = params[:ref_name] || user_project.try(:default_branch) || 'master' + path = params[:path] || nil + + commit = user_project.repository.commit(ref) + tree = Tree.new(user_project.repository, commit.id, ref, path) + + trees = [] + + %w(trees blobs submodules).each do |type| + trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } } + end + + trees + 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 + get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do + authorize! :download_code, user_project + required_attributes! [:filepath] + + ref = params[:sha] + + repo = user_project.repository + + commit = repo.commit(ref) + not_found! "Commit" unless commit + + blob = Gitlab::Git::Blob.new(repo, commit.id, ref, params[:filepath]) + not_found! "File" unless blob.exists? + + env['api.format'] = :txt + + content_type blob.mime_type + present blob.data + end + end + end +end + diff --git a/lib/api/session.rb b/lib/api/session.rb index b4050160ae4..cc646895914 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -1,20 +1,21 @@ -module Gitlab +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 post "/session" do - resource = User.find_for_database_authentication(email: params[:email]) - - return unauthorized! unless resource + auth = Gitlab::Auth.new + user = auth.find(params[:email] || params[:login], params[:password]) - if resource.valid_password?(params[:password]) - present resource, with: Entities::UserLogin - else - unauthorized! - end + return unauthorized! unless user + present user, with: Entities::UserLogin end end end diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb new file mode 100644 index 00000000000..3e239c5afe7 --- /dev/null +++ b/lib/api/system_hooks.rb @@ -0,0 +1,70 @@ +module API + # Hooks API + class SystemHooks < Grape::API + before { + authenticate! + authenticated_as_admin! + } + + resource :hooks do + # Get the list of system hooks + # + # Example Request: + # GET /hooks + get do + @hooks = SystemHook.all + present @hooks, with: Entities::Hook + end + + # Create new system hook + # + # Parameters: + # url (required) - url for system hook + # Example Request + # POST /hooks + post do + attrs = attributes_for_keys [:url] + required_attributes! [:url] + @hook = SystemHook.new attrs + if @hook.save + present @hook, with: Entities::Hook + else + not_found! + end + end + + # Test a hook + # + # Example Request + # GET /hooks/:id + get ":id" do + @hook = SystemHook.find(params[:id]) + data = { + event_name: "project_create", + name: "Ruby", + path: "ruby", + project_id: 1, + owner_name: "Someone", + owner_email: "example@gitlabhq.com" + } + @hook.execute(data) + data + end + + # Delete a hook. This is an idempotent function. + # + # Parameters: + # id (required) - ID of the hook + # Example Request: + # DELETE /hooks/:id + delete ":id" do + begin + @hook = SystemHook.find(params[:id]) + @hook.destroy + rescue + # SystemHook raises an Error if no hook with id found + end + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index 7ea90c75e9e..00dc2311ffd 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1,4 +1,4 @@ -module Gitlab +module API # Users API class Users < Grape::API before { authenticate! } @@ -9,7 +9,10 @@ module Gitlab # Example Request: # GET /users get do - @users = paginate User + @users = User.scoped + @users = @users.active if params[:active].present? + @users = @users.search(params[:search]) if params[:search].present? + @users = paginate @users present @users, with: Entities::User end @@ -41,8 +44,9 @@ module Gitlab # POST /users post do authenticated_as_admin! + required_attributes! [:email, :password, :name, :username] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] - user = User.new attrs, as: :admin + user = User.build_user(attrs, as: :admin) if user.save present user, with: Entities::User else @@ -59,7 +63,7 @@ module Gitlab # skype - Skype ID # linkedin - Linkedin # twitter - Twitter account - # projects_limit - Limit projects wich user can create + # projects_limit - Limit projects each user can create # extern_uid - External authentication provider UID # provider - External provider # bio - Bio @@ -67,16 +71,38 @@ module Gitlab # PUT /users/:id put ":id" do authenticated_as_admin! + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio] - user = User.find_by_id(params[:id]) + user = User.find(params[:id]) + not_found!("User not found") unless user - if user && user.update_attributes(attrs) + if user.update_attributes(attrs) present user, with: Entities::User else not_found! 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 + post ":id/keys" do + authenticated_as_admin! + user = User.find(params[:id]) + attrs = attributes_for_keys [:title, :key] + key = user.keys.new attrs + if key.save + present key, with: Entities::SSHKey + else + not_found! + end + end + # Delete user. Available only for admin # # Example Request: @@ -99,7 +125,7 @@ module Gitlab # Example Request: # GET /user get do - present @current_user, with: Entities::User + present @current_user, with: Entities::UserLogin end # Get currently authenticated user's keys @@ -127,6 +153,8 @@ module Gitlab # Example Request: # POST /user/keys post "keys" do + required_attributes! [:title, :key] + attrs = attributes_for_keys [:title, :key] key = current_user.keys.new attrs if key.save @@ -136,15 +164,18 @@ module Gitlab end end - # Delete existed ssh key of currently authenticated user + # Delete existing ssh key of currently authenticated user # # Parameters: # id (required) - SSH Key ID # Example Request: # DELETE /user/keys/:id delete "keys/:id" do - key = current_user.keys.find params[:id] - key.delete + begin + key = current_user.keys.find params[:id] + key.destroy + rescue + end end end end |