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 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,19 @@ 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 + [UsersGroup::GUEST, UsersGroup::REPORTER, UsersGroup::DEVELOPER, UsersGroup::MASTER].include? level.to_i
  18 + end
  19 + end
7 20 # Get a groups list
8 21 #
9 22 # Example Request:
... ... @@ -46,12 +59,8 @@ module API
46 59 # Example Request:
47 60 # GET /groups/:id
48 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 64 end
56 65  
57 66 # Transfer a project to the Group namespace
... ... @@ -71,6 +80,58 @@ module API
71 80 not_found!
72 81 end
73 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 135 end
75 136 end
76 137 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,8 +104,8 @@ 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 111  
... ... @@ -123,4 +123,104 @@ describe API::API do
123 123 end
124 124 end
125 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 226 end
... ...