Commit 201158f1dee15accf6abbd7ad5a50af023ba5d23

Authored by Dmitriy Zaporozhets
2 parents 9109a207 fadcc251

Merge pull request #4990 from karlhungus/feature_group_membership_api

Add group membership api
doc/api/groups.md
@@ -55,3 +55,65 @@ POST /groups/:id/projects/:project_id @@ -55,3 +55,65 @@ POST /groups/:id/projects/:project_id
55 Parameters: 55 Parameters:
56 + `id` (required) - The ID of a group 56 + `id` (required) - The ID of a group
57 + `project_id (required) - The ID of a project 57 + `project_id (required) - The ID of a project
  58 +
  59 +
  60 +## Group members
  61 +
  62 +### List group members
  63 +
  64 +Get a list of group members viewable by the authenticated user.
  65 +
  66 +```
  67 +GET /groups/:id/members
  68 +```
  69 +
  70 +```json
  71 +[
  72 + {
  73 + id: 1,
  74 + username: "raymond_smith",
  75 + email: "ray@smith.org",
  76 + name: "Raymond Smith",
  77 + state: "active",
  78 + created_at: "2012-10-22T14:13:35Z",
  79 + access_level: 30
  80 + },
  81 + {
  82 + id: 2,
  83 + username: "john_doe",
  84 + email: "joh@doe.org",
  85 + name: "John Doe",
  86 + state: "active",
  87 + created_at: "2012-10-22T14:13:35Z",
  88 + access_level: 30
  89 + }
  90 +]
  91 +```
  92 +
  93 +### Add group member
  94 +
  95 +Adds a user to the list of group members.
  96 +
  97 +```
  98 +POST /groups/:id/members
  99 +```
  100 +
  101 +Parameters:
  102 +
  103 ++ `id` (required) - The ID of a group
  104 ++ `user_id` (required) - The ID of a user to add
  105 ++ `access_level` (required) - Project access level
  106 +
  107 +
  108 +### Remove user team member
  109 +
  110 +Removes user from user team.
  111 +
  112 +```
  113 +DELETE /groups/:id/members/:user_id
  114 +```
  115 +
  116 +Parameters:
  117 +
  118 ++ `id` (required) - The ID of a user group
  119 ++ `user_id` (required) - The ID of a group member
lib/api/entities.rb
@@ -67,6 +67,12 @@ module API @@ -67,6 +67,12 @@ module API
67 expose :projects, using: Entities::Project 67 expose :projects, using: Entities::Project
68 end 68 end
69 69
  70 + class GroupMember < UserBasic
  71 + expose :group_access, as: :access_level do |user, options|
  72 + options[:group].users_groups.find_by_user_id(user.id).group_access
  73 + end
  74 + end
  75 +
70 class RepoObject < Grape::Entity 76 class RepoObject < Grape::Entity
71 expose :name, :commit 77 expose :name, :commit
72 expose :protected do |repo, options| 78 expose :protected do |repo, options|
lib/api/groups.rb
@@ -4,6 +4,20 @@ module API @@ -4,6 +4,20 @@ module API
4 before { authenticate! } 4 before { authenticate! }
5 5
6 resource :groups do 6 resource :groups do
  7 + helpers do
  8 + def find_group(id)
  9 + group = Group.find(id)
  10 + if current_user.admin or current_user.groups.include? group
  11 + group
  12 + else
  13 + render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
  14 + end
  15 + end
  16 + def validate_access_level?(level)
  17 + Gitlab::Access.options_with_owner.values.include? level.to_i
  18 + end
  19 + end
  20 +
7 # Get a groups list 21 # Get a groups list
8 # 22 #
9 # Example Request: 23 # Example Request:
@@ -46,12 +60,8 @@ module API @@ -46,12 +60,8 @@ module API
46 # Example Request: 60 # Example Request:
47 # GET /groups/:id 61 # GET /groups/:id
48 get ":id" do 62 get ":id" do
49 - @group = Group.find(params[:id])  
50 - if current_user.admin or current_user.groups.include? @group  
51 - present @group, with: Entities::GroupDetail  
52 - else  
53 - not_found!  
54 - end 63 + group = find_group(params[:id])
  64 + present group, with: Entities::GroupDetail
