rt 4.2.14 (#13852)
[freeside.git] / rt / lib / RT / Groups.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2017 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::Groups - a collection of RT::Group objects
52
53 =head1 SYNOPSIS
54
55   use RT::Groups;
56   my $groups = RT::Groups->new($CurrentUser);
57   $groups->UnLimit();
58   while (my $group = $groups->Next()) {
59      print $group->Id ." is a group id\n";
60   }
61
62 =head1 DESCRIPTION
63
64
65 =head1 METHODS
66
67
68
69 =cut
70
71
72 package RT::Groups;
73
74 use strict;
75 use warnings;
76
77 use base 'RT::SearchBuilder';
78
79 sub Table { 'Groups'}
80
81 use RT::Group;
82 use RT::Users;
83
84 # XXX: below some code is marked as subject to generalize in Groups, Users classes.
85 # RUZ suggest name Principals::Generic or Principals::Base as abstract class, but
86 # Jesse wants something that doesn't imply it's a Principals.pm subclass.
87 # See comments below for candidats.
88
89
90
91 sub _Init { 
92   my $self = shift;
93   $self->{'with_disabled_column'} = 1;
94
95   my @result = $self->SUPER::_Init(@_);
96
97   $self->OrderBy( ALIAS => 'main',
98                   FIELD => 'Name',
99                   ORDER => 'ASC');
100
101   # XXX: this code should be generalized
102   $self->{'princalias'} = $self->Join(
103     ALIAS1 => 'main',
104     FIELD1 => 'id',
105     TABLE2 => 'Principals',
106     FIELD2 => 'id'
107   );
108
109   # even if this condition is useless and ids in the Groups table
110   # only match principals with type 'Group' this could speed up
111   # searches in some DBs.
112   $self->Limit( ALIAS => $self->{'princalias'},
113                 FIELD => 'PrincipalType',
114                 VALUE => 'Group',
115               );
116
117   return (@result);
118 }
119
120 =head2 PrincipalsAlias
121
122 Returns the string that represents this Users object's primary "Principals" alias.
123
124 =cut
125
126 # XXX: should be generalized, code duplication
127 sub PrincipalsAlias {
128     my $self = shift;
129     return($self->{'princalias'});
130
131 }
132
133
134
135 =head2 LimitToSystemInternalGroups
136
137 Return only SystemInternal Groups, such as "privileged" "unprivileged" and "everyone" 
138
139 =cut
140
141
142 sub LimitToSystemInternalGroups {
143     my $self = shift;
144     $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'SystemInternal', CASESENSITIVE => 0 );
145     # All system internal groups have the same instance. No reason to limit down further
146     #$self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => '0');
147 }
148
149
150
151
152 =head2 LimitToUserDefinedGroups
153
154 Return only UserDefined Groups
155
156 =cut
157
158
159 sub LimitToUserDefinedGroups {
160     my $self = shift;
161     $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined', CASESENSITIVE => 0 );
162     # All user-defined groups have the same instance. No reason to limit down further
163     #$self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => '');
164 }
165
166 =head2 LimitToRolesForObject OBJECT
167
168 Limits the set of groups to role groups specifically for the object in question
169 based on the object's class and ID.  If the object has no ID, the roles are not
170 limited by group C<Instance>.  That is, calling this method on an unloaded
171 object will find all role groups for that class of object.
172
173 Replaces L</LimitToRolesForQueue>, L</LimitToRolesForTicket>, and
174 L</LimitToRolesForSystem>.
175
176 =cut
177
178 sub LimitToRolesForObject {
179     my $self   = shift;
180     my $object = shift;
181     $self->Limit(FIELD => 'Domain',   OPERATOR => '=', VALUE => ref($object) . "-Role", CASESENSITIVE => 0 );
182     $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => $object->id);
183 }
184
185 =head2 LimitToRolesForQueue QUEUE_ID
186
187 B<DEPRECATED>. Use L</LimitToRolesForObject> instead.
188
189 Limits the set of groups found to role groups for queue QUEUE_ID
190
191 =cut
192
193 sub LimitToRolesForQueue {
194     my $self = shift;
195     my $queue = shift;
196     RT->Deprecated(
197         Instead => "LimitToRolesForObject",
198         Remove => "4.4",
199     );
200     $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'RT::Queue-Role', CASESENSITIVE => 0 );
201     $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => $queue);
202 }
203
204
205
206 =head2 LimitToRolesForTicket Ticket_ID
207
208 B<DEPRECATED>. Use L</LimitToRolesForObject> instead.
209
210 Limits the set of groups found to role groups for Ticket Ticket_ID
211
212 =cut
213
214 sub LimitToRolesForTicket {
215     my $self = shift;
216     my $Ticket = shift;
217     RT->Deprecated(
218         Instead => "LimitToRolesForObject",
219         Remove => "4.4",
220     );
221     $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'RT::Ticket-Role', CASESENSITIVE => 0 );
222     $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => $Ticket);
223 }
224
225
226
227 =head2 LimitToRolesForSystem System_ID
228
229 B<DEPRECATED>. Use L</LimitToRolesForObject> instead.
230
231 Limits the set of groups found to role groups for System System_ID
232
233 =cut
234
235 sub LimitToRolesForSystem {
236     my $self = shift;
237     RT->Deprecated(
238         Instead => "LimitToRolesForObject",
239         Remove => "4.4",
240     );
241     $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'RT::System-Role', CASESENSITIVE => 0 );
242     $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => RT::System->Id );
243 }
244
245
246 =head2 WithMember {PrincipalId => PRINCIPAL_ID, Recursively => undef}
247
248 Limits the set of groups returned to groups which have
249 Principal PRINCIPAL_ID as a member. Returns the alias used for the join.
250
251 =cut
252
253 sub WithMember {
254     my $self = shift;
255     my %args = ( PrincipalId => undef,
256                  Recursively => undef,
257                  @_);
258     my $members = $self->Join(
259         ALIAS1 => 'main', FIELD1 => 'id',
260         $args{'Recursively'}
261             ? (TABLE2 => 'CachedGroupMembers')
262             # (GroupId, MemberId) is unique in GM table
263             : (TABLE2 => 'GroupMembers', DISTINCT => 1)
264         ,
265         FIELD2 => 'GroupId',
266     );
267
268     $self->Limit(ALIAS => $members, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'PrincipalId'});
269     $self->Limit(ALIAS => $members, FIELD => 'Disabled', VALUE => 0)
270         if $args{'Recursively'};
271
272     return $members;
273 }
274
275 sub WithCurrentUser {
276     my $self = shift;
277     $self->{with_current_user} = 1;
278     return $self->WithMember(
279         PrincipalId => $self->CurrentUser->PrincipalId,
280         Recursively => 1,
281     );
282 }
283
284 sub WithoutMember {
285     my $self = shift;
286     my %args = (
287         PrincipalId => undef,
288         Recursively => undef,
289         @_
290     );
291
292     my $members = $args{'Recursively'} ? 'CachedGroupMembers' : 'GroupMembers';
293     my $members_alias = $self->Join(
294         TYPE   => 'LEFT',
295         FIELD1 => 'id',
296         TABLE2 => $members,
297         FIELD2 => 'GroupId',
298         DISTINCT => $members eq 'GroupMembers',
299     );
300     $self->Limit(
301         LEFTJOIN => $members_alias,
302         ALIAS    => $members_alias,
303         FIELD    => 'MemberId',
304         OPERATOR => '=',
305         VALUE    => $args{'PrincipalId'},
306     );
307     $self->Limit(
308         LEFTJOIN => $members_alias,
309         ALIAS    => $members_alias,
310         FIELD    => 'Disabled',
311         VALUE    => 0
312     ) if $args{'Recursively'};
313     $self->Limit(
314         ALIAS    => $members_alias,
315         FIELD    => 'MemberId',
316         OPERATOR => 'IS',
317         VALUE    => 'NULL',
318         QUOTEVALUE => 0,
319     );
320 }
321
322 =head2 WithRight { Right => RIGHTNAME, Object => RT::Record, IncludeSystemRights => 1, IncludeSuperusers => 0, EquivObjects => [ ] }
323
324
325 Find all groups which have RIGHTNAME for RT::Record. Optionally include global rights and superusers. By default, include the global rights, but not the superusers.
326
327
328
329 =cut
330
331 #XXX: should be generilized
332 sub WithRight {
333     my $self = shift;
334     my %args = ( Right                  => undef,
335                  Object =>              => undef,
336                  IncludeSystemRights    => 1,
337                  IncludeSuperusers      => undef,
338                  IncludeSubgroupMembers => 0,
339                  EquivObjects           => [ ],
340                  @_ );
341
342     my $from_role = $self->Clone;
343     $from_role->WithRoleRight( %args );
344
345     my $from_group = $self->Clone;
346     $from_group->WithGroupRight( %args );
347
348     #XXX: DIRTY HACK
349     use DBIx::SearchBuilder 1.50; #no version on ::Union :(
350     use DBIx::SearchBuilder::Union;
351     my $union = DBIx::SearchBuilder::Union->new();
352     $union->add($from_role);
353     $union->add($from_group);
354     %$self = %$union;
355     bless $self, ref($union);
356
357     return;
358 }
359
360 #XXX: methods are active aliases to Users class to prevent code duplication
361 # should be generalized
362 sub _JoinGroups {
363     my $self = shift;
364     my %args = (@_);
365     return 'main' unless $args{'IncludeSubgroupMembers'};
366     return $self->RT::Users::_JoinGroups( %args );
367 }
368 sub _JoinGroupMembers {
369     my $self = shift;
370     my %args = (@_);
371     return 'main' unless $args{'IncludeSubgroupMembers'};
372     return $self->RT::Users::_JoinGroupMembers( %args );
373 }
374 sub _JoinGroupMembersForGroupRights {
375     my $self = shift;
376     my %args = (@_);
377     my $group_members = $self->_JoinGroupMembers( %args );
378     unless( $group_members eq 'main' ) {
379         return $self->RT::Users::_JoinGroupMembersForGroupRights( %args );
380     }
381     $self->Limit( ALIAS => $args{'ACLAlias'},
382                   FIELD => 'PrincipalId',
383                   VALUE => "main.id",
384                   QUOTEVALUE => 0,
385                 );
386 }
387 sub _JoinACL                  { return (shift)->RT::Users::_JoinACL( @_ ) }
388 sub _RoleClauses              { return (shift)->RT::Users::_RoleClauses( @_ ) }
389 sub _WhoHaveRoleRightSplitted { return (shift)->RT::Users::_WhoHaveRoleRightSplitted( @_ ) }
390 sub _GetEquivObjects          { return (shift)->RT::Users::_GetEquivObjects( @_ ) }
391 sub WithGroupRight            { return (shift)->RT::Users::WhoHaveGroupRight( @_ ) }
392 sub WithRoleRight             { return (shift)->RT::Users::WhoHaveRoleRight( @_ ) }
393
394 sub ForWhichCurrentUserHasRight {
395     my $self = shift;
396     my %args = (
397         Right => undef,
398         IncludeSuperusers => undef,
399         @_,
400     );
401
402     # Non-disabled groups...
403     $self->LimitToEnabled;
404
405     # ...which are the target object of an ACL with that right, or
406     # where the target is the system object (a global right)
407     my $acl = $self->_JoinACL( %args );
408     $self->_AddSubClause(
409         ACLObjects => "( (main.id = $acl.ObjectId AND $acl.ObjectType = 'RT::Group')"
410                    . " OR $acl.ObjectType = 'RT::System')");
411
412     # ...and where that right is granted to any group..
413     my $member = $self->Join(
414         ALIAS1 => $acl,
415         FIELD1 => 'PrincipalId',
416         TABLE2 => 'CachedGroupMembers',
417         FIELD2 => 'GroupId',
418     );
419     $self->Limit(
420         ALIAS => $member,
421         FIELD => 'Disabled',
422         VALUE => '0',
423     );
424
425     # ...with the current user in it
426     $self->Limit(
427         ALIAS => $member,
428         FIELD => 'MemberId',
429         VALUE => $self->CurrentUser->Id,
430     );
431
432     return;
433 }
434
435 =head2 LimitToEnabled
436
437 Only find items that haven't been disabled
438
439 =cut
440
441 sub LimitToEnabled {
442     my $self = shift;
443
444     $self->{'handled_disabled_column'} = 1;
445     $self->Limit(
446         ALIAS => $self->PrincipalsAlias,
447         FIELD => 'Disabled',
448         VALUE => '0',
449     );
450 }
451
452
453 =head2 LimitToDeleted
454
455 Only find items that have been deleted.
456
457 =cut
458
459 sub LimitToDeleted {
460     my $self = shift;
461     
462     $self->{'handled_disabled_column'} = $self->{'find_disabled_rows'} = 1;
463     $self->Limit(
464         ALIAS => $self->PrincipalsAlias,
465         FIELD => 'Disabled',
466         VALUE => 1,
467     );
468 }
469
470
471
472 sub AddRecord {
473     my $self = shift;
474     my ($record) = @_;
475
476     # If we've explicitly limited to groups the user is a member of (for
477     # dashboard or savedsearch privacy objects), skip the ACL.
478     return unless $self->{with_current_user}
479         or $record->CurrentUserHasRight('SeeGroup');
480
481     return $self->SUPER::AddRecord( $record );
482 }
483
484
485
486 sub _DoSearch {
487     my $self = shift;
488
489     #unless we really want to find disabled rows, make sure we're only finding enabled ones.
490     unless($self->{'find_disabled_rows'}) {
491         $self->LimitToEnabled();
492     }
493
494     return($self->SUPER::_DoSearch(@_));
495
496 }
497
498 RT::Base->_ImportOverlays();
499
500 1;