rt 4.0.23
[freeside.git] / rt / lib / RT / Users.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
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
16 # from www.gnu.org.
17 #
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.
22 #
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.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
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.)
37 #
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.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 =head1 NAME
50
51   RT::Users - Collection of RT::User objects
52
53 =head1 SYNOPSIS
54
55   use RT::Users;
56
57
58 =head1 DESCRIPTION
59
60
61 =head1 METHODS
62
63
64 =cut
65
66
67 package RT::Users;
68
69 use strict;
70 use warnings;
71
72 use RT::User;
73
74 use base 'RT::SearchBuilder';
75
76 sub Table { 'Users'}
77
78
79 sub _Init {
80     my $self = shift;
81     $self->{'with_disabled_column'} = 1;
82
83     my @result = $self->SUPER::_Init(@_);
84     # By default, order by name
85     $self->OrderBy( ALIAS => 'main',
86                     FIELD => 'Name',
87                     ORDER => 'ASC' );
88
89     $self->{'princalias'} = $self->NewAlias('Principals');
90
91     # XXX: should be generalized
92     $self->Join( ALIAS1 => 'main',
93                  FIELD1 => 'id',
94                  ALIAS2 => $self->{'princalias'},
95                  FIELD2 => 'id' );
96     $self->Limit( ALIAS => $self->{'princalias'},
97                   FIELD => 'PrincipalType',
98                   VALUE => 'User',
99                 );
100
101     return (@result);
102 }
103
104
105 =head2 PrincipalsAlias
106
107 Returns the string that represents this Users object's primary "Principals" alias.
108
109 =cut
110
111 # XXX: should be generalized
112 sub PrincipalsAlias {
113     my $self = shift;
114     return($self->{'princalias'});
115
116 }
117
118
119 =head2 LimitToEnabled
120
121 Only find items that haven't been disabled
122
123 =cut
124
125 # XXX: should be generalized
126 sub LimitToEnabled {
127     my $self = shift;
128
129     $self->{'handled_disabled_column'} = 1;
130     $self->Limit(
131         ALIAS    => $self->PrincipalsAlias,
132         FIELD    => 'Disabled',
133         VALUE    => '0',
134     );
135 }
136
137 =head2 LimitToDeleted
138
139 Only find items that have been deleted.
140
141 =cut
142
143 sub LimitToDeleted {
144     my $self = shift;
145     
146     $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1;
147     $self->Limit(
148         ALIAS => $self->PrincipalsAlias,
149         FIELD => 'Disabled',
150         VALUE => 1,
151     );
152 }
153
154
155
156 =head2 LimitToEmail
157
158 Takes one argument. an email address. limits the returned set to
159 that email address
160
161 =cut
162
163 sub LimitToEmail {
164     my $self = shift;
165     my $addr = shift;
166     $self->Limit( FIELD => 'EmailAddress', VALUE => "$addr" );
167 }
168
169
170
171 =head2 MemberOfGroup PRINCIPAL_ID
172
173 takes one argument, a group's principal id. Limits the returned set
174 to members of a given group
175
176 =cut
177
178 sub MemberOfGroup {
179     my $self  = shift;
180     my $group = shift;
181
182     return $self->loc("No group specified") if ( !defined $group );
183
184     my $groupalias = $self->NewAlias('CachedGroupMembers');
185
186     # Join the principal to the groups table
187     $self->Join( ALIAS1 => $self->PrincipalsAlias,
188                  FIELD1 => 'id',
189                  ALIAS2 => $groupalias,
190                  FIELD2 => 'MemberId' );
191     $self->Limit( ALIAS => $groupalias,
192                   FIELD => 'Disabled',
193                   VALUE => 0 );
194
195     $self->Limit( ALIAS    => "$groupalias",
196                   FIELD    => 'GroupId',
197                   VALUE    => "$group",
198                   OPERATOR => "=" );
199 }
200
201
202
203 =head2 LimitToPrivileged
204
205 Limits to users who can be made members of ACLs and groups
206
207 =cut
208
209 sub LimitToPrivileged {
210     my $self = shift;
211     $self->MemberOfGroup( RT->PrivilegedUsers->id );
212 }
213
214 =head2 LimitToUnprivileged
215
216 Limits to unprivileged users only
217
218 =cut
219
220 sub LimitToUnprivileged {
221     my $self = shift;
222     $self->MemberOfGroup( RT->UnprivilegedUsers->id);
223 }
224
225
226 sub Limit {
227     my $self = shift;
228     my %args = @_;
229     $args{'CASESENSITIVE'} = 0 unless exists $args{'CASESENSITIVE'};
230     return $self->SUPER::Limit( %args );
231 }
232
233 =head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] }
234
235
236 find all users who the right Right for this group, either individually
237 or as members of groups
238
239 If passed a queue object, with no id, it will find users who have that right for _any_ queue
240
241 =cut
242
243 # XXX: should be generalized
244 sub _JoinGroupMembers
245 {
246     my $self = shift;
247     my %args = (
248         IncludeSubgroupMembers => 1,
249         @_
250     );
251
252     my $principals = $self->PrincipalsAlias;
253
254     # The cachedgroupmembers table is used for unrolling group memberships
255     # to allow fast lookups. if we bind to CachedGroupMembers, we'll find
256     # all members of groups recursively. if we don't we'll find only 'direct'
257     # members of the group in question
258     my $group_members;
259     if ( $args{'IncludeSubgroupMembers'} ) {
260         $group_members = $self->NewAlias('CachedGroupMembers');
261     }
262     else {
263         $group_members = $self->NewAlias('GroupMembers');
264     }
265
266     $self->Join(
267         ALIAS1 => $group_members,
268         FIELD1 => 'MemberId',
269         ALIAS2 => $principals,
270         FIELD2 => 'id'
271     );
272     $self->Limit(
273         ALIAS => $group_members,
274         FIELD => 'Disabled',
275         VALUE => 0,
276     ) if $args{'IncludeSubgroupMembers'};
277
278     return $group_members;
279 }
280
281 # XXX: should be generalized
282 sub _JoinGroups
283 {
284     my $self = shift;
285     my %args = (@_);
286
287     my $group_members = $self->_JoinGroupMembers( %args );
288     my $groups = $self->NewAlias('Groups');
289     $self->Join(
290         ALIAS1 => $groups,
291         FIELD1 => 'id',
292         ALIAS2 => $group_members,
293         FIELD2 => 'GroupId'
294     );
295
296     return $groups;
297 }
298
299 # XXX: should be generalized
300 sub _JoinACL
301 {
302     my $self = shift;
303     my %args = (
304         Right                  => undef,
305         IncludeSuperusers      => undef,
306         @_,
307     );
308
309     if ( $args{'Right'} ) {
310         my $canonic = RT::ACE->CanonicalizeRightName( $args{'Right'} );
311         unless ( $canonic ) {
312             $RT::Logger->error("Invalid right. Couldn't canonicalize right '$args{'Right'}'");
313         }
314         else {
315             $args{'Right'} = $canonic;
316         }
317     }
318
319     my $acl = $self->NewAlias('ACL');
320     $self->Limit(
321         ALIAS    => $acl,
322         FIELD    => 'RightName',
323         OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ),
324         VALUE => $args{Right} || 'NULL',
325         ENTRYAGGREGATOR => 'OR'
326     );
327     if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
328         $self->Limit(
329             ALIAS           => $acl,
330             FIELD           => 'RightName',
331             OPERATOR        => '=',
332             VALUE           => 'SuperUser',
333             ENTRYAGGREGATOR => 'OR'
334         );
335     }
336     return $acl;
337 }
338
339 # XXX: should be generalized
340 sub _GetEquivObjects
341 {
342     my $self = shift;
343     my %args = (
344         Object                 => undef,
345         IncludeSystemRights    => undef,
346         EquivObjects           => [ ],
347         @_
348     );
349     return () unless $args{'Object'};
350
351     my @objects = ($args{'Object'});
352     if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) {
353         # If we're looking at ticket rights, we also want to look at the associated queue rights.
354         # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
355         # we load the queue object and ask all the rest of our questions about the queue.
356
357         # XXX: This should be abstracted into object itself
358         if( $args{'Object'}->id ) {
359             push @objects, $args{'Object'}->ACLEquivalenceObjects;
360         } else {
361             push @objects, 'RT::Queue';
362         }
363     }
364
365     if( $args{'IncludeSystemRights'} ) {
366         push @objects, 'RT::System';
367     }
368     push @objects, @{ $args{'EquivObjects'} };
369     return grep $_, @objects;
370 }
371
372 # XXX: should be generalized
373 sub WhoHaveRight {
374     my $self = shift;
375     my %args = (
376         Right                  => undef,
377         Object                 => undef,
378         IncludeSystemRights    => undef,
379         IncludeSuperusers      => undef,
380         IncludeSubgroupMembers => 1,
381         EquivObjects           => [ ],
382         @_
383     );
384
385     if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
386         $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
387         return (undef);
388     }
389
390     my $from_role = $self->Clone;
391     $from_role->WhoHaveRoleRight( %args );
392
393     my $from_group = $self->Clone;
394     $from_group->WhoHaveGroupRight( %args );
395
396     #XXX: DIRTY HACK
397     use DBIx::SearchBuilder 1.50; #no version on ::Union :(
398     use DBIx::SearchBuilder::Union;
399     my $union = DBIx::SearchBuilder::Union->new();
400     $union->add( $from_group );
401     $union->add( $from_role );
402     %$self = %$union;
403     bless $self, ref($union);
404
405     return;
406 }
407
408 # XXX: should be generalized
409 sub WhoHaveRoleRight
410 {
411     my $self = shift;
412     my %args = (
413         Right                  => undef,
414         Object                 => undef,
415         IncludeSystemRights    => undef,
416         IncludeSuperusers      => undef,
417         IncludeSubgroupMembers => 1,
418         EquivObjects           => [ ],
419         @_
420     );
421
422     my @objects = $self->_GetEquivObjects( %args );
423
424     # RT::Principal->RolesWithRight only expects EquivObjects, so we need to
425     # fill it.  At the very least it needs $args{Object}, which
426     # _GetEquivObjects above does for us.
427     unshift @{$args{'EquivObjects'}}, @objects;
428
429     my @roles = RT::Principal->RolesWithRight( %args );
430     unless ( @roles ) {
431         $self->_AddSubClause( "WhichRole", "(main.id = 0)" );
432         return;
433     }
434
435     my $groups = $self->_JoinGroups( %args );
436
437     # no system user
438     $self->Limit( ALIAS => $self->PrincipalsAlias,
439                   FIELD => 'id',
440                   OPERATOR => '!=',
441                   VALUE => RT->SystemUser->id
442                 );
443
444     $self->_AddSubClause( "WhichRole", "(". join( ' OR ', map "$groups.Type = '$_'", @roles ) .")" );
445
446     my @groups_clauses = $self->_RoleClauses( $groups, @objects );
447     $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @groups_clauses ) .")" )
448         if @groups_clauses;
449
450     return;
451 }
452
453 sub _RoleClauses {
454     my $self = shift;
455     my $groups = shift;
456     my @objects = @_;
457
458     my @groups_clauses;
459     foreach my $obj ( @objects ) {
460         my $type = ref($obj)? ref($obj): $obj;
461         my $id;
462         $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
463
464         my $role_clause = "$groups.Domain = '$type-Role'";
465         # XXX: Groups.Instance is VARCHAR in DB, we should quote value
466         # if we want mysql 4.0 use indexes here. we MUST convert that
467         # field to integer and drop this quotes.
468         $role_clause   .= " AND $groups.Instance = '$id'" if $id;
469         push @groups_clauses, "($role_clause)";
470     }
471     return @groups_clauses;
472 }
473
474 # XXX: should be generalized
475 sub _JoinGroupMembersForGroupRights
476 {
477     my $self = shift;
478     my %args = (@_);
479     my $group_members = $self->_JoinGroupMembers( %args );
480     $self->Limit( ALIAS => $args{'ACLAlias'},
481                   FIELD => 'PrincipalId',
482                   VALUE => "$group_members.GroupId",
483                   QUOTEVALUE => 0,
484                 );
485     return $group_members;
486 }
487
488 # XXX: should be generalized
489 sub WhoHaveGroupRight
490 {
491     my $self = shift;
492     my %args = (
493         Right                  => undef,
494         Object                 => undef,
495         IncludeSystemRights    => undef,
496         IncludeSuperusers      => undef,
497         IncludeSubgroupMembers => 1,
498         EquivObjects           => [ ],
499         @_
500     );
501
502     # Find only rows where the right granted is
503     # the one we're looking up or _possibly_ superuser
504     my $acl = $self->_JoinACL( %args );
505
506     my ($check_objects) = ('');
507     my @objects = $self->_GetEquivObjects( %args );
508
509     if ( @objects ) {
510         my @object_clauses;
511         foreach my $obj ( @objects ) {
512             my $type = ref($obj)? ref($obj): $obj;
513             my $id;
514             $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
515
516             my $object_clause = "$acl.ObjectType = '$type'";
517             $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
518             push @object_clauses, "($object_clause)";
519         }
520
521         $check_objects = join ' OR ', @object_clauses;
522     } else {
523         if( !$args{'IncludeSystemRights'} ) {
524             $check_objects = "($acl.ObjectType != 'RT::System')";
525         }
526     }
527     $self->_AddSubClause( "WhichObject", "($check_objects)" );
528     
529     my $group_members = $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
530     # Find only members of groups that have the right.
531     $self->Limit( ALIAS => $acl,
532                   FIELD => 'PrincipalType',
533                   VALUE => 'Group',
534                 );
535     
536     # no system user
537     $self->Limit( ALIAS => $self->PrincipalsAlias,
538                   FIELD => 'id',
539                   OPERATOR => '!=',
540                   VALUE => RT->SystemUser->id
541                 );
542     return $group_members;
543 }
544
545
546 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1, IncludeUnprivileged => 0 }
547
548 Return members who belong to any of the groups passed in the groups whose IDs
549 are included in the Groups arrayref.
550
551 If IncludeSubgroupMembers is true (default) then members of any group that's a
552 member of one of the passed groups are returned. If it's cleared then only
553 direct member users are returned.
554
555 If IncludeUnprivileged is false (default) then only privileged members are
556 returned; otherwise either privileged or unprivileged group members may be
557 returned.
558
559 =cut
560
561 sub WhoBelongToGroups {
562     my $self = shift;
563     my %args = ( Groups                 => undef,
564                  IncludeSubgroupMembers => 1,
565                  IncludeUnprivileged    => 0,
566                  @_ );
567
568     if (!$args{'IncludeUnprivileged'}) {
569         $self->LimitToPrivileged();
570     }
571     my $group_members = $self->_JoinGroupMembers( %args );
572
573     foreach my $groupid (@{$args{'Groups'}}) {
574         $self->Limit( ALIAS           => $group_members,
575                       FIELD           => 'GroupId',
576                       VALUE           => $groupid,
577                       QUOTEVALUE      => 0,
578                       ENTRYAGGREGATOR => 'OR',
579                     );
580     }
581 }
582
583
584 =head2 NewItem
585
586 Returns an empty new RT::User item
587
588 =cut
589
590 sub NewItem {
591     my $self = shift;
592     return(RT::User->new($self->CurrentUser));
593 }
594 RT::Base->_ImportOverlays();
595
596 1;