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 55 Parameters:
56 56 + `id` (required) - The ID of a group
57 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 67 expose :projects, using: Entities::Project
68 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 76 class RepoObject < Grape::Entity
71 77 expose :name, :commit
72 78 expose :protected do |repo, options|
... ...
lib/api/groups.rb
... ... @@ -4,6 +4,20 @@ module API
4 4 before { authenticate! }
5 5  
6 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 21 # Get a groups list
8 22 #
9 23 # Example Request:
... ... @@ -46,12 +60,8 @@ module API
46 60 # Example Request:
47 61 # GET /groups/:id
48 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 65 end
56 66  
57 67 # Transfer a project to the Group namespace
... ... @@ -71,6 +81,58 @@ module API
71 81 not_found!
72 82 end
73 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 136 end
75 137 end
76 138 end
... ...
spec/requests/api/groups_spec.rb
... ... @@ -3,11 +3,11 @@ require &#39;spec_helper&#39;
3 3 describe API::API do
4 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 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 12 describe "GET /groups" do
13 13 context "when unauthenticated" do
... ... @@ -52,7 +52,7 @@ describe API::API do
52 52  
53 53 it "should not return a group not attached to user1" do
54 54 get api("/groups/#{group2.id}", user1)
55   - response.status.should == 404
  55 + response.status.should == 403
56 56 end
57 57 end
58 58  
... ... @@ -90,7 +90,7 @@ describe API::API do
90 90 end
91 91  
92 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 94 response.status.should == 400
95 95 end
96 96  
... ... @@ -104,11 +104,10 @@ describe API::API do
104 104 describe "POST /groups/:id/projects/:project_id" do
105 105 let(:project) { create(:project) }
106 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 109 end
110 110  
111   -
112 111 context "when authenticated as user" do
113 112 it "should not transfer project to group" do
114 113 post api("/groups/#{group1.id}/projects/#{project.id}", user2)
... ... @@ -123,4 +122,109 @@ describe API::API do
123 122 end
124 123 end
125 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 230 end
... ...