X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FUsers_Overlay.pm;h=96409251fce41b520a7ae7402ddcbb4d071da9de;hp=b397c3bc45a5a28d18043bb7d0ad14650f092cb4;hb=75162bb14b3e38d66617077843f4dfdcaf09d5c4;hpb=945721f48f74d5cfffef7c7cf3a3d6bc2521f5dd diff --git a/rt/lib/RT/Users_Overlay.pm b/rt/lib/RT/Users_Overlay.pm index b397c3bc4..96409251f 100644 --- a/rt/lib/RT/Users_Overlay.pm +++ b/rt/lib/RT/Users_Overlay.pm @@ -1,27 +1,51 @@ -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2002 Jesse Vincent -# -# (Except where explictly superceded by other copyright notices) -# +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# +# +# (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 @@ -36,13 +60,11 @@ =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); } # }}}