import rt 3.8.10
[freeside.git] / rt / lib / RT / Users_Overlay.pm
index b397c3b..9640925 100644 (file)
@@ -1,27 +1,51 @@
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2002 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2011 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
-# 
+# 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.
-# 
-# 
-# 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.
-# 
-# 
-# END LICENSE BLOCK
+#
+# 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 NAME
 
   RT::Users - Collection of RT::User objects
 
 =head1 METHODS
 
-=begin testing
 
-ok(require RT::Users);
+=cut
 
-=end testing
 
-=cut
+package RT::Users;
 
 use strict;
 no warnings qw(redefine);
@@ -51,11 +73,10 @@ no warnings qw(redefine);
 sub _Init {
     my $self = shift;
     $self->{'table'} = 'Users';
-        $self->{'primary_key'} = 'id';
+    $self->{'primary_key'} = 'id';
+    $self->{'with_disabled_column'} = 1;
 
-
-
-    my @result =          $self->SUPER::_Init(@_);
+    my @result = $self->SUPER::_Init(@_);
     # By default, order by name
     $self->OrderBy( ALIAS => 'main',
                     FIELD => 'Name',
@@ -63,42 +84,34 @@ sub _Init {
 
     $self->{'princalias'} = $self->NewAlias('Principals');
 
+    # XXX: should be generalized
     $self->Join( ALIAS1 => 'main',
                  FIELD1 => 'id',
                  ALIAS2 => $self->{'princalias'},
                  FIELD2 => 'id' );
+    $self->Limit( ALIAS => $self->{'princalias'},
+                  FIELD => 'PrincipalType',
+                  VALUE => 'User',
+                );
 
-    $self->Limit( ALIAS    => $self->{'princalias'},
-                  FIELD    => 'PrincipalType',
-                  OPERATOR => '=',
-                  VALUE    => 'User' );
     return (@result);
 }
 
 # }}}
 
-# {{{ sub _DoSearch 
-
-=head2 _DoSearch
+=head2 PrincipalsAlias
 
-  A subclass of DBIx::SearchBuilder::_DoSearch that makes sure that _Disabled rows never get seen unless
-we're explicitly trying to see them.
+Returns the string that represents this Users object's primary "Principals" alias.
 
 =cut
 
-sub _DoSearch {
+# XXX: should be generalized
+sub PrincipalsAlias {
     my $self = shift;
-
-    #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
-    unless ( $self->{'find_disabled_rows'} ) {
-        $self->LimitToEnabled();
-    }
-    return ( $self->SUPER::_DoSearch(@_) );
+    return($self->{'princalias'});
 
 }
 
-# }}}
-# {{{ sub LimitToEnabled
 
 =head2 LimitToEnabled
 
@@ -106,16 +119,35 @@ Only find items that haven\'t been disabled
 
 =cut
 
+# XXX: should be generalized
 sub LimitToEnabled {
     my $self = shift;
 
-    $self->Limit( ALIAS    => $self->{'princalias'},
-                  FIELD    => 'Disabled',
-                  VALUE    => '0',
-                  OPERATOR => '=' );
+    $self->{'handled_disabled_column'} = 1;
+    $self->Limit(
+        ALIAS    => $self->PrincipalsAlias,
+        FIELD    => 'Disabled',
+        VALUE    => '0',
+    );
+}
+
+=head2 LimitToDeleted
+
+Only find items that have been deleted.
+
+=cut
+
+sub LimitToDeleted {
+    my $self = shift;
+    
+    $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1;
+    $self->Limit(
+        ALIAS => $self->PrincipalsAlias,
+        FIELD => 'Disabled',
+        VALUE => 1,
+    );
 }
 
-# }}}
 
 # {{{ LimitToEmail
 
@@ -152,7 +184,7 @@ sub MemberOfGroup {
     my $groupalias = $self->NewAlias('CachedGroupMembers');
 
     # Join the principal to the groups table
-    $self->Join( ALIAS1 => $self->{'princalias'},
+    $self->Join( ALIAS1 => $self->PrincipalsAlias,
                  FIELD1 => 'id',
                  ALIAS2 => $groupalias,
                  FIELD2 => 'MemberId' );
@@ -188,101 +220,339 @@ sub LimitToPrivileged {
 
 # {{{ WhoHaveRight
 
-=head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef }
+=head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] }
 
-=begin testing
 
-ok(my $users = RT::Users->new($RT::SystemUser));
-$users->WhoHaveRight(Object =>$RT::System, Right =>'SuperUser');
-ok($users->Count == 1, "There is one privileged superuser - Found ". $users->Count );
-# TODO: this wants more testing
+find all users who the right Right for this group, either individually
+or as members of groups
 
+If passed a queue object, with no id, it will find users who have that right for _any_ queue
 
-=end testing
+=cut
 
+# XXX: should be generalized
+sub _JoinGroupMembers
+{
+    my $self = shift;
+    my %args = (
+        IncludeSubgroupMembers => 1,
+        @_
+    );
+
+    my $principals = $self->PrincipalsAlias;
+
+    # The cachedgroupmembers table is used for unrolling group memberships
+    # to allow fast lookups. if we bind to CachedGroupMembers, we'll find
+    # all members of groups recursively. if we don't we'll find only 'direct'
+    # members of the group in question
+    my $group_members;
+    if ( $args{'IncludeSubgroupMembers'} ) {
+        $group_members = $self->NewAlias('CachedGroupMembers');
+    }
+    else {
+        $group_members = $self->NewAlias('GroupMembers');
+    }
 
-find all users who the right Right for this group, either individually
-or as members of groups
+    $self->Join(
+        ALIAS1 => $group_members,
+        FIELD1 => 'MemberId',
+        ALIAS2 => $principals,
+        FIELD2 => 'id'
+    );
 
+    return $group_members;
+}
 
+# XXX: should be generalized
+sub _JoinGroups
+{
+    my $self = shift;
+    my %args = (@_);
+
+    my $group_members = $self->_JoinGroupMembers( %args );
+    my $groups = $self->NewAlias('Groups');
+    $self->Join(
+        ALIAS1 => $groups,
+        FIELD1 => 'id',
+        ALIAS2 => $group_members,
+        FIELD2 => 'GroupId'
+    );
+
+    return $groups;
+}
 
+# XXX: should be generalized
+sub _JoinACL
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        IncludeSuperusers      => undef,
+        @_,
+    );
+
+    if ( $args{'Right'} ) {
+        my $canonic = RT::ACE->CanonicalizeRightName( $args{'Right'} );
+        unless ( $canonic ) {
+            $RT::Logger->error("Invalid right. Couldn't canonicalize right '$args{'Right'}'");
+        }
+        else {
+            $args{'Right'} = $canonic;
+        }
+    }
 
+    my $acl = $self->NewAlias('ACL');
+    $self->Limit(
+        ALIAS    => $acl,
+        FIELD    => 'RightName',
+        OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ),
+        VALUE => $args{Right} || 'NULL',
+        ENTRYAGGREGATOR => 'OR'
+    );
+    if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
+        $self->Limit(
+            ALIAS           => $acl,
+            FIELD           => 'RightName',
+            OPERATOR        => '=',
+            VALUE           => 'SuperUser',
+            ENTRYAGGREGATOR => 'OR'
+        );
+    }
+    return $acl;
+}
 
