rt 4.2.15
[freeside.git] / rt / lib / RT / ACE.pm
index d4681cf..ad37bba 100755 (executable)
-#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/ACE.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
-
-=head1 NAME
-
-  RT::ACE - RT\'s ACE object
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2018 Best Practical Solutions, LLC
+#                                          <sales@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
 
 =head1 SYNOPSIS
 
   use RT::ACE;
 
 =head1 SYNOPSIS
 
   use RT::ACE;
-  my $ace = new RT::ACE($CurrentUser);
+  my $ace = RT::ACE->new($CurrentUser);
 
 
 =head1 DESCRIPTION
 
 
 
 
 =head1 DESCRIPTION
 
 
-=head1 METHODS
-
-=begin testing
 
 
-ok(require RT::TestHarness);
-ok(require RT::ACE);
+=head1 METHODS
 
 
-=end testing
 
 =cut
 
 
 =cut
 
-package RT::ACE;
-use RT::Record;
-@ISA= qw(RT::Record);
-
-use vars qw (%SCOPES
-            %QUEUERIGHTS
-            %SYSTEMRIGHTS
-            %LOWERCASERIGHTNAMES
-           ); 
-
-%SCOPES = (
-          System => 'System-level right',
-          Queue => 'Queue-level right'
-         );
 
 
-# {{{ Descriptions of rights
+package RT::ACE;
+use base 'RT::Record';
 
 
-# Queue rights are the sort of queue rights that can only be granted
-# to real people or groups
-%QUEUERIGHTS = ( 
-               SeeQueue => 'Can this principal see this queue',
-               AdminQueue => 'Create, delete and modify queues', 
-               ShowACL => 'Display Access Control List',
-               ModifyACL => 'Modify Access Control List',
-               ModifyQueueWatchers => 'Modify the queue watchers',
-                AdminKeywordSelects => 'Create, delete and modify keyword selections',
-
-               
-               ModifyTemplate => 'Modify email templates for this queue',
-               ShowTemplate => 'Display email templates for this queue',
-               ModifyScrips => 'Modify Scrips for this queue',
-               ShowScrips => 'Display Scrips for this queue',
-
-               ShowTicket => 'Show ticket summaries',
-               ShowTicketComments => 'Show ticket private commentary',
-
-               Watch => 'Sign up as a ticket Requestor or ticket or queue Cc',
-               WatchAsAdminCc => 'Sign up as a ticket or queue AdminCc',
-               CreateTicket => 'Create tickets in this queue',
-               ReplyToTicket => 'Reply to tickets',
-               CommentOnTicket => 'Comment on tickets',
-               OwnTicket => 'Own tickets',
-               ModifyTicket => 'Modify tickets',
-               DeleteTicket => 'Delete tickets'
-
-              );       
-
-
-# System rights are rights granted to the whole system
-%SYSTEMRIGHTS = (
-                SuperUser => 'Do anything and everything',
-               AdminKeywords => 'Creatte, delete and modify keywords',  
-               AdminGroups => 'Create, delete and modify groups',
-               AdminUsers => 'Create, Delete and Modify users',
-               ModifySelf => 'Modify one\'s own RT account',
-
-               );
+sub Table {'ACL'}
 
 
-# }}}
 
 
-# {{{ Descriptions of principals
+use strict;
+use warnings;
 
 
-%TICKET_METAPRINCIPALS = ( Owner => 'The owner of a ticket',
-                                  Requestor => 'The requestor of a ticket',
-                                  Cc => 'The CC of a ticket',
-                                      AdminCc => 'The administrative CC of a ticket',
-                        );
+require RT::Principals;
+require RT::Queues;
+require RT::Groups;
 
 
-# }}}
+our %RIGHTS;
 
 
-# {{{ We need to build a hash of all rights, keyed by lower case names
+my (@_ACL_CACHE_HANDLERS);
 
 
-#since you can't do case insensitive hash lookups
 
 
-foreach $right (keys %QUEUERIGHTS) {
-    $LOWERCASERIGHTNAMES{lc $right}=$right;
-}
-foreach $right (keys %SYSTEMRIGHTS) {
-    $LOWERCASERIGHTNAMES{lc $right}=$right;
-}
 
 
-# }}}
+=head1 Rights
 
 
-# {{{ sub _Init
-sub _Init  {
-  my $self = shift;
-  $self->{'table'} = "ACL";
-  return($self->SUPER::_Init(@_));
-}
-# }}}
+# Queue rights are the sort of queue rights that can only be granted
+# to real people or groups
 
 
-# {{{ sub LoadByValues
+=cut
 
 =head2 LoadByValues PARAMHASH
 
 Load an ACE by specifying a paramhash with the following fields:
 
               PrincipalId => undef,
 
 =head2 LoadByValues PARAMHASH
 
 Load an ACE by specifying a paramhash with the following fields:
 
               PrincipalId => undef,
