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