-=cut
+# XXX: should be generalized
+sub _GetEquivObjects
+{
+    my $self = shift;
+    my %args = (
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        EquivObjects           => [ ],
+        @_
+    );
+    return () unless $args{'Object'};
+
+    my @objects = ($args{'Object'});
+    if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) {
+        # If we're looking at ticket rights, we also want to look at the associated queue rights.
+        # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
+        # we load the queue object and ask all the rest of our questions about the queue.
+
+        # XXX: This should be abstracted into object itself
+        if( $args{'Object'}->id ) {
+            push @objects, $args{'Object'}->ACLEquivalenceObjects;
+        } else {
+            push @objects, 'RT::Queue';
+        }
+    }
 
+    if( $args{'IncludeSystemRights'} ) {
+        push @objects, 'RT::System';
+    }
+    push @objects, @{ $args{'EquivObjects'} };
+    return grep $_, @objects;
+}
+
+# XXX: should be generalized
 sub WhoHaveRight {
     my $self = shift;
-    my %args = ( Right                  => undef,
-                 Object =>              => undef,
-                 IncludeSystemRights    => undef,
-                 IncludeSuperusers      => undef,
-                 IncludeSubgroupMembers => 1,
-                 @_ );
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
+        $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
+        return (undef);
+    }
+
+    my $from_role = $self->Clone;
+    $from_role->WhoHaveRoleRight( %args );
+
+    my $from_group = $self->Clone;
+    $from_group->WhoHaveGroupRight( %args );
+
+    #XXX: DIRTY HACK
+    use DBIx::SearchBuilder::Union;
+    my $union = new DBIx::SearchBuilder::Union;
+    $union->add( $from_group );
+    $union->add( $from_role );
+    %$self = %$union;
+    bless $self, ref($union);
+
+    return;
+}
+# }}}
 