-             PrincipalType => undef,
-             RightName => undef,
-             RightScope => undef,
-             RightAppliesTo => undef,
+              PrincipalType => undef,
+              RightName => undef,
+
+        And either:
+
+              Object => undef,
+
+            OR
+
+              ObjectType => undef,
+              ObjectId => undef
 
 =cut
 
 sub LoadByValues {
 
 =cut
 
 sub LoadByValues {
-  my $self = shift;
-  my %args = (PrincipalId => undef,
-             PrincipalType => undef,
-             RightName => undef,
-             RightScope => undef,
-             RightAppliesTo => undef,
-             @_);
-  
-  $self->LoadByCols (PrincipalId => $args{'PrincipalId'},
-                    PrincipalType => $args{'PrincipalType'},
-                    RightName => $args{'RightName'},
-                    RightScope => $args{'RightScope'},
-                    RightAppliesTo => $args{'RightAppliesTo'}
-                   );
-  
-  #If we couldn't load it.
-  unless ($self->Id) {
-      return (0, "ACE not found");
-  }
-  # if we could
-  return ($self->Id, "ACE Loaded");
-  
+    my $self = shift;
+    my %args = ( PrincipalId   => undef,
+                 PrincipalType => undef,
+                 RightName     => undef,
+                 Object    => undef,
+                 ObjectId    => undef,
+                 ObjectType    => undef,
+                 @_ );
+
+    if ( $args{'RightName'} ) {
+        my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
+        unless ( $canonic_name ) {
+            return wantarray ? ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) ) : 0;
+        }
+        $args{'RightName'} = $canonic_name;
+    }
+
+    my $princ_obj;
+    ( $princ_obj, $args{'PrincipalType'} ) =
+      $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+                                     $args{'PrincipalType'} );
+
+    unless ( $princ_obj->id ) {
+        return wantarray ? ( 0,
+                 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+        ) : 0;
+    }
+
+    my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args );
+    unless( $object ) {
+        return wantarray ? ( 0, $self->loc("System error. Right not granted.")) : 0;
+    }
+
+    $self->LoadByCols( PrincipalId   => $princ_obj->Id,
+                       PrincipalType => $args{'PrincipalType'},
+                       RightName     => $args{'RightName'},
+                       ObjectType    => $object_type,
+                       ObjectId      => $object_id);
+
+    #If we couldn't load it.
+    unless ( $self->Id ) {
+        return wantarray ? ( 0, $self->loc("ACE not found") ) : 0;
+    }
+
+    # if we could
+    return wantarray ? ( $self->Id, $self->loc("Right Loaded") ) : $self->Id;
+
 }
 
 }
 
-# }}}
 
 
-# {{{ sub Create
 
 =head2 Create <PARAMS>
 
 PARAMS is a parameter hash with the following elements:
 
 
 =head2 Create <PARAMS>
 
 PARAMS is a parameter hash with the following elements:
 
-   PrincipalType => "Queue"|"User"
-   PrincipalId => an intentifier you can use to ->Load a user or group
+   PrincipalId => The id of an RT::Principal object
+   PrincipalType => "User" "Group" or any Role type
    RightName => the name of a right. in any case
    RightName => the name of a right. in any case
-   RightScope => "System" | "Queue"
-   RightAppliesTo => a queue id or undef
+
+
+    Either:
+
+   Object => An object to create rights for. ususally, an RT::Queue or RT::Group
+             This should always be a DBIx::SearchBuilder::Record subclass
+
+        OR
+
+   ObjectType => the type of the object in question (ref ($object))
+   ObjectId => the id of the object in question $object->Id
+
+
+
+   Returns a tuple of (STATUS, MESSAGE);  If the call succeeded, STATUS is true. Otherwise it's false.
+
+
 
 =cut
 
 sub Create {
     my $self = shift;
 
 =cut
 
 sub Create {
     my $self = shift;
-    my %args = ( PrincipalId => undef,
-                PrincipalType => undef,
-                RightName => undef,
-                RightScope => undef,
-                RightAppliesTo => undef,
-                @_
-              );
-    
-    # {{{ Validate the principal
-    my ($princ_obj);
-    if ($args{'PrincipalType'} eq 'User') {
-       $princ_obj = new RT::User($RT::SystemUser);
-       
-    }  
-    elsif ($args{'PrincipalType'} eq 'Group') {
-       require RT::Group;
-       $princ_obj = new RT::Group($RT::SystemUser);
+    my %args = (
+        PrincipalId   => undef,
+        PrincipalType => undef,
+        RightName     => undef,
+        Object        => undef,
+        @_
+    );
+
+    unless ( $args{'RightName'} ) {
+        return ( 0, $self->loc('No right specified') );
     }
     }
-    else {
-       return (0, 'Principal type '.$args{'PrincipalType'} . ' is invalid.');
-    }  
-    
-    $princ_obj->Load($args{'PrincipalId'});
-    my $princ_id = $princ_obj->Id();
-    
-    unless ($princ_id) {
-       return (0, 'Principal '.$args{'PrincipalId'}.' not found.');
+
+    #if we haven't specified any sort of right, we're talking about a global right
+    if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
+        $args{'Object'} = $RT::System;
+    }
+    ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args );
+    unless( $args{'Object'} ) {
+        return ( 0, $self->loc("System error. Right not granted.") );
+    }
+
+    # Validate the principal
+    my $princ_obj;
+    ( $princ_obj, $args{'PrincipalType'} ) =
+      $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+                                     $args{'PrincipalType'} );
+
+    unless ( $princ_obj->id ) {
+        return ( 0,
+                 $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+        );
     }
 
     # }}}
     }
 
     # }}}
