[Rt-commit] r18951 - rt/3.999/trunk/lib/RT/IsPrincipal
ruz at bestpractical.com
ruz at bestpractical.com
Fri Mar 27 12:43:19 EDT 2009
Author: ruz
Date: Fri Mar 27 12:43:18 2009
New Revision: 18951
Added:
rt/3.999/trunk/lib/RT/IsPrincipal/
rt/3.999/trunk/lib/RT/IsPrincipal/HasMembers.pm
rt/3.999/trunk/lib/RT/IsPrincipal/HasNoMembers.pm
Log:
* add two new roles for principals: HasMembers and HasNoMembers, both
share the same set of methods, however the latter returns always false
booleans and empty collections. Using this roles we can avoid type checks
like is_group or is_user and implement new types of principals
Added: rt/3.999/trunk/lib/RT/IsPrincipal/HasMembers.pm
==============================================================================
--- (empty file)
+++ rt/3.999/trunk/lib/RT/IsPrincipal/HasMembers.pm Fri Mar 27 12:43:18 2009
@@ -0,0 +1,392 @@
+use strict;
+use warnings;
+
+package RT::IsPrincipal::HasMembers;
+use base 'RT::IsPrincipal';
+
+use Scalar::Util qw(blessed);
+
+=head2 members
+
+Returns either an L<RT::Model::GroupMemberCollection> or L<RT::Model::CachedGroupMemberCollection>
+object depending on 'recursively' argument of this group's members.
+
+=cut
+
+sub members {
+ my $self = shift;
+ my %args = ( recursively => 0, @_ );
+
+ my $class = $args{'recursively'}
+ ? 'RT::Model::CachedGroupMemberCollection'
+ : 'RT::Model::GroupMemberCollection';
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+
+ my $res = $class->new( current_user => $self->current_user );
+ $res->limit_to_members_of_group( $self->id );
+
+ return $res;
+}
+
+=head2 group_members [recursively => 1]
+
+Returns an L<RT::Model::GroupCollection> object of this group's members.
+By default returns groups including all subgroups, but
+could be changed with C<recursively> named argument.
+
+B<Note> that groups are not filtered by type and result
+may contain as well system groups and other.
+
+=cut
+
+sub group_members {
+ my $self = shift;
+ my %args = ( recursively => 1, @_ );
+
+ my $groups = RT::Model::GroupCollection->new( current_user => $self->current_user );
+ my $members_table = $args{'recursively'} ? 'CachedGroupMembers' : 'GroupMembers';
+
+ my $members_alias = $groups->new_alias($members_table);
+ $groups->join(
+ alias1 => $members_alias,
+ column1 => 'member_id',
+ alias2 => $groups->principals_alias,
+ column2 => 'id',
+ );
+ $groups->limit(
+ alias => $members_alias,
+ column => 'group_id',
+ value => $self->id,
+ );
+ $groups->limit(
+ alias => $members_alias,
+ column => 'disabled',
+ value => 0,
+ ) if $args{'recursively'};
+
+ return $groups;
+}
+
+
+=head2 user_members
+
+Returns an L<RT::Model::UserCollection> object of this group's members, by default
+returns users including all members of subgroups, but could be
+changed with C<recursively> named argument.
+
+=cut
+
+sub user_members {
+ my $self = shift;
+ my %args = ( recursively => 1, @_ );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+
+ my $members_table = $args{'recursively'} ? 'CachedGroupMembers' : 'GroupMembers';
+
+ my $users = RT::Model::UserCollection->new( current_user => $self->current_user );
+ my $members_alias = $users->new_alias($members_table);
+ $users->join(
+ alias1 => $members_alias,
+ column1 => 'member_id',
+ alias2 => $users->principals_alias,
+ column2 => 'id',
+ );
+ $users->limit(
+ alias => $members_alias,
+ column => 'group_id',
+ value => $self->id,
+ );
+ $users->limit(
+ alias => $members_alias,
+ column => 'disabled',
+ value => 0,
+ ) if $args{'recursively'};
+
+ return ($users);
+}
+
+
+=head2 member_emails
+
+Returns an array of the email addresses of all of this group's members
+
+=cut
+
+sub member_emails {
+ my $self = shift;
+
+ my %addresses;
+ my $members = $self->user_members;
+ while ( my $member = $members->next ) {
+ $addresses{ $member->email } = 1;
+ }
+ return ( sort keys %addresses );
+}
+
+=head2 member_emails_as_string
+
+Returns a comma delimited string of the email addresses of all users
+who are members of this group.
+
+=cut
+
+sub member_emails_as_string {
+ my $self = shift;
+ return ( join( ', ', $self->member_emails ) );
+}
+
+=head2 has_member
+
+Takes an L<RT::Model::Principal> object or its id and optional 'recursively'
+argument. Returns id of a GroupMember or CachedGroupMember record if that user
+is a member of this group. By default lookup is not recursive.
+
+Returns undef if the user isn't a member of the group or if the current
+user doesn't have permission to find out. Arguably, it should differentiate
+between ACL failure and non membership.
+
+=cut
+
+sub has_member {
+ my $self = shift;
+ my %args = (
+ principal => undef,
+ recursively => 0,
+ @_
+ );
+
+ Carp::confess("boo") unless defined $args{'principal'};
+
+ my $id;
+ if ( blessed $args{'principal'} ) {
+ if ( $args{'principal'}->isa('RT::Model::Principal') ) {
+ $id = $args{'principal'}->id;
+ } elsif ( $args{'principal'}->isa('RT::IsPrincipal') ) {
+ $id = $args{'principal'}->principal_id;
+ } else {
+ Jifty->log->error(
+ "has_member was called with an object that"
+ . " isn't a principal. It's ". ref $args{'principal'}
+ );
+ return undef;
+ }
+ } elsif ( $args{'principal'} !~ /\D/ ) {
+ $id = $args{'principal'};
+ } else {
+ Jifty->log->error(
+ "Group::has_member was called with an argument that"
+ . " isn't a principal or its id. It's "
+ . ( $args{'principal'} || '(undefined)' )
+ );
+ return undef;
+ }
+ return undef unless $id;
+
+ my $class = $args{'recursively'}
+ ? 'RT::Model::CachedGroupMember'
+ : 'RT::Model::GroupMember';
+
+ my $member_obj = new $class;
+ $member_obj->load_by_cols(
+ member_id => $id,
+ group_id => $self->id,
+ );
+
+ if ( my $member_id = $member_obj->id ) {
+ return $member_id;
+ } else {
+ return (undef);
+ }
+}
+
+
+
+
+=head2 add_member PRINCIPAL_ID
+
+add_member adds a principal to this group. It takes a single principal id.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+=cut
+
+sub add_member {
+ my $self = shift;
+ my $new_member = shift;
+
+ # We should only allow membership changes if the user has the right
+ # to modify group membership or the user is the principal in question
+ # and the user has the right to modify his own membership
+ return ( 0, _("Permission Denied") )
+ unless $self->current_user_has_right('AdminGroupMembership');
+
+ if ( blessed $new_member ) {
+ $self->_add_member( principal => $new_member )
+ if $new_member == $self->current_user->id
+ && $self->current_user_has_right('ModifyOwnMembership');
+
+ return ( 0, _("Permission Denied") )
+ }
+
+ unless ( $new_member == $self->current_user->user_object->id
+ && $self->current_user_has_right('ModifyOwnMembership') )
+ {
+
+ #User has no permission to be doing this
+ return ( 0, _("Permission Denied") );
+ }
+
+ $self->_add_member( principal => $new_member );
+}
+
+# A helper subroutine for add_member that bypasses the ACL checks
+# this should _ONLY_ ever be called from Ticket/Queue AddWatcher
+# when we want to deal with groups according to queue rights
+# In the dim future, this will all get factored out and life
+# will get better
+
+# takes a paramhash of { principal_id => undef }
+
+sub _add_member {
+ my $self = shift;
+ my %args = (
+ principal => undef,
+ @_
+ );
+
+ unless ( $self->id ) {
+ Jifty->log->fatal( "Attempting to add a member to a group which wasn't loaded. 'oops'" );
+ return ( 0, _("Group not found") );
+ }
+
+ my ($id, $principal, $object);
+ if ( blessed $args{'principal'} ) {
+ if ( $args{'principal'}->isa('RT::Model::Principal') ) {
+ $principal = $args{'principal'};
+ $object = $principal->object;
+ $id = $principal->id;
+ }
+ elsif ( $args{'principal'}->isa('RT::IsPrincipal') ) {
+ $object = $args{'principal'};
+ $id = $args{'principal'}->principal_id;
+ }
+ else {
+ Jifty->log->fatal("_add_member called with an object that is not principal.");
+ return ( 0, _("System error") );
+ }
+ unless ( $object ) {
+ Jifty->log->fatal("_add_member called with an object this is not loaded.");
+ return ( 0, _("System error") );
+ }
+ }
+ else {
+ if ( $args{'principal'} =~ /\D/ ) {
+ Jifty->log->fatal("_add_member called with a parameter that's not an object or an integer.");
+ return ( 0, _("System error") );
+ }
+
+ $principal = RT::Model::Principal->new( current_user => $self->current_user );
+ $principal->load( $args{'principal'} );
+ unless ( $principal->id ) {
+ Jifty->log->error("Couldn't find that principal");
+ return ( 0, _("Couldn't find that principal") );
+ }
+ $id = $principal->id;
+ $object = $principal->object;
+ }
+
+ if ( $self->has_member( principal => $id) ) {
+ #User is already a member of this group. no need to add it
+ return ( 0, _("Group already has member: %1", $object->name) );
+ }
+ if ( $object->has_member( principal => $self->principal, recursively => 1 ) ) {
+ #This group can't be made to be a member of itself
+ return ( 0, _("Groups can't be members of their members") );
+ }
+
+ my $member_object = RT::Model::GroupMember->new( current_user => $self->current_user );
+ my ($gm_id, $msg) = $member_object->create(
+ member => $object,
+ group => $self->principal,
+ );
+ if ($gm_id) {
+ return ( 1, _( "Member added: %1", $object->name ) );
+ } else {
+ return ( 0, _("Couldn't add member to group") );
+ }
+}
+
+
+=head2 delete_member PRINCIPAL_ID
+
+Takes the principal id of a current user or group.
+If the current user has apropriate rights,
+removes that GroupMember from this group.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+=cut
+
+sub delete_member {
+ my $self = shift;
+ my $member_id = shift;
+
+ # We should only allow membership changes if the user has the right
+ # to modify group membership or the user is the principal in question
+ # and the user has the right to modify his own membership
+ unless ( ( $member_id == $self->current_user->id && $self->current_user_has_right('ModifyOwnMembership') )
+ || $self->current_user_has_right('AdminGroupMembership') )
+ {
+
+ #User has no permission to be doing this
+ return ( 0, _("Permission Denied") );
+ }
+ $self->_delete_member($member_id);
+}
+
+# A helper subroutine for delete_member that bypasses the ACL checks
+# this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
+# when we want to deal with groups according to queue rights
+# In the dim future, this will all get factored out and life
+# will get better
+
+sub _delete_member {
+ my $self = shift;
+ my $member_id = shift;
+
+ my $member_obj = RT::Model::GroupMember->new( current_user => $self->current_user );
+
+ $member_obj->load_by_cols(
+ member_id => $member_id,
+ group_id => $self->id
+ );
+
+ #If we couldn't load it, return undef.
+ unless ( $member_obj->id() ) {
+ Jifty->log->debug("Group has no member with that id");
+ return ( 0, _("Group has no such member") );
+ }
+
+ #Now that we've checked ACLs and sanity, delete the groupmember
+ my $val = $member_obj->delete();
+
+ if ($val) {
+ return ( $val, _("Member deleted") );
+ } else {
+ Jifty->log->debug( "Failed to delete group " . $self->id . " member " . $member_id );
+ return ( 0, _("Member not deleted") );
+ }
+}
+
+
+
+
+
+
+
+
+1;
Added: rt/3.999/trunk/lib/RT/IsPrincipal/HasNoMembers.pm
==============================================================================
--- (empty file)
+++ rt/3.999/trunk/lib/RT/IsPrincipal/HasNoMembers.pm Fri Mar 27 12:43:18 2009
@@ -0,0 +1,139 @@
+use strict;
+use warnings;
+
+package RT::IsPrincipal::HasNoMembers;
+use base 'RT::IsPrincipal';
+
+=head2 members
+
+Returns an empty either an L<RT::Model::GroupMemberCollection>
+or L<RT::Model::CachedGroupMemberCollection> object depending on
+'recursively' argument.
+
+=cut
+
+sub members {
+ my $self = shift;
+ my %args = ( recursively => 0, @_ );
+
+ my $class = $args{'recursively'}
+ ? 'RT::Model::CachedGroupMemberCollection'
+ : 'RT::Model::GroupMemberCollection';
+
+ my $res = $class->new( current_user => $self->current_user );
+ $res->limit( column => 'id', value => 0 );
+ return $res;
+}
+
+=head2 group_members [recursively => 1]
+
+Returns an empty L<RT::Model::GroupCollection> object.
+
+=cut
+
+sub group_members {
+ my $self = shift;
+ my $groups = RT::Model::GroupCollection->new( current_user => $self->current_user );
+ $groups->limit( column => 'id', value => 0 );
+ return $groups;
+}
+
+
+=head2 user_members
+
+Returns an empty L<RT::Model::UserCollection> object.
+
+=cut
+
+sub user_members {
+ my $self = shift;
+ my %args = ( recursively => 1, @_ );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+
+ my $members_table = $args{'recursively'} ? 'CachedGroupMembers' : 'GroupMembers';
+
+ my $users = RT::Model::UserCollection->new( current_user => $self->current_user );
+ my $members_alias = $users->new_alias($members_table);
+ $users->join(
+ alias1 => $members_alias,
+ column1 => 'member_id',
+ alias2 => $users->principals_alias,
+ column2 => 'id',
+ );
+ $users->limit(
+ alias => $members_alias,
+ column => 'group_id',
+ value => $self->id,
+ );
+ $users->limit(
+ alias => $members_alias,
+ column => 'disabled',
+ value => 0,
+ ) if $args{'recursively'};
+
+ return ($users);
+}
+
+
+=head2 member_emails
+
+Returns an empty list.
+
+=cut
+
+sub member_emails { return () }
+
+=head2 member_emails_as_string
+
+Returns an empty string.
+
+=cut
+
+sub member_emails_as_string { return '' }
+
+=head2 has_member
+
+Always return false value.
+Takes an L<RT::Model::Principal> object or its id and optional 'recursively'
+argument. Returns id of a GroupMember or CachedGroupMember record if that user
+is a member of this group. By default lookup is not recursive.
+
+Returns undef if the user isn't a member of the group or if the current
+user doesn't have permission to find out. Arguably, it should differentiate
+between ACL failure and non membership.
+
+=cut
+
+sub has_member { return 0 }
+
+=head2 add_member
+
+Returns false value and error message.
+
+=cut
+
+sub add_member {
+ return ( 0, _("This principal can not have members") );
+}
+
+sub _add_member {
+ return ( 0, _("This principal can not have members") );
+}
+
+=head2 delete_member PRINCIPAL_ID
+
+Takes the principal id of a current user or group.
+If the current user has apropriate rights,
+removes that GroupMember from this group.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+=cut
+
+sub delete_member { return ( 0, _("This principal can not have members") ) }
+
+sub _delete_member { return ( 0, _("This principal can not have members") ) }
+
+1;
More information about the Rt-commit
mailing list