1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
51 RT::Users - Collection of RT::User objects
72 use base 'RT::SearchBuilder';
81 $self->{'with_disabled_column'} = 1;
83 my @result = $self->SUPER::_Init(@_);
84 # By default, order by name
85 $self->OrderBy( ALIAS => 'main',
89 # XXX: should be generalized
90 $self->{'princalias'} = $self->Join(
93 TABLE2 => 'Principals',
95 $self->Limit( ALIAS => $self->{'princalias'},
96 FIELD => 'PrincipalType',
104 =head2 PrincipalsAlias
106 Returns the string that represents this Users object's primary "Principals" alias.
110 # XXX: should be generalized
111 sub PrincipalsAlias {
113 return($self->{'princalias'});
118 =head2 LimitToEnabled
120 Only find items that haven't been disabled
124 # XXX: should be generalized
128 $self->{'handled_disabled_column'} = 1;
130 ALIAS => $self->PrincipalsAlias,
136 =head2 LimitToDeleted
138 Only find items that have been deleted.
145 $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1;
147 ALIAS => $self->PrincipalsAlias,
157 Takes one argument. an email address. limits the returned set to
165 $self->Limit( FIELD => 'EmailAddress', VALUE => $addr, CASESENSITIVE => 0 );
170 =head2 MemberOfGroup PRINCIPAL_ID
172 takes one argument, a group's principal id. Limits the returned set
173 to members of a given group
181 return $self->loc("No group specified") if ( !defined $group );
183 my $groupalias = $self->NewAlias('CachedGroupMembers');
185 # Join the principal to the groups table
186 $self->Join( ALIAS1 => $self->PrincipalsAlias,
188 ALIAS2 => $groupalias,
189 FIELD2 => 'MemberId' );
190 $self->Limit( ALIAS => $groupalias,
194 $self->Limit( ALIAS => "$groupalias",
202 =head2 LimitToPrivileged
204 Limits to users who can be made members of ACLs and groups
208 sub LimitToPrivileged {
210 $self->MemberOfGroup( RT->PrivilegedUsers->id );
213 =head2 LimitToUnprivileged
215 Limits to unprivileged users only
219 sub LimitToUnprivileged {
221 $self->MemberOfGroup( RT->UnprivilegedUsers->id);
228 $args{'CASESENSITIVE'} = 0 unless exists $args{'CASESENSITIVE'} or $args{'ALIAS'};
229 return $self->SUPER::Limit( %args );
232 =head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] }
235 find all users who the right Right for this group, either individually
236 or as members of groups
238 If passed a queue object, with no id, it will find users who have that right for _any_ queue
242 # XXX: should be generalized
243 sub _JoinGroupMembers
247 IncludeSubgroupMembers => 1,
251 my $principals = $self->PrincipalsAlias;
253 # The cachedgroupmembers table is used for unrolling group memberships
254 # to allow fast lookups. if we bind to CachedGroupMembers, we'll find
255 # all members of groups recursively. if we don't we'll find only 'direct'
256 # members of the group in question
258 if ( $args{'IncludeSubgroupMembers'} ) {
259 $group_members = $self->NewAlias('CachedGroupMembers');
262 $group_members = $self->NewAlias('GroupMembers');
266 ALIAS1 => $group_members,
267 FIELD1 => 'MemberId',
268 ALIAS2 => $principals,
272 ALIAS => $group_members,
275 ) if $args{'IncludeSubgroupMembers'};
277 return $group_members;
280 # XXX: should be generalized
286 my $group_members = $self->_JoinGroupMembers( %args );
287 my $groups = $self->NewAlias('Groups');
291 ALIAS2 => $group_members,
298 # XXX: should be generalized
304 IncludeSuperusers => undef,
308 if ( $args{'Right'} ) {
309 my $canonic = RT::ACE->CanonicalizeRightName( $args{'Right'} );
310 unless ( $canonic ) {
311 $RT::Logger->error("Invalid right. Couldn't canonicalize right '$args{'Right'}'");
314 $args{'Right'} = $canonic;
318 my $acl = $self->NewAlias('ACL');
321 FIELD => 'RightName',
322 OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ),
323 VALUE => $args{Right} || 'NULL',
324 ENTRYAGGREGATOR => 'OR'
326 if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
329 FIELD => 'RightName',
331 VALUE => 'SuperUser',
332 ENTRYAGGREGATOR => 'OR'
338 # XXX: should be generalized
344 IncludeSystemRights => undef,
348 return () unless $args{'Object'};
350 my @objects = ($args{'Object'});
351 if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) {
352 # If we're looking at ticket rights, we also want to look at the associated queue rights.
353 # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
354 # we load the queue object and ask all the rest of our questions about the queue.
356 # XXX: This should be abstracted into object itself
357 if( $args{'Object'}->id ) {
358 push @objects, $args{'Object'}->ACLEquivalenceObjects;
360 push @objects, 'RT::Queue';
364 if( $args{'IncludeSystemRights'} ) {
365 push @objects, $RT::System;
367 push @objects, @{ $args{'EquivObjects'} };
368 return grep $_, @objects;
371 # XXX: should be generalized
377 IncludeSystemRights => undef,
378 IncludeSuperusers => undef,
379 IncludeSubgroupMembers => 1,
384 if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
385 $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
389 my $from_role = $self->Clone;
390 $from_role->WhoHaveRoleRight( %args );
392 my $from_group = $self->Clone;
393 $from_group->WhoHaveGroupRight( %args );
396 use DBIx::SearchBuilder 1.50; #no version on ::Union :(
397 use DBIx::SearchBuilder::Union;
398 my $union = DBIx::SearchBuilder::Union->new();
399 $union->add( $from_group );
400 $union->add( $from_role );
402 bless $self, ref($union);
407 # XXX: should be generalized
414 IncludeSystemRights => undef,
415 IncludeSuperusers => undef,
416 IncludeSubgroupMembers => 1,
421 my @objects = $self->_GetEquivObjects( %args );
423 # RT::Principal->RolesWithRight only expects EquivObjects, so we need to
424 # fill it. At the very least it needs $args{Object}, which
425 # _GetEquivObjects above does for us.
426 unshift @{$args{'EquivObjects'}}, @objects;
428 my @roles = RT::Principal->RolesWithRight( %args );
430 $self->_AddSubClause( "WhichRole", "(main.id = 0)" );
434 my $groups = $self->_JoinGroups( %args );
437 $self->Limit( ALIAS => $self->PrincipalsAlias,
440 VALUE => RT->SystemUser->id
443 $self->_AddSubClause( "WhichRole", "(". join( ' OR ',
444 map $RT::Handle->__MakeClauseCaseInsensitive("$groups.Name", '=', "'$_'"), @roles
447 my @groups_clauses = $self->_RoleClauses( $groups, @objects );
448 $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @groups_clauses ) .")" )
460 foreach my $obj ( @objects ) {
461 my $type = ref($obj)? ref($obj): $obj;
463 my $role_clause = $RT::Handle->__MakeClauseCaseInsensitive("$groups.Domain", '=', "'$type-Role'");
465 if ( my $id = eval { $obj->id } ) {
466 $role_clause .= " AND $groups.Instance = $id";
468 push @groups_clauses, "($role_clause)";
470 return @groups_clauses;
473 # XXX: should be generalized
474 sub _JoinGroupMembersForGroupRights
478 my $group_members = $self->_JoinGroupMembers( %args );
479 $self->Limit( ALIAS => $args{'ACLAlias'},
480 FIELD => 'PrincipalId',
481 VALUE => "$group_members.GroupId",
484 return $group_members;
487 # XXX: should be generalized
488 sub WhoHaveGroupRight
494 IncludeSystemRights => undef,
495 IncludeSuperusers => undef,
496 IncludeSubgroupMembers => 1,
501 # Find only rows where the right granted is
502 # the one we're looking up or _possibly_ superuser
503 my $acl = $self->_JoinACL( %args );
505 my ($check_objects) = ('');
506 my @objects = $self->_GetEquivObjects( %args );
511 foreach my $obj ( @objects ) {
512 my $type = ref($obj)? ref($obj): $obj;
514 $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
515 next if $seen{"$type-$id"}++;
517 my $object_clause = "$acl.ObjectType = '$type'";
518 $object_clause .= " AND $acl.ObjectId = $id" if $id;
519 push @object_clauses, "($object_clause)";
522 $check_objects = join ' OR ', @object_clauses;
524 if( !$args{'IncludeSystemRights'} ) {
525 $check_objects = "($acl.ObjectType != 'RT::System')";
528 $self->_AddSubClause( "WhichObject", "($check_objects)" );
530 my $group_members = $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
531 # Find only members of groups that have the right.
532 $self->Limit( ALIAS => $acl,
533 FIELD => 'PrincipalType',
538 $self->Limit( ALIAS => $self->PrincipalsAlias,
541 VALUE => RT->SystemUser->id
543 return $group_members;
547 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1, IncludeUnprivileged => 0 }
549 Return members who belong to any of the groups passed in the groups whose IDs
550 are included in the Groups arrayref.
552 If IncludeSubgroupMembers is true (default) then members of any group that's a
553 member of one of the passed groups are returned. If it's cleared then only
554 direct member users are returned.
556 If IncludeUnprivileged is false (default) then only privileged members are
557 returned; otherwise either privileged or unprivileged group members may be
562 sub WhoBelongToGroups {
564 my %args = ( Groups => undef,
565 IncludeSubgroupMembers => 1,
566 IncludeUnprivileged => 0,
569 if (!$args{'IncludeUnprivileged'}) {
570 $self->LimitToPrivileged();
572 my $group_members = $self->_JoinGroupMembers( %args );
575 ALIAS => $group_members,
578 VALUE => [ 0, @{$args{'Groups'}} ],
584 Does a 'simple' search of Users against a specified Term.
586 This Term is compared to a number of fields using various types of SQL
587 comparison operators.
589 Ensures that the returned collection of Users will have a value for Return.
591 This method is passed the following. You must specify a Term and a Return.
593 Privileged - Whether or not to limit to Privileged Users (0 or 1)
594 Fields - Hashref of data - defaults to C<$UserSearchFields> emulate that if you want to override
595 Term - String that is in the fields specified by Fields
596 Return - What field on the User you want to be sure isn't empty
597 Exclude - Array reference of ids to exclude
598 Max - What to limit this collection to
606 Fields => RT->Config->Get('UserSearchFields'),
614 return $self unless defined $args{Return}
615 and defined $args{Term}
616 and length $args{Term};
618 $self->RowsPerPage( $args{Max} );
620 $self->LimitToPrivileged() if $args{Privileged};
622 while (my ($name, $op) = each %{$args{Fields}}) {
624 unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i;
626 if ($name =~ /^CF\.(?:\{(.*)}|(.*))$/) {
627 my $cfname = $1 || $2;
628 my $cf = RT::CustomField->new(RT->SystemUser);
629 my ($ok, $msg) = $cf->LoadByName( Name => $cfname, LookupType => 'RT::User');
631 $self->LimitCustomField(
632 CUSTOMFIELD => $cf->Id,
634 VALUE => $args{Term},
635 ENTRYAGGREGATOR => 'OR',
636 SUBCLAUSE => 'autocomplete',
639 RT->Logger->warning("Asked to search custom field $name but unable to load a User CF with the name $cfname: $msg");
645 VALUE => $args{Term},
646 ENTRYAGGREGATOR => 'OR',
647 SUBCLAUSE => 'autocomplete',
652 # Exclude users we don't want
653 $self->Limit(FIELD => 'id', OPERATOR => 'NOT IN', VALUE => $args{Exclude} )
654 if @{$args{Exclude}};
656 if ( RT->Config->Get('DatabaseType') eq 'Oracle' ) {
658 FIELD => $args{Return},
659 OPERATOR => 'IS NOT',
664 $self->Limit( FIELD => $args{Return}, OPERATOR => '!=', VALUE => '' );
666 FIELD => $args{Return},
667 OPERATOR => 'IS NOT',
669 ENTRYAGGREGATOR => 'AND'
676 RT::Base->_ImportOverlays();