-    
-    #TODO allow loading of queues by name.    
-    
-    # {{{ Check the ACL
-    if ($args{'RightScope'} eq 'System') {
-       
-       unless ($self->CurrentUserHasSystemRight('ModifyACL')) {
-           $RT::Logger->error("Permission Denied.");
-           return(undef);
-       }
-    }
-    
-    elsif ($args{'RightScope'} eq 'Queue') {
-       unless ($self->CurrentUserHasQueueRight( Queue => $args{'RightAppliesTo'},
-                                                Right => 'ModifyACL')) {
-           return (0, 'Permission Denied.');
-       }
-       
-       
-       
-       
-    }
-    #If it's not a scope we recognise, something scary is happening.
-    else {
-       $RT::Logger->err("RT::ACE->Create got a scope it didn't recognize: ".
-                        $args{'RightScope'}." Bailing. \n");
-       return(0,"System error. Unable to grant rights.");
+
+    # Check the ACL
+
+    if (ref( $args{'Object'}) eq 'RT::Group' ) {
+        unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
+                                                  Right => 'AdminGroup' )
+          ) {
+            return ( 0, $self->loc('Permission Denied') );
+        }
     }
 
     }
 
+    else {
+        unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
+            return ( 0, $self->loc('Permission Denied') );
+        }
+    }
     # }}}
 
     # }}}
 
-    # {{{ Canonicalize and check the right name
-    $args{'RightName'} = $self->CanonicalizeRightName($args{'RightName'});
-    
+    # Canonicalize and check the right name
+    my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
+    unless ( $canonic_name ) {
+        return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
+    }
+    $args{'RightName'} = $canonic_name;
+
     #check if it's a valid RightName
     #check if it's a valid RightName
-    if ($args{'RightScope'} eq 'Queue') {
-       unless (exists $QUEUERIGHTS{$args{'RightName'}}) {
-           return(0, 'Invalid right');
-       }       
-       }       
-    elsif ($args{'RightScope' eq 'System'}) {
-       unless (exists $SYSTEMRIGHTS{$args{'RightName'}}) {
-           return(0, 'Invalid right');
-       }                   
-    }  
+    if ( $args{'Object'}->can('AvailableRights') ) {
+        my $available = $args{'Object'}->AvailableRights($princ_obj);
+        unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) {
+            $RT::Logger->warning(
+                "Couldn't validate right name '$args{'RightName'}'"
+                ." for object of ". ref( $args{'Object'} ) ." class"
+            );
+            return ( 0, $self->loc('Invalid right') );
+        }
+    }
     # }}}
     # }}}
-    
+
     # Make sure the right doesn't already exist.
     # Make sure the right doesn't already exist.
-    $self->LoadByCols (PrincipalId => $princ_id,
-                      PrincipalType => $args{'PrincipalType'},
-                      RightName => $args{'RightName'},
-                      RightScope => $args {'RightScope'},
-                      RightAppliesTo => $args{'RightAppliesTo'}
-                     );
-    if ($self->Id) {
-       return (0, 'That user already has that right');
-    }  
-
-    my $id = $self->SUPER::Create( PrincipalId => $princ_id,
-                                  PrincipalType => $args{'PrincipalType'},
-                                  RightName => $args{'RightName'},
-                                  RightScope => $args {'RightScope'},
-                                  RightAppliesTo => $args{'RightAppliesTo'}
-                                );
-    
-    
-    if ($id > 0 ) {
-       return ($id, 'Right Granted');
+    $self->LoadByCols( PrincipalId   => $princ_obj->id,
+                       PrincipalType => $args{'PrincipalType'},
+                       RightName     => $args{'RightName'},
+                       ObjectType    => $args{'ObjectType'},
+                       ObjectId      => $args{'ObjectId'},
+                   );
+    if ( $self->Id ) {
+        return ( 0, $self->loc('[_1] already has that right',
+                    $princ_obj->Object->Name) );
+    }
+
+    my $id = $self->SUPER::Create( PrincipalId   => $princ_obj->id,
+                                   PrincipalType => $args{'PrincipalType'},
+                                   RightName     => $args{'RightName'},
+                                   ObjectType    => ref( $args{'Object'} ),
+                                   ObjectId      => $args{'Object'}->id,
+                               );
+
+    if ( $id ) {
+        RT::ACE->InvalidateCaches(
+            Action      => "Grant",
+            RightName   => $self->RightName,
+            ACE         => $self,
+        );
+        return ( $id, $self->loc('Right Granted') );
     }
     else {
     }
     else {
-       $RT::Logger->err('System error. right not granted.');
-       return(0, 'System Error. right not granted');
+        return ( 0, $self->loc('System error. Right not granted.') );
     }
 }
 
     }
 }
 
