This commit was generated by cvs2svn to compensate for changes in r4407,
[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     $self->Join( ALIAS1 => 'main',
92                  FIELD1 => 'id',
93                  ALIAS2 => $self->{'princalias'},
94                  FIELD2 => 'id' );
95
96     return (@result);
97 }
98
99 # }}}
100
101 =head2 PrincipalsAlias
102
103 Returns the string that represents this Users object's primary "Principals" alias.
104
105
106 =cut
107
108 sub PrincipalsAlias {
109     my $self = shift;
110     return($self->{'princalias'});
111
112 }
113
114
115 # {{{ sub _DoSearch 
116
117 =head2 _DoSearch
118
119   A subclass of DBIx::SearchBuilder::_DoSearch that makes sure that _Disabled rows never get seen unless
120 we're explicitly trying to see them.
121
122 =cut
123
124 sub _DoSearch {
125     my $self = shift;
126
127     #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
128     unless ( $self->{'find_disabled_rows'} ) {
129         $self->LimitToEnabled();
130     }
131     return ( $self->SUPER::_DoSearch(@_) );
132
133 }
134
135 # }}}
136 # {{{ sub LimitToEnabled
137
138 =head2 LimitToEnabled
139
140 Only find items that haven\'t been disabled
141
142 =cut
143
144 sub LimitToEnabled {
145     my $self = shift;
146
147     $self->Limit( ALIAS    => $self->{'princalias'},
148                   FIELD    => 'Disabled',
149                   VALUE    => '0',
150                   OPERATOR => '=' );
151 }
152
153 # }}}
154
155 # {{{ LimitToEmail
156
157 =head2 LimitToEmail
158
159 Takes one argument. an email address. limits the returned set to
160 that email address
161
162 =cut
163
164 sub LimitToEmail {
165     my $self = shift;
166     my $addr = shift;
167     $self->Limit( FIELD => 'EmailAddress', VALUE => "$addr" );
168 }
169
170 # }}}
171
172 # {{{ MemberOfGroup
173
174 =head2 MemberOfGroup PRINCIPAL_ID
175
176 takes one argument, a group's principal id. Limits the returned set
177 to members of a given group
178
179 =cut
180
181 sub MemberOfGroup {
182     my $self  = shift;
183     my $group = shift;
184
185     return $self->loc("No group specified") if ( !defined $group );
186
187     my $groupalias = $self->NewAlias('CachedGroupMembers');
188
189     # Join the principal to the groups table
190     $self->Join( ALIAS1 => $self->{'princalias'},
191                  FIELD1 => 'id',
192                  ALIAS2 => $groupalias,
193                  FIELD2 => 'MemberId' );
194
195     $self->Limit( ALIAS    => "$groupalias",
196                   FIELD    => 'GroupId',
197                   VALUE    => "$group",
198                   OPERATOR => "=" );
199 }
200
201 # }}}
202
203 # {{{ LimitToPrivileged
204
205 =head2 LimitToPrivileged
206
207 Limits to users who can be made members of ACLs and groups
208
209 =cut
210
211 sub LimitToPrivileged {
212     my $self = shift;
213
214     my $priv = RT::Group->new( $self->CurrentUser );
215     $priv->LoadSystemInternalGroup('Privileged');
216     unless ( $priv->Id ) {
217         $RT::Logger->crit("Couldn't find a privileged users group");
218     }
219     $self->MemberOfGroup( $priv->PrincipalId );
220 }
221
222 # }}}
223
224 # {{{ WhoHaveRight
225
226 =head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] }
227
228 =begin testing
229
230 ok(my $users = RT::Users->new($RT::SystemUser));
231 $users->WhoHaveRight(Object =>$RT::System, Right =>'SuperUser');
232 ok($users->Count == 1, "There is one privileged superuser - Found ". $users->Count );
233 # TODO: this wants more testing
234
235 my $RTxUser = RT::User->new($RT::SystemUser);
236 ($id, $msg) = $RTxUser->Create( Name => 'RTxUser', Comments => "RTx extension user", Privileged => 1);
237 ok ($id,$msg);
238
239 my $group = RT::Group->new($RT::SystemUser);
240 $group->LoadACLEquivalenceGroup($RTxUser->PrincipalObj);
241
242 my $RTxSysObj = {};
243 bless $RTxSysObj, 'RTx::System';
244 *RTx::System::Id = sub { 1; };
245 *RTx::System::id = *RTx::System::Id;
246 my $ace = RT::Record->new($RT::SystemUser);
247 $ace->Table('ACL');
248 $ace->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
249 ($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System', ObjectId  => 1 );
250 ok ($id, "ACL for RTxSysObj created");
251
252 my $RTxObj = {};
253 bless $RTxObj, 'RTx::System::Record';
254 *RTx::System::Record::Id = sub { 4; };
255 *RTx::System::Record::id = *RTx::System::Record::Id;
256
257 $users = RT::Users->new($RT::SystemUser);
258 $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxSysObj);
259 is($users->Count, 1, "RTxUserRight found for RTxSysObj");
260
261 $users = RT::Users->new($RT::SystemUser);
262 $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj);
263 is($users->Count, 0, "RTxUserRight not found for RTxObj");
264
265 $users = RT::Users->new($RT::SystemUser);
266 $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj, EquivObjects => [ $RTxSysObj ]);
267 is($users->Count, 1, "RTxUserRight found for RTxObj using EquivObjects");
268
269 $ace = RT::Record->new($RT::SystemUser);
270 $ace->Table('ACL');
271 $ace->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
272 ($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System::Record', ObjectId => 5 );
273 ok ($id, "ACL for RTxObj created");
274
275 my $RTxObj2 = {};
276 bless $RTxObj2, 'RTx::System::Record';
277 *RTx::System::Record::Id = sub { 5; };
278 *RTx::System::Record::id = sub { 5; };
279
280 $users = RT::Users->new($RT::SystemUser);
281 $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2);
282 is($users->Count, 1, "RTxUserRight found for RTxObj2");
283
284 $users = RT::Users->new($RT::SystemUser);
285 $users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2, EquivObjects => [ $RTxSysObj ]);
286 is($users->Count, 1, "RTxUserRight found for RTxObj2");
287
288
289 =end testing
290
291
292 find all users who the right Right for this group, either individually
293 or as members of groups
294
295
296 If passed a queue object, with no id, it will find users who have that right for _any_ queue
297
298
299
300 =cut
301
302 sub WhoHaveRight {
303     my $self = shift;
304     my %args = (
305         Right                  => undef,
306         Object                 => undef,
307         IncludeSystemRights    => undef,
308         IncludeSuperusers      => undef,
309         IncludeSubgroupMembers => 1,
310         EquivObjects           => [ ],
311         @_
312     );
313
314     if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
315         $RT::Logger->crit( "$self WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
316         return (undef);
317     }
318     
319
320     # Find only members of groups that have the right.
321
322     my $acl       = $self->NewAlias('ACL');
323     my $groups    = $self->NewAlias('Groups');
324     my $userprinc = $self->{'princalias'};
325
326 # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups
327 # if we bind to CachedGroupMembers, we'll find all members of groups recursively.
328 # if we don't we'll find only 'direct' members of the group in question
329     my $cgm;
330
331     if ( $args{'IncludeSubgroupMembers'} ) {
332         $cgm = $self->NewAlias('CachedGroupMembers');
333     }
334     else {
335         $cgm = $self->NewAlias('GroupMembers');
336     }
337
338 #Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
339     $self->Join(
340         ALIAS1 => $cgm,
341         FIELD1 => 'MemberId',
342         ALIAS2 => $userprinc,
343         FIELD2 => 'id'
344     );
345
346     $self->Join(
347         ALIAS1 => $groups,
348         FIELD1 => 'id',
349         ALIAS2 => $cgm,
350         FIELD2 => 'GroupId'
351     );
352
353 # {{{ Find only rows where the right granted is the one we're looking up or _possibly_ superuser
354     $self->Limit(
355         ALIAS    => $acl,
356         FIELD    => 'RightName',
357         OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ),
358         VALUE => $args{Right} || 'NULL',
359         ENTRYAGGREGATOR => 'OR'
360     );
361
362     if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
363         $self->Limit(
364             ALIAS           => $acl,
365             FIELD           => 'RightName',
366             OPERATOR        => '=',
367             VALUE           => 'SuperUser',
368             ENTRYAGGREGATOR => 'OR'
369         );
370     }
371
372     # }}}
373
374     my ( $or_check_ticket_roles, $or_check_roles );
375     my $which_object = "$acl.ObjectType = 'RT::System'";
376
377     if ( defined $args{'Object'} ) {
378         if ( ref( $args{'Object'} ) eq 'RT::Ticket' ) {
379             $or_check_ticket_roles = " OR ( $groups.Domain = 'RT::Ticket-Role' AND $groups.Instance = " . $args{'Object'}->Id . ") ";
380
381 # If we're looking at ticket rights, we also want to look at the associated queue rights.
382 # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
383 # we load the queue object and ask all the rest of our questions about the queue.
384             $args{'Object'} = $args{'Object'}->QueueObj;
385         }
386
387         # TODO XXX This really wants some refactoring
388         if ( ref( $args{'Object'} ) eq 'RT::Queue' ) {
389             $or_check_roles = " OR ( ( ($groups.Domain = 'RT::Queue-Role' ";
390             $or_check_roles .= "AND $groups.Instance = " . $args{'Object'}->id if ( $args{'Object'}->id );
391             $or_check_roles .= ") $or_check_ticket_roles ) " . " AND $groups.Type = $acl.PrincipalType) ";
392         }
393         if ( $args{'IncludeSystemRights'} ) {
394             $which_object .= ' OR ';
395         }
396         else {
397             $which_object = '';
398         }
399         foreach my $obj ( @{ $args{'EquivObjects'} } ) {
400             $which_object .= "($acl.ObjectType = '" . ref( $obj ) . "' AND $acl.ObjectId = " . $obj->id . ") OR ";
401         }
402         $which_object .= " ($acl.ObjectType = '" . ref( $args{'Object'} ) . "'";
403         if ( $args{'Object'}->id ) {
404             $which_object .= " AND $acl.ObjectId = " . $args{'Object'}->id;
405         }
406
407         $which_object .=  ") ";
408     }
409     $self->_AddSubClause( "WhichObject", "($which_object)" );
410     $self->_AddSubClause(
411         "WhichGroup",
412             qq{ ( (    $acl.PrincipalId = $groups.id AND $acl.PrincipalType = 'Group' 
413                 AND (   $groups.Domain = 'SystemInternal' OR $groups.Domain = 'UserDefined' OR $groups.Domain = 'ACLEquivalence')) 
414                 $or_check_roles) }
415     );
416     # only include regular RT users
417     $self->LimitToEnabled;
418
419     # no system user
420     $self->Limit( ALIAS => $userprinc, FIELD => 'id', OPERATOR => '!=', VALUE => $RT::SystemUser->id);
421
422 }
423 # }}}
424
425 # {{{ WhoBelongToGroups 
426
427 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
428
429 =cut
430
431 sub WhoBelongToGroups {
432     my $self = shift;
433     my %args = ( Groups                 => undef,
434                  IncludeSubgroupMembers => 1,
435                  @_ );
436
437     # Unprivileged users can't be granted real system rights. 
438     # is this really the right thing to be saying?
439     $self->LimitToPrivileged();
440
441     my $userprinc  = $self->{'princalias'};
442     my $cgm;
443
444     # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups 
445     # if we bind to CachedGroupMembers, we'll find all members of groups recursively.
446     # if we don't we'll find only 'direct' members of the group in question
447
448     if ( $args{'IncludeSubgroupMembers'} ) {
449         $cgm = $self->NewAlias('CachedGroupMembers');
450     }
451     else {
452         $cgm = $self->NewAlias('GroupMembers');
453     }
454
455     #Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
456     $self->Join( ALIAS1 => $cgm, FIELD1 => 'MemberId',
457                  ALIAS2 => $userprinc, FIELD2 => 'id' );
458
459     foreach my $groupid (@{$args{'Groups'}}) {
460         $self->Limit(ALIAS => $cgm, FIELD => 'GroupId', VALUE => $groupid, QUOTEVALUE => 0, ENTRYAGGREGATOR=> 'OR')
461
462     }
463 }
464 # }}}
465
466
467 1;