55 end 65 end
56 66
57 # Transfer a project to the Group namespace 67 # Transfer a project to the Group namespace
@@ -71,6 +81,58 @@ module API @@ -71,6 +81,58 @@ module API
71 not_found! 81 not_found!
72 end 82 end
73 end 83 end
  84 +
  85 + # Get a list of group members viewable by the authenticated user.
  86 + #
  87 + # Example Request:
  88 + # GET /groups/:id/members
  89 + get ":id/members" do
  90 + group = find_group(params[:id])
  91 + members = group.users_groups
  92 + users = (paginate members).collect(&:user)
  93 + present users, with: Entities::GroupMember, group: group
  94 + end
  95 +
  96 + # Add a user to the list of group members
  97 + #
  98 + # Parameters:
  99 + # id (required) - group id
  100 + # user_id (required) - the users id
  101 + # access_level (required) - Project access level
  102 + # Example Request:
  103 + # POST /groups/:id/members
  104 + post ":id/members" do
  105 + required_attributes! [:user_id, :access_level]
  106 + unless validate_access_level?(params[:access_level])
  107 + render_api_error!("Wrong access level", 422)
  108 + end
  109 + group = find_group(params[:id])
  110 + if group.users_groups.find_by_user_id(params[:user_id])
  111 + render_api_error!("Already exists", 409)
  112 + end
  113 + group.add_users([params[:user_id]], params[:access_level])
  114 + member = group.users_groups.find_by_user_id(params[:user_id])
  115 + present member.user, with: Entities::GroupMember, group: group
  116 + end
  117 +
  118 + # Remove member.
  119 + #
  120 + # Parameters:
  121 + # id (required) - group id
  122 + # user_id (required) - the users id
  123 + #
  124 + # Example Request:
  125 + # DELETE /groups/:id/members/:user_id
  126 + delete ":id/members/:user_id" do
  127 + group = find_group(params[:id])
  128 + member = group.users_groups.find_by_user_id(params[:user_id])
  129 + if member.nil?
  130 + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
  131 + else
  132 + member.destroy
  133 + end
  134 + end
  135 +
74 end 136 end
75 end 137 end
76 end 138 end
spec/requests/api/groups_spec.rb
@@ -3,11 +3,11 @@ require &#39;spec_helper&#39; @@ -3,11 +3,11 @@ require &#39;spec_helper&#39;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 5
6 - let(:user1) { create(:user) }  
7 - let(:user2) { create(:user) } 6 + let(:user1) { create(:user) }
  7 + let(:user2) { create(:user) }
8 let(:admin) { create(:admin) } 8 let(:admin) { create(:admin) }
9 - let!(:group1) { create(:group, owner: user1) }  
10 - let!(:group2) { create(:group, owner: user2) } 9 + let!(:group1) { create(:group, owner: user1) }
  10 + let!(:group2) { create(:group, owner: user2) }
11 11
12 describe "GET /groups" do 12 describe "GET /groups" do
13 context "when unauthenticated" do 13 context "when unauthenticated" do
@@ -52,7 +52,7 @@ describe API::API do @@ -52,7 +52,7 @@ describe API::API do
52 52
53 it "should not return a group not attached to user1" do 53 it "should not return a group not attached to user1" do
54 get api("/groups/#{group2.id}", user1) 54 get api("/groups/#{group2.id}", user1)
55 - response.status.should == 404 55 + response.status.should == 403
56 end 56 end
57 end 57 end
58 58
@@ -90,7 +90,7 @@ describe API::API do @@ -90,7 +90,7 @@ describe API::API do
90 end 90 end
91 91
92 it "should return 400 bad request error if name not given" do 92 it "should return 400 bad request error if name not given" do
93 - post api("/groups", admin), { path: group2.path } 93 + post api("/groups", admin), {path: group2.path}
94 response.status.should == 400 94 response.status.should == 400
95 end 95 end
96 96
@@ -104,11 +104,10 @@ describe API::API do @@ -104,11 +104,10 @@ describe API::API do
104 describe "POST /groups/:id/projects/:project_id" do 104 describe "POST /groups/:id/projects/:project_id" do
105 let(:project) { create(:project) } 105 let(:project) { create(:project) }
106 before(:each) do 106 before(:each) do
107 - project.stub!(:transfer).and_return(true)  
108 - Project.stub(:find).and_return(project) 107 + project.stub!(:transfer).and_return(true)
  108 + Project.stub(:find).and_return(project)
