Upgrade to 4.2.2
[usit-rt.git] / lib / RT / Group.pm
CommitLineData
84fb5b46
MKG
1
2# BEGIN BPS TAGGED BLOCK {{{
3#
4# COPYRIGHT:
5#
320f0092 6# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
7# <sales@bestpractical.com>
8#
9# (Except where explicitly superseded by other copyright notices)
10#
11#
12# LICENSE:
13#
14# This work is made available to you under the terms of Version 2 of
15# the GNU General Public License. A copy of that license should have
16# been provided with this software, but in any event can be snarfed
17# from www.gnu.org.
18#
19# This work is distributed in the hope that it will be useful, but
20# WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22# General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program; if not, write to the Free Software
26# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27# 02110-1301 or visit their web page on the internet at
28# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
29#
30#
31# CONTRIBUTION SUBMISSION POLICY:
32#
33# (The following paragraph is not intended to limit the rights granted
34# to you to modify and distribute this software under the terms of
35# the GNU General Public License and is only of importance to you if
36# you choose to contribute your changes and enhancements to the
37# community by submitting them to Best Practical Solutions, LLC.)
38#
39# By intentionally submitting any modifications, corrections or
40# derivatives to this work, or any other work intended for use with
41# Request Tracker, to Best Practical Solutions, LLC, you confirm that
42# you are the copyright holder for those contributions and you grant
43# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
44# royalty-free, perpetual, license to use, copy, create derivative
45# works based on those contributions, and sublicense and distribute
46# those contributions and any derivatives thereof.
47#
48# END BPS TAGGED BLOCK }}}
49
50# Released under the terms of version 2 of the GNU Public License
51
52=head1 NAME
53
403d7b0b 54 RT::Group - RT's group object
84fb5b46
MKG
55
56=head1 SYNOPSIS
57
58use RT::Group;
59my $group = RT::Group->new($CurrentUser);
60
61=head1 DESCRIPTION
62
63An RT group object.
64
84fb5b46
MKG
65=cut
66
67
68package RT::Group;
69
70
71use strict;
72use warnings;
73
74use base 'RT::Record';
75
af59614d
MKG
76use Role::Basic 'with';
77with "RT::Record::Role::Rights";
78
84fb5b46
MKG
79sub Table {'Groups'}
80
81
82
83use RT::Users;
84use RT::GroupMembers;
85use RT::Principals;
86use RT::ACL;
87
320f0092
MKG
88__PACKAGE__->AddRight( Admin => AdminGroup => 'Modify group metadata or delete group'); # loc
89__PACKAGE__->AddRight( Admin => AdminGroupMembership => 'Modify group membership roster'); # loc
90__PACKAGE__->AddRight( Staff => ModifyOwnMembership => 'Join or leave group'); # loc
91__PACKAGE__->AddRight( Admin => EditSavedSearches => 'Create, modify and delete saved searches'); # loc
92__PACKAGE__->AddRight( Staff => ShowSavedSearches => 'View saved searches'); # loc
93__PACKAGE__->AddRight( Staff => SeeGroup => 'View group'); # loc
94__PACKAGE__->AddRight( Staff => SeeGroupDashboard => 'View group dashboards'); # loc
95__PACKAGE__->AddRight( Admin => CreateGroupDashboard => 'Create group dashboards'); # loc
96__PACKAGE__->AddRight( Admin => ModifyGroupDashboard => 'Modify group dashboards'); # loc
97__PACKAGE__->AddRight( Admin => DeleteGroupDashboard => 'Delete group dashboards'); # loc
84fb5b46 98
af59614d 99=head1 METHODS
84fb5b46
MKG
100
101=head2 SelfDescription
102
103Returns a user-readable description of what this group is for and what it's named.
104
105=cut
106
107sub SelfDescription {
af59614d
MKG
108 my $self = shift;
109 if ($self->Domain eq 'ACLEquivalence') {
110 my $user = RT::Principal->new($self->CurrentUser);
111 $user->Load($self->Instance);
112 return $self->loc("user [_1]",$user->Object->Name);
113 }
114 elsif ($self->Domain eq 'UserDefined') {
115 return $self->loc("group '[_1]'",$self->Name);
116 }
117 elsif ($self->Domain eq 'RT::System-Role') {
118 return $self->loc("system [_1]",$self->Name);
119 }
120 elsif ($self->Domain eq 'RT::Queue-Role') {
121 my $queue = RT::Queue->new($self->CurrentUser);
122 $queue->Load($self->Instance);
123 return $self->loc("queue [_1] [_2]",$queue->Name, $self->Name);
124 }
125 elsif ($self->Domain eq 'RT::Ticket-Role') {
126 return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Name);
127 }
128 elsif ($self->RoleClass) {
129 my $class = lc $self->RoleClass;
130 $class =~ s/^RT:://i;
131 return $self->loc("[_1] #[_2] [_3]", $self->loc($class), $self->Instance, $self->Name);
132 }
133 elsif ($self->Domain eq 'SystemInternal') {
134 return $self->loc("system group '[_1]'",$self->Name);
135 }
136 else {
137 return $self->loc("undescribed group [_1]",$self->Id);
138 }
84fb5b46
MKG
139}
140
141
142
143=head2 Load ID
144
145Load a group object from the database. Takes a single argument.
146If the argument is numerical, load by the column 'id'. Otherwise,
147complain and return.
148
149=cut
150
151sub Load {
152 my $self = shift;
153 my $identifier = shift || return undef;
154
155 if ( $identifier !~ /\D/ ) {
156 $self->SUPER::LoadById($identifier);
157 }
158 else {
159 $RT::Logger->crit("Group -> Load called with a bogus argument");
160 return undef;
161 }
162}
163
164
165
166=head2 LoadUserDefinedGroup NAME
167
168Loads a system group from the database. The only argument is
169the group's name.
170
171
172=cut
173
174sub LoadUserDefinedGroup {
175 my $self = shift;
176 my $identifier = shift;
177
178 if ( $identifier =~ /^\d+$/ ) {
179 return $self->LoadByCols(
180 Domain => 'UserDefined',
181 id => $identifier,
182 );
183 } else {
184 return $self->LoadByCols(
185 Domain => 'UserDefined',
186 Name => $identifier,
187 );
188 }
189}
190
191
192
193=head2 LoadACLEquivalenceGroup PRINCIPAL
194
195Loads a user's acl equivalence group. Takes a principal object or its ID.
196ACL equivalnce groups are used to simplify the acl system. Each user
197has one group that only he is a member of. Rights granted to the user
198are actually granted to that group. This greatly simplifies ACL checks.
199While this results in a somewhat more complex setup when creating users
200and granting ACLs, it _greatly_ simplifies acl checks.
201
202=cut
203
204sub LoadACLEquivalenceGroup {
205 my $self = shift;
206 my $principal = shift;
207 $principal = $principal->id if ref $principal;
208
209 return $self->LoadByCols(
210 Domain => 'ACLEquivalence',
af59614d 211 Name => 'UserEquiv',
84fb5b46
MKG
212 Instance => $principal,
213 );
214}
215
216
217
218
219=head2 LoadSystemInternalGroup NAME
220
221Loads a Pseudo group from the database. The only argument is
222the group's name.
223
224
225=cut
226
227sub LoadSystemInternalGroup {
228 my $self = shift;
229 my $identifier = shift;
230
231 return $self->LoadByCols(
232 Domain => 'SystemInternal',
af59614d 233 Name => $identifier,
84fb5b46
MKG
234 );
235}
236
af59614d 237=head2 LoadRoleGroup
84fb5b46 238
af59614d
MKG
239Takes a paramhash of Object and Name and attempts to load the suitable role
240group for said object.
84fb5b46 241
af59614d 242=cut
84fb5b46 243
af59614d
MKG
244sub LoadRoleGroup {
245 my $self = shift;
246 my %args = (
247 Object => undef,
248 Name => undef,
249 @_
250 );
251
252 my $object = delete $args{Object};
84fb5b46 253
af59614d
MKG
254 return wantarray ? (0, $self->loc("Object passed is not loaded")) : 0
255 unless $object->id;
256
257 # Translate Object to Domain + Instance
258 $args{Domain} = ref($object) . "-Role";
259 $args{Instance} = $object->id;
260
261 return $self->LoadByCols(%args);
262}
84fb5b46 263
af59614d
MKG
264
265=head2 LoadTicketRoleGroup { Ticket => TICKET_ID, Name => TYPE }
266
267Deprecated in favor of L</LoadRoleGroup> or L<RT::Record/RoleGroup>.
84fb5b46
MKG
268
269=cut
270
271sub LoadTicketRoleGroup {
af59614d
MKG
272 my $self = shift;
273 my %args = (
274 Ticket => '0',
275 Name => undef,
276 @_,
277 );
278 RT->Deprecated(
279 Instead => "RT::Group->LoadRoleGroup or RT::Ticket->RoleGroup",
280 Remove => "4.4",
281 );
282 $args{'Name'} = $args{'Type'} if exists $args{'Type'};
283 $self->LoadByCols(
284 Domain => 'RT::Ticket-Role',
285 Instance => $args{'Ticket'},
286 Name => $args{'Name'},
287 );
84fb5b46
MKG
288}
289
290
291
292=head2 LoadQueueRoleGroup { Queue => Queue_ID, Type => TYPE }
293
af59614d 294Deprecated in favor of L</LoadRoleGroup> or L<RT::Record/RoleGroup>.
84fb5b46
MKG
295
296=cut
297
298sub LoadQueueRoleGroup {
af59614d
MKG
299 my $self = shift;
300 my %args = (
301 Queue => undef,
302 Name => undef,
303 @_,
304 );
305 RT->Deprecated(
306 Instead => "RT::Group->LoadRoleGroup or RT::Queue->RoleGroup",
307 Remove => "4.4",
308 );
309 $args{'Name'} = $args{'Type'} if exists $args{'Type'};
310 $self->LoadByCols(
311 Domain => 'RT::Queue-Role',
312 Instance => $args{'Queue'},
313 Name => $args{'Name'},
314 );
84fb5b46
MKG
315}
316
317
318
af59614d 319=head2 LoadSystemRoleGroup Name
84fb5b46 320
af59614d 321Deprecated in favor of L</LoadRoleGroup> or L<RT::Record/RoleGroup>.
84fb5b46
MKG
322
323=cut
324
325sub LoadSystemRoleGroup {
af59614d 326 my $self = shift;
84fb5b46 327 my $type = shift;
af59614d
MKG
328 RT->Deprecated(
329 Instead => "RT::Group->LoadRoleGroup or RT::System->RoleGroup",
330 Remove => "4.4",
331 );
332 $self->LoadByCols(
333 Domain => 'RT::System-Role',
334 Instance => RT::System->Id,
335 Name => $type
336 );
337}
338
339sub LoadByCols {
340 my $self = shift;
341 my %args = ( @_ );
342 if ( exists $args{'Type'} ) {
343 RT->Deprecated( Instead => 'Name', Arguments => 'Type', Remove => '4.4' );
344 $args{'Name'} = $args{'Type'};
345 }
346 return $self->SUPER::LoadByCols( %args );
84fb5b46
MKG
347}
348
349
350
351=head2 Create
352
353You need to specify what sort of group you're creating by calling one of the other
354Create_____ routines.
355
356=cut
357
358sub Create {
359 my $self = shift;
360 $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil");
361 return(0,$self->loc('Permission Denied'));
362}
363
364
365
366=head2 _Create
367
368Takes a paramhash with named arguments: Name, Description.
369
370Returns a tuple of (Id, Message). If id is 0, the create failed
371
372=cut
373
374sub _Create {
375 my $self = shift;
376 my %args = (
377 Name => undef,
378 Description => undef,
379 Domain => undef,
84fb5b46
MKG
380 Instance => '0',
381 InsideTransaction => undef,
382 _RecordTransaction => 1,
383 @_
384 );
af59614d
MKG
385 if ( $args{'Type'} ) {
386 RT->Deprecated( Instead => 'Name', Arguments => 'Type', Remove => '4.4' );
387 $args{'Name'} = $args{'Type'};
388 } else {
389 $args{'Type'} = $args{'Name'};
390 }
84fb5b46
MKG
391
392 # Enforce uniqueness on user defined group names
393 if ($args{'Domain'} and $args{'Domain'} eq 'UserDefined') {
394 my ($ok, $msg) = $self->_ValidateUserDefinedName($args{'Name'});
395 return ($ok, $msg) if not $ok;
396 }
397
398 $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
399 # Groups deal with principal ids, rather than user ids.
400 # When creating this group, set up a principal Id for it.
401 my $principal = RT::Principal->new( $self->CurrentUser );
402 my $principal_id = $principal->Create(
403 PrincipalType => 'Group',
404 ObjectId => '0'
405 );
406 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
407
408 $self->SUPER::Create(
409 id => $principal_id,
410 Name => $args{'Name'},
411 Description => $args{'Description'},
412 Type => $args{'Type'},
413 Domain => $args{'Domain'},
414 Instance => ($args{'Instance'} || '0')
415 );
416 my $id = $self->Id;
417 unless ($id) {
418 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
419 return ( 0, $self->loc('Could not create group') );
420 }
421
422 # If we couldn't create a principal Id, get the fuck out.
423 unless ($principal_id) {
424 $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
425 $RT::Logger->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
426 return ( 0, $self->loc('Could not create group') );
427 }
428
429 # Now we make the group a member of itself as a cached group member
430 # this needs to exist so that group ACL checks don't fall over.
431 # you're checking CachedGroupMembers to see if the principal in question
432 # is a member of the principal the rights have been granted too
433
434 # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as
435 # cached members. thankfully, we're creating the group now...so it has no members.
436 my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
437 $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
438
439
440 if ( $args{'_RecordTransaction'} ) {
441 $self->_NewTransaction( Type => "Create" );
442 }
443
444 $RT::Handle->Commit() unless ($args{'InsideTransaction'});
445
446 return ( $id, $self->loc("Group created") );
447}
448
449
450
451=head2 CreateUserDefinedGroup { Name => "name", Description => "Description"}
452
453A helper subroutine which creates a system group
454
455Returns a tuple of (Id, Message). If id is 0, the create failed
456
457=cut
458
459sub CreateUserDefinedGroup {
460 my $self = shift;
461
462 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
463 $RT::Logger->warning( $self->CurrentUser->Name
464 . " Tried to create a group without permission." );
465 return ( 0, $self->loc('Permission Denied') );
466 }
467
af59614d 468 return($self->_Create( Domain => 'UserDefined', Instance => '', @_));
84fb5b46
MKG
469}
470
471=head2 ValidateName VALUE
472
473Enforces unique user defined group names when updating
474
475=cut
476
477sub ValidateName {
478 my ($self, $value) = @_;
479
480 if ($self->Domain and $self->Domain eq 'UserDefined') {
481 my ($ok, $msg) = $self->_ValidateUserDefinedName($value);
482 # It's really too bad we can't pass along the actual error
483 return 0 if not $ok;
484 }
485 return $self->SUPER::ValidateName($value);
486}
487
488=head2 _ValidateUserDefinedName VALUE
489
490Returns true if the user defined group name isn't in use, false otherwise.
491
492=cut
493
494sub _ValidateUserDefinedName {
495 my ($self, $value) = @_;
496
497 return (0, 'Name is required') unless length $value;
498
499 my $dupcheck = RT::Group->new(RT->SystemUser);
500 $dupcheck->LoadUserDefinedGroup($value);
5b0d0914
MKG
501 if ( $dupcheck->id && ( !$self->id || $self->id != $dupcheck->id ) ) {
502 return ( 0, $self->loc( "Group name '[_1]' is already in use", $value ) );
503 }
84fb5b46
MKG
504 return 1;
505}
506
507=head2 _CreateACLEquivalenceGroup { Principal }
508
509A helper subroutine which creates a group containing only
510an individual user. This gets used by the ACL system to check rights.
511Yes, it denormalizes the data, but that's ok, as we totally win on performance.
512
513Returns a tuple of (Id, Message). If id is 0, the create failed
514
515=cut
516
517sub _CreateACLEquivalenceGroup {
518 my $self = shift;
519 my $princ = shift;
520
521 my $id = $self->_Create( Domain => 'ACLEquivalence',
af59614d 522 Name => 'UserEquiv',
84fb5b46
MKG
523 Description => 'ACL equiv. for user '.$princ->Object->Id,
524 Instance => $princ->Id,
525 InsideTransaction => 1,
526 _RecordTransaction => 0 );
527 unless ($id) {
528 $RT::Logger->crit("Couldn't create ACL equivalence group");
529 return undef;
530 }
531
532 # We use stashuser so we don't get transactions inside transactions
533 # and so we bypass all sorts of cruft we don't need
534 my $aclstash = RT::GroupMember->new($self->CurrentUser);
535 my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
536 Member => $princ);
537
538 unless ($stash_id) {
539 $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
540 # We call super delete so we don't get acl checked.
541 $self->SUPER::Delete();
542 return(undef);
543 }
544 return ($id);
545}
546
547
548
549
af59614d 550=head2 CreateRoleGroup
84fb5b46 551
af59614d 552A convenience method for creating a role group on an object.
84fb5b46 553
af59614d
MKG
554This method expects to be called from B<inside of a database transaction>! If
555you're calling it outside of one, you B<MUST> pass a false value for
556InsideTransaction.
84fb5b46 557
af59614d
MKG
558Takes a paramhash of:
559
560=over 4
561
562=item Name
563
564Required. RT's core role types are C<Requestor>, C<Cc>, C<AdminCc>, and
565C<Owner>. Extensions may add their own.
566
567=item Object
568
569Optional. The object on which this role applies, used to set Domain and
570Instance automatically.
571
572=item Domain
573
574Optional. The class on which this role applies, with C<-Role> appended. RT's
575supported core role group domains are C<RT::Ticket-Role>, C<RT::Queue-Role>,
576and C<RT::System-Role>.
577
578Not required if you pass an Object.
579
580=item Instance
581
582Optional. The numeric ID of the object (of the class encoded in Domain) on
583which this role applies. If Domain is C<RT::System-Role>, Instance should be C<1>.
584
585Not required if you pass an Object.
586
587=item InsideTransaction
588
589Optional. Defaults to true in expectation of usual call sites. If you call
590this method while not inside a transaction, you C<MUST> pass a false value for
591this parameter.
592
593=back
594
595You must pass either an Object or both Domain and Instance.
596
597Returns a tuple of (id, Message). If id is false, the create failed and
598Message should contain an error string.
84fb5b46
MKG
599
600=cut
601
602sub CreateRoleGroup {
603 my $self = shift;
604 my %args = ( Instance => undef,
af59614d 605 Name => undef,
84fb5b46 606 Domain => undef,
af59614d
MKG
607 Object => undef,
608 InsideTransaction => 1,
84fb5b46
MKG
609 @_ );
610
af59614d
MKG
611 # Translate Object to Domain + Instance
612 my $object = delete $args{Object};
613 if ( $object ) {
614 $args{Domain} = ref($object) . "-Role";
615 $args{Instance} = $object->id;
616 }
617
618 unless ($args{Instance}) {
619 return ( 0, $self->loc("An Instance must be provided") );
84fb5b46
MKG
620 }
621
af59614d
MKG
622 unless ($self->ValidateRoleGroup(%args)) {
623 return ( 0, $self->loc("Invalid Group Name and Domain") );
624 }
625
626 if ( exists $args{'Type'} ) {
627 RT->Deprecated( Instead => 'Name', Arguments => 'Type', Remove => '4.4' );
628 $args{'Name'} = $args{'Type'};
629 }
630
631 my %create = map { $_ => $args{$_} } qw(Domain Instance Name);
632
633 my $duplicate = RT::Group->new( RT->SystemUser );
634 $duplicate->LoadByCols( %create );
635 if ($duplicate->id) {
636 return ( 0, $self->loc("Role group exists already") );
637 }
638
639 my ($id, $msg) = $self->_Create(
640 InsideTransaction => $args{InsideTransaction},
641 %create,
642 );
643
644 if ($self->SingleMemberRoleGroup) {
645 $self->_AddMember(
646 PrincipalId => RT->Nobody->Id,
647 InsideTransaction => $args{InsideTransaction},
648 RecordTransaction => 0,
649 Object => $object,
650 );
651 }
652
653 return ($id, $msg);
654}
655
656sub RoleClass {
657 my $self = shift;
658 my $domain = shift || $self->Domain;
659 return unless $domain =~ /^(.+)-Role$/;
660 return unless $1->DOES("RT::Record::Role::Roles");
661 return $1;
662}
663
664=head2 ValidateRoleGroup
665
666Takes a param hash containing Domain and Type which are expected to be values
667passed into L</CreateRoleGroup>. Returns true if the specified Type is a
668registered role on the specified Domain. Otherwise returns false.
669
670=cut
671
672sub ValidateRoleGroup {
673 my $self = shift;
674 my %args = (@_);
675 return 0 unless $args{Domain} and ($args{Type} or $args{'Name'});
676
677 my $class = $self->RoleClass($args{Domain});
678 return 0 unless $class;
679
680 return $class->HasRole($args{Type}||$args{'Name'});
681}
682
683=head2 SingleMemberRoleGroup
684
685=cut
686
687sub SingleMemberRoleGroup {
688 my $self = shift;
689 my $class = $self->RoleClass;
690 return unless $class;
691 return $class->Role($self->Name)->{Single};
692}
693
694sub SingleMemberRoleGroupColumn {
695 my $self = shift;
696 my ($class) = $self->Domain =~ /^(.+)-Role$/;
697 return unless $class;
698
699 my $role = $class->Role($self->Name);
700 return unless $role->{Class} eq $class;
701 return $role->{Column};
702}
703
704sub RoleGroupObject {
705 my $self = shift;
706 my ($class) = $self->Domain =~ /^(.+)-Role$/;
707 return unless $class;
708 my $obj = $class->new( $self->CurrentUser );
709 $obj->Load( $self->Instance );
710 return $obj;
711}
712
713sub Type {
714 my $self = shift;
715 RT->Deprecated( Instead => 'Name', Remove => '4.4' );
716 return $self->_Value('Type', @_);
717}
84fb5b46 718
af59614d
MKG
719sub SetType {
720 my $self = shift;
721 RT->Deprecated( Instead => 'Name', Remove => '4.4' );
722 return $self->SetName(@_);
84fb5b46
MKG
723}
724
af59614d
MKG
725sub SetName {
726 my $self = shift;
727 my $value = shift;
728
729 my ($status, $msg) = $self->_Set( Field => 'Name', Value => $value );
730 return ($status, $msg) unless $status;
84fb5b46 731
af59614d
MKG
732 {
733 my ($status, $msg) = $self->__Set( Field => 'Type', Value => $value );
734 RT->Logger->error("Couldn't set Type: $msg") unless $status;
735 }
736
737 return ($status, $msg);
738}
84fb5b46
MKG
739
740=head2 Delete
741
742Delete this object
743
744=cut
745
746sub Delete {
747 my $self = shift;
748
749 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
750 return ( 0, 'Permission Denied' );
751 }
752
753 $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
754 # TODO XXX
755
756 # Remove the principal object
757 # Remove this group from anything it's a member of.
758 # Remove all cached members of this group
759 # Remove any rights granted to this group
760 # remove any rights delegated by way of this group
761
762 return ( $self->SUPER::Delete(@_) );
763}
764
765
766=head2 SetDisabled BOOL
767
768If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
769It will not appear in most group listings.
770
771This routine finds all the cached group members that are members of this group (recursively) and disables them.
772
773=cut
774
775 # }}}
776
777 sub SetDisabled {
778 my $self = shift;
779 my $val = shift;
780 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
781 return (0, $self->loc('Permission Denied'));
782 }
783 $RT::Handle->BeginTransaction();
784 $self->PrincipalObj->SetDisabled($val);
785
786
787
788
789 # Find all occurrences of this member as a member of this group
790 # in the cache and nuke them, recursively.
791
792 # The following code will delete all Cached Group members
793 # where this member's group is _not_ the primary group
794 # (Ie if we're deleting C as a member of B, and B happens to be
795 # a member of A, will delete C as a member of A without touching
796 # C as a member of B
797
798 my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
799
800 $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
801
802 #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
803 # TODO what about the groups key cache?
804 RT::Principal->InvalidateACLCache();
805
806
807
808 while ( my $item = $cached_submembers->Next() ) {
809 my $del_err = $item->SetDisabled($val);
810 unless ($del_err) {
811 $RT::Handle->Rollback();
812 $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
813 return (undef);
814 }
815 }
816
817 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
818
819 $RT::Handle->Commit();
820 if ( $val == 1 ) {
821 return (1, $self->loc("Group disabled"));
822 } else {
823 return (1, $self->loc("Group enabled"));
824 }
825
826}
827
828
829
830
831sub Disabled {
832 my $self = shift;
833 $self->PrincipalObj->Disabled(@_);
834}
835
836
837
838=head2 DeepMembersObj
839
840Returns an RT::CachedGroupMembers object of this group's members,
841including all members of subgroups.
842
843=cut
844
845sub DeepMembersObj {
846 my $self = shift;
847 my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
848
849 #If we don't have rights, don't include any results
850 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
851 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
852
853 return ( $members_obj );
854
855}
856
857
858
859=head2 MembersObj
860
861Returns an RT::GroupMembers object of this group's direct members.
862
863=cut
864
865sub MembersObj {
866 my $self = shift;
867 my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
868
869 #If we don't have rights, don't include any results
870 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
871 $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
872
873 return ( $members_obj );
874
875}
876
877
878
879=head2 GroupMembersObj [Recursively => 1]
880
881Returns an L<RT::Groups> object of this group's members.
882By default returns groups including all subgroups, but
883could be changed with C<Recursively> named argument.
884
885B<Note> that groups are not filtered by type and result
886may contain as well system groups and others.
887
888=cut
889
890sub GroupMembersObj {
891 my $self = shift;
892 my %args = ( Recursively => 1, @_ );
893
894 my $groups = RT::Groups->new( $self->CurrentUser );
895 my $members_table = $args{'Recursively'}?
896 'CachedGroupMembers': 'GroupMembers';
897
898 my $members_alias = $groups->NewAlias( $members_table );
899 $groups->Join(
900 ALIAS1 => $members_alias, FIELD1 => 'MemberId',
901 ALIAS2 => $groups->PrincipalsAlias, FIELD2 => 'id',
902 );
903 $groups->Limit(
904 ALIAS => $members_alias,
905 FIELD => 'GroupId',
906 VALUE => $self->PrincipalId,
907 );
908 $groups->Limit(
909 ALIAS => $members_alias,
910 FIELD => 'Disabled',
911 VALUE => 0,
912 ) if $args{'Recursively'};
913
914 return $groups;
915}
916
917
918
919=head2 UserMembersObj
920
921Returns an L<RT::Users> object of this group's members, by default
922returns users including all members of subgroups, but could be
923changed with C<Recursively> named argument.
924
925=cut
926
927sub UserMembersObj {
928 my $self = shift;
929 my %args = ( Recursively => 1, @_ );
930
931 #If we don't have rights, don't include any results
932 # TODO XXX WHY IS THERE NO ACL CHECK HERE?
933
934 my $members_table = $args{'Recursively'}?
935 'CachedGroupMembers': 'GroupMembers';
936
937 my $users = RT::Users->new($self->CurrentUser);
938 my $members_alias = $users->NewAlias( $members_table );
939 $users->Join(
940 ALIAS1 => $members_alias, FIELD1 => 'MemberId',
941 ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id',
942 );
943 $users->Limit(
944 ALIAS => $members_alias,
945 FIELD => 'GroupId',
946 VALUE => $self->PrincipalId,
947 );
948 $users->Limit(
949 ALIAS => $members_alias,
950 FIELD => 'Disabled',
951 VALUE => 0,
952 ) if $args{'Recursively'};
953
954 return ( $users);
955}
956
957
958
959=head2 MemberEmailAddresses
960
961Returns an array of the email addresses of all of this group's members
962
963
964=cut
965
966sub MemberEmailAddresses {
967 my $self = shift;
968 return sort grep defined && length,
969 map $_->EmailAddress,
970 @{ $self->UserMembersObj->ItemsArrayRef };
971}
972
973
974
975=head2 MemberEmailAddressesAsString
976
977Returns a comma delimited string of the email addresses of all users
978who are members of this group.
979
980=cut
981
982
983sub MemberEmailAddressesAsString {
984 my $self = shift;
985 return (join(', ', $self->MemberEmailAddresses));
986}
987
988
989
990=head2 AddMember PRINCIPAL_ID
991
992AddMember adds a principal to this group. It takes a single principal id.
993Returns a two value array. the first value is true on successful
994addition or 0 on failure. The second value is a textual status msg.
995
996=cut
997
998sub AddMember {
999 my $self = shift;
1000 my $new_member = shift;
1001
1002
1003
1004 # We should only allow membership changes if the user has the right
1005 # to modify group membership or the user is the principal in question
1006 # and the user has the right to modify his own membership
1007 unless ( ($new_member == $self->CurrentUser->PrincipalId &&
af59614d
MKG
1008 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1009 $self->CurrentUserHasRight('AdminGroupMembership') ) {
84fb5b46
MKG
1010 #User has no permission to be doing this
1011 return ( 0, $self->loc("Permission Denied") );
1012 }
1013
1014 $self->_AddMember(PrincipalId => $new_member);
1015}
1016
1017# A helper subroutine for AddMember that bypasses the ACL checks
1018# this should _ONLY_ ever be called from Ticket/Queue AddWatcher
1019# when we want to deal with groups according to queue rights
1020# In the dim future, this will all get factored out and life
af59614d 1021# will get better
84fb5b46
MKG
1022
1023# takes a paramhash of { PrincipalId => undef, InsideTransaction }
1024
1025sub _AddMember {
1026 my $self = shift;
1027 my %args = ( PrincipalId => undef,
1028 InsideTransaction => undef,
af59614d 1029 RecordTransaction => 1,
84fb5b46 1030 @_);
af59614d
MKG
1031
1032 # RecordSetTransaction is used by _DeleteMember to get one txn but not the other
1033 $args{RecordSetTransaction} = $args{RecordTransaction}
1034 unless exists $args{RecordSetTransaction};
1035
84fb5b46
MKG
1036 my $new_member = $args{'PrincipalId'};
1037
1038 unless ($self->Id) {
1039 $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
1040 return(0, $self->loc("Group not found"));
1041 }
1042
1043 unless ($new_member =~ /^\d+$/) {
1044 $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
1045 }
1046
1047
1048 my $new_member_obj = RT::Principal->new( $self->CurrentUser );
1049 $new_member_obj->Load($new_member);
1050
1051
1052 unless ( $new_member_obj->Id ) {
1053 $RT::Logger->debug("Couldn't find that principal");
1054 return ( 0, $self->loc("Couldn't find that principal") );
1055 }
1056
1057 if ( $self->HasMember( $new_member_obj ) ) {
1058
1059 #User is already a member of this group. no need to add it
1060 return ( 0, $self->loc("Group already has member: [_1]", $new_member_obj->Object->Name) );
1061 }
1062 if ( $new_member_obj->IsGroup &&
1063 $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
1064
1065 #This group can't be made to be a member of itself
1066 return ( 0, $self->loc("Groups can't be members of their members"));
1067 }
1068
af59614d
MKG
1069 my @purge;
1070 push @purge, @{$self->MembersObj->ItemsArrayRef}
1071 if $self->SingleMemberRoleGroup;
84fb5b46
MKG
1072
1073 my $member_object = RT::GroupMember->new( $self->CurrentUser );
1074 my $id = $member_object->Create(
1075 Member => $new_member_obj,
1076 Group => $self->PrincipalObj,
1077 InsideTransaction => $args{'InsideTransaction'}
1078 );
af59614d
MKG
1079
1080 return(0, $self->loc("Couldn't add member to group"))
1081 unless $id;
1082
1083 # Purge all previous members (we're a single member role group)
1084 my $old_member_id;
1085 for my $member (@purge) {
1086 my $old_member = $member->MemberId;
1087 my ($ok, $msg) = $member->Delete();
1088 return(0, $self->loc("Couldn't remove previous member: [_1]", $msg))
1089 unless $ok;
1090
1091 # We remove all members in this loop, but there should only ever be one
1092 # member. Keep track of the last one successfully removed for the
1093 # SetWatcher transaction below.
1094 $old_member_id = $old_member;
84fb5b46 1095 }
af59614d
MKG
1096
1097 # Update the column
1098 if (my $col = $self->SingleMemberRoleGroupColumn) {
1099 my $obj = $args{Object} || $self->RoleGroupObject;
1100 my ($ok, $msg) = $obj->_Set(
1101 Field => $col,
1102 Value => $new_member_obj->Id,
1103 CheckACL => 0, # don't check acl
1104 RecordTransaction => $args{'RecordSetTransaction'},
1105 );
1106 return (0, $self->loc("Could not update column [_1]: [_2]", $col, $msg))
1107 unless $ok;
1108 }
1109
1110 # Record an Add/SetWatcher txn on the object if we're a role group
1111 if ($args{RecordTransaction} and $self->RoleClass) {
1112 my $obj = $args{Object} || $self->RoleGroupObject;
1113
1114 if ($self->SingleMemberRoleGroup) {
1115 $obj->_NewTransaction(
1116 Type => 'SetWatcher',
1117 OldValue => $old_member_id,
1118 NewValue => $new_member_obj->Id,
1119 Field => $self->Name,
1120 );
1121 } else {
1122 $obj->_NewTransaction(
1123 Type => 'AddWatcher', # use "watcher" for history's sake
1124 NewValue => $new_member_obj->Id,
1125 Field => $self->Name,
1126 );
1127 }
84fb5b46 1128 }
af59614d
MKG
1129
1130 return (1, $self->loc("[_1] set to [_2]",
1131 $self->loc($self->Name), $new_member_obj->Object->Name) )
1132 if $self->SingleMemberRoleGroup;
1133
1134 return ( 1, $self->loc("Member added: [_1]", $new_member_obj->Object->Name) );
84fb5b46
MKG
1135}
1136
1137
1138=head2 HasMember RT::Principal|id
1139
1140Takes an L<RT::Principal> object or its id returns a GroupMember Id if that user is a
1141member of this group.
1142Returns undef if the user isn't a member of the group or if the current
1143user doesn't have permission to find out. Arguably, it should differentiate
1144between ACL failure and non membership.
1145
1146=cut
1147
1148sub HasMember {
1149 my $self = shift;
1150 my $principal = shift;
1151
1152 my $id;
1153 if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
1154 $id = $principal->id;
1155 } elsif ( $principal =~ /^\d+$/ ) {
1156 $id = $principal;
1157 } else {
1158 $RT::Logger->error("Group::HasMember was called with an argument that".
1159 " isn't an RT::Principal or id. It's ".($principal||'(undefined)'));
1160 return(undef);
1161 }
1162 return undef unless $id;
1163
1164 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1165 $member_obj->LoadByCols(
1166 MemberId => $id,
1167 GroupId => $self->PrincipalId
1168 );
1169
1170 if ( my $member_id = $member_obj->id ) {
1171 return $member_id;
1172 }
1173 else {
1174 return (undef);
1175 }
1176}
1177
1178
1179
1180=head2 HasMemberRecursively RT::Principal|id
1181
1182Takes an L<RT::Principal> object or its id and returns true if that user is a member of
1183this group.
1184Returns undef if the user isn't a member of the group or if the current
1185user doesn't have permission to find out. Arguably, it should differentiate
1186between ACL failure and non membership.
1187
1188=cut
1189
1190sub HasMemberRecursively {
1191 my $self = shift;
1192 my $principal = shift;
1193
1194 my $id;
1195 if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
1196 $id = $principal->id;
1197 } elsif ( $principal =~ /^\d+$/ ) {
1198 $id = $principal;
1199 } else {
1200 $RT::Logger->error("Group::HasMemberRecursively was called with an argument that".
1201 " isn't an RT::Principal or id. It's $principal");
1202 return(undef);
1203 }
1204 return undef unless $id;
1205
1206 my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
1207 $member_obj->LoadByCols(
1208 MemberId => $id,
1209 GroupId => $self->PrincipalId
1210 );
1211
1212 if ( my $member_id = $member_obj->id ) {
1213 return $member_id;
1214 }
1215 else {
1216 return (undef);
1217 }
1218}
1219
1220
1221
1222=head2 DeleteMember PRINCIPAL_ID
1223
1224Takes the principal id of a current user or group.
1225If the current user has apropriate rights,
1226removes that GroupMember from this group.
1227Returns a two value array. the first value is true on successful
1228addition or 0 on failure. The second value is a textual status msg.
1229
af59614d
MKG
1230Optionally takes a hash of key value flags, such as RecordTransaction.
1231
84fb5b46
MKG
1232=cut
1233
1234sub DeleteMember {
1235 my $self = shift;
1236 my $member_id = shift;
1237
1238
1239 # We should only allow membership changes if the user has the right
1240 # to modify group membership or the user is the principal in question
1241 # and the user has the right to modify his own membership
1242
1243 unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
af59614d
MKG
1244 $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
1245 $self->CurrentUserHasRight('AdminGroupMembership') ) {
84fb5b46
MKG
1246 #User has no permission to be doing this
1247 return ( 0, $self->loc("Permission Denied") );
1248 }
af59614d 1249 $self->_DeleteMember($member_id, @_);
84fb5b46
MKG
1250}
1251
1252# A helper subroutine for DeleteMember that bypasses the ACL checks
1253# this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
1254# when we want to deal with groups according to queue rights
1255# In the dim future, this will all get factored out and life
af59614d 1256# will get better
84fb5b46
MKG
1257
1258sub _DeleteMember {
1259 my $self = shift;
1260 my $member_id = shift;
af59614d
MKG
1261 my %args = (
1262 RecordTransaction => 1,
1263 @_,
1264 );
1265
84fb5b46
MKG
1266
1267 my $member_obj = RT::GroupMember->new( $self->CurrentUser );
1268
1269 $member_obj->LoadByCols( MemberId => $member_id,
1270 GroupId => $self->PrincipalId);
1271
1272
1273 #If we couldn't load it, return undef.
1274 unless ( $member_obj->Id() ) {
1275 $RT::Logger->debug("Group has no member with that id");
1276 return ( 0,$self->loc( "Group has no such member" ));
1277 }
1278
af59614d
MKG
1279 my $old_member = $member_obj->MemberId;
1280
84fb5b46
MKG
1281 #Now that we've checked ACLs and sanity, delete the groupmember
1282 my $val = $member_obj->Delete();
1283
af59614d 1284 unless ($val) {
84fb5b46
MKG
1285 $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
1286 return ( 0, $self->loc("Member not deleted" ));
1287 }
af59614d
MKG
1288
1289 if ($self->RoleClass) {
1290 my %txn = (
1291 OldValue => $old_member,
1292 Field => $self->Name,
1293 );
1294
1295 if ($self->SingleMemberRoleGroup) {
1296 # _AddMember creates the Set-Owner txn (for example) but we handle
1297 # the SetWatcher-Owner txn below.
1298 $self->_AddMember(
1299 PrincipalId => RT->Nobody->Id,
1300 RecordTransaction => 0,
1301 RecordSetTransaction => $args{RecordTransaction},
1302 );
1303 $txn{Type} = "SetWatcher";
1304 $txn{NewValue} = RT->Nobody->id;
1305 } else {
1306 $txn{Type} = "DelWatcher";
1307 }
1308
1309 if ($args{RecordTransaction}) {
1310 my $obj = $args{Object} || $self->RoleGroupObject;
1311 $obj->_NewTransaction(%txn);
1312 }
1313 }
1314
1315 return ( $val, $self->loc("Member deleted") );
84fb5b46
MKG
1316}
1317
1318
1319
1320sub _Set {
1321 my $self = shift;
1322 my %args = (
1323 Field => undef,
1324 Value => undef,
af59614d
MKG
1325 TransactionType => 'Set',
1326 RecordTransaction => 1,
84fb5b46
MKG
1327 @_
1328 );
1329
1330 unless ( $self->CurrentUserHasRight('AdminGroup') ) {
af59614d
MKG
1331 return ( 0, $self->loc('Permission Denied') );
1332 }
84fb5b46
MKG
1333
1334 my $Old = $self->SUPER::_Value("$args{'Field'}");
af59614d 1335
84fb5b46 1336 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
af59614d
MKG
1337 Value => $args{'Value'} );
1338
84fb5b46
MKG
1339 #If we can't actually set the field to the value, don't record
1340 # a transaction. instead, get out of here.
1341 if ( $ret == 0 ) { return ( 0, $msg ); }
1342
1343 if ( $args{'RecordTransaction'} == 1 ) {
1344
1345 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1346 Type => $args{'TransactionType'},
1347 Field => $args{'Field'},
1348 NewValue => $args{'Value'},
1349 OldValue => $Old,
1350 TimeTaken => $args{'TimeTaken'},
1351 );
1352 return ( $Trans, scalar $TransObj->Description );
1353 }
1354 else {
1355 return ( $ret, $msg );
1356 }
1357}
1358
84fb5b46
MKG
1359=head2 CurrentUserCanSee
1360
1361Always returns 1; unfortunately, for historical reasons, users have
1362always been able to examine groups they have indirect access to, even if
1363they do not have SeeGroup explicitly.
1364
1365=cut
1366
1367sub CurrentUserCanSee {
1368 my $self = shift;
1369 return 1;
1370}
1371
1372
1373=head2 PrincipalObj
1374
1375Returns the principal object for this user. returns an empty RT::Principal
1376if there's no principal object matching this user.
1377The response is cached. PrincipalObj should never ever change.
1378
1379
1380=cut
1381
1382
1383sub PrincipalObj {
1384 my $self = shift;
af59614d
MKG
1385 my $res = RT::Principal->new( $self->CurrentUser );
1386 $res->Load( $self->id );
1387 return $res;
84fb5b46
MKG
1388}
1389
1390
1391=head2 PrincipalId
1392
1393Returns this user's PrincipalId
1394
1395=cut
1396
1397sub PrincipalId {
1398 my $self = shift;
1399 return $self->Id;
1400}
1401
af59614d
MKG
1402sub InstanceObj {
1403 my $self = shift;
1404
1405 my $class;
1406 if ( $self->Domain eq 'ACLEquivalence' ) {
1407 $class = "RT::User";
1408 } elsif ($self->Domain eq 'RT::Queue-Role') {
1409 $class = "RT::Queue";
1410 } elsif ($self->Domain eq 'RT::Ticket-Role') {
1411 $class = "RT::Ticket";
1412 }
1413
1414 return unless $class;
1415
1416 my $obj = $class->new( $self->CurrentUser );
1417 $obj->Load( $self->Instance );
1418 return $obj;
1419}
84fb5b46
MKG
1420
1421sub BasicColumns {
1422 (
af59614d
MKG
1423 [ Name => 'Name' ],
1424 [ Description => 'Description' ],
84fb5b46
MKG
1425 );
1426}
1427
1428
1429=head1 AUTHOR
1430
1431Jesse Vincent, jesse@bestpractical.com
1432
1433=head1 SEE ALSO
1434
1435RT
1436
1437=cut
1438
1439
1440
1441
1442
1443=head2 id
1444
1445Returns the current value of id.
1446(In the database, id is stored as int(11).)
1447
1448
1449=cut
1450
1451
1452=head2 Name
1453
1454Returns the current value of Name.
1455(In the database, Name is stored as varchar(200).)
1456
1457
1458
1459=head2 SetName VALUE
1460
1461
1462Set Name to VALUE.
1463Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1464(In the database, Name will be stored as a varchar(200).)
1465
1466
1467=cut
1468
1469
1470=head2 Description
1471
1472Returns the current value of Description.
1473(In the database, Description is stored as varchar(255).)
1474
1475
1476
1477=head2 SetDescription VALUE
1478
1479
1480Set Description to VALUE.
1481Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1482(In the database, Description will be stored as a varchar(255).)
1483
1484
1485=cut
1486
1487
1488=head2 Domain
1489
1490Returns the current value of Domain.
1491(In the database, Domain is stored as varchar(64).)
1492
1493
1494
1495=head2 SetDomain VALUE
1496
1497
1498Set Domain to VALUE.
1499Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1500(In the database, Domain will be stored as a varchar(64).)
1501
1502
1503=cut
1504
1505
1506=head2 Type
1507
1508Returns the current value of Type.
1509(In the database, Type is stored as varchar(64).)
1510
af59614d 1511Deprecated, use Name instead, will be removed in 4.4.
84fb5b46
MKG
1512
1513=head2 SetType VALUE
1514
1515
1516Set Type to VALUE.
1517Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1518(In the database, Type will be stored as a varchar(64).)
1519
af59614d 1520Deprecated, use SetName instead, will be removed in 4.4.
84fb5b46
MKG
1521
1522=cut
1523
1524
1525=head2 Instance
1526
1527Returns the current value of Instance.
1528(In the database, Instance is stored as int(11).)
1529
1530
1531
1532=head2 SetInstance VALUE
1533
1534
1535Set Instance to VALUE.
1536Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1537(In the database, Instance will be stored as a int(11).)
1538
1539
1540=cut
1541
1542
1543=head2 Creator
1544
1545Returns the current value of Creator.
1546(In the database, Creator is stored as int(11).)
1547
1548
1549=cut
1550
1551
1552=head2 Created
1553
1554Returns the current value of Created.
1555(In the database, Created is stored as datetime.)
1556
1557
1558=cut
1559
1560
1561=head2 LastUpdatedBy
1562
1563Returns the current value of LastUpdatedBy.
1564(In the database, LastUpdatedBy is stored as int(11).)
1565
1566
1567=cut
1568
1569
1570=head2 LastUpdated
1571
1572Returns the current value of LastUpdated.
1573(In the database, LastUpdated is stored as datetime.)
1574
1575
1576=cut
1577
1578
1579
1580sub _CoreAccessible {
1581 {
1582
1583 id =>
af59614d 1584 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 1585 Name =>
af59614d 1586 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
84fb5b46 1587 Description =>
af59614d 1588 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
84fb5b46 1589 Domain =>
af59614d 1590 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
84fb5b46 1591 Type =>
af59614d 1592 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
84fb5b46 1593 Instance =>
af59614d 1594 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
84fb5b46 1595 Creator =>
af59614d 1596 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 1597 Created =>
af59614d 1598 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46 1599 LastUpdatedBy =>
af59614d 1600 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
84fb5b46 1601 LastUpdated =>
af59614d 1602 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
84fb5b46
MKG
1603
1604 }
1605};
1606
af59614d
MKG
1607sub FindDependencies {
1608 my $self = shift;
1609 my ($walker, $deps) = @_;
1610
1611 $self->SUPER::FindDependencies($walker, $deps);
1612
1613 my $instance = $self->InstanceObj;
1614 $deps->Add( out => $instance ) if $instance;
1615
1616 # Group members records, unless we're a system group
1617 if ($self->Domain ne "SystemInternal") {
1618 my $objs = RT::GroupMembers->new( $self->CurrentUser );
1619 $objs->LimitToMembersOfGroup( $self->PrincipalId );
1620 $deps->Add( in => $objs );
1621 }
1622
1623 # Group member records group belongs to
1624 my $objs = RT::GroupMembers->new( $self->CurrentUser );
1625 $objs->Limit( FIELD => 'MemberId', VALUE => $self->PrincipalId );
1626 $deps->Add( in => $objs );
1627}
1628
1629sub Serialize {
1630 my $self = shift;
1631 my %args = (@_);
1632 my %store = $self->SUPER::Serialize(@_);
1633
1634 my $instance = $self->InstanceObj;
1635 $store{Instance} = \($instance->UID) if $instance;
1636
1637 $store{Disabled} = $self->PrincipalObj->Disabled;
1638 $store{Principal} = $self->PrincipalObj->UID;
1639 $store{PrincipalId} = $self->PrincipalObj->Id;
1640 return %store;
1641}
1642
1643sub PreInflate {
1644 my $class = shift;
1645 my ($importer, $uid, $data) = @_;
1646
1647 my $principal_uid = delete $data->{Principal};
1648 my $principal_id = delete $data->{PrincipalId};
1649 my $disabled = delete $data->{Disabled};
1650
1651 # Inflate refs into their IDs
1652 $class->SUPER::PreInflate( $importer, $uid, $data );
1653
1654 # Factored out code, in case we find an existing version of this group
1655 my $obj = RT::Group->new( RT->SystemUser );
1656 my $duplicated = sub {
1657 $importer->SkipTransactions( $uid );
1658 $importer->Resolve(
1659 $principal_uid,
1660 ref($obj->PrincipalObj),
1661 $obj->PrincipalObj->Id
1662 );
1663 $importer->Resolve( $uid => ref($obj), $obj->Id );
1664 return;
1665 };
1666
1667 # Go looking for the pre-existing version of the it
1668 if ($data->{Domain} eq "ACLEquivalence") {
1669 $obj->LoadACLEquivalenceGroup( $data->{Instance} );
1670 return $duplicated->() if $obj->Id;
1671
1672 # Update the name and description for the new ID
1673 $data->{Name} = 'User '. $data->{Instance};
1674 $data->{Description} = 'ACL equiv. for user '.$data->{Instance};
1675 } elsif ($data->{Domain} eq "UserDefined") {
1676 $data->{Name} = $importer->Qualify($data->{Name});
1677 $obj->LoadUserDefinedGroup( $data->{Name} );
1678 if ($obj->Id) {
1679 $importer->MergeValues($obj, $data);
1680 return $duplicated->();
1681 }
1682 } elsif ($data->{Domain} =~ /^(SystemInternal|RT::System-Role)$/) {
320f0092 1683 $obj->LoadByCols( Domain => $data->{Domain}, Name => $data->{Name} );
af59614d
MKG
1684 return $duplicated->() if $obj->Id;
1685 } elsif ($data->{Domain} eq "RT::Queue-Role") {
320f0092
MKG
1686 my $queue = RT::Queue->new( RT->SystemUser );
1687 $queue->Load( $data->{Instance} );
1688 $obj->LoadRoleGroup( Object => $queue, Name => $data->{Name} );
af59614d
MKG
1689 return $duplicated->() if $obj->Id;
1690 }
1691
1692 my $principal = RT::Principal->new( RT->SystemUser );
1693 my ($id) = $principal->Create(
1694 PrincipalType => 'Group',
1695 Disabled => $disabled,
1696 ObjectId => 0,
1697 );
320f0092
MKG
1698
1699 # Now we have a principal id, set the id for the group record
1700 $data->{id} = $id;
1701
af59614d
MKG
1702 $importer->Resolve( $principal_uid => ref($principal), $id );
1703
1704 $importer->Postpone(
1705 for => $uid,
1706 uid => $principal_uid,
1707 column => "ObjectId",
1708 );
1709
1710 return 1;
1711}
1712
1713sub PostInflate {
1714 my $self = shift;
1715
1716 my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
1717 $cgm->Create(
1718 Group => $self->PrincipalObj,
1719 Member => $self->PrincipalObj,
1720 ImmediateParent => $self->PrincipalObj
1721 );
1722}
1723
84fb5b46
MKG
1724RT::Base->_ImportOverlays();
1725
17261;