-# }}}
 
 
 
 
-# {{{ sub Delete 
+=head2 Delete { InsideTransaction => undef}
 
 
-=head2 Delete
+Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
+If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
+Really, DBIx::SearchBuilder should use and/or fake subtransactions
 
 
-Delete this object.
+This routine will also recurse and delete any delegations of this right
 
 =cut
 
 sub Delete {
     my $self = shift;
 
 =cut
 
 sub Delete {
     my $self = shift;
-    
-    unless ($self->CurrentUserHasRight('ModifyACL')) {
-       return (0, 'Permission Denied');
-    }  
-    
-    
-    my ($val,$msg) = $self->SUPER::Delete(@_);
+
+    unless ( $self->Id ) {
+        return ( 0, $self->loc('Right not loaded.') );
+    }
+
+    # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
+    # or if it's a delegated ACE and it was delegated by the current user
+    unless ($self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)) {
+        return ( 0, $self->loc('Permission Denied') );
+    }
+    $self->_Delete(@_);
+}
+
+# Helper for Delete with no ACL check
+sub _Delete {
+    my $self = shift;
+    my %args = ( InsideTransaction => undef,
+                 @_ );
+
+    my $InsideTransaction = $args{'InsideTransaction'};
+
+    $RT::Handle->BeginTransaction() unless $InsideTransaction;
+
+    my $right = $self->RightName;
+
+    my ( $val, $msg ) = $self->SUPER::Delete(@_);
+
     if ($val) {
     if ($val) {
-       return ($val, 'ACE Deleted');
-    }  
-    else {
-       return (0, 'ACE could not be deleted');
+        RT::ACE->InvalidateCaches( Action => "Revoke", RightName => $right );
+        $RT::Handle->Commit() unless $InsideTransaction;
+        return ( $val, $self->loc('Right revoked') );
     }
     }
+
+    $RT::Handle->Rollback() unless $InsideTransaction;
+    return ( 0, $self->loc('Right could not be revoked') );
 }
 
 }
 
-# }}}
 
 
-# {{{ sub _BootstrapRight 
 
 
-=head2 _BootstrapRight
+=head2 _BootstrapCreate
 
 Grant a right with no error checking and no ACL. this is _only_ for 
 
 Grant a right with no error checking and no ACL. this is _only_ for 
-installation. If you use this routine without jesse@fsck.com's explicit 
+installation. If you use this routine without the author's explicit 
 written approval, he will hunt you down and make you spend eternity
 translating mozilla's code into FORTRAN or intercal.
 
 written approval, he will hunt you down and make you spend eternity
 translating mozilla's code into FORTRAN or intercal.
 
+If you think you need this routine, you've mistaken. 
+
 =cut
 
 =cut
 
-sub _BootstrapRight {
+sub _BootstrapCreate {
     my $self = shift;
     my $self = shift;
-    my %args = @_;
-
-    my $id = $self->SUPER::Create( PrincipalId => $args{'PrincipalId'},
-                                  PrincipalType => $args{'PrincipalType'},
-                                  RightName => $args{'RightName'},
-                                  RightScope => $args {'RightScope'},
-                                  RightAppliesTo => $args{'RightAppliesTo'}
-                                );
-    
-    if ($id > 0 ) {
-       return ($id);
+    my %args = (@_);
+
+    # When bootstrapping, make sure we get the _right_ users
+    if ( $args{'UserId'} ) {
+        my $user = RT::User->new( $self->CurrentUser );
+        $user->Load( $args{'UserId'} );
+        delete $args{'UserId'};
+        $args{'PrincipalId'}   = $user->PrincipalId;
+        $args{'PrincipalType'} = 'User';
+    }
+
+    my $id = $self->SUPER::Create(%args);
+
+    if ( $id > 0 ) {
+        return ($id);
     }
     else {
     }
     else {
-       $RT::Logger->err('System error. right not granted.');
-       return(undef);
+        $RT::Logger->err('System error. right not granted.');
+        return (undef);
     }
     }
-    
-}
 
 
-# }}}
+}
 
 
-# {{{ sub CanonicalizeRightName
+=head2 InvalidateCaches
 
 
-=head2 CanonicalizeRightName <RIGHT>
+Calls any registered ACL cache handlers (see L</RegisterCacheHandler>).
 
 
-Takes a queue or system right name in any case and returns it in
-the correct case. If it's not found, will return undef.
+Usually called from L</Create> and L</Delete>.
 
 =cut
 
 
 =cut
 
