Commit 8248e1f2b83895f394a3fecd25dcea4a8b40303b

Authored by Izaak Alpert
1 parent 62635983

Add group membership api

Change-Id: I5b174bba02856ede788dcb51ec9b0d598ea7d0df
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,19 @@ module API @@ -4,6 +4,19 @@ 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 + [UsersGroup::GUEST, UsersGroup::REPORTER, UsersGroup::DEVELOPER, UsersGroup::MASTER].include? level.to_i
  18 + end
  19 + end
7 # Get a groups list 20 # Get a groups list
8 # 21 #
9 # Example Request: 22 # Example Request:
@@ -46,12 +59,8 @@ module API @@ -46,12 +59,8 @@ module API
46 # Example Request: 59 # Example Request:
47 # GET /groups/:id 60 # GET /groups/:id
48 get ":id" do 61 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 62 + group = find_group(params[:id])
  63 + present group, with: Entities::GroupDetail
55 end 64 end
56 65
57 # Transfer a project to the Group namespace 66 # Transfer a project to the Group namespace
@@ -71,6 +80,58 @@ module API @@ -71,6 +80,58 @@ module API
71 not_found! 80 not_found!
72 end 81 end
73 end 82 end
  83 +
  84 + # Get a list of group members viewable by the authenticated user.
  85 + #
  86 + # Example Request:
  87 + # GET /groups/:id/members
  88 + get ":id/members" do
  89 + group = find_group(params[:id])
  90 + members = group.users_groups
  91 + users = (paginate members).collect { | member| member.user}
  92 + present users, with: Entities::GroupMember, group: group
  93 + end
  94 +
  95 + # Add a user to the list of group members
  96 + #
  97 + # Parameters:
  98 + # id (required) - group id
  99 + # user_id (required) - the users id
  100 + # access_level (required) - Project access level
  101 + # Example Request:
  102 + # POST /groups/:id/members
  103 + post ":id/members" do
  104 + required_attributes! [:user_id, :access_level]
  105 + if not validate_access_level?(params[:access_level])
  106 + render_api_error!("Wrong access level", 422)
  107 + end
  108 + group = find_group(params[:id])
  109 + if group.users_groups.find_by_user_id(params[:user_id])
  110 + render_api_error!("Already exists", 409)
  111 + end
  112 + group.add_users([params[:user_id]], params[:access_level])
  113 + member = group.users_groups.find_by_user_id(params[:user_id])
  114 + present member.user, with: Entities::GroupMember, group: group
  115 + end
  116 +
  117 + # Remove member.
  118 + #
  119 + # Parameters:
  120 + # id (required) - group id
  121 + # user_id (required) - the users id
  122 + #
  123 + # Example Request:
  124 + # DELETE /groups/:id/members/:user_id
  125 + delete ":id/members/:user_id" do
  126 + group = find_group(params[:id])
  127 + member = group.users_groups.find_by_user_id(params[:user_id])
  128 + if member.nil?
  129 + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
  130 + else
  131 + member.destroy
  132 + end
  133 + end
  134 +
74 end 135 end
75 end 136 end
76 end 137 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,8 +104,8 @@ describe API::API do @@ -104,8 +104,8 @@ 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 111
@@ -123,4 +123,104 @@ describe API::API do @@ -123,4 +123,104 @@ describe API::API do
123 end 123 end
124 end 124 end
125 end 125 end
  126 +
  127 + describe "members" do
  128 + let(:owner) { create(:user) }
  129 + let(:reporter) { create(:user) }
  130 + let(:developer) { create(:user) }
  131 + let(:master) { create(:user) }
  132 + let(:guest) { create(:user) }
  133 + let!(:group_with_members) do
  134 + group = create(:group, owner: owner)
  135 + group.add_users([reporter.id], UsersGroup::REPORTER)
  136 + group.add_users([developer.id], UsersGroup::DEVELOPER)
  137 + group.add_users([master.id], UsersGroup::MASTER)
  138 + group.add_users([guest.id], UsersGroup::GUEST)
  139 + group
  140 + end
  141 + let!(:group_no_members) { create(:group, owner: owner) }
  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 + it "users not part of the group should get access error" do
  158 + get api("/groups/#{group_with_members.id}/members", user1)
  159 + response.status.should == 403
  160 + end
  161 + end
  162 + end
  163 +
  164 + describe "POST /groups/:id/members" do
  165 + context "when not a member of the group" do
  166 + it "should not add guest as member of group_no_members when adding being done by person outside the group" do
  167 + post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: UsersGroup::MASTER
  168 + response.status.should == 403
  169 + end
  170 + end
  171 +
  172 + context "when a member of the group" do
  173 + it "should return ok and add new member" do
  174 + count_before=group_no_members.users_groups.count
  175 + new_user = create(:user)
  176 + post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: UsersGroup::MASTER
  177 + response.status.should == 201
  178 + json_response['name'].should == new_user.name
  179 + json_response['access_level'].should == UsersGroup::MASTER
  180 + group_no_members.users_groups.count.should == count_before + 1
  181 + end
  182 + it "should return error if member already exists" do
  183 + post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: UsersGroup::MASTER
  184 + response.status.should == 409
  185 + end
  186 + it "should return a 400 error when user id is not given" do
  187 + post api("/groups/#{group_no_members.id}/members", owner), access_level: UsersGroup::MASTER
  188 + response.status.should == 400
  189 + end
  190 + it "should return a 400 error when access level is not given" do
  191 + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
  192 + response.status.should == 400
  193 + end
  194 +
  195 + it "should return a 422 error when access level is not known" do
  196 + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
  197 + response.status.should == 422
  198 + end
  199 +
  200 + end
  201 + end
  202 +
  203 + describe "DELETE /groups/:id/members/:user_id" do
  204 + context "when not a member of the group" do
  205 + it "should not delete guest's membership of group_with_members" do
  206 + random_user = create(:user)
  207 + delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
  208 + response.status.should == 403
  209 + end
  210 + end
  211 +
  212 + context "when a member of the group" do
  213 + it "should delete guest's membership of group" do
  214 + count_before=group_with_members.users_groups.count
  215 + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
  216 + response.status.should == 200
  217 + group_with_members.users_groups.count.should == count_before - 1
  218 + end
  219 + it "should return a 404 error when user id is not known" do
  220 + delete api("/groups/#{group_with_members.id}/members/1328", owner)
  221 + response.status.should == 404
  222 + end
  223 + end
  224 + end
  225 + end
126 end 226 end