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