-sub CanonicalizeRightName {
-    my $self = shift;
-    my $right = shift;
-    $right = lc $right;
-    if (exists $LOWERCASERIGHTNAMES{"$right"}) {
-       return ($LOWERCASERIGHTNAMES{"$right"});
-    }
-    else {
-       return (undef);
+sub InvalidateCaches {
+    my $class = shift;
+
+    for my $handler (@_ACL_CACHE_HANDLERS) {
+        next unless ref($handler) eq "CODE";
+        $handler->(@_);
     }
 }
 
     }
 }
 
-# }}}
+=head2 RegisterCacheHandler
 
 
-# {{{ sub QueueRights
+Class method.  Takes a coderef and adds it to the ACL cache handlers.  These
+handlers are called by L</InvalidateCaches>, usually called itself from
+L</Create> and L</Delete>.
 
 
-=head2 QueueRights
+The handlers are passed a hash which may contain any (or none) of these
+optional keys:
 
 
-Returns a hash of all the possible rights at the queue scope
+=over
 
 
-=cut
+=item Action
 
 
-sub QueueRights {
-        return (%QUEUERIGHTS);
-}
+A string indicating the action that (may have) invalidated the cache.  Expected
+values are currently:
 
 
-# }}}
+=over
+
+=item Grant
+
+=item Revoke
+
+=back
+
+However, other values may be passed in the future.
 
 
-# {{{ sub SystemRights
+=item RightName
 
 
-=head2 SystemRights
+The (canonicalized) right being granted or revoked.
 
 
-Returns a hash of all the possible rights at the system scope
+=item ACE
+
+The L<RT::ACE> object just created.
+
+=back
+
+Your handler should be flexible enough to account for additional arguments
+being passed in the future.
 
 =cut
 
 
 =cut
 
-sub SystemRights {
-       return (%SYSTEMRIGHTS);
+sub RegisterCacheHandler {
+    push @_ACL_CACHE_HANDLERS, $_[1];
 }
 
 }
 
+sub RightName {
+    my $self = shift;
+    my $val = $self->_Value('RightName');
+    return $val unless $val;
 
 
-# }}}
+    my $available = $self->Object->AvailableRights;
+    foreach my $right ( keys %$available ) {
+        return $right if $val eq $self->CanonicalizeRightName($right);
+    }
 
 
-# {{{ sub _Accessible 
-
-sub _Accessible  {
-  my $self = shift;  
-  my %Cols = (
-             PrincipalId => 'read/write',
-             PrincipalType => 'read/write',
-             RightName => 'read/write', 
-             RightScope => 'read/write',
-             RightAppliesTo => 'read/write'
-           );
-  return($self->SUPER::_Accessible(@_, %Cols));
+    $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
+    return $val;
 }
 }
-# }}}
 
 
-# {{{ sub AppliesToObj
+=head2 CanonicalizeRightName <RIGHT>
+
+Takes a queue or system right name in any case and returns it in
+the correct case. If it's not found, will return undef.
+
+=cut
+
+sub CanonicalizeRightName {
+    my $self = shift;
+    my $name = shift;
+    for my $class (sort keys %RIGHTS) {
+        return $RIGHTS{$class}{ lc $name }{Name}
+            if $RIGHTS{$class}{ lc $name };
+    }
+    return undef;
+}
+
+
 
 
-=head2 AppliesToObj
+=head2 Object
 
 
-If the AppliesTo is a queue, returns the queue object. If it's 
-the system object, returns undef. If the user has no rights, returns undef.
+If the object this ACE applies to is a queue, returns the queue object. 
+If the object this ACE applies to is a group, returns the group object. 
+If it's the system object, returns undef. 
+
+If the user has no rights, returns undef.
 
 =cut
 
 
 =cut
 
-sub AppliesToObj {
+
+
+
+sub Object {
     my $self = shift;
     my $self = shift;
-    if ($self->RightScope eq 'Queue') {
-       my $appliesto_obj = new RT::Queue($self->CurrentUser);
-       $appliesto_obj->Load($self->RightAppliesTo);
-       return($appliesto_obj);
-    }
-    elsif ($self->RightScope eq 'System') {
-       return (undef);
-    }  
+
+    my $appliesto_obj;
+
+    if ($self->__Value('ObjectType') && $self->__Value('ObjectType')->DOES('RT::Record::Role::Rights') ) {
+        $appliesto_obj =  $self->__Value('ObjectType')->new($self->CurrentUser);
+        unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
+            return undef;
+        }
+        $appliesto_obj->Load( $self->__Value('ObjectId') );
+        return ($appliesto_obj);
+     }
     else {
     else {
-       $RT::Logger->warning("$self -> AppliesToObj called for an object ".
-                            "of an unknown scope:" . $self->RightScope);
-       return(undef);
+        $RT::Logger->warning( "$self -> Object called for an object "
+                              . "of an unknown type:"
+                              . $self->__Value('ObjectType') );
+        return (undef);
     }
     }
-}      
+}
 
 
-# }}}
 
 
-# {{{ sub PrincipalObj
 
 =head2 PrincipalObj
 
 
 =head2 PrincipalObj
 
