[Rt-commit] r2446 - in rt/branches/PLATANO-EXPERIMENTAL-CSS: . lib/RT lib/t/regression

jesse at bestpractical.com jesse at bestpractical.com
Mon Mar 14 03:02:30 EST 2005


Author: jesse
Date: Mon Mar 14 03:02:28 2005
New Revision: 2446

Added:
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/t/regression/18stale_delegations_cleanup.t
Modified:
   rt/branches/PLATANO-EXPERIMENTAL-CSS/   (props changed)
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACE_Overlay.pm
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACL_Overlay.pm
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/GroupMember_Overlay.pm
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Group_Overlay.pm
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Principal_Overlay.pm
   rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/User_Overlay.pm
Log:
 r8606 at hualien:  jesse | 2005-03-14 02:42:52 -0500
  r8494 at hualien:  jesse | 2005-03-14 02:28:08 -0500
   r6277 at hualien:  jesse | 2005-03-05 17:06:08 -0500
   RT-Ticket: 6184
   RT-Status: resolved
   RT-Update: correspond
   
   Much better coverage of delegation revocation when users' group memberships are changed - Mike Whitson (BPS)
   
  
 


Modified: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACE_Overlay.pm
==============================================================================
--- rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACE_Overlay.pm	(original)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACE_Overlay.pm	Mon Mar 14 03:02:28 2005
@@ -710,18 +710,23 @@
 
     my ( $val, $msg ) = $self->SUPER::Delete(@_);
 
-    #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
-    # TODO what about the groups key cache?
-    RT::Principal->InvalidateACLCache();
+    # If we're revoking delegation rights (see above), we may need to
+    # revoke all rights delegated by the recipient.
+    if ($val and ($self->RightName() eq 'DelegateRights' or
+		  $self->RightName() eq 'SuperUser')) {
+	$val = $self->PrincipalObj->_CleanupInvalidDelegations( InsideTransaction => 1 );
+    }
 
     if ($val) {
+	#Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
+	# TODO what about the groups key cache?
+	RT::Principal->InvalidateACLCache();
         $RT::Handle->Commit() unless $InsideTransaction;
         return ( $val, $self->loc('Right revoked') );
     }
-    else {
-        $RT::Handle->Rollback() unless $InsideTransaction;
-        return ( 0, $self->loc('Right could not be revoked') );
-    }
+
+    $RT::Handle->Rollback() unless $InsideTransaction;
+    return ( 0, $self->loc('Right could not be revoked') );
 }
 
 # }}}

Modified: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACL_Overlay.pm
==============================================================================
--- rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACL_Overlay.pm	(original)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/ACL_Overlay.pm	Mon Mar 14 03:02:28 2005
@@ -115,6 +115,42 @@
 
 # }}}
 
+# {{{ LimitNotObject
+
+=head2 LimitNotObject $object
+
+Limit the ACL to rights NOT on the object $object.  $object needs to be
+an RT::Record class.
+
+=cut
+
+sub LimitNotObject {
+    my $self = shift;
+    my $obj  = shift;
+    unless ( defined($obj)
+        && ref($obj)
+        && UNIVERSAL::can( $obj, 'id' )
+        && $obj->id )
+    {
+        return undef;
+    }
+    $self->Limit( FIELD => 'ObjectType',
+		  OPERATOR => '!=',
+		  VALUE => ref($obj),
+		  ENTRYAGGREGATOR => 'OR',
+		  SUBCLAUSE => $obj->id
+		);
+    $self->Limit( FIELD => 'ObjectId',
+		  OPERATOR => '!=',
+		  VALUE => $obj->id,
+		  ENTRYAGGREGATOR => 'OR',
+		  QUOTEVALUE => 0,
+		  SUBCLAUSE => $obj->id
+		);
+}
+
+# }}}
+
 # {{{ LimitToPrincipal 
 
 =head2 LimitToPrincipal { Type => undef, Id => undef, IncludeGroupMembership => undef }

Modified: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/GroupMember_Overlay.pm
==============================================================================
--- rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/GroupMember_Overlay.pm	(original)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/GroupMember_Overlay.pm	Mon Mar 14 03:02:28 2005
@@ -306,9 +306,6 @@
         VALUE    => $self->GroupObj->Id
     );
 
-    #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
-    # TODO what about the groups key cache?
-    RT::Principal->InvalidateACLCache();
 
 
 