-    if (defined $args{'ObjectType'} || defined $args{'ObjectId'}) {
-        $RT::Logger->crit("$self WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
-        return(undef);
+# XXX: should be generalized
+sub WhoHaveRoleRight
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    my @objects = $self->_GetEquivObjects( %args );
+
+    # RT::Principal->RolesWithRight only expects EquivObjects, so we need to
+    # fill it.  At the very least it needs $args{Object}, which
+    # _GetEquivObjects above does for us.
+    unshift @{$args{'EquivObjects'}}, @objects;
+
+    my @roles = RT::Principal->RolesWithRight( %args );
+    unless ( @roles ) {
+        $self->_AddSubClause( "WhichRole", "(main.id = 0)" );
+        return;
     }
-        my @privgroups;
-        my $Groups = RT::Groups->new($RT::SystemUser);
-        $Groups->WithRight(Right=> $args{'Right'},
-                     Object => $args{'Object'},
-                     IncludeSystemRights => $args{'IncludeSystemRights'},
-                     IncludeSuperusers => $args{'IncludeSuperusers'});
-        while (my $Group = $Groups->Next()) {
-                push @privgroups, $Group->Id();
-        }
 
-        $self->WhoBelongToGroups(Groups => \@privgroups,
-                                 IncludeSubgroupMembers => $args{'IncludeSubgroupMembers'});
+    my $groups = $self->_JoinGroups( %args );
+
+    # no system user
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'id',
+                  OPERATOR => '!=',
+                  VALUE => $RT::SystemUser->id
+                );
+
+    $self->_AddSubClause( "WhichRole", "(". join( ' OR ', map "$groups.Type = '$_'", @roles ) .")" );
+
+    my @groups_clauses = $self->_RoleClauses( $groups, @objects );
+    $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @groups_clauses ) .")" )
+        if @groups_clauses;
+
+    return;
 }
 
