-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
#
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
#
-# (Except where explictly superceded by other copyright notices)
+# (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
# 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:
#
-# END LICENSE BLOCK
+# (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
=cut
+
+package RT::Users;
+
use strict;
no warnings qw(redefine);
$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);
}
Returns the string that represents this Users object's primary "Principals" alias.
-
=cut
+# XXX: should be generalized
sub PrincipalsAlias {
my $self = shift;
return($self->{'princalias'});
=cut
+# XXX: should be generalized
sub LimitToEnabled {
my $self = shift;
- $self->Limit( ALIAS => $self->{'princalias'},
+ $self->Limit( ALIAS => $self->PrincipalsAlias,
FIELD => 'Disabled',
VALUE => '0',
OPERATOR => '=' );
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' );
# {{{ 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($users->Count == 1, "There is one privileged superuser - Found ". $users->Count );
# TODO: this wants more testing
+my $RTxUser = RT::User->new($RT::SystemUser);
+($id, $msg) = $RTxUser->Create( Name => 'RTxUser', Comments => "RTx extension user", Privileged => 1);
+ok ($id,$msg);
-=end testing
+my $group = RT::Group->new($RT::SystemUser);
+$group->LoadACLEquivalenceGroup($RTxUser->PrincipalObj);
+
+my $RTxSysObj = {};
+bless $RTxSysObj, 'RTx::System';
+*RTx::System::Id = sub { 1; };
+*RTx::System::id = *RTx::System::Id;
+my $ace = RT::Record->new($RT::SystemUser);
+$ace->Table('ACL');
+$ace->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
+($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System', ObjectId => 1 );
+ok ($id, "ACL for RTxSysObj created");
+
+my $RTxObj = {};
+bless $RTxObj, 'RTx::System::Record';
+*RTx::System::Record::Id = sub { 4; };
+*RTx::System::Record::id = *RTx::System::Record::Id;
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxSysObj);
+is($users->Count, 1, "RTxUserRight found for RTxSysObj");
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj);
+is($users->Count, 0, "RTxUserRight not found for RTxObj");
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj, EquivObjects => [ $RTxSysObj ]);
+is($users->Count, 1, "RTxUserRight found for RTxObj using EquivObjects");
+
+$ace = RT::Record->new($RT::SystemUser);
+$ace->Table('ACL');
+$ace->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
+($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System::Record', ObjectId => 5 );
+ok ($id, "ACL for RTxObj created");
+
+my $RTxObj2 = {};
+bless $RTxObj2, 'RTx::System::Record';
+*RTx::System::Record::Id = sub { 5; };
+*RTx::System::Record::id = sub { 5; };
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2);
+is($users->Count, 1, "RTxUserRight found for RTxObj2");
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2, EquivObjects => [ $RTxSysObj ]);
+is($users->Count, 1, "RTxUserRight found for RTxObj2");
+
+
+=end 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
+=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');
+ }
+ $self->Join(
+ ALIAS1 => $group_members,
+ FIELD1 => 'MemberId',
+ ALIAS2 => $principals,
+ FIELD2 => 'id'
+ );
-=cut
+ return $group_members;
+}
-sub WhoHaveRight {
+# XXX: should be generalized
+sub _JoinGroups
+{
my $self = shift;
- my %args = ( Right => undef,
- Object => => undef,
- IncludeSystemRights => undef,
- IncludeSuperusers => undef,
- IncludeSubgroupMembers => 1,
- @_ );
+ 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;
+}
- 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 _JoinACL
+{
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ IncludeSuperusers => undef,
+ @_,
+ );
+
+ 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'
+ );
}
- 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();
+ return $acl;
+}
+
+# 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'}->QueueObj;
+ } else {
+ push @objects, 'RT::Queue';
}
+ }
+ if( $args{'IncludeSystemRights'} ) {
+ push @objects, 'RT::System';
+ }
+ push @objects, @{ $args{'EquivObjects'} };
+ return grep $_, @objects;
+}
- if (@privgroups) {
- $self->WhoBelongToGroups(Groups => \@privgroups,
- IncludeSubgroupMembers => $args{'IncludeSubgroupMembers'});
+# XXX: should be generalized
+sub WhoHaveRight {
+ my $self = shift;
+ 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);
}
- else {
- # We don't have any group that matches -- make it impossible.
- $self->Limit( FIELD => 'Id', VALUE => 'IS', OPERATOR => 'NULL' );
+
+ my @from_role = $self->Clone->_WhoHaveRoleRightSplitted( %args );
+
+ my $from_group = $self->Clone;
+ $from_group->WhoHaveGroupRight( %args );
+
+ #XXX: DIRTY HACK
+ use DBIx::SearchBuilder 1.50; #no version on ::Union :(
+ use DBIx::SearchBuilder::Union;
+ my $union = new DBIx::SearchBuilder::Union;
+ $union->add( $_ ) foreach @from_role;
+ $union->add( $from_group );
+ %$self = %$union;
+ bless $self, ref($union);
+
+ return;
+}
+# }}}
+
+# XXX: should be generalized
+sub WhoHaveRoleRight
+{
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Object => undef,
+ IncludeSystemRights => undef,
+ IncludeSuperusers => undef,
+ IncludeSubgroupMembers => 1,
+ EquivObjects => [ ],
+ @_
+ );
+
+ my $groups = $self->_JoinGroups( %args );
+ my $acl = $self->_JoinACL( %args );
+
+ $self->Limit( ALIAS => $acl,
+ FIELD => 'PrincipalType',
+ VALUE => "$groups.Type",
+ QUOTEVALUE => 0,
+ );
+
+ # no system user
+ $self->Limit( ALIAS => $self->PrincipalsAlias,
+ FIELD => 'id',
+ OPERATOR => '!=',
+ VALUE => $RT::SystemUser->id
+ );
+
+ my @objects = $self->_GetEquivObjects( %args );
+ unless ( @objects ) {
+ unless ( $args{'IncludeSystemRights'} ) {
+ $self->_AddSubClause( WhichObjects => "($acl.ObjectType != 'RT::System')" );
+ }
+ return;
}
+
+ my ($groups_clauses, $acl_clauses) = $self->_RoleClauses( $groups, $acl, @objects );
+ $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @$groups_clauses ) .")" );
+ $self->_AddSubClause( "WhichRole", "(". join( ' OR ', @$acl_clauses ) .")" );
+
+ return;
}
-# }}}
+sub _WhoHaveRoleRightSplitted {
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Object => undef,
+ IncludeSystemRights => undef,
+ IncludeSuperusers => undef,
+ IncludeSubgroupMembers => 1,
+ EquivObjects => [ ],
+ @_
+ );
+
+ my $groups = $self->_JoinGroups( %args );
+ my $acl = $self->_JoinACL( %args );
+
+ $self->Limit( ALIAS => $acl,
+ FIELD => 'PrincipalType',
+ VALUE => "$groups.Type",
+ QUOTEVALUE => 0,
+ );
+
+ # no system user
+ $self->Limit( ALIAS => $self->PrincipalsAlias,
+ FIELD => 'id',
+ OPERATOR => '!=',
+ VALUE => $RT::SystemUser->id
+ );
+
+ my @objects = $self->_GetEquivObjects( %args );
+ unless ( @objects ) {
+ unless ( $args{'IncludeSystemRights'} ) {
+ $self->_AddSubClause( WhichObjects => "($acl.ObjectType != 'RT::System')" );
+ }
+ return $self;
+ }
+
+ my ($groups_clauses, $acl_clauses) = $self->_RoleClauses( $groups, $acl, @objects );
+ $self->_AddSubClause( "WhichRole", "(". join( ' OR ', @$acl_clauses ) .")" );
+
+ my @res;
+ foreach ( @$groups_clauses ) {
+ my $tmp = $self->Clone;
+ $tmp->_AddSubClause( WhichObject => $_ );
+ push @res, $tmp;
+ }
+
+ return @res;
+}
+
+sub _RoleClauses {
+ my $self = shift;
+ my $groups = shift;
+ my $acl = shift;
+ my @objects = @_;
+
+ my @groups_clauses;
+ my @acl_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)";
+
+ my $object_clause = "$acl.ObjectType = '$type'";
+ $object_clause .= " AND $acl.ObjectId = $id" if $id;
+ push @acl_clauses, "($object_clause)";
+ }
+ return (\@groups_clauses, \@acl_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,
+ );
+}
-# {{{ WhoBelongToGroups
+# 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
=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);
}
# }}}