@@ -328,6 +325,21 @@
         $RT::Handle->Rollback();
         return (undef);
     }
+
+    # Since this deletion may have changed the former member's
+    # delegation rights, we need to ensure that no invalid delegations
+    # remain.
+    $err = $self->MemberObj->_CleanupInvalidDelegations(InsideTransaction => 1);
+    unless ($err) {
+	$RT::Logger->warning("Unable to revoke delegated rights for principal ".$self->Id);
+	$RT::Handle->Rollback();
+	return (undef);
+    }
+
+    #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
+    # TODO what about the groups key cache?
+    RT::Principal->InvalidateACLCache();
+
     $RT::Handle->Commit();
     return ($err);
 

Modified: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Group_Overlay.pm
==============================================================================
--- rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Group_Overlay.pm	(original)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Group_Overlay.pm	Mon Mar 14 03:02:28 2005
@@ -1174,6 +1174,58 @@
 
 # }}}
 
+# {{{ sub _CleanupInvalidDelegations
+
+=head2 sub _CleanupInvalidDelegations { InsideTransaction => undef }
+
+Revokes all ACE entries delegated by members of this group which are
+inconsistent with their current delegation rights.  Does not perform
+permission checks.  Should only ever be called from inside the RT
+library.
+
+If called from inside a transaction, specify a true value for the
+InsideTransaction parameter.
+
+Returns a true value if the deletion succeeded; returns a false value
+and logs an internal error if the deletion fails (should not happen).
+
+=cut
+
+# XXX Currently there is a _CleanupInvalidDelegations method in both
+# RT::User and RT::Group.  If the recursive cleanup call for groups is
+# ever unrolled and merged, this code will probably want to be
+# factored out into RT::Principal.
+
+sub _CleanupInvalidDelegations {
+    my $self = shift;
+    my %args = ( InsideTransaction => undef,
+		  @_ );
+
+    unless ( $self->Id ) {
+	$RT::Logger->warning("Group not loaded.");
+	return (undef);
+    }
+
+    my $in_trans = $args{InsideTransaction};
+
+    # TODO: Can this be unrolled such that the number of DB queries is constant rather than linear in exploded group size?
+    my $members = $self->DeepMembersObj();
+    $members->LimitToUsers();
+    $RT::Handle->BeginTransaction() unless $in_trans;
+    while ( my $member = $members->Next()) {
+	my $ret = $member->MemberObj->_CleanupInvalidDelegations(InsideTransaction => 1,
+								 Object => $args{Object});
+	unless ($ret) {
+	    $RT::Handle->Rollback() unless $in_trans;
+	    return (undef);
+	}
+    }
+    $RT::Handle->Commit() unless $in_trans;
+    return(1);
+}
+
+# }}}
+
 # {{{ ACL Related routines
 
 # {{{ sub _Set

Modified: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Principal_Overlay.pm
==============================================================================
--- rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Principal_Overlay.pm	(original)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/Principal_Overlay.pm	Mon Mar 14 03:02:28 2005
@@ -228,7 +228,40 @@
 
 # }}}
 
+# {{{ sub _CleanupInvalidDelegations
 
+=head2 sub _CleanupInvalidDelegations { InsideTransaction => undef }
+
+Revokes all ACE entries delegated by this principal which are
+inconsistent with this principal's current delegation rights.  Does
+not perform permission checks, but takes no action and returns success
+if this principal still retains DelegateRights.  Should only ever be
+called from inside the RT library.
+
+If this principal is a group, recursively calls this method on each
+cached user member of itself.
+
+If called from inside a transaction, specify a true value for the
+InsideTransaction parameter.
+
+Returns a true value if the deletion succeeded; returns a false value
+and logs an internal error if the deletion fails (should not happen).
+
+=cut
+
+# This is currently just a stub for the methods of the same name in
+# RT::User and RT::Group.
+
+sub _CleanupInvalidDelegations {
+    my $self = shift;
+    unless ( $self->Id ) {
+	$RT::Logger->warning("Principal not loaded.");
+	return (undef);
+    }
+    return ($self->Object->_CleanupInvalidDelegations(@_));
+}
+
+# }}}
 
 # {{{ sub HasRight
 

