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