[Bps-public-commit] rt-extension-rest2 branch, add-group-members-user-memberships, created. 1.04-15-g76d7951
? sunnavy
sunnavy at bestpractical.com
Wed Oct 31 16:38:38 EDT 2018
The branch, add-group-members-user-memberships has been created
at 76d795106ff817a736291516866411eb9437b821 (commit)
- Log -----------------------------------------------------------------
commit 6caf780aa31ea2bfecfe88bfd79a34c9cbde5527
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 10:29:49 2018 +0200
Add POST /group/ to create a group
diff --git a/lib/RT/Extension/REST2/Resource/Group.pm b/lib/RT/Extension/REST2/Resource/Group.pm
index 14910cf..d4e22fa 100644
--- a/lib/RT/Extension/REST2/Resource/Group.pm
+++ b/lib/RT/Extension/REST2/Resource/Group.pm
@@ -9,6 +9,7 @@ use RT::Extension::REST2::Util qw(expand_uid);
extends 'RT::Extension::REST2::Resource::Record';
with 'RT::Extension::REST2::Resource::Record::Readable'
=> { -alias => { serialize => '_default_serialize' } },
+ 'RT::Extension::REST2::Resource::Record::Writable',
'RT::Extension::REST2::Resource::Record::Hypermedia'
=> { -alias => { hypermedia_links => '_default_hypermedia_links' } };
diff --git a/lib/RT/Extension/REST2/Resource/Record/Writable.pm b/lib/RT/Extension/REST2/Resource/Record/Writable.pm
index aa548dd..b7eb98f 100644
--- a/lib/RT/Extension/REST2/Resource/Record/Writable.pm
+++ b/lib/RT/Extension/REST2/Resource/Record/Writable.pm
@@ -284,7 +284,8 @@ sub create_record {
}
}
- my ($ok, @rest) = $record->Create(%args);
+ my $method = $record->isa('RT::Group') ? 'CreateUserDefinedGroup' : 'Create';
+ my ($ok, @rest) = $record->$method(%args);
if ($ok && $cfs) {
$self->_update_custom_fields($cfs);
commit 353de0d045aa502e85cbe74797a00b238ecabd57
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 10:30:42 2018 +0200
Add DELETE /group/:id to disable a group
diff --git a/lib/RT/Extension/REST2/Resource/Group.pm b/lib/RT/Extension/REST2/Resource/Group.pm
index d4e22fa..c32a7e4 100644
--- a/lib/RT/Extension/REST2/Resource/Group.pm
+++ b/lib/RT/Extension/REST2/Resource/Group.pm
@@ -9,6 +9,7 @@ use RT::Extension::REST2::Util qw(expand_uid);
extends 'RT::Extension::REST2::Resource::Record';
with 'RT::Extension::REST2::Resource::Record::Readable'
=> { -alias => { serialize => '_default_serialize' } },
+ 'RT::Extension::REST2::Resource::Record::DeletableByDisabling',
'RT::Extension::REST2::Resource::Record::Writable',
'RT::Extension::REST2::Resource::Record::Hypermedia'
=> { -alias => { hypermedia_links => '_default_hypermedia_links' } };
commit 42110ddd4daed62ddc15ce2da930df87a0756ffb
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 10:45:39 2018 +0200
Add Disabled in returned properties of a group with GET /group/:id
diff --git a/lib/RT/Extension/REST2/Resource/Group.pm b/lib/RT/Extension/REST2/Resource/Group.pm
index c32a7e4..fe47e7d 100644
--- a/lib/RT/Extension/REST2/Resource/Group.pm
+++ b/lib/RT/Extension/REST2/Resource/Group.pm
@@ -34,6 +34,8 @@ sub serialize {
@{ $self->record->MembersObj->ItemsArrayRef }
];
+ $data->{Disabled} = $self->record->PrincipalObj->Disabled;
+
return $data;
}
commit 40968cf0c08030b51aca18c6f4fd45231b47d061
Author: gibus <gibus at easter-eggs.com>
Date: Thu Sep 20 15:16:44 2018 +0200
Fix permissions for group endpoints
diff --git a/lib/RT/Extension/REST2/Resource/Group.pm b/lib/RT/Extension/REST2/Resource/Group.pm
index fe47e7d..8235fa0 100644
--- a/lib/RT/Extension/REST2/Resource/Group.pm
+++ b/lib/RT/Extension/REST2/Resource/Group.pm
@@ -10,7 +10,9 @@ extends 'RT::Extension::REST2::Resource::Record';
with 'RT::Extension::REST2::Resource::Record::Readable'
=> { -alias => { serialize => '_default_serialize' } },
'RT::Extension::REST2::Resource::Record::DeletableByDisabling',
+ => { -alias => { delete_resource => '_delete_resource' } },
'RT::Extension::REST2::Resource::Record::Writable',
+ => { -alias => { create_record => '_create_record' } },
'RT::Extension::REST2::Resource::Record::Hypermedia'
=> { -alias => { hypermedia_links => '_default_hypermedia_links' } };
@@ -46,6 +48,34 @@ sub hypermedia_links {
return $links;
}
+sub create_record {
+ my $self = shift;
+ my $data = shift;
+
+ return (\403, $self->record->loc("Permission Denied"))
+ unless $self->current_user->HasRight(
+ Right => "AdminGroup",
+ Object => RT->System,
+ );
+
+ return $self->_create_record($data);
+}
+
+sub delete_resource {
+ my $self = shift;
+
+ return (\403, $self->record->loc("Permission Denied"))
+ unless $self->record->CurrentUserHasRight('AdminGroup');
+
+ return $self->_delete_resource;
+}
+
+sub forbidden {
+ my $self = shift;
+ return 0 unless $self->record->id;
+ return !$self->record->CurrentUserHasRight('SeeGroup');
+}
+
__PACKAGE__->meta->make_immutable;
1;
commit 4092a6124a49276f7cbfaba01c3daf40c2857e80
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 10:49:17 2018 +0200
Add setting/unsetting Disabled for a user or a group with PUT /user/:id and PUT /group/:id
diff --git a/lib/RT/Extension/REST2/Resource/Record/Writable.pm b/lib/RT/Extension/REST2/Resource/Record/Writable.pm
index b7eb98f..6d04d3b 100644
--- a/lib/RT/Extension/REST2/Resource/Record/Writable.pm
+++ b/lib/RT/Extension/REST2/Resource/Record/Writable.pm
@@ -54,6 +54,8 @@ sub update_record {
push @results, $self->_update_custom_fields($data->{CustomFields});
push @results, $self->_update_role_members($data);
+ push @results, $self->_update_disabled($data->{Disabled})
+ unless grep { $_ eq 'Disabled' } $self->record->WritableAttributes;
# XXX TODO: Figure out how to return success/failure? Core RT::Record's
# ->Update will need to be replaced or improved.
@@ -248,6 +250,22 @@ sub _update_role_members {
return @results;
}
+sub _update_disabled {
+ my $self = shift;
+ my $data = shift;
+ my @results;
+
+ my $record = $self->record;
+ return unless defined $data and $data =~ /^[01]$/;
+
+ return unless $record->can('SetDisabled');
+
+ my ($ok, $msg) = $record->SetDisabled($data);
+ push @results, $msg;
+
+ return @results;
+}
+
sub update_resource {
my $self = shift;
my $data = shift;
commit a0bf988b9034eef920df7ee20d568040da7ef626
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 11:18:24 2018 +0200
Add endpoints to operate on group members
* Getting direct members of a group: GET /group/:id/members
* Getting direct and undirect members of a group: GET /group/:id/members?recursively=1
* Getting user members of a group: GET /group/:id/members?groups=0&recursively=[01]
* Getting group members of a group: GET /group/:id/members?users=0&recursively=[01]
* Removing a member from a group: DELETE /group/:id/member/:id
* Removing all members of a group: DELETE /group/:id/members
* Adding members to a group: PUT /group/:id/members passing JSON array of principals ids
diff --git a/lib/RT/Extension/REST2/Resource/GroupMembers.pm b/lib/RT/Extension/REST2/Resource/GroupMembers.pm
new file mode 100644
index 0000000..546c545
--- /dev/null
+++ b/lib/RT/Extension/REST2/Resource/GroupMembers.pm
@@ -0,0 +1,152 @@
+package RT::Extension::REST2::Resource::GroupMembers;
+use strict;
+use warnings;
+
+use Moose;
+use namespace::autoclean;
+
+extends 'RT::Extension::REST2::Resource::Collection';
+with 'RT::Extension::REST2::Resource::Role::RequestBodyIsJSON' =>
+ {type => 'ARRAY'};
+
+has 'group' => (
+ is => 'ro',
+);
+
+sub dispatch_rules {
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/group/(\d+)/members/?$},
+ block => sub {
+ my ($match, $req) = @_;
+ my $group_id = $match->pos(1);
+ my $group = RT::Group->new($req->env->{"rt.current_user"});
+ $group->Load($group_id);
+ my $collection;
+
+ my $recursively = $req->parameters->{recursively} // 0;
+ my $users = $req->parameters->{users} // 1;
+ my $groups = $req->parameters->{groups} // 1;
+
+ if ( $users && $groups ) {
+ if ( $recursively ) {
+ $collection = $group->DeepMembersObj;
+ }
+ else {
+ $collection = $group->MembersObj;
+ }
+ }
+ elsif ( $users ) {
+ $collection = $group->UserMembersObj(Recursively => $recursively);
+ }
+ elsif ( $groups ) {
+ $collection = $group->GroupMembersObj(Recursively => $recursively);
+ }
+ else {
+ $collection = RT::GroupMembers->new( $req->env->{"rt.current_user"} );
+ $collection->Limit(FIELD => 'id', VALUE => 0);
+ }
+
+ return {group => $group, collection => $collection};
+ },
+ ),
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/group/(\d+)/member/(\d+)/?$},
+ block => sub {
+ my ($match, $req) = @_;
+ my $group_id = $match->pos(1);
+ my $member_id = $match->pos(2) || '';
+ my $group = RT::Group->new($req->env->{"rt.current_user"});
+ $group->Load($group_id);
+ my $collection = $group->MembersObj;
+ $collection->Limit(FIELD => 'MemberId', VALUE => $member_id);
+ return {group => $group, collection => $collection};
+ },
+ ),
+}
+
+sub forbidden {
+ my $self = shift;
+ return 0 unless $self->group->id;
+ return !$self->group->CurrentUserHasRight('AdminGroupMembership');
+ return 1;
+}
+
+sub serialize {
+ my $self = shift;
+ my $collection = $self->collection;
+ my @results;
+
+ while (my $item = $collection->Next) {
+ my ($id, $class);
+ if (ref $item eq 'RT::GroupMember' || ref $item eq 'RT::CachedGroupMember') {
+ my $principal = $item->MemberObj;
+ $class = $principal->IsGroup ? 'group' : 'user';
+ $id = $principal->id;
+ } elsif (ref $item eq 'RT::Group') {
+ $class = 'group';
+ $id = $item->id;
+ } elsif (ref $item eq 'RT::User') {
+ $class = 'user';
+ $id = $item->id;
+ }
+ else {
+ next;
+ }
+
+ my $result = {
+ type => $class,
+ id => $id,
+ _url => RT::Extension::REST2->base_uri . "/$class/$id",
+ };
+ push @results, $result;
+ }
+ return {
+ count => scalar(@results) + 0,
+ total => $collection->CountAll,
+ per_page => $collection->RowsPerPage + 0,
+ page => ($collection->FirstRow / $collection->RowsPerPage) + 1,
+ items => \@results,
+ };
+}
+
+sub allowed_methods {
+ my @ok = ('GET', 'HEAD', 'DELETE', 'PUT');
+ return \@ok;
+}
+
+sub content_types_accepted {[{'application/json' => 'from_json'}]}
+
+sub delete_resource {
+ my $self = shift;
+ my $collection = $self->collection;
+ while (my $group_member = $collection->Next) {
+ $RT::Logger->info('Delete ' . ($group_member->MemberObj->IsGroup ? 'group' : 'user') . ' ' . $group_member->MemberId . ' from group '.$group_member->GroupId);
+ $group_member->GroupObj->Object->DeleteMember($group_member->MemberId);
+ }
+ return 1;
+}
+
+sub from_json {
+ my $self = shift;
+ my $params = JSON::decode_json($self->request->content);
+ my $group = $self->group;
+
+ my $method = $self->request->method;
+ my @results;
+ if ($method eq 'PUT') {
+ for my $param (@$params) {
+ if ($param =~ /^\d+$/) {
+ my ($ret, $msg) = $group->AddMember($param);
+ push @results, $msg;
+ } else {
+ push @results, 'You should provide principal id for each member to add';
+ }
+ }
+ }
+ $self->response->body(JSON::encode_json(\@results));
+ return;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
commit a1b14989536a57258a09784ebc1c5ebd26a34cff
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 11:22:42 2018 +0200
Add Memberships in returned properties of a user with GET /user/:id|:name
diff --git a/lib/RT/Extension/REST2/Resource/User.pm b/lib/RT/Extension/REST2/Resource/User.pm
index c39b7f1..a4ae3ae 100644
--- a/lib/RT/Extension/REST2/Resource/User.pm
+++ b/lib/RT/Extension/REST2/Resource/User.pm
@@ -4,6 +4,7 @@ use warnings;
use Moose;
use namespace::autoclean;
+use RT::Extension::REST2::Util qw(expand_uid);
extends 'RT::Extension::REST2::Resource::Record';
with (
@@ -40,6 +41,10 @@ around 'serialize' => sub {
my $data = $self->$orig(@_);
$data->{Privileged} = $self->record->Privileged ? 1 : 0;
$data->{Disabled} = $self->record->PrincipalObj->Disabled;
+ $data->{Memberships} = [
+ map { expand_uid($_->UID) }
+ @{ $self->record->OwnGroups->ItemsArrayRef }
+ ];
return $data;
};
commit dc173e739d8683df34c49323060f9631086c9a02
Author: gibus <gibus at easter-eggs.com>
Date: Mon Sep 17 11:23:31 2018 +0200
Add endpoints to operate on user memberships
* Getting groups which a user is a member of: GET /user/:id|:name/groups
* Removing a user from all groups: DELETE /user/:id|:name/groups
* Removing a user from a group: DELETE /user/:id|:name/group/:id
* Adding a user to some groups: PUT /user/:id|:name/groups passing JSON array of groups ids
diff --git a/lib/RT/Extension/REST2/Resource/UserGroups.pm b/lib/RT/Extension/REST2/Resource/UserGroups.pm
new file mode 100644
index 0000000..3efe15e
--- /dev/null
+++ b/lib/RT/Extension/REST2/Resource/UserGroups.pm
@@ -0,0 +1,120 @@
+package RT::Extension::REST2::Resource::UserGroups;
+use strict;
+use warnings;
+
+use Moose;
+use namespace::autoclean;
+
+extends 'RT::Extension::REST2::Resource::Collection';
+with 'RT::Extension::REST2::Resource::Role::RequestBodyIsJSON' =>
+ {type => 'ARRAY'};
+
+has 'user' => (
+ is => 'ro',
+ isa => 'RT::User',
+);
+
+sub dispatch_rules {
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/user/([^/]+)/groups/?$},
+ block => sub {
+ my ($match, $req) = @_;
+ my $user_id = $match->pos(1);
+ my $user = RT::User->new($req->env->{"rt.current_user"});
+ $user->Load($user_id);
+
+ return {user => $user, collection => $user->OwnGroups};
+ },
+ ),
+ Path::Dispatcher::Rule::Regex->new(
+ regex => qr{^/user/([^/]+)/group/(\d+)/?$},
+ block => sub {
+ my ($match, $req) = @_;
+ my $user_id = $match->pos(1);
+ my $group_id = $match->pos(2) || '';
+ my $user = RT::User->new($req->env->{"rt.current_user"});
+ $user->Load($user_id);
+ my $collection = $user->OwnGroups();
+ $collection->Limit(FIELD => 'id', VALUE => $group_id);
+ return {user => $user, collection => $collection};
+ },
+ ),
+}
+
+sub forbidden {
+ my $self = shift;
+ return 0 if
+ ($self->current_user->HasRight(
+ Right => "ModifyOwnMembership",
+ Object => RT->System,
+ ) && $self->current_user->id == $self->user->id) ||
+ $self->current_user->HasRight(
+ Right => 'AdminGroupMembership',
+ Object => RT->System);
+ return 1;
+}
+
+sub serialize {
+ my $self = shift;
+ my $collection = $self->collection;
+ my @results;
+
+ while (my $item = $collection->Next) {
+ my $result = {
+ type => 'group',
+ id => $item->id,
+ _url => RT::Extension::REST2->base_uri . "/group/" . $item->id,
+ };
+ push @results, $result;
+ }
+ return {
+ count => scalar(@results) + 0,
+ total => $collection->CountAll + 0,
+ per_page => $collection->RowsPerPage + 0,
+ page => ($collection->FirstRow / $collection->RowsPerPage) + 1,
+ items => \@results,
+ };
+}
+
+sub allowed_methods {
+ my @ok = ('GET', 'HEAD', 'DELETE', 'PUT');
+ return \@ok;
+}
+
+sub content_types_accepted {[{'application/json' => 'from_json'}]}
+
+sub delete_resource {
+ my $self = shift;
+ my $collection = $self->collection;
+ while (my $group = $collection->Next) {
+ $RT::Logger->info('Delete user ' . $self->user->Name . ' from group '.$group->id);
+ $group->DeleteMember($self->user->id);
+ }
+ return 1;
+}
+
+sub from_json {
+ my $self = shift;
+ my $params = JSON::decode_json($self->request->content);
+ my $user = $self->user;
+
+ my $method = $self->request->method;
+ my @results;
+ if ($method eq 'PUT') {
+ for my $param (@$params) {
+ if ($param =~ /^\d+$/) {
+ my $group = RT::Group->new($self->request->env->{"rt.current_user"});
+ $group->Load($param);
+ push @results, $group->AddMember($user->id);
+ } else {
+ push @results, [0, 'You should provide group id for each group user should be added'];
+ }
+ }
+ }
+ $self->response->body(JSON::encode_json(\@results));
+ return;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
commit 9b653a5f1084423b147fb6fcc6c8d8450e1bb265
Author: gibus <gibus at easter-eggs.com>
Date: Thu Sep 20 15:17:05 2018 +0200
Add tests for group members endpoints
diff --git a/t/group-members.t b/t/group-members.t
new file mode 100644
index 0000000..af14071
--- /dev/null
+++ b/t/group-members.t
@@ -0,0 +1,260 @@
+use strict;
+use warnings;
+use lib 't/lib';
+use RT::Extension::REST2::Test tests => undef;
+use Test::Warn;
+
+my $mech = RT::Extension::REST2::Test->mech;
+
+my $auth = RT::Extension::REST2::Test->authorization_header;
+my $rest_base_path = '/REST/2.0';
+my $user = RT::Extension::REST2::Test->user;
+
+my $group1 = RT::Group->new(RT->SystemUser);
+my ($ok, $msg) = $group1->CreateUserDefinedGroup(Name => 'Group 1');
+ok($ok, $msg);
+
+my $user1 = RT::User->new(RT->SystemUser);
+($ok, $msg) = $user1->Create(Name => 'User 1');
+ok($ok, $msg);
+
+my $user2 = RT::User->new(RT->SystemUser);
+($ok, $msg) = $user2->Create(Name => 'User 2');
+ok($ok, $msg);
+
+# Group creation
+my ($group2_url, $group2_id);
+{
+ my $payload = {
+ Name => 'Group 2',
+ };
+
+ # Rights Test - No AdminGroup
+ my $res = $mech->post_json("$rest_base_path/group",
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 403, 'Cannot create group without AdminGroup right');
+
+ # Rights Test - With AdminGroup
+ $user->PrincipalObj->GrantRight(Right => 'AdminGroup');
+ $res = $mech->post_json("$rest_base_path/group",
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 201, 'Create group with AdminGroup right');
+ ok($group2_url = $res->header('location'), 'Created group url');
+ ok(($group2_id) = $group2_url =~ qr[/group/(\d+)], 'Created group id');
+}
+
+my $group2 = RT::Group->new(RT->SystemUser);
+$group2->Load($group2_id);
+
+# Group disabling
+{
+ # Rights Test - No AdminGroup
+ $user->PrincipalObj->RevokeRight(Right => 'AdminGroup');
+ my $res = $mech->delete($group2_url,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 403, 'Cannot disable group without AdminGroup right');
+
+ # Rights Test - With AdminGroup, no SeeGroup
+ $user->PrincipalObj->GrantRight(Right => 'AdminGroup', Object => $group2);
+ $res = $mech->delete($group2_url,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 403, 'Cannot disable group without SeeGroup right');
+
+ # Rights Test - With AdminGroup, no SeeGroup
+ $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group2);
+ $res = $mech->delete($group2_url,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 204, 'Disable group with AdminGroup & SeeGroup rights');
+
+ is($group2->Disabled, 1, "Group disabled");
+}
+
+# Group enabling
+{
+ my $payload = {
+ Disabled => 0,
+ };
+
+ # Rights Test - No AdminGroup
+ $user->PrincipalObj->RevokeRight(Right => 'AdminGroup', Object => $group2);
+ $user->PrincipalObj->RevokeRight(Right => 'SeeGroup', Object => $group2);
+
+ my $res = $mech->put_json($group2_url,
+ $payload,
+ 'Authorization' => $auth);
+ is($res->code, 403, 'Cannot enable group without AdminGroup right');
+
+ # Rights Test - With AdminGroup, no SeeGroup
+ $user->PrincipalObj->GrantRight(Right => 'AdminGroup', Object => $group2);
+ $res = $mech->put_json($group2_url,
+ $payload,
+ 'Authorization' => $auth);
+ is($res->code, 403, 'Cannot enable group without SeeGroup right');
+
+ # Rights Test - With AdminGroup, no SeeGroup
+ $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group2);
+ $res = $mech->put_json($group2_url,
+ $payload,
+ 'Authorization' => $auth);
+ is($res->code, 200, 'Enable group with AdminGroup & SeeGroup rights');
+ is_deeply($mech->json_response, ['Group enabled']);
+
+ is($group2->Disabled, 0, "Group enabled");
+}
+
+my $group1_id = $group1->id;
+(my $group1_url = $group2_url) =~ s/$group2_id/$group1_id/;
+$user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group1);
+
+# Members addition
+{
+ my $payload = [
+ $user1->id,
+ $group2->id,
+ $user1->id + 666,
+ ];
+
+ # Rights Test - No AdminGroupMembership
+ my $res = $mech->put_json($group1_url . '/members',
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 403, 'Cannot add members to group without AdminGroupMembership right');
+
+ # Rights Test - With AdminGroupMembership
+ $user->PrincipalObj->GrantRight(Right => 'AdminGroupMembership', Object => $group1);
+ $res = $mech->put_json($group1_url . '/members',
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'Add members to group with AdminGroupMembership right');
+ is_deeply($mech->json_response, [
+ "Member added: " . $user1->Name,
+ "Member added: " . $group2->Name,
+ "Couldn't find that principal",
+ ], 'Two members added, bad principal rejected');
+ my $members1 = $group1->MembersObj;
+ is($members1->Count, 2, 'Two members added');
+ my $member = $members1->Next;
+ is($member->MemberObj->PrincipalType, 'User', 'User added as member');
+ is($member->MemberObj->id, $user1->id, 'Accurate user added as member');
+ $member = $members1->Next;
+ is($member->MemberObj->PrincipalType, 'Group', 'Group added as member');
+ is($member->MemberObj->id, $group2->id, 'Accurate group added as member');
+}
+
+# Members list
+{
+ # Add user to subgroup
+ $group2->AddMember($user2->id);
+
+ # Direct members
+ my $res = $mech->get($group1_url . '/members',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List direct members');
+
+ my $content = $mech->json_response;
+ is($content->{total}, 2, 'Two direct members');
+ is($content->{items}->[0]->{type}, 'user', 'User member');
+ is($content->{items}->[0]->{id}, $user1->id, 'Accurate user member');
+ is($content->{items}->[1]->{type}, 'group', 'Group member');
+ is($content->{items}->[1]->{id}, $group2->id, 'Accurate group member');
+
+ # Deep members
+ $res = $mech->get($group1_url . '/members?recursively=1',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List deep members');
+
+ $content = $mech->json_response;
+ is($content->{total}, 4, 'Four deep members');
+ is($content->{items}->[0]->{type}, 'group', 'First group deep member');
+ is($content->{items}->[0]->{id}, $group1->id, 'First accurate group deep member');
+ is($content->{items}->[1]->{type}, 'user', 'First user deep member');
+ is($content->{items}->[1]->{id}, $user1->id, 'First accurate user deep member');
+ is($content->{items}->[2]->{type}, 'user', 'Second user member');
+ is($content->{items}->[2]->{id}, $user2->id, 'Second accurate user deep member');
+ is($content->{items}->[3]->{type}, 'group', 'Second group deep member');
+ is($content->{items}->[3]->{id}, $group2->id, 'Second accurate group deep member');
+
+ # Direct user members
+ $res = $mech->get($group1_url . '/members?groups=0',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List direct user members');
+
+ $content = $mech->json_response;
+ is($content->{total}, 1, 'One direct user member');
+ is($content->{items}->[0]->{type}, 'user', 'Direct user member');
+ is($content->{items}->[0]->{id}, $user1->id, 'Accurate direct user member');
+
+ # Recursive user members
+ $res = $mech->get($group1_url . '/members?groups=0&recursively=1',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List recursive user members');
+
+ $content = $mech->json_response;
+ is($content->{total}, 2, 'Two recursive user members');
+ is($content->{items}->[0]->{type}, 'user', 'First recursive user member');
+ is($content->{items}->[0]->{id}, $user1->id, 'First accurate recursive user member');
+ is($content->{items}->[1]->{type}, 'user', 'Second recursive user member');
+ is($content->{items}->[1]->{id}, $user2->id, 'Second accurate recursive user member');
+
+ # Direct group members
+ $res = $mech->get($group1_url . '/members?users=0',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List direct group members');
+
+ $content = $mech->json_response;
+ is($content->{total}, 1, 'One direct group member');
+ is($content->{items}->[0]->{type}, 'group', 'Direct group member');
+ is($content->{items}->[0]->{id}, $group2->id, 'Accurate direct group member');
+
+ # Recursive group members
+ $res = $mech->get($group1_url . '/members?users=0&recursively=1',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List recursive group members');
+
+ $content = $mech->json_response;
+ is($content->{total}, 2, 'Two recursive group members');
+ is($content->{items}->[0]->{type}, 'group', 'First recursive group member');
+ is($content->{items}->[0]->{id}, $group1->id, 'First accurate recursive group member');
+ is($content->{items}->[1]->{type}, 'group', 'Second recursive group member');
+ is($content->{items}->[1]->{id}, $group2->id, 'Second accurate recursive group member');
+}
+
+# Members removal
+{
+ my $res = $mech->delete($group1_url . '/member/' . $user1->id,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 204, 'Remove member');
+ my $members1 = $group1->MembersObj;
+ is($members1->Count, 1, 'One member removed');
+ my $member = $members1->Next;
+ is($member->MemberObj->PrincipalType, 'Group', 'Group remaining member');
+ is($member->MemberObj->id, $group2->id, 'Accurate remaining member');
+}
+
+# All members removal
+{
+ my $res = $mech->delete($group1_url . '/members',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 204, 'Remove all members');
+ my $members1 = $group1->MembersObj;
+ is($members1->Count, 0, 'All members removed');
+}
+
+done_testing;
commit eadd1ed3d42383e17f5ba57b49ff568202bb2504
Author: gibus <gibus at easter-eggs.com>
Date: Thu Sep 20 17:34:05 2018 +0200
Add tests for user memberships endpoints
diff --git a/t/user-memberships.t b/t/user-memberships.t
new file mode 100644
index 0000000..47f969a
--- /dev/null
+++ b/t/user-memberships.t
@@ -0,0 +1,105 @@
+use strict;
+use warnings;
+use lib 't/lib';
+use RT::Extension::REST2::Test tests => undef;
+use Test::Warn;
+
+my $mech = RT::Extension::REST2::Test->mech;
+
+my $auth = RT::Extension::REST2::Test->authorization_header;
+my $rest_base_path = '/REST/2.0';
+my $user = RT::Extension::REST2::Test->user;
+
+my $group1 = RT::Group->new(RT->SystemUser);
+my ($ok, $msg) = $group1->CreateUserDefinedGroup(Name => 'Group 1');
+ok($ok, $msg);
+
+my $group2 = RT::Group->new(RT->SystemUser);
+($ok, $msg) = $group2->CreateUserDefinedGroup(Name => 'Group 2');
+ok($ok, $msg);
+
+($ok, $msg) = $group1->AddMember($group2->id);
+ok($ok, $msg);
+
+# Membership addition
+{
+ my $payload = [ $group2->id ];
+
+ # Rights Test - No ModifyOwnMembership
+ my $res = $mech->put_json("$rest_base_path/user/" . $user->id . '/groups',
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 403, 'Cannot add user to group without ModifyOwnMembership right');
+
+ # Rights Test - With ModifyOwnMembership
+ $user->PrincipalObj->GrantRight(Right => 'ModifyOwnMembership');
+ $res = $mech->put_json("$rest_base_path/user/" . $user->id . '/groups',
+ $payload,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'Add user to group with ModifyOwnMembership right');
+ my $members2 = $group2->MembersObj;
+ is($members2->Count, 1, 'One member added');
+ my $member = $members2->Next;
+ is($member->MemberObj->PrincipalType, 'User', 'User added as member');
+ is($member->MemberObj->id, $user->id, 'Accurate user added as member');
+}
+
+
+# Memberships list
+{
+ # Rights Test - No SeeGroup
+ my $res = $mech->get("$rest_base_path/user/" . $user->id . '/groups',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List direct members');
+
+ my $content = $mech->json_response;
+ is($content->{total}, 2, 'Two recursive memberships');
+ is(scalar(@{$content->{items}}), 0, 'Cannot see memberships content withtout SeeGroup right');
+
+ # Recursive memberships
+ $user->PrincipalObj->GrantRight(Right => 'SeeGroup');
+ $res = $mech->get("$rest_base_path/user/" . $user->id . '/groups',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200, 'List direct members');
+
+ $content = $mech->json_response;
+ is($content->{total}, 2, 'Two recursive memberships');
+ is($content->{items}->[0]->{type}, 'group', 'First group membership');
+ is($content->{items}->[0]->{id}, $group1->id, 'Accurate first group membership');
+ is($content->{items}->[1]->{type}, 'group', 'Second group membership');
+ is($content->{items}->[1]->{id}, $group2->id, 'Accurate second group membership');
+}
+
+($ok, $msg) = $group1->AddMember($user->id);
+ok($ok, $msg);
+
+# Membership removal
+{
+ my $res = $mech->delete("$rest_base_path/user/" . $user->id . '/group/' . $group2->id,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 204, 'Remove membership');
+ my $memberships = $user->OwnGroups;
+ is($memberships->Count, 1, 'One membership removed');
+ my $membership = $memberships->Next;
+ is($membership->id, $group1->id, 'Accurate membership removed');
+}
+
+($ok, $msg) = $group2->AddMember($user->id);
+ok($ok, $msg);
+
+# All members removal
+{
+ my $res = $mech->delete("$rest_base_path/user/" . $user->id . '/groups',
+ 'Authorization' => $auth,
+ );
+ is($res->code, 204, 'Remove all memberships');
+ my $memberships = $user->OwnGroups;
+ is($memberships->Count, 0, 'All membership removed');
+}
+
+done_testing;
commit 02572fb4b5369867b2ff125f2fa6ab7b324a7e99
Author: gibus <gibus at easter-eggs.com>
Date: Thu Sep 20 17:34:44 2018 +0200
Fix existing tests missing SeeGroup right
diff --git a/t/ticket-customroles.t b/t/ticket-customroles.t
index 3b692a3..f20c4a1 100644
--- a/t/ticket-customroles.t
+++ b/t/ticket-customroles.t
@@ -41,7 +41,7 @@ for my $email (qw/multi at example.com test at localhost multi2 at example.com single2 at ex
}
$user->PrincipalObj->GrantRight( Right => $_ )
- for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers/;
+ for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers SeeGroup/;
# Create and view ticket with no watchers
{
diff --git a/t/ticket-watchers.t b/t/ticket-watchers.t
index 20446fd..76bdbdc 100644
--- a/t/ticket-watchers.t
+++ b/t/ticket-watchers.t
@@ -13,7 +13,7 @@ my $user = RT::Extension::REST2::Test->user;
my $queue = RT::Test->load_or_create_queue( Name => "General" );
$user->PrincipalObj->GrantRight( Right => $_ )
- for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers/;
+ for qw/CreateTicket ShowTicket ModifyTicket OwnTicket AdminUsers SeeGroup/;
# Create and view ticket with no watchers
{
commit 5c23a78c6e879ee5fecc97ef12a9ee9de0e680b3
Author: gibus <gibus at easter-eggs.com>
Date: Fri Sep 21 15:49:30 2018 +0200
Add documentation for group members and user memberships
diff --git a/README b/README
index 18aa7fa..c955661 100644
--- a/README
+++ b/README
@@ -398,11 +398,11 @@ USAGE
GET /user/:id
GET /user/:name
- retrieve a user by numeric id or username
+ retrieve a user by numeric id or username (including its memberships and whether it is disabled)
PUT /user/:id
PUT /user/:name
- update a user's metadata; provide JSON content
+ update a user's metadata (including its Disabled status); provide JSON content
DELETE /user/:id
DELETE /user/:name
@@ -417,12 +417,66 @@ USAGE
POST /groups
search for groups using L</JSON searches> syntax
+ POST /group
+ create a (user defined) group; provide JSON content
+
GET /group/:id
- retrieve a group (including its members)
+ retrieve a group (including its members and whether it is disabled)
+
+ PUT /group/:id
+ update a groups's metadata (including its Disabled status); provide JSON content
+
+ DELETE /group/:id
+ disable group
GET /group/:id/history
retrieve list of transactions for group
+ User Memberships
+ GET /user/:id/groups
+ GET /user/:name/groups
+ retrieve list of groups which a user is a member of
+
+ PUT /user/:id/groups
+ PUT /user/:name/groups
+ add a user to groups; provide a JSON array of groups ids
+
+ DELETE /user/:id/group/:id
+ DELETE /user/:name/group/:id
+ remove a user from a group
+
+ DELETE /user/:id/groups
+ DELETE /user/:name/groups
+ remove a user from all groups
+
+ Group Members
+ GET /group/:id/members
+ retrieve list of direct members of a group
+
+ GET /group/:id/members?recursively=1
+ retrieve list of direct and recursive members of a group
+
+ GET /group/:id/members?users=0
+ retrieve list of direct group members of a group
+
+ GET /group/:id/members?users=0&recursively=1
+ retrieve list of direct and recursive group members of a group
+
+ GET /group/:id/members?groups=0
+ retrieve list of direct user members of a group
+
+ GET /group/:id/members?groups=0&recursively=1
+ retrieve list of direct and recursive user members of a group
+
+ PUT /group/:id/members
+ add members to a group; provide a JSON array of principal ids
+
+ DELETE /group/:id/member/:id
+ remove a member from a group
+
+ DELETE /group/:id/members
+ remove all members from a group
+
Custom Fields
GET /customfields?query=<JSON>
POST /customfields
diff --git a/lib/RT/Extension/REST2.pm b/lib/RT/Extension/REST2.pm
index e7e06fe..c4023df 100644
--- a/lib/RT/Extension/REST2.pm
+++ b/lib/RT/Extension/REST2.pm
@@ -440,11 +440,11 @@ Below are some examples using the endpoints above.
GET /user/:id
GET /user/:name
- retrieve a user by numeric id or username
+ retrieve a user by numeric id or username (including its memberships and whether it is disabled)
PUT /user/:id
PUT /user/:name
- update a user's metadata; provide JSON content
+ update a user's metadata (including its Disabled status); provide JSON content
DELETE /user/:id
DELETE /user/:name
@@ -460,12 +460,68 @@ Below are some examples using the endpoints above.
POST /groups
search for groups using L</JSON searches> syntax
+ POST /group
+ create a (user defined) group; provide JSON content
+
GET /group/:id
- retrieve a group (including its members)
+ retrieve a group (including its members and whether it is disabled)
+
+ PUT /group/:id
+ update a groups's metadata (including its Disabled status); provide JSON content
+
+ DELETE /group/:id
+ disable group
GET /group/:id/history
retrieve list of transactions for group
+=head3 User Memberships
+
+ GET /user/:id/groups
+ GET /user/:name/groups
+ retrieve list of groups which a user is a member of
+
+ PUT /user/:id/groups
+ PUT /user/:name/groups
+ add a user to groups; provide a JSON array of groups ids
+
+ DELETE /user/:id/group/:id
+ DELETE /user/:name/group/:id
+ remove a user from a group
+
+ DELETE /user/:id/groups
+ DELETE /user/:name/groups
+ remove a user from all groups
+
+=head3 Group Members
+
+ GET /group/:id/members
+ retrieve list of direct members of a group
+
+ GET /group/:id/members?recursively=1
+ retrieve list of direct and recursive members of a group
+
+ GET /group/:id/members?users=0
+ retrieve list of direct group members of a group
+
+ GET /group/:id/members?users=0&recursively=1
+ retrieve list of direct and recursive group members of a group
+
+ GET /group/:id/members?groups=0
+ retrieve list of direct user members of a group
+
+ GET /group/:id/members?groups=0&recursively=1
+ retrieve list of direct and recursive user members of a group
+
+ PUT /group/:id/members
+ add members to a group; provide a JSON array of principal ids
+
+ DELETE /group/:id/member/:id
+ remove a member from a group
+
+ DELETE /group/:id/members
+ remove all members from a group
+
=head3 Custom Fields
GET /customfields?query=<JSON>
commit c6fdebf98faea399b866c6d3995b9a563540b42f
Author: gibus <gibus at easter-eggs.com>
Date: Tue Oct 9 14:36:51 2018 +0200
Add new files in MANIFEST
diff --git a/MANIFEST b/MANIFEST
index bac4498..3613aa2 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -36,6 +36,7 @@ lib/RT/Extension/REST2/Resource/CustomFields.pm
lib/RT/Extension/REST2/Resource/CustomRole.pm
lib/RT/Extension/REST2/Resource/CustomRoles.pm
lib/RT/Extension/REST2/Resource/Group.pm
+lib/RT/Extension/REST2/Resource/GroupMmebers.pm
lib/RT/Extension/REST2/Resource/Groups.pm
lib/RT/Extension/REST2/Resource/Message.pm
lib/RT/Extension/REST2/Resource/Queue.pm
@@ -56,6 +57,7 @@ lib/RT/Extension/REST2/Resource/TicketsBulk.pm
lib/RT/Extension/REST2/Resource/Transaction.pm
lib/RT/Extension/REST2/Resource/Transactions.pm
lib/RT/Extension/REST2/Resource/User.pm
+lib/RT/Extension/REST2/Resource/UserGroups.pm
lib/RT/Extension/REST2/Resource/Users.pm
lib/RT/Extension/REST2/Util.pm
Makefile.PL
@@ -66,6 +68,7 @@ t/asset-customfields.t
t/assets.t
t/catalogs.t
t/conflict.t
+t/group-members.t
t/lib/RT/Extension/REST2/Test.pm.in
t/not_found.t
t/organization.t
@@ -80,4 +83,5 @@ t/tickets-bulk.t
t/tickets.t
t/transactions.t
t/user-customfields.t
+t/user-memberships.t
TODO
commit e4916bfa02b80b495608d51d44dae1f7be480b5d
Author: gibus <gibus at easter-eggs.com>
Date: Tue Oct 9 15:12:49 2018 +0200
Add memberships in user's hypermedia links
diff --git a/lib/RT/Extension/REST2/Resource/User.pm b/lib/RT/Extension/REST2/Resource/User.pm
index a4ae3ae..8a74630 100644
--- a/lib/RT/Extension/REST2/Resource/User.pm
+++ b/lib/RT/Extension/REST2/Resource/User.pm
@@ -60,6 +60,12 @@ sub hypermedia_links {
my $self = shift;
my $links = $self->_default_hypermedia_links(@_);
push @$links, $self->_transaction_history_link;
+
+ my $id = $self->record->id;
+ push @$links,
+ { ref => 'memberships',
+ _url => RT::Extension::REST2->base_uri . "/user/$id/groups",
+ };
return $links;
}
diff --git a/t/user-memberships.t b/t/user-memberships.t
index 47f969a..b27ad12 100644
--- a/t/user-memberships.t
+++ b/t/user-memberships.t
@@ -102,4 +102,18 @@ ok($ok, $msg);
is($memberships->Count, 0, 'All membership removed');
}
+# User hypermedia links
+{
+ my $res = $mech->get("$rest_base_path/user/" . $user->id,
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+ my $content = $mech->json_response;
+ my $links = $content->{_hyperlinks};
+ my @memberships_links = grep { $_->{ref} eq 'memberships' } @$links;
+ is(scalar(@memberships_links), 1);
+ my $user_id = $user->id;
+ like($memberships_links[0]->{_url}, qr{$rest_base_path/user/$user_id/groups$});
+}
+
done_testing;
commit 76d795106ff817a736291516866411eb9437b821
Author: gibus <gibus at easter-eggs.com>
Date: Tue Oct 9 16:07:23 2018 +0200
Add members in group's hypermedia links
diff --git a/lib/RT/Extension/REST2/Resource/Group.pm b/lib/RT/Extension/REST2/Resource/Group.pm
index 8235fa0..64377a3 100644
--- a/lib/RT/Extension/REST2/Resource/Group.pm
+++ b/lib/RT/Extension/REST2/Resource/Group.pm
@@ -45,6 +45,12 @@ sub hypermedia_links {
my $self = shift;
my $links = $self->_default_hypermedia_links(@_);
push @$links, $self->_transaction_history_link;
+
+ my $id = $self->record->id;
+ push @$links,
+ { ref => 'members',
+ _url => RT::Extension::REST2->base_uri . "/group/$id/members",
+ };
return $links;
}
diff --git a/t/group-members.t b/t/group-members.t
index af14071..f9cbccf 100644
--- a/t/group-members.t
+++ b/t/group-members.t
@@ -257,4 +257,17 @@ $user->PrincipalObj->GrantRight(Right => 'SeeGroup', Object => $group1);
is($members1->Count, 0, 'All members removed');
}
+# Group hypermedia links
+{
+ my $res = $mech->get("$rest_base_path/group/$group1_id",
+ 'Authorization' => $auth,
+ );
+ is($res->code, 200);
+ my $content = $mech->json_response;
+ my $links = $content->{_hyperlinks};
+ my @members_links = grep { $_->{ref} =~ /members/ } @$links;
+ is(scalar(@members_links), 1);
+ like($members_links[0]->{_url}, qr{$rest_base_path/group/$group1_id/members$});
+}
+
done_testing;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list