Upgrade to 4.0.10.
[usit-rt.git] / lib / RT / Users.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 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::Union;
398     my $union = DBIx::SearchBuilder::Union->new();
399     $union->add( $from_group );
400     $union->add( $from_role );
401     %$self = %$union;
402     bless $self, ref($union);
403
404     return;
405 }
406
407 # XXX: should be generalized
408 sub WhoHaveRoleRight
409 {
410     my $self = shift;
411     my %args = (
412         Right                  => undef,
413         Object                 => undef,
414         IncludeSystemRights    => undef,
415         IncludeSuperusers      => undef,
416         IncludeSubgroupMembers => 1,
417         EquivObjects           => [ ],
418         @_
419     );
420
421     my @objects = $self->_GetEquivObjects( %args );
422
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;
427
428     my @roles = RT::Principal->RolesWithRight( %args );
429     unless ( @roles ) {
430         $self->_AddSubClause( "WhichRole", "(main.id = 0)" );
431         return;
432     }
433
434     my $groups = $self->_JoinGroups( %args );
435
436     # no system user
437     $self->Limit( ALIAS => $self->PrincipalsAlias,
438                   FIELD => 'id',
439                   OPERATOR => '!=',
440                   VALUE => RT->SystemUser->id
441                 );
442
443     $self->_AddSubClause( "WhichRole", "(". join( ' OR ', map "$groups.Type = '$_'", @roles ) .")" );
444
445     my @groups_clauses = $self->_RoleClauses( $groups, @objects );
446     $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @groups_clauses ) .")" )
447         if @groups_clauses;
448
449     return;
450 }
451
452 sub _RoleClauses {
453     my $self = shift;
454     my $groups = shift;
455     my @objects = @_;
456
457     my @groups_clauses;
458     foreach my $obj ( @objects ) {
459         my $type = ref($obj)? ref($obj): $obj;
460         my $id;
461         $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
462
463         my $role_clause = "$groups.Domain = '$type-Role'";
464         # XXX: Groups.Instance is VARCHAR in DB, we should quote value
465         # if we want mysql 4.0 use indexes here. we MUST convert that
466         # field to integer and drop this quotes.
467         $role_clause   .= " AND $groups.Instance = '$id'" if $id;
468         push @groups_clauses, "($role_clause)";
469     }
470     return @groups_clauses;
471 }
472
473 # XXX: should be generalized
474 sub _JoinGroupMembersForGroupRights
475 {
476     my $self = shift;
477     my %args = (@_);
478     my $group_members = $self->_JoinGroupMembers( %args );
479     $self->Limit( ALIAS => $args{'ACLAlias'},
480                   FIELD => 'PrincipalId',
481                   VALUE => "$group_members.GroupId",
482                   QUOTEVALUE => 0,
483                 );
484     return $group_members;
485 }
486
487 # XXX: should be generalized
488 sub WhoHaveGroupRight
489 {
490     my $self = shift;
491     my %args = (
492         Right                  => undef,
493         Object                 => undef,
494         IncludeSystemRights    => undef,
495         IncludeSuperusers      => undef,
496         IncludeSubgroupMembers => 1,
497         EquivObjects           => [ ],
498         @_
499     );
500
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 );
504
505     my ($check_objects) = ('');
506     my @objects = $self->_GetEquivObjects( %args );
507
508     if ( @objects ) {
509         my @object_clauses;
510         foreach my $obj ( @objects ) {
511             my $type = ref($obj)? ref($obj): $obj;
512             my $id;
513             $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
514
515             my $object_clause = "$acl.ObjectType = '$type'";
516             $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
517             push @object_clauses, "($object_clause)";
518         }
519
520         $check_objects = join ' OR ', @object_clauses;
521     } else {
522         if( !$args{'IncludeSystemRights'} ) {
523             $check_objects = "($acl.ObjectType != 'RT::System')";
524         }
525     }
526     $self->_AddSubClause( "WhichObject", "($check_objects)" );
527     
528     my $group_members = $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
529     # Find only members of groups that have the right.
530     $self->Limit( ALIAS => $acl,
531                   FIELD => 'PrincipalType',
532                   VALUE => 'Group',
533                 );
534     
535     # no system user
536     $self->Limit( ALIAS => $self->PrincipalsAlias,
537                   FIELD => 'id',
538                   OPERATOR => '!=',
539                   VALUE => RT->SystemUser->id
540                 );
541     return $group_members;
542 }
543
544
545 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
546
547 =cut
548
549 # XXX: should be generalized
550 sub WhoBelongToGroups {
551     my $self = shift;
552     my %args = ( Groups                 => undef,
553                  IncludeSubgroupMembers => 1,
554                  @_ );
555
556     # Unprivileged users can't be granted real system rights.
557     # is this really the right thing to be saying?
558     $self->LimitToPrivileged();
559
560     my $group_members = $self->_JoinGroupMembers( %args );
561
562     foreach my $groupid (@{$args{'Groups'}}) {
563         $self->Limit( ALIAS           => $group_members,
564                       FIELD           => 'GroupId',
565                       VALUE           => $groupid,
566                       QUOTEVALUE      => 0,
567                       ENTRYAGGREGATOR => 'OR',
568                     );
569     }
570 }
571
572
573 =head2 NewItem
574
575 Returns an empty new RT::User item
576
577 =cut
578
579 sub NewItem {
580     my $self = shift;
581     return(RT::User->new($self->CurrentUser));
582 }
583 RT::Base->_ImportOverlays();
584
585 1;