-# }}}
+sub _RoleClauses {
+    my $self = shift;
+    my $groups = shift;
+    my @objects = @_;
+
+    my @groups_clauses;
+    foreach my $obj ( @objects ) {
+        my $type = ref($obj)? ref($obj): $obj;
+        my $id;
+        $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+        my $role_clause = "$groups.Domain = '$type-Role'";
+        # XXX: Groups.Instance is VARCHAR in DB, we should quote value
+        # if we want mysql 4.0 use indexes here. we MUST convert that
+        # field to integer and drop this quotes.
+        $role_clause   .= " AND $groups.Instance = '$id'" if $id;
+        push @groups_clauses, "($role_clause)";
+    }
+    return @groups_clauses;
+}
+
+# XXX: should be generalized
+sub _JoinGroupMembersForGroupRights
+{
+    my $self = shift;
+    my %args = (@_);
+    my $group_members = $self->_JoinGroupMembers( %args );
+    $self->Limit( ALIAS => $args{'ACLAlias'},
+                  FIELD => 'PrincipalId',
+                  VALUE => "$group_members.GroupId",
+                  QUOTEVALUE => 0,
+                );
+}
+
+# XXX: should be generalized
+sub WhoHaveGroupRight
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    # Find only rows where the right granted is
+    # the one we're looking up or _possibly_ superuser
+    my $acl = $self->_JoinACL( %args );
+
+    my ($check_objects) = ('');
+    my @objects = $self->_GetEquivObjects( %args );
+
+    if ( @objects ) {
+        my @object_clauses;
+        foreach my $obj ( @objects ) {
+            my $type = ref($obj)? ref($obj): $obj;
+            my $id;
+            $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+            my $object_clause = "$acl.ObjectType = '$type'";
+            $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
+            push @object_clauses, "($object_clause)";
+        }
+
+        $check_objects = join ' OR ', @object_clauses;
+    } else {
+        if( !$args{'IncludeSystemRights'} ) {
+            $check_objects = "($acl.ObjectType != 'RT::System')";
+        }
+    }
+    $self->_AddSubClause( "WhichObject", "($check_objects)" );
+    
+    $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
+    # Find only members of groups that have the right.
+    $self->Limit( ALIAS => $acl,
+                  FIELD => 'PrincipalType',
+                  VALUE => 'Group',
+                );
+    
+    # no system user
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'id',
+                  OPERATOR => '!=',
+                  VALUE => $RT::SystemUser->id
+                );
+    return;
+}
 
-# {{{ WhoBelongToGroups 
+# {{{ WhoBelongToGroups
 
 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
 
 =cut
 
+# XXX: should be generalized
 sub WhoBelongToGroups {
     my $self = shift;
     my %args = ( Groups                 => undef,
                  IncludeSubgroupMembers => 1,
                  @_ );
 
-    # Unprivileged users can't be granted real system rights. 
+    # Unprivileged users can't be granted real system rights.
     # is this really the right thing to be saying?
     $self->LimitToPrivileged();
 
-    my $userprinc  = $self->{'princalias'};
-    my $cgm;
-
-    # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups 
-    # if we bind to CachedGroupMembers, we'll find all members of groups recursively.
-    # if we don't we'll find only 'direct' members of the group in question
-
-    if ( $args{'IncludeSubgroupMembers'} ) {
-        $cgm = $self->NewAlias('CachedGroupMembers');
-    }
-    else {
-        $cgm = $self->NewAlias('GroupMembers');
-    }
-
-    # {{{ Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
-    $self->Join( ALIAS1 => $cgm, FIELD1 => 'MemberId',
-                 ALIAS2 => $userprinc, FIELD2 => 'id' );
-    # }}} 
+    my $group_members = $self->_JoinGroupMembers( %args );
 
- #   my $and_check_groups = "($cgm.GroupId = NULL";
     foreach my $groupid (@{$args{'Groups'}}) {
-        $self->Limit(ALIAS => $cgm, FIELD => 'GroupId', VALUE => $groupid, QUOTEVALUE => 0, ENTRYAGGREGATOR=> 'OR')
-
-        #$and_check_groups .= " OR $cgm.GroupId = $groupid";
+        $self->Limit( ALIAS           => $group_members,
+                      FIELD           => 'GroupId',
+                      VALUE           => $groupid,
+                      QUOTEVALUE      => 0,
+                      ENTRYAGGREGATOR => 'OR',
+                    );
     }
-    #$and_check_groups .= ")";
-
-    #$self->_AddSubClause("WhichGroup", $and_check_groups);
 }
 # }}}