-If the AppliesTo is a group, returns the group object.
-If the AppliesTo is a user, returns the user object.
-Otherwise, it logs a warning and returns undef.
+Returns the RT::Principal object for this ACE. 
 
 =cut
 
 sub PrincipalObj {
     my $self = shift;
 
 =cut
 
 sub PrincipalObj {
     my $self = shift;
-    my ($princ_obj);
 
 
-    if ($self->PrincipalType eq 'Group') {
-       use RT::Group;
-       $princ_obj = new RT::Group($self->CurrentUser);
-    }
-    elsif ($self->PrincipalType eq 'User') {
-       $princ_obj = new RT::User($self->CurrentUser);
-    }
-    else {
-       $RT::Logger->warning("$self -> PrincipalObj called for an object ".
-                            "of an unknown principal type:" . 
-                            $self->PrincipalType ."\n");
-       return(undef);
+    my $princ_obj = RT::Principal->new( $self->CurrentUser );
+    $princ_obj->Load( $self->__Value('PrincipalId') );
+
+    unless ( $princ_obj->Id ) {
+        $RT::Logger->err(
+                   "ACE " . $self->Id . " couldn't load its principal object" );
     }
     }
-    
-    $princ_obj->Load($self->PrincipalId);
-    return($princ_obj);
+    return ($princ_obj);
 
 
-}      
+}
 
 
-# }}}
 
 
-# {{{ ACL related methods
 
 
-# {{{ sub _Set
 
 sub _Set {
 
 sub _Set {
-  my $self = shift;
-  return (0, "ACEs can only be created and deleted.");
+    my $self = shift;
+    return ( 0, $self->loc("ACEs can only be created and deleted.") );
 }
 
 }
 
-# }}}
 
 
-# {{{ sub _Value
 
 sub _Value {
     my $self = shift;
 
 
 sub _Value {
     my $self = shift;
 
-    unless ($self->CurrentUserHasRight('ShowACL')) {
-       return (undef);
+    if ( $self->PrincipalObj->IsGroup
+            && $self->PrincipalObj->Object->HasMemberRecursively(
+                                                $self->CurrentUser->PrincipalObj
+            )
+      ) {
+        return ( $self->__Value(@_) );
+    }
+    elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
+        return ( $self->__Value(@_) );
+    }
+    else {
+        return undef;
     }
     }
-
-    return ($self->__Value(@_));
 }
 
 }
 
-# }}}
 
 
 
 
-# {{{ sub CurrentUserHasQueueRight 
 
 
-=head2 CurrentUserHasQueueRight ( Queue => QUEUEID, Right => RIGHTNANAME )
 
 
-Check to see whether the current user has the specified right for the specified queue.
+=head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
+
+Takes a principal id and a principal type.
+
+If the principal is a user, resolves it to the proper acl equivalence group.
+Returns a tuple of  (RT::Principal, PrincipalType)  for the principal we really want to work with
 
 =cut
 
 
 =cut
 
-sub CurrentUserHasQueueRight {
-    my $self = shift;
-    my %args = (Queue => undef,
-               Right => undef,
-               @_
-               );
-    return ($self->HasRight( Right => $args{'Right'},
-                            Principal => $self->CurrentUser->UserObj,
-                            Queue => $args{'Queue'}));
-}
+sub _CanonicalizePrincipal {
+    my $self       = shift;
+    my $princ_id   = shift;
+    my $princ_type = shift || '';
 
 
-# }}}
+    my $princ_obj = RT::Principal->new(RT->SystemUser);
+    $princ_obj->Load($princ_id);
 
 
-# {{{ sub CurrentUserHasSystemRight 
-=head2 CurrentUserHasSystemRight RIGHTNAME
+    unless ( $princ_obj->Id ) {
+        use Carp;
+        $RT::Logger->crit(Carp::longmess);
+        $RT::Logger->crit("Can't load a principal for id $princ_id");
+        return ( $princ_obj, undef );
+    }
 
 
-Check to see whether the current user has the specified right for the 'system' scope.
+    # Rights never get granted to users. they get granted to their 
+    # ACL equivalence groups
+    if ( $princ_type eq 'User' ) {
+        my $equiv_group = RT::Group->new( $self->CurrentUser );
+        $equiv_group->LoadACLEquivalenceGroup($princ_obj);
+        unless ( $equiv_group->Id ) {
+            $RT::Logger->crit( "No ACL equiv group for princ " . $princ_obj->id );
+            return ( RT::Principal->new(RT->SystemUser), undef );
+        }
+        $princ_obj  = $equiv_group->PrincipalObj();
+        $princ_type = 'Group';
 
 
-=cut
+    }
+    return ( $princ_obj, $princ_type );
+}
 
 
-sub CurrentUserHasSystemRight {
+sub _ParseObjectArg {
     my $self = shift;
     my $self = shift;
-    my $right = shift;
-    return ($self->HasRight( Right => $right,
-                            Principal => $self->CurrentUser->UserObj,
-                            System => 1
-                          ));
+    my %args = ( Object    => undef,
+                 ObjectId    => undef,
+                 ObjectType    => undef,
+                 @_ );
+
+    if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) {
+        $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" );
+        return ();
+    } elsif( $args{'Object'} && ref($args{'Object'}) &&  !$args{'Object'}->can('id') ) {
+        $RT::Logger->crit( "Method called called Object that has no id method" );
+        return ();
+    } elsif( $args{'Object'} ) {
+        my $obj = $args{'Object'};
+        return ($obj, ref $obj, $obj->id);
+    } elsif ( $args{'ObjectType'} ) {
+        my $obj =  $args{'ObjectType'}->new( $self->CurrentUser );
+        $obj->Load( $args{'ObjectId'} );
+        return ($obj, ref $obj, $obj->id);
+    } else {
+        $RT::Logger->crit( "Method called with wrong args" );
+        return ();
+    }
 }
 
 
 # }}}
 
 }
 
 
 # }}}
 
