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