import rt 3.6.6
[freeside.git] / rt / lib / RT / Group_Overlay.pm
index 9215025..d2e2364 100644 (file)
@@ -1,8 +1,15 @@
-# BEGIN LICENSE BLOCK
+
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded 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
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# 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/copyleft/gpl.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.)
 # 
-# END LICENSE BLOCK
+# 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 }}}
 # Released under the terms of version 2 of the GNU Public License
 
 =head1 NAME
 
 =head1 SYNOPSIS
 
-  use RT::Group;
+use RT::Group;
 my $group = new RT::Group($CurrentUser);
 
 =head1 DESCRIPTION
 
 An RT group object.
 
-=head1 AUTHOR
-
-Jesse Vincent, jesse@bestpractical.com
-
-=head1 SEE ALSO
-
-RT
-
 =head1 METHODS
 
 
@@ -131,6 +148,9 @@ ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 has member 2
 
 =cut
 
+
+package RT::Group;
+
 use strict;
 no warnings qw(redefine);
 
@@ -145,7 +165,10 @@ $RIGHTS = {
     AdminGroup           => 'Modify group metadata or delete group',  # loc_pair
     AdminGroupMembership =>
       'Modify membership roster for this group',                      # loc_pair
-    ModifyOwnMembership => 'Join or leave this group'                 # loc_pair
+    ModifyOwnMembership => 'Join or leave this group',                 # loc_pair
+    EditSavedSearches => 'Edit saved searches for this group',        # loc_pair
+    ShowSavedSearches => 'Display saved searches for this group',        # loc_pair
+    SeeGroup => 'Make this group visible to user',                    # loc_pair
 };
 
 # Tell RT::ACE that this sort of object can get acls granted
@@ -327,8 +350,6 @@ sub LoadSystemInternalGroup {
     my $identifier = shift;
 
         $self->LoadByCols( "Domain" => 'SystemInternal',
-                           "Instance" => '',
-                           "Name" => '',
                            "Type" => $identifier );
 }
 