Modified: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/User_Overlay.pm
==============================================================================
--- rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/User_Overlay.pm	(original)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/RT/User_Overlay.pm	Mon Mar 14 03:02:28 2005
@@ -1579,6 +1579,77 @@
 
 # }}}
 
+# {{{ sub _CleanupInvalidDelegations
+
+=head2 sub _CleanupInvalidDelegations { InsideTransaction => undef }
+
+Revokes all ACE entries delegated by this user which are inconsistent
+with their current delegation rights.  Does not perform permission
+checks.  Should only ever be called from inside the RT library.
+
+If called from inside a transaction, specify a true value for the
+InsideTransaction parameter.
+
+Returns a true value if the deletion succeeded; returns a false value
+and logs an internal error if the deletion fails (should not happen).
+
+=cut
+
+# XXX Currently there is a _CleanupInvalidDelegations method in both
+# RT::User and RT::Group.  If the recursive cleanup call for groups is
+# ever unrolled and merged, this code will probably want to be
+# factored out into RT::Principal.
+
+sub _CleanupInvalidDelegations {
+    my $self = shift;
+    my %args = ( InsideTransaction => undef,
+		  @_ );
+
+    unless ( $self->Id ) {
+	$RT::Logger->warning("User not loaded.");
+	return (undef);
+    }
+
+    my $in_trans = $args{InsideTransaction};
+
+    return(1) if ($self->HasRight(Right => 'DelegateRights',
+				  Object => $RT::System));
+
+    # Look up all delegation rights currently posessed by this user.
+    my $deleg_acl = RT::ACL->new($RT::SystemUser);
+    $deleg_acl->LimitToPrincipal(Type => 'User',
+				 Id => $self->PrincipalId,
+				 IncludeGroupMembership => 1);
+    $deleg_acl->Limit( FIELD => 'RightName',
+		       OPERATOR => '=',
+		       VALUE => 'DelegateRights' );
+    my @allowed_deleg_objects = map {$_->Object()}
+	@{$deleg_acl->ItemsArrayRef()};
+
+    # Look up all rights delegated by this principal which are
+    # inconsistent with the allowed delegation objects.
+    my $acl_to_del = RT::ACL->new($RT::SystemUser);
+    $acl_to_del->DelegatedBy(Id => $self->Id);
+    foreach (@allowed_deleg_objects) {
+	$acl_to_del->LimitNotObject($_);
+    }
+
+    # Delete all disallowed delegations
+    while ( my $ace = $acl_to_del->Next() ) {
+	my $ret = $ace->_Delete(InsideTransaction => 1);
+	unless ($ret) {
+	    $RT::Handle->Rollback() unless $in_trans;
+	    $RT::Logger->warning("Couldn't delete delegated ACL entry ".$ace->Id);
+	    return (undef);
+	}
+    }
+
+    $RT::Handle->Commit() unless $in_trans;
+    return (1);
+}
+
+# }}}
+
 # {{{ sub _Set
 
 sub _Set {

Added: rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/t/regression/18stale_delegations_cleanup.t
==============================================================================
--- (empty file)
+++ rt/branches/PLATANO-EXPERIMENTAL-CSS/lib/t/regression/18stale_delegations_cleanup.t	Mon Mar 14 03:02:28 2005
@@ -0,0 +1,458 @@
+#!/usr/bin/perl -w
+
+# Regression test suite for http://rt3.fsck.com/Ticket/Display.html?id=6184
+# and related corner cases related to cleanup of delegated ACEs when
+# the delegator loses the right to delegate.  This causes complexities
+# due to the fact that multiple ACEs can grant different delegation
+# rights to a principal, and because DelegateRights and SuperUser can
+# themselves be delegated.
+
+# The case where the "parent" delegated ACE is removed is handled in
+# the embedded regression tests in lib/RT/ACE_Overlay.pm .
+
+use Test::More qw(no_plan);
+
+use RT;
+
+ok( RT::LoadConfig, "Locating config files" );
+ok( RT::Init,       "Basic initialization and DB connectivity" );
+
+my ($u1, $u2, $g1, $g2, $g3, $pg1, $pg2, $ace, @groups, @users, @principals);
+ at groups = (\$g1, \$g2, \$g3, \$pg1, \$pg2);
+ at users = (\$u1, \$u2);
+ at principals = (@groups, @users);
+
+my($ret, $msg);
+
+$u1 = RT::User->new($RT::SystemUser);
+( $ret, $msg ) = $u1->LoadOrCreateByEmail('delegtest1 at example.com');
+ok( $ret, "Load / Create test user 1: $msg" );
+$u1->SetPrivileged(1);
+$u2 = RT::User->new($RT::SystemUser);
+( $ret, $msg ) = $u2->LoadOrCreateByEmail('delegtest2 at example.com');
+ok( $ret, "Load / Create test user 2: $msg" );
+$u2->SetPrivileged(1);
+$g1 = RT::Group->new($RT::SystemUser);
+( $ret, $msg) = $g1->LoadUserDefinedGroup('dg1');
+unless ($ret) {
+    ( $ret, $msg ) = $g1->CreateUserDefinedGroup( Name => 'dg1' );
+}
+ok( $ret, "Load / Create test group 1: $msg" );
+$g2 = RT::Group->new($RT::SystemUser);
+( $ret, $msg) = $g2->LoadUserDefinedGroup('dg2');
+unless ($ret) {
+    ( $ret, $msg ) = $g2->CreateUserDefinedGroup( Name => 'dg2' );
+}
+ok( $ret, "Load / Create test group 2: $msg" );
+$g3 = RT::Group->new($RT::SystemUser);
+( $ret, $msg) = $g3->LoadUserDefinedGroup('dg3');
+unless ($ret) {
+    ( $ret, $msg ) = $g3->CreateUserDefinedGroup( Name => 'dg3' );
+}
+ok( $ret, "Load / Create test group 3: $msg" );
+$pg1 = RT::Group->new($RT::SystemUser);
+( $ret, $msg ) = $pg1->LoadPersonalGroup( Name => 'dpg1',
+					  User => $u1->PrincipalId );
+unless ($ret) {
+    ( $ret, $msg ) = $pg1->CreatePersonalGroup( Name => 'dpg1',
+						PrincipalId => $u1->PrincipalId );
+}
+ok( $ret, "Load / Create test personal group 1: $msg" );
+$pg2 = RT::Group->new($RT::SystemUser);
+( $ret, $msg ) = $pg2->LoadPersonalGroup( Name => 'dpg2',
+					  User => $u2->PrincipalId );
+unless ($ret) {
+    ( $ret, $msg ) = $pg2->CreatePersonalGroup( Name => 'dpg2',
+						PrincipalId => $u2->PrincipalId );
+}
+ok( $ret, "Load / Create test personal group 2: $msg" );
+
+
+
+# Basic case: u has global DelegateRights through g1 and ShowConfigTab
+# through g2; then u is removed from g1.
+
+clear_acls_and_groups();
+
+( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
+ok( $ret, "Grant DelegateRights to g1: $msg" );
+( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
+ok( $ret, "Grant ShowConfigTab to g2: $msg" );
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g1: $msg" );
+ok(
+    $u1->PrincipalObj->HasRight(
+        Right  => 'DelegateRights',
+        Object => $RT::System
+    ),
+    "test user 1 has DelegateRights after joining g1"
+);
+( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g2: $msg" );
+ok(
+    $u1->PrincipalObj->HasRight(
+        Right  => 'ShowConfigTab',
+        Object => $RT::System
+    ),
+    "test user 1 has ShowConfigTab after joining g2"
+);
+
+$ace = RT::ACE->new($u1);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'ShowConfigTab',
+    Object        => $RT::System,
+    PrincipalType => 'Group',
+    PrincipalId   => $g2->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
+ok(
+    $pg1->PrincipalObj->HasRight(
+        Right  => 'ShowConfigTab',
+        Object => $RT::System
+    ),
+    "Test personal group 1 has ShowConfigTab right after delegation"
+);
+
+( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
+ok( $ret, "Delete test user 1 from g1: $msg" );
+ok(
+    not(
+        $pg1->PrincipalObj->HasRight(
+            Right  => 'ShowConfigTab',
+            Object => $RT::System
+        )
+    ),
+    "Test personal group 1 lacks ShowConfigTab right after user removed from g1"
+);
+
+# Basic case: u has global DelegateRights through g1 and ShowConfigTab
+# through g2; then DelegateRights revoked from g1.
+
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g1: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
+( $ret, $msg ) = $g1->PrincipalObj->RevokeRight( Right => 'DelegateRights' );
+ok( $ret, "Revoke DelegateRights from g1: $msg" );
+ok(
+    not(
+        $pg1->PrincipalObj->HasRight(
+            Right  => 'ShowConfigTab',
+            Object => $RT::System
+        )
+    ),
+    "Test personal group 1 lacks ShowConfigTab right after DelegateRights revoked from g1"
+);
+
+
+
+# Corner case - restricted delegation: u has DelegateRights on pg1
+# through g1 and AdminGroup on pg1 through g2; then DelegateRights
+# revoked from g1.
+
+clear_acls_and_groups();
+
+( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights',
+					        Object => $pg1);
+ok( $ret, "Grant DelegateRights on pg1 to g1: $msg" );
+( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'AdminGroup',
+					        Object => $pg1);
+ok( $ret, "Grant AdminGroup on pg1 to g2: $msg" );
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g1: $msg" );
+( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g2: $msg" );
+ok( $u1->PrincipalObj->HasRight(
+        Right  => 'DelegateRights',
+        Object => $pg1 ),
+    "test user 1 has DelegateRights on pg1 after joining g1" );
+ok( not( $u1->PrincipalObj->HasRight(
+            Right  => 'DelegateRights',
+            Object => $RT::System )),
+    "Test personal group 1 lacks global DelegateRights after joining g1" );
+$ace = RT::ACE->new($u1);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'AdminGroup',
+    Object        => $pg1,
+    PrincipalType => 'Group',
+    PrincipalId   => $g2->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
+ok( $pg1->PrincipalObj->HasRight(
+        Right  => 'AdminGroup',
+        Object => $pg1 ),
+    "Test personal group 1 has AdminGroup right on pg1 after delegation" );
+( $ret, $msg ) = $g1->PrincipalObj->RevokeRight ( Right => 'DelegateRights',
+						  Object => $pg1 );
+ok( $ret, "Revoke DelegateRights on pg1 from g1: $msg" );
+ok( not( $pg1->PrincipalObj->HasRight(
+            Right  => 'AdminGroup',
+            Object => $pg1 )),
+    "Test personal group 1 lacks AdminGroup right on pg1 after DelegateRights revoked from g1" );
+( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights',
+					        Object => $pg1);
+
+# Corner case - restricted delegation: u has DelegateRights on pg1
+# through g1 and AdminGroup on pg1 through g2; then u removed from g1.
+
+ok( $ret, "Grant DelegateRights on pg1 to g1: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
+ok( $pg1->PrincipalObj->HasRight(
+        Right  => 'AdminGroup',
+        Object => $pg1 ),
+    "Test personal group 1 has AdminGroup right on pg1 after delegation" );
+( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
+ok( $ret, "Delete test user 1 from g1: $msg" );
+ok( not( $pg1->PrincipalObj->HasRight(
+            Right  => 'AdminGroup',
+            Object => $pg1 )),
+    "Test personal group 1 lacks AdminGroup right on pg1 after user removed from g1" );
+
+clear_acls_and_groups();
+
+
+
+# Corner case - multiple delegation rights: u has global
+# DelegateRights directly and DelegateRights on pg1 through g1, and
+# AdminGroup on pg1 through g2; then u removed from g1 (delegation
+# should remain); then DelegateRights revoked from u (delegation
+# should not remain).
+
+( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights',
+					        Object => $pg1);
+ok( $ret, "Grant DelegateRights on pg1 to g1: $msg" );
+( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'AdminGroup',
+					        Object => $pg1);
+ok( $ret, "Grant AdminGroup on pg1 to g2: $msg" );
+( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights',
+					       Object => $RT::System);
+ok( $ret, "Grant DelegateRights to user: $msg" );
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g1: $msg" );
+( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g2: $msg" );
+$ace = RT::ACE->new($u1);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'AdminGroup',
+    Object        => $pg1,
+    PrincipalType => 'Group',
+    PrincipalId   => $g2->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
+( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
+ok( $ret, "Delete test user 1 from g1: $msg" );
+ok( $pg1->PrincipalObj->HasRight(Right  => 'AdminGroup',
+				Object => $pg1),
+    "Test personal group 1 retains AdminGroup right on pg1 after user removed from g1" );
+( $ret, $msg ) = $u1->PrincipalObj->RevokeRight( Right => 'DelegateRights',
+						Object => $RT::System );
+ok( not ($pg1->PrincipalObj->HasRight(Right  => 'AdminGroup',
+				     Object => $pg1)),
+    "Test personal group 1 lacks AdminGroup right on pg1 after DelegateRights revoked");
+
+# Corner case - multiple delegation rights and selectivity: u has
+# DelegateRights globally and on g2 directly and DelegateRights on pg1
+# through g1, and AdminGroup on pg1 through g2; then global
+# DelegateRights revoked from u (delegation should remain),
+# DelegateRights on g2 revoked from u (delegation should remain), and
+# u removed from g1 (delegation should not remain).
+
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g1: $msg" );
+( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights',
+					       Object => $RT::System);
+ok( $ret, "Grant DelegateRights to user: $msg" );
+( $ret, $msg ) = $u1->PrincipalObj->GrantRight( Right => 'DelegateRights',
+					       Object => $g2);
+ok( $ret, "Grant DelegateRights on g2 to user: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate AdminGroup on pg1 to pg1: $msg" );
+( $ret, $msg ) = $u1->PrincipalObj->RevokeRight( Right => 'DelegateRights',
+						Object => $RT::System );
+ok( $pg1->PrincipalObj->HasRight(Right  => 'AdminGroup',
+				Object => $pg1),
+    "Test personal group 1 retains AdminGroup right on pg1 after global DelegateRights revoked" );
+( $ret, $msg ) = $u1->PrincipalObj->RevokeRight( Right => 'DelegateRights',
+						Object => $g2 );
+ok( $pg1->PrincipalObj->HasRight(Right  => 'AdminGroup',
+				Object => $pg1),
+    "Test personal group 1 retains AdminGroup right on pg1 after DelegateRights on g2 revoked" );
+( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
+ok( $ret, "Delete test user 1 from g1: $msg" );
+ok( not ($pg1->PrincipalObj->HasRight(Right  => 'AdminGroup',
+				     Object => $pg1)),
+    "Test personal group 1 lacks AdminGroup right on pg1 after user removed from g1");
+
+
+
+# Corner case - indirect delegation rights: u has DelegateRights
+# through g1 via g3, and ShowConfigTab via g2; then g3 removed from
+# g1.
+
+clear_acls_and_groups();
+
+( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
+ok( $ret, "Grant DelegateRights to g1: $msg" );
+( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
+ok( $ret, "Grant ShowConfigTab to g2: $msg" );
+( $ret, $msg ) = $g1->AddMember( $g3->PrincipalId );
+ok( $ret, "Add g3 to g1: $msg" );
+( $ret, $msg ) = $g3->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g3: $msg" );
+( $ret, $msg ) = $g2->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g2: $msg" );
+
+$ace = RT::ACE->new($u1);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'ShowConfigTab',
+    Object        => $RT::System,
+    PrincipalType => 'Group',
+    PrincipalId   => $g2->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
+
+( $ret, $msg ) = $g1->DeleteMember( $g3->PrincipalId );
+ok( $ret, "Delete g3 from g1: $msg" );
+ok( not ($pg1->PrincipalObj->HasRight(Right  => 'ShowConfigTab',
+				     Object => $RT::System)),
+	 "Test personal group 1 lacks ShowConfigTab right after g3 removed from g1");
+
+# Corner case - indirect delegation rights: u has DelegateRights
+# through g1 via g3, and ShowConfigTab via g2; then DelegateRights
+# revoked from g1.
+
+( $ret, $msg ) = $g1->AddMember( $g3->PrincipalId );
+ok( $ret, "Add g3 to g1: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate ShowConfigTab to pg1: $msg" );
+( $ret, $msg ) = $g1->PrincipalObj->RevokeRight ( Right => 'DelegateRights' );
+ok( $ret, "Revoke DelegateRights from g1: $msg" );
+
+ok( not ($pg1->PrincipalObj->HasRight(Right  => 'ShowConfigTab',
+				     Object => $RT::System)),
+	 "Test personal group 1 lacks ShowConfigTab right after DelegateRights revoked from g1");
+
+
+
+# Corner case - delegation of DelegateRights: u1 has DelegateRights
+# via g1 and delegates DelegateRights to pg1; u2 has DelegateRights
+# via pg1 and ShowConfigTab via g2; then u1 removed from g1.
+
+clear_acls_and_groups();
+
+( $ret, $msg ) = $g1->PrincipalObj->GrantRight( Right => 'DelegateRights' );
+ok( $ret, "Grant DelegateRights to g1: $msg" );
+( $ret, $msg ) = $g2->PrincipalObj->GrantRight( Right => 'ShowConfigTab' );
+ok( $ret, "Grant ShowConfigTab to g2: $msg" );
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add test user 1 to g1: $msg" );
+$ace = RT::ACE->new($u1);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'DelegateRights',
+    Object        => $RT::System,
+    PrincipalType => 'Group',
+    PrincipalId   => $g1->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate DelegateRights to pg1: $msg" );
+
+( $ret, $msg ) = $pg1->AddMember( $u2->PrincipalId );
+ok( $ret, "Add test user 2 to pg1: $msg" );
+( $ret, $msg ) = $g2->AddMember( $u2->PrincipalId );
+ok( $ret, "Add test user 2 to g2: $msg" );
+$ace = RT::ACE->new($u2);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'ShowConfigTab',
+    Object        => $RT::System,
+    PrincipalType => 'Group',
+    PrincipalId   => $g2->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg2->PrincipalId );
+ok( $ret, "Delegate ShowConfigTab to pg2: $msg" );
+
+ok( $pg2->PrincipalObj->HasRight(Right  => 'ShowConfigTab',
+				 Object => $RT::System),
+    "Test personal group 2 has ShowConfigTab right after delegation");
+( $ret, $msg ) = $g1->DeleteMember( $u1->PrincipalId );
+ok( $ret, "Delete u1 from g1: $msg" );
+ok( not ($pg2->PrincipalObj->HasRight(Right  => 'ShowConfigTab',
+				      Object => $RT::System)),
+	 "Test personal group 2 lacks ShowConfigTab right after u1 removed from g1");
+
+# Corner case - delegation of DelegateRights: u1 has DelegateRights
+# via g1 and delegates DelegateRights to pg1; u2 has DelegateRights
+# via pg1 and ShowConfigTab via g2; then DelegateRights revoked from
+# g1.
+
+( $ret, $msg ) = $g1->AddMember( $u1->PrincipalId );
+ok( $ret, "Add u1 to g1: $msg" );
+$ace = RT::ACE->new($u1);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'DelegateRights',
+    Object        => $RT::System,
+    PrincipalType => 'Group',
+    PrincipalId   => $g1->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg1->PrincipalId );
+ok( $ret, "Delegate DelegateRights to pg1: $msg" );
+$ace = RT::ACE->new($u2);
+( $ret, $msg ) = $ace->LoadByValues(
+    RightName     => 'ShowConfigTab',
+    Object        => $RT::System,
+    PrincipalType => 'Group',
+    PrincipalId   => $g2->PrincipalId
+);
+ok( $ret, "Look up ACE to be delegated: $msg" );
+( $ret, $msg ) = $ace->Delegate( PrincipalId => $pg2->PrincipalId );
+ok( $ret, "Delegate ShowConfigTab to pg2: $msg" );
+
+( $ret, $msg ) = $g1->PrincipalObj->RevokeRight ( Right => 'DelegateRights' );
+ok( $ret, "Revoke DelegateRights from g1: $msg" );
+ok( not ($pg2->PrincipalObj->HasRight(Right  => 'ShowConfigTab',
+				      Object => $RT::System)),
+	 "Test personal group 2 lacks ShowConfigTab right after DelegateRights revoked from g1");
+
+
+
+
+#######
+
+sub clear_acls_and_groups {
+    # Revoke all rights granted to our cast
+    my $acl = RT::ACL->new($RT::SystemUser);
+    foreach (@principals) {
+	$acl->LimitToPrincipal(Type => $$_->PrincipalObj->PrincipalType,
+			       Id => $$_->PrincipalObj->Id);
+    }
+    while (my $ace = $acl->Next()) {
+	$ace->Delete();
+    }
+
+    # Remove all group memberships
+    my $members = RT::GroupMembers->new($RT::SystemUser);
+    foreach (@groups) {
+	$members->LimitToMembersOfGroup( $$_->PrincipalId );
+    }
+    while (my $member = $members->Next()) {
+	$member->Delete();
+    }
+
+    $acl->RedoSearch();
+    ok( $acl->Count() == 0,
+       "All principals have no rights after clearing ACLs" );
+    $members->RedoSearch();
+    ok( $members->Count() == 0,
+       "All groups have no members after clearing groups" );
+}


More information about the Rt-commit mailing list