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