109 end 109 end
110 110
111 -  
112 context "when authenticated as user" do 111 context "when authenticated as user" do
113 it "should not transfer project to group" do 112 it "should not transfer project to group" do
114 post api("/groups/#{group1.id}/projects/#{project.id}", user2) 113 post api("/groups/#{group1.id}/projects/#{project.id}", user2)
@@ -123,4 +122,109 @@ describe API::API do @@ -123,4 +122,109 @@ describe API::API do
123 end 122 end
124 end 123 end
125 end 124 end
  125 +
  126 + describe "members" do
  127 + let(:owner) { create(:user) }
  128 + let(:reporter) { create(:user) }
  129 + let(:developer) { create(:user) }
  130 + let(:master) { create(:user) }
  131 + let(:guest) { create(:user) }
  132 + let!(:group_with_members) do
  133 + group = create(:group, owner: owner)
  134 + group.add_users([reporter.id], UsersGroup::REPORTER)
  135 + group.add_users([developer.id], UsersGroup::DEVELOPER)
  136 + group.add_users([master.id], UsersGroup::MASTER)
  137 + group.add_users([guest.id], UsersGroup::GUEST)
  138 + group
  139 + end
  140 + let!(:group_no_members) { create(:group, owner: owner) }
  141 +
  142 + describe "GET /groups/:id/members" do
  143 + context "when authenticated as user that is part or the group" do
  144 + it "each user: should return an array of members groups of group3" do
  145 + [owner, master, developer, reporter, guest].each do |user|
  146 + get api("/groups/#{group_with_members.id}/members", user)
  147 + response.status.should == 200
  148 + json_response.should be_an Array
  149 + json_response.size.should == 5
  150 + json_response.find { |e| e['id']==owner.id }['access_level'].should == UsersGroup::OWNER
  151 + json_response.find { |e| e['id']==reporter.id }['access_level'].should == UsersGroup::REPORTER
  152 + json_response.find { |e| e['id']==developer.id }['access_level'].should == UsersGroup::DEVELOPER
  153 + json_response.find { |e| e['id']==master.id }['access_level'].should == UsersGroup::MASTER
  154 + json_response.find { |e| e['id']==guest.id }['access_level'].should == UsersGroup::GUEST
  155 + end
  156 + end
  157 +
  158 + it "users not part of the group should get access error" do
  159 + get api("/groups/#{group_with_members.id}/members", user1)
  160 + response.status.should == 403
  161 + end
  162 + end
  163 + end
  164 +
  165 + describe "POST /groups/:id/members" do
  166 + context "when not a member of the group" do
  167 + it "should not add guest as member of group_no_members when adding being done by person outside the group" do
  168 + post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: UsersGroup::MASTER
  169 + response.status.should == 403
  170 + end
  171 + end
  172 +
  173 + context "when a member of the group" do
  174 + it "should return ok and add new member" do
  175 + count_before=group_no_members.users_groups.count
  176 + new_user = create(:user)
  177 + post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: UsersGroup::MASTER
  178 + response.status.should == 201
  179 + json_response['name'].should == new_user.name
  180 + json_response['access_level'].should == UsersGroup::MASTER
  181 + group_no_members.users_groups.count.should == count_before + 1
  182 + end
  183 +
  184 + it "should return error if member already exists" do
  185 + post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: UsersGroup::MASTER
  186 + response.status.should == 409
  187 + end
  188 +
  189 + it "should return a 400 error when user id is not given" do
  190 + post api("/groups/#{group_no_members.id}/members", owner), access_level: UsersGroup::MASTER
  191 + response.status.should == 400
  192 + end
  193 +
  194 + it "should return a 400 error when access level is not given" do
  195 + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
  196 + response.status.should == 400
  197 + end
  198 +
  199 + it "should return a 422 error when access level is not known" do
  200 + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
  201 + response.status.should == 422
  202 + end
  203 + end
  204 + end
  205 +
  206 + describe "DELETE /groups/:id/members/:user_id" do
  207 + context "when not a member of the group" do
  208 + it "should not delete guest's membership of group_with_members" do
  209 + random_user = create(:user)
  210 + delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
  211 + response.status.should == 403
  212 + end
  213 + end
  214 +
  215 + context "when a member of the group" do
  216 + it "should delete guest's membership of group" do
  217 + count_before=group_with_members.users_groups.count
  218 + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
  219 + response.status.should == 200
  220 + group_with_members.users_groups.count.should == count_before - 1
  221 + end
  222 +
  223 + it "should return a 404 error when user id is not known" do
  224 + delete api("/groups/#{group_with_members.id}/members/1328", owner)
  225 + response.status.should == 404
  226 + end
  227 + end
  228 + end
  229 + end
126 end 230 end