RT 3.8.17
[freeside.git] / rt / lib / RT / Users_Overlay.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 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     $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 # {{{ LimitToPrivileged
204
205 =head2 LimitToPrivileged
206
207 Limits to users who can be made members of ACLs and groups
208
209 =cut
210
211 sub LimitToPrivileged {
212     my $self = shift;
213
214     my $priv = RT::Group->new( $self->CurrentUser );
215     $priv->LoadSystemInternalGroup('Privileged');
216     unless ( $priv->Id ) {
217         $RT::Logger->crit("Couldn't find a privileged users group");
218     }
219     $self->MemberOfGroup( $priv->PrincipalId );
220 }
221
222 # }}}
223
224 # {{{ WhoHaveRight
225
226 =head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] }
227
228
229 find all users who the right Right for this group, either individually
230 or as members of groups
231
232 If passed a queue object, with no id, it will find users who have that right for _any_ queue
233
234 =cut
235
236 # XXX: should be generalized
237 sub _JoinGroupMembers
238 {
239     my $self = shift;
240     my %args = (
241         IncludeSubgroupMembers => 1,
242         @_
243     );
244
245     my $principals = $self->PrincipalsAlias;
246
247     # The cachedgroupmembers table is used for unrolling group memberships
248     # to allow fast lookups. if we bind to CachedGroupMembers, we'll find
249     # all members of groups recursively. if we don't we'll find only 'direct'
250     # members of the group in question
251     my $group_members;
252     if ( $args{'IncludeSubgroupMembers'} ) {
253         $group_members = $self->NewAlias('CachedGroupMembers');
254     }
255     else {
256         $group_members = $self->NewAlias('GroupMembers');
257     }
258
259     $self->Join(
260         ALIAS1 => $group_members,
261         FIELD1 => 'MemberId',
262         ALIAS2 => $principals,
263         FIELD2 => 'id'
264     );
265     $self->Limit(
266         ALIAS => $group_members,
267         FIELD => 'Disabled',
268         VALUE => 0,
269     ) if $args{'IncludeSubgroupMembers'};
270
271     return $group_members;
272 }
273
274 # XXX: should be generalized
275 sub _JoinGroups
276 {
277     my $self = shift;
278     my %args = (@_);
279
280     my $group_members = $self->_JoinGroupMembers( %args );
281     my $groups = $self->NewAlias('Groups');
282     $self->Join(
283         ALIAS1 => $groups,
284         FIELD1 => 'id',
285         ALIAS2 => $group_members,
286         FIELD2 => 'GroupId'
287     );
288
289     return $groups;
290 }
291
292 # XXX: should be generalized
293 sub _JoinACL
294 {
295     my $self = shift;
296     my %args = (
297         Right                  => undef,
298         IncludeSuperusers      => undef,
299         @_,
300     );
301
302     if ( $args{'Right'} ) {
303         my $canonic = RT::ACE->CanonicalizeRightName( $args{'Right'} );
304         unless ( $canonic ) {
305             $RT::Logger->error("Invalid right. Couldn't canonicalize right '$args{'Right'}'");
306         }
307         else {
308             $args{'Right'} = $canonic;
309         }
310     }
311
312     my $acl = $self->NewAlias('ACL');
313     $self->Limit(
314         ALIAS    => $acl,
315         FIELD    => 'RightName',
316         OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ),
317         VALUE => $args{Right} || 'NULL',
318         ENTRYAGGREGATOR => 'OR'
319     );
320     if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
321         $self->Limit(
322             ALIAS           => $acl,
323             FIELD           => 'RightName',
324             OPERATOR        => '=',
325             VALUE           => 'SuperUser',
326             ENTRYAGGREGATOR => 'OR'
327         );
328     }
329     return $acl;
330 }
331
332 # XXX: should be generalized
333 sub _GetEquivObjects
334 {
335     my $self = shift;
336     my %args = (
337         Object                 => undef,
338         IncludeSystemRights    => undef,
339         EquivObjects           => [ ],
340         @_
341     );
342     return () unless $args{'Object'};
343
344     my @objects = ($args{'Object'});
345     if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) {
346         # If we're looking at ticket rights, we also want to look at the associated queue rights.
347         # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
348         # we load the queue object and ask all the rest of our questions about the queue.
349
350         # XXX: This should be abstracted into object itself
351         if( $args{'Object'}->id ) {
352             push @objects, $args{'Object'}->ACLEquivalenceObjects;
353         } else {
354             push @objects, 'RT::Queue';
355         }
356     }
357
358     if( $args{'IncludeSystemRights'} ) {
359         push @objects, 'RT::System';
360     }
361     push @objects, @{ $args{'EquivObjects'} };
362     return grep $_, @objects;
363 }
364
365 # XXX: should be generalized
366 sub WhoHaveRight {
367     my $self = shift;
368     my %args = (
369         Right                  => undef,
370         Object                 => undef,
371         IncludeSystemRights    => undef,
372         IncludeSuperusers      => undef,
373         IncludeSubgroupMembers => 1,
374         EquivObjects           => [ ],
375         @_
376     );
377
378     if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
379         $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
380         return (undef);
381     }
382
383     my $from_role = $self->Clone;
384     $from_role->WhoHaveRoleRight( %args );
385
386     my $from_group = $self->Clone;
387     $from_group->WhoHaveGroupRight( %args );
388
389     #XXX: DIRTY HACK
390     use DBIx::SearchBuilder 1.50; #no version on ::Union :(
391     use DBIx::SearchBuilder::Union;
392     my $union = new DBIx::SearchBuilder::Union;
393     $union->add( $from_group );
394     $union->add( $from_role );
395     %$self = %$union;
396     bless $self, ref($union);
397
398     return;
399 }
400 # }}}
401
402 # XXX: should be generalized
403 sub WhoHaveRoleRight
404 {
405     my $self = shift;
406     my %args = (
407         Right                  => undef,
408         Object                 => undef,
409         IncludeSystemRights    => undef,
410         IncludeSuperusers      => undef,
411         IncludeSubgroupMembers => 1,
412         EquivObjects           => [ ],
413         @_
414     );
415
416     my @objects = $self->_GetEquivObjects( %args );
417
418     # RT::Principal->RolesWithRight only expects EquivObjects, so we need to
419     # fill it.  At the very least it needs $args{Object}, which
420     # _GetEquivObjects above does for us.
421     unshift @{$args{'EquivObjects'}}, @objects;
422
423     my @roles = RT::Principal->RolesWithRight( %args );
424     unless ( @roles ) {
425         $self->_AddSubClause( "WhichRole", "(main.id = 0)" );
426         return;
427     }
428
429     my $groups = $self->_JoinGroups( %args );
430
431     # no system user
432     $self->Limit( ALIAS => $self->PrincipalsAlias,
433                   FIELD => 'id',
434                   OPERATOR => '!=',
435                   VALUE => $RT::SystemUser->id
436                 );
437
438     $self->_AddSubClause( "WhichRole", "(". join( ' OR ', map "$groups.Type = '$_'", @roles ) .")" );
439
440     my @groups_clauses = $self->_RoleClauses( $groups, @objects );
441     $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @groups_clauses ) .")" )
442         if @groups_clauses;
443
444     return;
445 }
446
447 sub _RoleClauses {
448     my $self = shift;
449     my $groups = shift;
450     my @objects = @_;
451
452     my @groups_clauses;
453     foreach my $obj ( @objects ) {
454         my $type = ref($obj)? ref($obj): $obj;
455         my $id;
456         $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
457
458         my $role_clause = "$groups.Domain = '$type-Role'";
459         # XXX: Groups.Instance is VARCHAR in DB, we should quote value
460         # if we want mysql 4.0 use indexes here. we MUST convert that
461         # field to integer and drop this quotes.
462         $role_clause   .= " AND $groups.Instance = '$id'" if $id;
463         push @groups_clauses, "($role_clause)";
464     }
465     return @groups_clauses;
466 }
467
468 # XXX: should be generalized
469 sub _JoinGroupMembersForGroupRights
470 {
471     my $self = shift;
472     my %args = (@_);
473     my $group_members = $self->_JoinGroupMembers( %args );
474     $self->Limit( ALIAS => $args{'ACLAlias'},
475                   FIELD => 'PrincipalId',
476                   VALUE => "$group_members.GroupId",
477                   QUOTEVALUE => 0,
478                 );
479 }
480
481 # XXX: should be generalized
482 sub WhoHaveGroupRight
483 {
484     my $self = shift;
485     my %args = (
486         Right                  => undef,
487         Object                 => undef,
488         IncludeSystemRights    => undef,
489         IncludeSuperusers      => undef,
490         IncludeSubgroupMembers => 1,
491         EquivObjects           => [ ],
492         @_
493     );
494
495     # Find only rows where the right granted is
496     # the one we're looking up or _possibly_ superuser
497     my $acl = $self->_JoinACL( %args );
498
499     my ($check_objects) = ('');
500     my @objects = $self->_GetEquivObjects( %args );
501
502     if ( @objects ) {
503         my @object_clauses;
504         foreach my $obj ( @objects ) {
505             my $type = ref($obj)? ref($obj): $obj;
506             my $id;
507             $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
508
509             my $object_clause = "$acl.ObjectType = '$type'";
510             $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
511             push @object_clauses, "($object_clause)";
512         }
513
514         $check_objects = join ' OR ', @object_clauses;
515     } else {
516         if( !$args{'IncludeSystemRights'} ) {
517             $check_objects = "($acl.ObjectType != 'RT::System')";
518         }
519     }
520     $self->_AddSubClause( "WhichObject", "($check_objects)" );
521     
522     $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
523     # Find only members of groups that have the right.
524     $self->Limit( ALIAS => $acl,
525                   FIELD => 'PrincipalType',
526                   VALUE => 'Group',
527                 );
528     
529     # no system user
530     $self->Limit( ALIAS => $self->PrincipalsAlias,
531                   FIELD => 'id',
532                   OPERATOR => '!=',
533                   VALUE => $RT::SystemUser->id
534                 );
535     return;
536 }
537
538 # {{{ WhoBelongToGroups
539
540 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
541
542 =cut
543
544 # XXX: should be generalized
545 sub WhoBelongToGroups {
546     my $self = shift;
547     my %args = ( Groups                 => undef,
548                  IncludeSubgroupMembers => 1,
549                  @_ );
550
551     # Unprivileged users can't be granted real system rights.
552     # is this really the right thing to be saying?
553     $self->LimitToPrivileged();
554
555     my $group_members = $self->_JoinGroupMembers( %args );
556
557     foreach my $groupid (@{$args{'Groups'}}) {
558         $self->Limit( ALIAS           => $group_members,
559                       FIELD           => 'GroupId',
560                       VALUE           => $groupid,
561                       QUOTEVALUE      => 0,
562                       ENTRYAGGREGATOR => 'OR',
563                     );
564     }
565 }
566 # }}}
567
568
569 1;