-# {{{ sub CurrentUserHasRight
 
 
-=item CurrentUserHasRight RIGHT 
-Takes a rightname as a string.
 
 
-Helper menthod for HasRight. Presets Principal to CurrentUser then 
-calls HasRight.
+=head2 id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
 
 =cut
 
 
 =cut
 
-sub CurrentUserHasRight {
-    my $self = shift;
-    my $right = shift;
-    return ($self->HasRight( Principal => $self->CurrentUser->UserObj,
-                             Right => $right,
-                          ));
-}
 
 
-# }}}
+=head2 PrincipalType
 
 
-# {{{ sub HasRight
+Returns the current value of PrincipalType.
+(In the database, PrincipalType is stored as varchar(25).)
 
 
-=item HasRight
 
 
-Takes a param-hash consisting of "Right" and "Principal"  Principal is 
-an RT::User object or an RT::CurrentUser object. "Right" is a textual
-Right string that applies to KeywordSelects
 
 
-=cut
+=head2 SetPrincipalType VALUE
 
 
-sub HasRight {
-    my $self = shift;
-    my %args = ( Right => undef,
-                 Principal => undef,
-                Queue => undef,
-                System => undef,
-                 @_ ); 
-
-    #If we're explicitly specifying a queue, as we need to do on create
-    if (defined $args{'Queue'}) {
-       return ($args{'Principal'}->HasQueueRight(Right => $args{'Right'},
-                                                 Queue => $args{'Queue'}));
-    }
-    #else if we're specifying to check a system right
-    elsif ((defined $args{'System'}) and (defined $args{'Right'})) {
-        return( $args{'Principal'}->HasSystemRight( $args{'Right'} ));
-    }  
-    
-    elsif ($self->__Value('RightScope') eq 'System') {
-       return $args{'Principal'}->HasSystemRight($args{'Right'});
-    }
-    elsif ($self->__Value('RightScope') eq 'Queue') {
-       return $args{'Principal'}->HasQueueRight( Queue => $self->__Value('RightAppliesTo'),
-                                                 Right => $args{'Right'} );
-    }  
-    else {
-       $RT::Logger->warning("$self: Trying to check an acl for a scope we ".
-                            "don't understand:" . $self->__Value('RightScope') ."\n");
-       return undef;
-    }
 
 
+Set PrincipalType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PrincipalType will be stored as a varchar(25).)
 
 
 
 
-}
-# }}}
+=cut
 
 
-# }}}
 
 
-1;
+=head2 PrincipalId
 
 
-__DATA__
+Returns the current value of PrincipalId.
+(In the database, PrincipalId is stored as int(11).)
 
 
-# {{{ POD
 
 
-=head1 Out of date docs
 
 
-=head2 Table Structure
+=head2 SetPrincipalId VALUE
 
 
-PrincipalType, PrincipalId, Right,Scope,AppliesTo
 
 
-=head1 The docs are out of date. so you know.
+Set PrincipalId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PrincipalId will be stored as a int(11).)
 
 
-=head1 Scopes
 
 
-Scope is the scope of the right granted, not the granularity of the grant.
-For example, Queue and Ticket rights are both granted for a "queue." 
-Rights with a scope of 'System' don't have an AppliesTo. (They're global).
-Rights with a scope of "Queue" are rights that act on a queue.
-Rights with a scope of "System" are rights that act on some other aspect
-of the system.
+=cut
 
 
 
 
-=item Queue
-=item System
+=head2 RightName
 
 
+Returns the current value of RightName.
+(In the database, RightName is stored as varchar(25).)
 
 
-=head1 Rights
 
 
-=head2 Scope: Queue
 
 
-=head2 Queue rights that apply to a ticket within a queue
+=head2 SetRightName VALUE
 
 
-Create Ticket in <queue>
 
 
-        Name: Create
-       Principals: <user> <group>
-Display Ticket Summary in <queue>
+Set RightName to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, RightName will be stored as a varchar(25).)
 
 
-       Name: Show
-       Principals: <user> <group> Owner Requestor Cc AdminCc
 
 
-Display Ticket History  <queue>
+=cut
 
 
-       Name: ShowHistory
-       Principals: <user> <group> Owner Requestor Cc AdminCc
 
 
-Display Ticket Private Comments  <queue>
+=head2 ObjectType
 
 
-       Name: ShowComments
-       Principals: <user> <group> Owner Requestor Cc AdminCc
+Returns the current value of ObjectType.
+(In the database, ObjectType is stored as varchar(25).)
 
 
-Reply to Ticket in <queue>
 
 
-       Name: Reply
-       Principals: <user> <group> Owner Requestor Cc AdminCc
 
 
-Comment on Ticket in <queue>
+=head2 SetObjectType VALUE
 
 
-       Name: Comment
-       Principals: <user> <group> Owner Requestor Cc AdminCc
 
 
-Modify Ticket in <queue>
+Set ObjectType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectType will be stored as a varchar(25).)
 
 
-       Name: Modify
-       Principals: <user> <group> Owner Requestor Cc AdminCc
 
 
-Delete Tickets in <queue>
+=cut
 
 
-       Name: Delete
-       Principals: <user> <group> Owner Requestor Cc AdminCc
 
 
+=head2 ObjectId
 
 
-=head2 Queue Rights that apply to a whole queue
+Returns the current value of ObjectId.
+(In the database, ObjectId is stored as int(11).)
 
 
-These rights can only be granted to "real people"
 
 
-List Tickets in <queue>
 
 
-       Name: ListQueue
-       Principals: <user> <group>
+=head2 SetObjectId VALUE
 
 
-Know that <queue> exists
-    
-    Name: See
-    Principals: <user> <group>
 
 
-Display queue settings
+Set ObjectId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
 
 
-    Name: Explore
-    Principals: <user> <group>
 
 
-Modify Queue Watchers for <queue>
+=cut
 
 
-       Name: ModifyQueueWatchers
-       Principals: <user> <group>
 
 
-Modify Queue Attributes for <queue> 
+=head2 Creator
 
 
-       Name: ModifyQueue
-       Principals: <user> <group>
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
 
 
-Modify Queue ACL for queue <queue>
+=cut
 
 
-       Name: ModifyACL
-       Principals: <user> <group>
 
 
+=head2 Created
 
 
-=head2 Rights that apply to the System scope
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
 
 
-=head2 SystemRights
+=cut
 
 
-Create Queue
-  
-        Name: CreateQueue
-       Principals: <user> <group>
-Delete Queue
-  
-        Name: DeleteQueue
-       Principals: <user> <group>
 
 
-Create Users
-  
-        Name: CreateUser
-       Principals: <user> <group>
+=head2 LastUpdatedBy
 
 
-Delete Users
-  
-        Name: DeleteUser
-       Principals: <user> <group>
-  
-Modify Users
-  
-        Name: ModifyUser
-       Principals: <user> <group>
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
 
 
-Modify Self
-        Name: ModifySelf
-       Principals: <user> <group>
+=cut
 
 
-Browse Users
 
 
-        Name: BrowseUsers (NOT IMPLEMENTED in 2.0)
-       Principals: <user> <group>
+=head2 LastUpdated
 
 
-Modify Self
-                   
-       Name: ModifySelf
-       Principals: <user> <group>
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
 
 
-Modify System ACL
+=cut
 
 
-       Name: ModifyACL           
-       Principals: <user> <group>
 
 
-=head1 The Principal Side of the ACE
 
 
-=head2 PrincipalTypes,PrincipalIds in our Neighborhood
+sub _CoreAccessible {
+    {
+
+        id =>
+                {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+        PrincipalType =>
+                {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
+        PrincipalId =>
+                {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        RightName =>
+                {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
+        ObjectType =>
+                {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
+        ObjectId =>
+                {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        Creator =>
+                {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        Created =>
+                {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+        LastUpdatedBy =>
+                {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+        LastUpdated =>
+                {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+
+ }
+};
+
+sub FindDependencies {
+    my $self = shift;
+    my ($walker, $deps) = @_;
 
 
-  User,<userid>
-  Group,<groupip>
-  Everyone,NULL
+    $self->SUPER::FindDependencies($walker, $deps);
 
 
-=cut
+    $deps->Add( out => $self->PrincipalObj->Object );
+    $deps->Add( out => $self->Object );
+}
 
 
-# }}}
+RT::Base->_ImportOverlays();
+
+1;