import rt 3.6.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-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->_WhoHaveRoleRightSplitted( %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( $_ ) foreach @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     $self->Limit( ALIAS => $acl,
472                   FIELD => 'PrincipalType',
473                   VALUE => "$groups.Type",
474                   QUOTEVALUE => 0,
475                 );
476
477     # no system user
478     $self->Limit( ALIAS => $self->PrincipalsAlias,
479                   FIELD => 'id',
480                   OPERATOR => '!=',
481                   VALUE => $RT::SystemUser->id
482                 );
483
484     my @objects = $self->_GetEquivObjects( %args );
485     unless ( @objects ) {
486         unless ( $args{'IncludeSystemRights'} ) {
487             $self->_AddSubClause( WhichObjects => "($acl.ObjectType != 'RT::System')" );
488         }
489         return;
490     }
491
492     my ($groups_clauses, $acl_clauses) = $self->_RoleClauses( $groups, $acl, @objects );
493     $self->_AddSubClause( "WhichObject", "(". join( ' OR ', @$groups_clauses ) .")" );
494     $self->_AddSubClause( "WhichRole", "(". join( ' OR ', @$acl_clauses ) .")" );
495
496     return;
497 }
498
499 sub _WhoHaveRoleRightSplitted {
500     my $self = shift;
501     my %args = (
502         Right                  => undef,
503         Object                 => undef,
504         IncludeSystemRights    => undef,
505         IncludeSuperusers      => undef,
506         IncludeSubgroupMembers => 1,
507         EquivObjects           => [ ],
508         @_
509     );
510
511     my $groups = $self->_JoinGroups( %args );
512     my $acl = $self->_JoinACL( %args );
513
514     $self->Limit( ALIAS => $acl,
515                   FIELD => 'PrincipalType',
516                   VALUE => "$groups.Type",
517                   QUOTEVALUE => 0,
518                 );
519
520     # no system user
521     $self->Limit( ALIAS => $self->PrincipalsAlias,
522                   FIELD => 'id',
523                   OPERATOR => '!=',
524                   VALUE => $RT::SystemUser->id
525                 );
526
527     my @objects = $self->_GetEquivObjects( %args );
528     unless ( @objects ) {
529         unless ( $args{'IncludeSystemRights'} ) {
530             $self->_AddSubClause( WhichObjects => "($acl.ObjectType != 'RT::System')" );
531         }
532         return $self;
533     }
534
535     my ($groups_clauses, $acl_clauses) = $self->_RoleClauses( $groups, $acl, @objects );
536     $self->_AddSubClause( "WhichRole", "(". join( ' OR ', @$acl_clauses ) .")" );
537     
538     my @res;
539     foreach ( @$groups_clauses ) {
540         my $tmp = $self->Clone;
541         $tmp->_AddSubClause( WhichObject => $_ );
542         push @res, $tmp;
543     }
544
545     return @res;
546 }
547
548 sub _RoleClauses {
549     my $self = shift;
550     my $groups = shift;
551     my $acl = shift;
552     my @objects = @_;
553
554     my @groups_clauses;
555     my @acl_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 $role_clause = "$groups.Domain = '$type-Role'";
562         # XXX: Groups.Instance is VARCHAR in DB, we should quote value
563         # if we want mysql 4.0 use indexes here. we MUST convert that
564         # field to integer and drop this quotes.
565         $role_clause   .= " AND $groups.Instance = '$id'" if $id;
566         push @groups_clauses, "($role_clause)";
567
568         my $object_clause = "$acl.ObjectType = '$type'";
569         $object_clause   .= " AND $acl.ObjectId = $id" if $id;
570         push @acl_clauses, "($object_clause)";
571     }
572     return (\@groups_clauses, \@acl_clauses);
573 }
574
575 # XXX: should be generalized
576 sub _JoinGroupMembersForGroupRights
577 {
578     my $self = shift;
579     my %args = (@_);
580     my $group_members = $self->_JoinGroupMembers( %args );
581     $self->Limit( ALIAS => $args{'ACLAlias'},
582                   FIELD => 'PrincipalId',
583                   VALUE => "$group_members.GroupId",
584                   QUOTEVALUE => 0,
585                 );
586 }
587
588 # XXX: should be generalized
589 sub WhoHaveGroupRight
590 {
591     my $self = shift;
592     my %args = (
593         Right                  => undef,
594         Object                 => undef,
595         IncludeSystemRights    => undef,
596         IncludeSuperusers      => undef,
597         IncludeSubgroupMembers => 1,
598         EquivObjects           => [ ],
599         @_
600     );
601
602     # Find only rows where the right granted is
603     # the one we're looking up or _possibly_ superuser
604     my $acl = $self->_JoinACL( %args );
605
606     my ($check_objects) = ('');
607     my @objects = $self->_GetEquivObjects( %args );
608
609     if ( @objects ) {
610         my @object_clauses;
611         foreach my $obj ( @objects ) {
612             my $type = ref($obj)? ref($obj): $obj;
613             my $id;
614             $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
615
616             my $object_clause = "$acl.ObjectType = '$type'";
617             $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
618             push @object_clauses, "($object_clause)";
619         }
620
621         $check_objects = join ' OR ', @object_clauses;
622     } else {
623         if( !$args{'IncludeSystemRights'} ) {
624             $check_objects = "($acl.ObjectType != 'RT::System')";
625         }
626     }
627     $self->_AddSubClause( "WhichObject", "($check_objects)" );
628     
629     $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
630     # Find only members of groups that have the right.
631     $self->Limit( ALIAS => $acl,
632                   FIELD => 'PrincipalType',
633                   VALUE => 'Group',
634                 );
635     
636     # no system user
637     $self->Limit( ALIAS => $self->PrincipalsAlias,
638                   FIELD => 'id',
639                   OPERATOR => '!=',
640                   VALUE => $RT::SystemUser->id
641                 );
642     return;
643 }
644
645 # {{{ WhoBelongToGroups
646
647 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
648
649 =cut
650
651 # XXX: should be generalized
652 sub WhoBelongToGroups {
653     my $self = shift;
654     my %args = ( Groups                 => undef,
655                  IncludeSubgroupMembers => 1,
656                  @_ );
657
658     # Unprivileged users can't be granted real system rights.
659     # is this really the right thing to be saying?
660     $self->LimitToPrivileged();
661
662     my $group_members = $self->_JoinGroupMembers( %args );
663
664     foreach my $groupid (@{$args{'Groups'}}) {
665         $self->Limit( ALIAS           => $group_members,
666                       FIELD           => 'GroupId',
667                       VALUE           => $groupid,
668                       QUOTEVALUE      => 0,
669                       ENTRYAGGREGATOR => 'OR',
670                     );
671     }
672 }
673 # }}}
674
675
676 1;