diff --git a/lib/api/api.rb b/lib/api/api.rb index 2c7cd9038c3504d33abe0e9a5104a45e5a5b73f7..d26667ba3f7ad34db41fbf8c1df19cc04b7cacfe 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -27,6 +27,7 @@ module API helpers APIHelpers mount Groups + mount GroupMembers mount Users mount Projects mount Repositories diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb new file mode 100644 index 0000000000000000000000000000000000000000..d596517c816e5cbc46cab7d08a34e4e763618956 --- /dev/null +++ b/lib/api/group_members.rb @@ -0,0 +1,80 @@ +module API + class GroupMembers < Grape::API + before { authenticate! } + + resource :groups do + helpers do + def find_group(id) + group = Group.find(id) + + if can?(current_user, :read_group, 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 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.group_members + 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 + group = find_group(params[:id]) + authorize! :manage_group, group + required_attributes! [:user_id, :access_level] + + unless validate_access_level?(params[:access_level]) + render_api_error!("Wrong access level", 422) + end + + if group.group_members.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.group_members.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]) + authorize! :manage_group, group + member = group.group_members.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/groups.rb b/lib/api/groups.rb index 4841e04689d481a81c476a19ae43dc5393c85de6..f0ab6938b1c3be04bc2413d4a474534ba5e23835 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -97,57 +97,6 @@ module API 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.group_members - 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.group_members.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.group_members.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.group_members.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/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4957186f6056d0b4e5269082a8ac537debbeb06c --- /dev/null +++ b/spec/requests/api/group_members_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:owner) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:stranger) { create(:user) } + + let!(:group_with_members) do + group = create(:group) + group.add_users([reporter.id], GroupMember::REPORTER) + group.add_users([developer.id], GroupMember::DEVELOPER) + group.add_users([master.id], GroupMember::MASTER) + group.add_users([guest.id], GroupMember::GUEST) + group + end + + let!(:group_no_members) { create(:group) } + + before do + group_with_members.add_owner owner + group_no_members.add_owner owner + end + + describe "GET /groups/:id/members" do + context "when authenticated as user that is part or the group" do + it "each user: should return an array of members groups of group3" do + [owner, master, developer, reporter, guest].each do |user| + get api("/groups/#{group_with_members.id}/members", user) + response.status.should == 200 + json_response.should be_an Array + json_response.size.should == 5 + json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER + json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER + json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER + json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER + json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST + end + end + + it "users not part of the group should get access error" do + get api("/groups/#{group_with_members.id}/members", stranger) + response.status.should == 403 + end + end + end + + describe "POST /groups/:id/members" do + context "when not a member of the group" do + it "should not add guest as member of group_no_members when adding being done by person outside the group" do + post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER + response.status.should == 403 + end + end + + context "when a member of the group" do + it "should return ok and add new member" do + new_user = create(:user) + + expect { + post api("/groups/#{group_no_members.id}/members", owner), + user_id: new_user.id, access_level: GroupMember::MASTER + }.to change { group_no_members.members.count }.by(1) + + response.status.should == 201 + json_response['name'].should == new_user.name + json_response['access_level'].should == GroupMember::MASTER + end + + it "should not allow guest to modify group members" do + new_user = create(:user) + + expect { + post api("/groups/#{group_with_members.id}/members", guest), + user_id: new_user.id, access_level: GroupMember::MASTER + }.not_to change { group_with_members.members.count } + + response.status.should == 403 + end + + it "should return error if member already exists" do + post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER + response.status.should == 409 + end + + it "should return a 400 error when user id is not given" do + post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER + response.status.should == 400 + end + + it "should return a 400 error when access level is not given" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id + response.status.should == 400 + end + + it "should return a 422 error when access level is not known" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 + response.status.should == 422 + end + end + end + + describe "DELETE /groups/:id/members/:user_id" do + context "when not a member of the group" do + it "should not delete guest's membership of group_with_members" do + random_user = create(:user) + delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) + response.status.should == 403 + end + end + + context "when a member of the group" do + it "should delete guest's membership of group" do + expect { + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + }.to change { group_with_members.members.count }.by(-1) + + response.status.should == 200 + end + + it "should return a 404 error when user id is not known" do + delete api("/groups/#{group_with_members.id}/members/1328", owner) + response.status.should == 404 + end + + it "should not allow guest to modify group members" do + delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest) + response.status.should == 403 + end + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 42ccad71aafdf586a2f5968d7d0ddfcc8b090e1a..8dfd2cd650e79965f2ebf24c388aada08553d8da 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -165,114 +165,4 @@ describe API::API, api: true do end end end - - describe "members" do - let(:owner) { create(:user) } - let(:reporter) { create(:user) } - let(:developer) { create(:user) } - let(:master) { create(:user) } - let(:guest) { create(:user) } - let!(:group_with_members) do - group = create(:group) - group.add_users([reporter.id], GroupMember::REPORTER) - group.add_users([developer.id], GroupMember::DEVELOPER) - group.add_users([master.id], GroupMember::MASTER) - group.add_users([guest.id], GroupMember::GUEST) - group - end - let!(:group_no_members) { create(:group) } - - before do - group_with_members.add_owner owner - group_no_members.add_owner owner - end - - describe "GET /groups/:id/members" do - context "when authenticated as user that is part or the group" do - it "each user: should return an array of members groups of group3" do - [owner, master, developer, reporter, guest].each do |user| - get api("/groups/#{group_with_members.id}/members", user) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 5 - json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER - json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER - json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER - json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER - json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST - end - end - - it "users not part of the group should get access error" do - get api("/groups/#{group_with_members.id}/members", user1) - response.status.should == 403 - end - end - end - - describe "POST /groups/:id/members" do - context "when not a member of the group" do - it "should not add guest as member of group_no_members when adding being done by person outside the group" do - post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should return ok and add new member" do - count_before=group_no_members.group_members.count - new_user = create(:user) - post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER - response.status.should == 201 - json_response['name'].should == new_user.name - json_response['access_level'].should == GroupMember::MASTER - group_no_members.group_members.count.should == count_before + 1 - end - - it "should return error if member already exists" do - post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER - response.status.should == 409 - end - - it "should return a 400 error when user id is not given" do - post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER - response.status.should == 400 - end - - it "should return a 400 error when access level is not given" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id - response.status.should == 400 - end - - it "should return a 422 error when access level is not known" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 - response.status.should == 422 - end - end - end - - describe "DELETE /groups/:id/members/:user_id" do - context "when not a member of the group" do - it "should not delete guest's membership of group_with_members" do - random_user = create(:user) - delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should delete guest's membership of group" do - count_before=group_with_members.group_members.count - delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) - response.status.should == 200 - group_with_members.group_members.count.should == count_before - 1 - end - - it "should return a 404 error when user id is not known" do - delete api("/groups/#{group_with_members.id}/members/1328", owner) - response.status.should == 404 - end - end - end - end end