@@ -350,7 +371,7 @@ Takes a param hash with 2 parameters:
 
 sub LoadTicketRoleGroup {
     my $self       = shift;
-    my %args = (Ticket => undef,
+    my %args = (Ticket => '0',
                 Type => undef,
                 @_);
         $self->LoadByCols( Domain => 'RT::Ticket-Role',
@@ -412,6 +433,7 @@ sub LoadSystemRoleGroup {
 # }}}
 
 # {{{ sub Create
+
 =head2 Create
 
 You need to specify what sort of group you're creating by calling one of the other
@@ -444,8 +466,9 @@ sub _Create {
         Description => undef,
         Domain      => undef,
         Type        => undef,
-        Instance    => undef,
+        Instance    => '0',
         InsideTransaction => undef,
+        _RecordTransaction => 1,
         @_
     );
 
@@ -466,7 +489,7 @@ sub _Create {
         Description => $args{'Description'},
         Type        => $args{'Type'},
         Domain      => $args{'Domain'},
-        Instance    => $args{'Instance'}
+        Instance    => ($args{'Instance'} || '0')
     );
     my $id = $self->Id;
     unless ($id) {
@@ -476,7 +499,7 @@ sub _Create {
     # If we couldn't create a principal Id, get the fuck out.
     unless ($principal_id) {
         $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
-        $self->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
+        $RT::Logger->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
         return ( 0, $self->loc('Could not create group') );
     }
 
@@ -491,8 +514,12 @@ sub _Create {
     $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
 
 
+    if ( $args{'_RecordTransaction'} ) {
+       $self->_NewTransaction( Type => "Create" );
+    }
 
     $RT::Handle->Commit() unless ($args{'InsideTransaction'});
+
     return ( $id, $self->loc("Group created") );
 }
 
@@ -687,6 +714,7 @@ If passed a positive value, this group will be disabled. No rights it commutes o
 It will not appear in most group listings.
 
 This routine finds all the cached group members that are members of this group  (recursively) and disables them.
+
 =cut 
 
  # }}}
@@ -731,7 +759,7 @@ This routine finds all the cached group members that are members of this group
 
     #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::Principal->InvalidateACLCache();
 
 
 
@@ -763,7 +791,8 @@ sub Disabled {
 
 =head2 DeepMembersObj
 
-Returns an RT::CachedGroupMembers object of this group's members.
+Returns an RT::CachedGroupMembers object of this group's members,
+including all members of subgroups.
 
 =cut
 
@@ -798,22 +827,14 @@ sub UserMembersObj {
     #If we don't have rights, don't include any results
     # TODO XXX  WHY IS THERE NO ACL CHECK HERE?
 
-    my $principals = $users->NewAlias('Principals');
-
-    $users->Join(ALIAS1 => 'main', FIELD1 => 'id',
-                 ALIAS2 => $principals, FIELD2 => 'ObjectId');
-    $users->Limit(ALIAS =>$principals,
-                  FIELD => 'PrincipalType', OPERATOR => '=', VALUE => 'User');
-
     my $cached_members = $users->NewAlias('CachedGroupMembers');
     $users->Join(ALIAS1 => $cached_members, FIELD1 => 'MemberId',
-                 ALIAS2 => $principals, FIELD2 => 'id');
+                 ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id');
     $users->Limit(ALIAS => $cached_members, 
                   FIELD => 'GroupId',
                   OPERATOR => '=',
                   VALUE => $self->PrincipalId);
 
-
     return ( $users);
 
 }
@@ -824,7 +845,7 @@ sub UserMembersObj {
 
 =head2 MembersObj
 
-Returns an RT::CachedGroupMembers object of this group's members.
+Returns an RT::GroupMembers object of this group's direct members.
 
 =cut
 
@@ -1009,6 +1030,10 @@ sub HasMember {
         return(undef);
     }
 
+    unless ($principal->Id) {
+        return(undef);
+    }
+
     my $member_obj = RT::GroupMember->new( $self->CurrentUser );
     $member_obj->LoadByCols( MemberId => $principal->id, 
                              GroupId => $self->PrincipalId );
@@ -1146,11 +1171,70 @@ sub _DeleteMember {
 
 # }}}
 
+# {{{ sub _CleanupInvalidDelegations
+
+=head2 _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
 sub _Set {
     my $self = shift;
+    my %args = (
+        Field => undef,
+        Value => undef,
+       TransactionType   => 'Set',
+       RecordTransaction => 1,
+        @_
+    );
 
        if ($self->Domain eq 'Personal') {
                if ($self->CurrentUser->PrincipalId == $self->Instance) {
@@ -1168,7 +1252,30 @@ sub _Set {
                return ( 0, $self->loc('Permission Denied') );
        }
        }
-    return ( $self->SUPER::_Set(@_) );
+
+    my $Old = $self->SUPER::_Value("$args{'Field'}");
+    
+    my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
+                                         Value => $args{'Value'} );
+    
+    #If we can't actually set the field to the value, don't record
+    # a transaction. instead, get out of here.
+    if ( $ret == 0 ) { return ( 0, $msg ); }
+
+    if ( $args{'RecordTransaction'} == 1 ) {
+
+        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+                                               Type => $args{'TransactionType'},
+                                               Field     => $args{'Field'},
+                                               NewValue  => $args{'Value'},
+                                               OldValue  => $Old,
+                                               TimeTaken => $args{'TimeTaken'},
+        );
+        return ( $Trans, scalar $TransObj->Description );
+    }
+    else {
+        return ( $ret, $msg );
+    }
 }
 
 # }}}
@@ -1176,7 +1283,7 @@ sub _Set {
 
 
 
-=item CurrentUserHasRight RIGHTNAME
+=head2 CurrentUserHasRight RIGHTNAME
 
 Returns true if the current user has the specified right for this group.
 
@@ -1256,5 +1363,21 @@ sub PrincipalId {
 }
 
 # }}}
+
+sub BasicColumns {
+    (
+       [ Name => 'Name' ],
+       [ Description => 'Description' ],
+    );
+}
+
 1;
 
+=head1 AUTHOR
+
+Jesse Vincent, jesse@bestpractical.com
+
+=head1 SEE ALSO
+
+RT
+