X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FUser_Overlay.pm;h=bc4cbc7add4f1d5b34199857a4d26725363ffe05;hb=8103c1fc1b2c27a6855feadf26f91b980a54bc52;hp=c2fe651920105d395859f189b72757b8e6a25c30;hpb=c582e92888b4a5553e1b4e5214cf35217e4a0cf0;p=freeside.git diff --git a/rt/lib/RT/User_Overlay.pm b/rt/lib/RT/User_Overlay.pm index c2fe65192..bc4cbc7ad 100644 --- a/rt/lib/RT/User_Overlay.pm +++ b/rt/lib/RT/User_Overlay.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,31 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) # +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. # -# END LICENSE BLOCK +# END BPS TAGGED BLOCK }}} =head1 NAME RT::User - RT User object @@ -43,6 +67,9 @@ ok(require RT::User); =cut + +package RT::User; + use strict; no warnings qw(redefine); @@ -53,86 +80,34 @@ use vars qw(%_USERS_KEY_CACHE); use Digest::MD5; use RT::Principals; use RT::ACE; -use RT::EmailParser; - +use RT::Interface::Email; +use Encode; # {{{ sub _Accessible -sub _ClassAccessible { +sub _OverlayAccessible { { - - id => - {read => 1, type => 'int(11)', default => ''}, - Name => - {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(120)', default => ''}, - Password => - { write => 1, type => 'varchar(40)', default => ''}, - Comments => - {read => 1, write => 1, admin => 1, type => 'blob', default => ''}, - Signature => - {read => 1, write => 1, type => 'blob', default => ''}, - EmailAddress => - {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''}, - FreeformContactInfo => - {read => 1, write => 1, type => 'blob', default => ''}, - Organization => - {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(200)', default => ''}, - RealName => - {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''}, - NickName => - {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''}, - Lang => - {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''}, - EmailEncoding => - {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''}, - WebEncoding => - {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''}, - ExternalContactInfoId => - {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''}, - ContactInfoSystem => - {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(30)', default => ''}, - ExternalAuthId => - {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''}, - AuthSystem => - {read => 1, write => 1, public => 1, admin => 1,type => 'varchar(30)', default => ''}, - Gecos => - {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(16)', default => ''}, - - PGPKey => { - {read => 1, write => 1, public => 1, admin => 1, type => 'text', default => ''}, - }, - HomePhone => - {read => 1, write => 1, type => 'varchar(30)', default => ''}, - WorkPhone => - {read => 1, write => 1, type => 'varchar(30)', default => ''}, - MobilePhone => - {read => 1, write => 1, type => 'varchar(30)', default => ''}, - PagerPhone => - {read => 1, write => 1, type => 'varchar(30)', default => ''}, - Address1 => - {read => 1, write => 1, type => 'varchar(200)', default => ''}, - Address2 => - {read => 1, write => 1, type => 'varchar(200)', default => ''}, - City => - {read => 1, write => 1, type => 'varchar(100)', default => ''}, - State => - {read => 1, write => 1, type => 'varchar(100)', default => ''}, - Zip => - {read => 1, write => 1, type => 'varchar(16)', default => ''}, - Country => - {read => 1, write => 1, type => 'varchar(50)', default => ''}, - Creator => - {read => 1, auto => 1, type => 'int(11)', default => ''}, - Created => - {read => 1, auto => 1, type => 'datetime', default => ''}, - LastUpdatedBy => - {read => 1, auto => 1, type => 'int(11)', default => ''}, - LastUpdated => - {read => 1, auto => 1, type => 'datetime', default => ''}, - - } -}; + + Name => { public => 1, admin => 1 }, + Password => { read => 0 }, + EmailAddress => { public => 1 }, + Organization => { public => 1, admin => 1 }, + RealName => { public => 1 }, + NickName => { public => 1 }, + Lang => { public => 1 }, + EmailEncoding => { public => 1 }, + WebEncoding => { public => 1 }, + ExternalContactInfoId => { public => 1, admin => 1 }, + ContactInfoSystem => { public => 1, admin => 1 }, + ExternalAuthId => { public => 1, admin => 1 }, + AuthSystem => { public => 1, admin => 1 }, + Gecos => { public => 1, admin => 1 }, + PGPKey => { public => 1, admin => 1 }, + + } +} + # }}} @@ -148,41 +123,41 @@ sub _ClassAccessible { my $u1 = RT::User->new($RT::SystemUser); is(ref($u1), 'RT::User'); -my ($id, $msg) = $u1->Create(Name => 'CreateTest1', EmailAddress => 'create-test-1@example.com'); +my ($id, $msg) = $u1->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-1@example.com'); ok ($id, "Creating user CreateTest1 - " . $msg ); # Make sure we can't create a second user with the same name my $u2 = RT::User->new($RT::SystemUser); -($id, $msg) = $u2->Create(Name => 'CreateTest1', EmailAddress => 'create-test-2@example.com'); +($id, $msg) = $u2->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-2@example.com'); ok (!$id, $msg); # Make sure we can't create a second user with the same EmailAddress address my $u3 = RT::User->new($RT::SystemUser); -($id, $msg) = $u3->Create(Name => 'CreateTest2', EmailAddress => 'create-test-1@example.com'); +($id, $msg) = $u3->Create(Name => 'CreateTest2'.$$, EmailAddress => $$.'create-test-1@example.com'); ok (!$id, $msg); # Make sure we can create a user with no EmailAddress address my $u4 = RT::User->new($RT::SystemUser); -($id, $msg) = $u4->Create(Name => 'CreateTest3'); +($id, $msg) = $u4->Create(Name => 'CreateTest3'.$$); ok ($id, $msg); # make sure we can create a second user with no EmailAddress address my $u5 = RT::User->new($RT::SystemUser); -($id, $msg) = $u5->Create(Name => 'CreateTest4'); +($id, $msg) = $u5->Create(Name => 'CreateTest4'.$$); ok ($id, $msg); # make sure we can create a user with a blank EmailAddress address my $u6 = RT::User->new($RT::SystemUser); -($id, $msg) = $u6->Create(Name => 'CreateTest6', EmailAddress => ''); +($id, $msg) = $u6->Create(Name => 'CreateTest6'.$$, EmailAddress => ''); ok ($id, $msg); # make sure we can create a second user with a blankEmailAddress address my $u7 = RT::User->new($RT::SystemUser); -($id, $msg) = $u7->Create(Name => 'CreateTest7', EmailAddress => ''); +($id, $msg) = $u7->Create(Name => 'CreateTest7'.$$, EmailAddress => ''); ok ($id, $msg); # Can we change the email address away from from ""; -($id,$msg) = $u7->SetEmailAddress('foo@bar'); +($id,$msg) = $u7->SetEmailAddress('foo@bar'.$$); ok ($id, $msg); # can we change the address back to ""; ($id,$msg) = $u7->SetEmailAddress(''); @@ -201,15 +176,25 @@ sub Create { Privileged => 0, Disabled => 0, EmailAddress => '', + _RecordTransaction => 1, @_ # get the real argumentlist ); + # remove the value so it does not cripple SUPER::Create + my $record_transaction = delete $args{'_RecordTransaction'}; + #Check the ACL unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return ( 0, $self->loc('No permission to create users') ); } + + unless ($self->CanonicalizeUserInfo(\%args)) { + return ( 0, $self->loc("Could not set user info") ); + } + $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'}); + # if the user doesn't have a name defined, set it to the email address $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'}); @@ -228,7 +213,7 @@ sub Create { $args{'Password'} = '*NO-PASSWORD*'; } elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) { - return ( 0, $self->loc("Password too short") ); + return ( 0, $self->loc("Password needs to be at least [_1] characters long",$RT::MinimumPasswordLength) ); } else { @@ -281,7 +266,7 @@ sub Create { #If the create failed. unless ($id) { $RT::Handle->Rollback(); - $RT::Logger->error("Could not create a new user - " .join('-'. %args)); + $RT::Logger->error("Could not create a new user - " .join('-', %args)); return ( 0, $self->loc('Could not create user') ); } @@ -338,7 +323,12 @@ sub Create { } + if ( $record_transaction ) { + $self->_NewTransaction( Type => "Create" ); + } + $RT::Handle->Commit; + return ( $id, $self->loc('User created') ); } @@ -597,12 +587,15 @@ sub LoadOrCreateByEmail { my ($val, $message); my ( $Address, $Name ) = - RT::EmailParser::ParseAddressFromHeader('', $email); + RT::Interface::Email::ParseAddressFromHeader($email); $email = $Address; $self->LoadByEmail($email); $message = $self->loc('User loaded'); unless ($self->Id) { + $self->Load($email); + } + unless($self->Id) { ( $val, $message ) = $self->Create( Name => $email, EmailAddress => $email, @@ -673,11 +666,13 @@ sub ValidateEmailAddress { -=item CanonicalizeEmailAddress ADDRESS +=head2 CanonicalizeEmailAddress ADDRESS -# CanonicalizeEmailAddress converts email addresses into canonical form. -# it takes one email address in and returns the proper canonical -# form. You can dump whatever your proper local config is in here +CanonicalizeEmailAddress converts email addresses into canonical form. +it takes one email address in and returns the proper canonical +form. You can dump whatever your proper local config is in here. Note +that it may be called as a static method; in this case the first argument +is class name not an object. =cut @@ -696,6 +691,32 @@ sub CanonicalizeEmailAddress { # }}} +# {{{ sub CanonicalizeUserInfo + + + +=head2 CanonicalizeUserInfo HASH of ARGS + +CanonicalizeUserInfo can convert all User->Create options. +it takes a hashref of all the params sent to User->Create and +returns that same hash, by default nothing is done. + +This function is intended to allow users to have their info looked up via +an outside source and modified upon creation. + +=cut + +sub CanonicalizeUserInfo { + my $self = shift; + my $args = shift; + my $success = 1; + + return ($success); +} + + +# }}} + # {{{ Password related functions @@ -716,7 +737,11 @@ sub SetRandomPassword { return ( 0, $self->loc("Permission Denied") ); } - my $pass = $self->GenerateRandomPassword( 6, 8 ); + + my $min = ( $RT::MinimumPasswordLength > 6 ? $RT::MinimumPasswordLength : 6); + my $max = ( $RT::MinimumPasswordLength > 8 ? $RT::MinimumPasswordLength : 8); + + my $pass = $self->GenerateRandomPassword( $min, $max) ; # If we have "notify user on @@ -762,7 +787,7 @@ sub ResetPassword { $template->LoadGlobalTemplate('RT_PasswordChange_Privileged'); } else { - $template->LoadGlobalTemplate('RT_PasswordChange_Privileged'); + $template->LoadGlobalTemplate('RT_PasswordChange_NonPrivileged'); } unless ( $template->Id ) { @@ -781,7 +806,7 @@ sub ResetPassword { Argument => $pass ); - $notification->SetTo( $self->EmailAddress ); + $notification->SetHeader( 'To', $self->EmailAddress ); my ($ret); $ret = $notification->Prepare(); @@ -1004,25 +1029,33 @@ sub SetPassword { my $password = shift; unless ( $self->CurrentUserCanModify('Password') ) { - return ( 0, $self->loc('Permission Denied') ); + return ( 0, $self->loc('Password: Permission Denied') ); } if ( !$password ) { return ( 0, $self->loc("No password set") ); } elsif ( length($password) < $RT::MinimumPasswordLength ) { - return ( 0, $self->loc("Password too short") ); + return ( 0, $self->loc("Password needs to be at least [_1] characters long", $RT::MinimumPasswordLength) ); } else { + my $new = !$self->HasPassword; $password = $self->_GeneratePassword($password); - return ( $self->SUPER::SetPassword( $password)); + my ( $val, $msg ) = $self->SUPER::SetPassword($password); + if ($val) { + return ( 1, $self->loc("Password set") ) if $new; + return ( 1, $self->loc("Password changed") ); + } + else { + return ( $val, $msg ); + } } } =head2 _GeneratePassword PASSWORD -returns an MD5 hash of the password passed in, in base64 encoding. +returns an MD5 hash of the password passed in, in hexadecimal encoding. =cut @@ -1031,13 +1064,48 @@ sub _GeneratePassword { my $password = shift; my $md5 = Digest::MD5->new(); - $md5->add($password); + $md5->add(encode_utf8($password)); + return ($md5->hexdigest); + +} + +=head2 _GeneratePasswordBase64 PASSWORD + +returns an MD5 hash of the password passed in, in base64 encoding +(obsoleted now). + +=cut + +sub _GeneratePasswordBase64 { + my $self = shift; + my $password = shift; + + my $md5 = Digest::MD5->new(); + $md5->add(encode_utf8($password)); return ($md5->b64digest); } # }}} + +=head2 HasPassword + +Returns true if the user has a valid password, otherwise returns false. + +=cut + + +sub HasPassword { + my $self = shift; + my $pwd = $self->__Value('Password'); + return undef if !defined $pwd + || $pwd eq '' + || $pwd eq '*NO-PASSWORD*'; + return 1; +} + + # {{{ sub IsPassword =head2 IsPassword @@ -1064,8 +1132,7 @@ sub IsPassword { return (undef); } - if ( ($self->__Value('Password') eq '') || - ($self->__Value('Password') eq undef) ) { + unless ($self->HasPassword) { return(undef); } @@ -1075,9 +1142,12 @@ sub IsPassword { } # if it's a historical password we say ok. - - if ( $self->__Value('Password') eq crypt( $value, $self->__Value('Password') ) ) { - return (1); + if ($self->__Value('Password') eq crypt($value, $self->__Value('Password')) + or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password')) + { + # ...but upgrade the legacy password inplace. + $self->SUPER::SetPassword( $self->_GeneratePassword($value) ); + return(1); } # no password check has succeeded. get out @@ -1129,7 +1199,7 @@ The response is cached. PrincipalObj should never ever change. ok(my $u = RT::User->new($RT::SystemUser)); ok($u->Load(1), "Loaded the first user"); ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal"); -ok($u->PrincipalObj->PrincipalType eq 'User' , "Principal 1 is a user, not a group"); +is($u->PrincipalObj->PrincipalType, 'User' , "Principal 1 is a user, not a group"); =end testing @@ -1218,9 +1288,29 @@ sub HasGroupRight { # }}} +# {{{ sub OwnGroups + +=head2 OwnGroups + +Returns a group collection object containing the groups of which this +user is a member. + +=cut + +sub OwnGroups { + my $self = shift; + my $groups = RT::Groups->new($self->CurrentUser); + $groups->LimitToUserDefinedGroups; + $groups->WithMember(PrincipalId => $self->Id, + Recursively => 1); + return $groups; +} + +# }}} + # {{{ sub Rights testing -=head2 Rights testing +=head1 Rights testing =begin testing @@ -1235,7 +1325,7 @@ ok($rootq->Id, "Loaded the first queue"); ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets"); my $new_user = RT::User->new($RT::SystemUser); -my ($id, $msg) = $new_user->Create(Name => 'ACLTest'); +my ($id, $msg) = $new_user->Create(Name => 'ACLTest'.$$); ok ($id, "Created a new user for acl test $msg"); @@ -1266,7 +1356,7 @@ ok($tickid, "Created ticket: $tickid"); ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); # Create a new group my $group = RT::Group->new($RT::SystemUser); -$group->CreateUserDefinedGroup(Name => 'ACLTest'); +$group->CreateUserDefinedGroup(Name => 'ACLTest'.$$); ok($group->Id, "Created a new group Ok"); # Grant a group the right to modify tickets in a queue ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets"); @@ -1293,7 +1383,7 @@ ok($q_as_system->Id, "Loaded the first queue"); my $new_tick2 = RT::Ticket->new($RT::SystemUser); my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id); ok($tick2id, "Created ticket: $tick2id"); -ok($new_tick2->QueueObj->id eq $q_as_system->Id, "Created a new ticket in queue 1"); +is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1"); # make sure that the user can't do this without subgroup membership @@ -1301,7 +1391,7 @@ ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User # Create a subgroup my $subgroup = RT::Group->new($RT::SystemUser); -$subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest'); +$subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest',$$); ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok"); #Add the subgroup as a subgroup of the group my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId); @@ -1316,8 +1406,8 @@ ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User c # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights my ($id, $msg); - ($id, $msg) = $group->SetDisabled(1); - ok ($id,$msg); +($id, $msg) = $group->SetDisabled(1); +ok ($id,$msg); ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled"); ($id, $msg) = $group->SetDisabled(0); ok($id,$msg); @@ -1462,7 +1552,7 @@ ok($rqv, "Revoked the right successfully - $rqm"); # {{{ sub HasRight -=head2 sub HasRight +=head2 HasRight Shim around PrincipalObj->HasRight. See RT::Principal @@ -1521,8 +1611,8 @@ sub CurrentUserCanModify { =head2 CurrentUserHasRight - Takes a single argument. returns 1 if $Self->CurrentUser - has the requested right. returns undef otherwise +Takes a single argument. returns 1 if $Self->CurrentUser +has the requested right. returns undef otherwise =cut @@ -1533,6 +1623,216 @@ sub CurrentUserHasRight { return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) ); } +sub _PrefName { + my $name = shift; + if (ref $name) { + $name = ref ($name).'-'.$name->Id; + } + + return 'Pref-'.$name; +} + +# {{{ sub Preferences + +=head2 Preferences NAME/OBJ DEFAULT + + Obtain user preferences associated with given object or name. + Returns DEFAULT if no preferences found. If DEFAULT is a hashref, + override the entries with user preferences. + +=cut + +sub Preferences { + my $self = shift; + my $name = _PrefName (shift); + my $default = shift; + + my $attr = RT::Attribute->new ($self->CurrentUser); + $attr->LoadByNameAndObject (Object => $self, Name => $name); + + my $content = $attr->Id ? $attr->Content : undef; + if (ref ($content) eq 'HASH') { + if (ref ($default) eq 'HASH') { + for (keys %$default) { + exists $content->{$_} or $content->{$_} = $default->{$_}; + } + } + elsif (defined $default) { + $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not"); + } + return $content; + } + else { + return defined $content ? $content : $default; + } +} + +# }}} + +# {{{ sub SetPreferences + +=head2 SetPreferences NAME/OBJ VALUE + + Set user preferences associated with given object or name. + +=cut + +sub SetPreferences { + my $self = shift; + my $name = _PrefName (shift); + my $value = shift; + my $attr = RT::Attribute->new ($self->CurrentUser); + $attr->LoadByNameAndObject (Object => $self, Name => $name); + if ($attr->Id) { + return $attr->SetContent ($value); + } + else { + return $self->AddAttribute ( Name => $name, Content => $value ); + } +} + +# }}} + + +=head2 WatchedQueues ROLE_LIST + +Returns a RT::Queues object containing every queue watched by the user. + +Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to: + +$user->WatchedQueues('Cc', 'AdminCc'); + +=cut + +sub WatchedQueues { + + my $self = shift; + my @roles = @_ || ('Cc', 'AdminCc'); + + $RT::Logger->debug('WatcheQueues got user ' . $self->Name); + + my $watched_queues = RT::Queues->new($self->CurrentUser); + + my $group_alias = $watched_queues->Join( + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'Groups', + FIELD2 => 'Instance', + ); + + $watched_queues->Limit( + ALIAS => $group_alias, + FIELD => 'Domain', + VALUE => 'RT::Queue-Role', + ENTRYAGGREGATOR => 'AND', + ); + if (grep { $_ eq 'Cc' } @roles) { + $watched_queues->Limit( + SUBCLAUSE => 'LimitToWatchers', + ALIAS => $group_alias, + FIELD => 'Type', + VALUE => 'Cc', + ENTRYAGGREGATOR => 'OR', + ); + } + if (grep { $_ eq 'AdminCc' } @roles) { + $watched_queues->Limit( + SUBCLAUSE => 'LimitToWatchers', + ALIAS => $group_alias, + FIELD => 'Type', + VALUE => 'AdminCc', + ENTRYAGGREGATOR => 'OR', + ); + } + + my $queues_alias = $watched_queues->Join( + ALIAS1 => $group_alias, + FIELD1 => 'id', + TABLE2 => 'CachedGroupMembers', + FIELD2 => 'GroupId', + ); + $watched_queues->Limit( + ALIAS => $queues_alias, + FIELD => 'MemberId', + VALUE => $self->PrincipalId, + ); + + $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues"); + + return $watched_queues; + +} + + +# {{{ sub _CleanupInvalidDelegations + +=head2 _CleanupInvalidDelegations { InsideTransaction => undef } + +Revokes all ACE entries delegated by this user which are inconsistent +with their current delegation rights. Does not perform permission +checks. Should only ever be called from inside the RT library. + +If called from inside a transaction, specify a true value for the +InsideTransaction parameter. + +Returns a true value if the deletion succeeded; returns a false value +and logs an internal error if the deletion fails (should not happen). + +=cut + +# XXX Currently there is a _CleanupInvalidDelegations method in both +# RT::User and RT::Group. If the recursive cleanup call for groups is +# ever unrolled and merged, this code will probably want to be +# factored out into RT::Principal. + +sub _CleanupInvalidDelegations { + my $self = shift; + my %args = ( InsideTransaction => undef, + @_ ); + + unless ( $self->Id ) { + $RT::Logger->warning("User not loaded."); + return (undef); + } + + my $in_trans = $args{InsideTransaction}; + + return(1) if ($self->HasRight(Right => 'DelegateRights', + Object => $RT::System)); + + # Look up all delegation rights currently posessed by this user. + my $deleg_acl = RT::ACL->new($RT::SystemUser); + $deleg_acl->LimitToPrincipal(Type => 'User', + Id => $self->PrincipalId, + IncludeGroupMembership => 1); + $deleg_acl->Limit( FIELD => 'RightName', + OPERATOR => '=', + VALUE => 'DelegateRights' ); + my @allowed_deleg_objects = map {$_->Object()} + @{$deleg_acl->ItemsArrayRef()}; + + # Look up all rights delegated by this principal which are + # inconsistent with the allowed delegation objects. + my $acl_to_del = RT::ACL->new($RT::SystemUser); + $acl_to_del->DelegatedBy(Id => $self->Id); + foreach (@allowed_deleg_objects) { + $acl_to_del->LimitNotObject($_); + } + + # Delete all disallowed delegations + while ( my $ace = $acl_to_del->Next() ) { + my $ret = $ace->_Delete(InsideTransaction => 1); + unless ($ret) { + $RT::Handle->Rollback() unless $in_trans; + $RT::Logger->warning("Couldn't delete delegated ACL entry ".$ace->Id); + return (undef); + } + } + + $RT::Handle->Commit() unless $in_trans; + return (1); +} + # }}} # {{{ sub _Set @@ -1543,6 +1843,8 @@ sub _Set { my %args = ( Field => undef, Value => undef, + TransactionType => 'Set', + RecordTransaction => 1, @_ ); @@ -1556,13 +1858,29 @@ sub _Set { return ( 0, $self->loc("Permission Denied") ); } - #Set the new value - my ( $ret, $msg ) = $self->SUPER::_Set( - Field => $args{'Field'}, - Value => $args{'Value'} - ); + my $Old = $self->SUPER::_Value("$args{'Field'}"); + + my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'}, + Value => $args{'Value'} ); + + #If we can't actually set the field to the value, don't record + # a transaction. instead, get out of here. + if ( $ret == 0 ) { return ( 0, $msg ); } - return ( $ret, $msg ); + if ( $args{'RecordTransaction'} == 1 ) { + + my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( + Type => $args{'TransactionType'}, + Field => $args{'Field'}, + NewValue => $args{'Value'}, + OldValue => $Old, + TimeTaken => $args{'TimeTaken'}, + ); + return ( $Trans, scalar $TransObj->BriefDescription ); + } + else { + return ( $ret, $msg ); + } } # }}} @@ -1612,6 +1930,14 @@ sub _Value { # }}} +sub BasicColumns { + ( + [ Name => 'User Id' ], + [ EmailAddress => 'Email' ], + [ RealName => 'Name' ], + [ Organization => 'Organization' ], + ); +} 1;