1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
51 RT::User - RT User object
69 use Scalar::Util qw(blessed);
71 use base 'RT::Record';
82 use Crypt::Eksblowfish::Bcrypt qw();
85 use RT::Interface::Email;
86 use Text::Password::Pronounceable;
88 sub _OverlayAccessible {
91 Name => { public => 1, admin => 1 }, # loc_left_pair
92 Password => { read => 0 },
93 EmailAddress => { public => 1 }, # loc_left_pair
94 Organization => { public => 1, admin => 1 }, # loc_left_pair
95 RealName => { public => 1 }, # loc_left_pair
96 NickName => { public => 1 }, # loc_left_pair
97 Lang => { public => 1 }, # loc_left_pair
98 EmailEncoding => { public => 1 },
99 WebEncoding => { public => 1 },
100 ExternalContactInfoId => { public => 1, admin => 1 },
101 ContactInfoSystem => { public => 1, admin => 1 },
102 ExternalAuthId => { public => 1, admin => 1 },
103 AuthSystem => { public => 1, admin => 1 },
104 Gecos => { public => 1, admin => 1 }, # loc_left_pair
105 PGPKey => { public => 1, admin => 1 }, # loc_left_pair
106 SMIMECertificate => { public => 1, admin => 1 }, # loc_left_pair
107 City => { public => 1 }, # loc_left_pair
108 Country => { public => 1 }, # loc_left_pair
109 Timezone => { public => 1 }, # loc_left_pair
115 =head2 Create { PARAMHASH }
128 _RecordTransaction => 1,
129 @_ # get the real argumentlist
132 # remove the value so it does not cripple SUPER::Create
133 my $record_transaction = delete $args{'_RecordTransaction'};
136 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
137 return ( 0, $self->loc('Permission Denied') );
141 unless ($self->CanonicalizeUserInfo(\%args)) {
142 return ( 0, $self->loc("Could not set user info") );
145 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
147 # if the user doesn't have a name defined, set it to the email address
148 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
152 my $privileged = delete $args{'Privileged'};
155 if ($args{'CryptedPassword'} ) {
156 $args{'Password'} = $args{'CryptedPassword'};
157 delete $args{'CryptedPassword'};
158 } elsif ( !$args{'Password'} ) {
159 $args{'Password'} = '*NO-PASSWORD*';
161 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
162 return ($ok, $msg) if !$ok;
164 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
167 #TODO Specify some sensible defaults.
169 unless ( $args{'Name'} ) {
170 return ( 0, $self->loc("Must specify 'Name' attribute") );
173 my ( $val, $msg ) = $self->ValidateName( $args{'Name'} );
174 return ( 0, $msg ) unless $val;
175 ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
176 return ( 0, $msg ) unless ($val);
178 $RT::Handle->BeginTransaction();
179 # Groups deal with principal ids, rather than user ids.
180 # When creating this user, set up a principal Id for it.
181 my $principal = RT::Principal->new($self->CurrentUser);
182 my $principal_id = $principal->Create(PrincipalType => 'User',
183 Disabled => $args{'Disabled'},
185 # If we couldn't create a principal Id, get the fuck out.
186 unless ($principal_id) {
187 $RT::Handle->Rollback();
188 $RT::Logger->crit("Couldn't create a Principal on new user create.");
189 $RT::Logger->crit("Strange things are afoot at the circle K");
190 return ( 0, $self->loc('Could not create user') );
193 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
194 delete $args{'Disabled'};
196 $self->SUPER::Create(id => $principal_id , %args);
199 #If the create failed.
201 $RT::Handle->Rollback();
202 $RT::Logger->error("Could not create a new user - " .join('-', %args));
204 return ( 0, $self->loc('Could not create user') );
207 my $aclstash = RT::Group->new($self->CurrentUser);
208 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
211 $RT::Handle->Rollback();
212 $RT::Logger->crit("Couldn't stash the user in groupmembers");
213 return ( 0, $self->loc('Could not create user') );
217 my $everyone = RT::Group->new($self->CurrentUser);
218 $everyone->LoadSystemInternalGroup('Everyone');
219 unless ($everyone->id) {
220 $RT::Logger->crit("Could not load Everyone group on user creation.");
221 $RT::Handle->Rollback();
222 return ( 0, $self->loc('Could not create user') );
226 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
227 unless ($everyone_id) {
228 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
229 $RT::Logger->crit($everyone_msg);
230 $RT::Handle->Rollback();
231 return ( 0, $self->loc('Could not create user') );
235 my $access_class = RT::Group->new($self->CurrentUser);
237 $access_class->LoadSystemInternalGroup('Privileged');
239 $access_class->LoadSystemInternalGroup('Unprivileged');
242 unless ($access_class->id) {
243 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
244 $RT::Handle->Rollback();
245 return ( 0, $self->loc('Could not create user') );
249 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
252 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
253 $RT::Logger->crit($ac_msg);
254 $RT::Handle->Rollback();
255 return ( 0, $self->loc('Could not create user') );
259 if ( $record_transaction ) {
260 $self->_NewTransaction( Type => "Create" );
265 return ( $id, $self->loc('User created') );
268 =head2 ValidateName STRING
270 Returns either (0, "failure reason") or 1 depending on whether the given
279 return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
281 my $TempUser = RT::User->new( RT->SystemUser );
282 $TempUser->Load($name);
284 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) {
285 return ( 0, $self->loc('Name in use') );
292 =head2 ValidatePassword STRING
294 Returns either (0, "failure reason") or 1 depending on whether the given
299 sub ValidatePassword {
301 my $password = shift;
303 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
304 return ( 0, $self->loc("Password needs to be at least [quant,_1,character,characters] long", RT->Config->Get('MinimumPasswordLength')) );
310 =head2 SetPrivileged BOOL
312 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
313 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
315 Returns a standard RT tuple of (val, msg);
325 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
326 return ( 0, $self->loc('Permission Denied') );
329 $self->_SetPrivileged($val);
335 my $priv = RT::Group->new($self->CurrentUser);
336 $priv->LoadSystemInternalGroup('Privileged');
338 $RT::Logger->crit("Could not find Privileged pseudogroup");
339 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
342 my $unpriv = RT::Group->new($self->CurrentUser);
343 $unpriv->LoadSystemInternalGroup('Unprivileged');
344 unless ($unpriv->Id) {
345 $RT::Logger->crit("Could not find unprivileged pseudogroup");
346 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
349 my $principal = $self->PrincipalId;
351 if ($priv->HasMember($principal)) {
352 #$RT::Logger->debug("That user is already privileged");
353 return (0,$self->loc("That user is already privileged"));
355 if ($unpriv->HasMember($principal)) {
356 $unpriv->_DeleteMember($principal);
358 # if we had layered transactions, life would be good
359 # sadly, we have to just go ahead, even if something
361 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
362 "unprivileged. something is drastically wrong.");
364 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
366 return (1, $self->loc("That user is now privileged"));
371 if ($unpriv->HasMember($principal)) {
372 #$RT::Logger->debug("That user is already unprivileged");
373 return (0,$self->loc("That user is already unprivileged"));
375 if ($priv->HasMember($principal)) {
376 $priv->_DeleteMember( $principal );
378 # if we had layered transactions, life would be good
379 # sadly, we have to just go ahead, even if something
381 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
382 "unprivileged. something is drastically wrong.");
384 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
386 return (1, $self->loc("That user is now unprivileged"));
395 Returns true if this user is privileged. Returns undef otherwise.
401 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
408 #create a user without validating _any_ data.
410 #To be used only on database init.
411 # We can't localize here because it's before we _have_ a loc framework
413 sub _BootstrapCreate {
417 $args{'Password'} = '*NO-PASSWORD*';
420 $RT::Handle->BeginTransaction();
422 # Groups deal with principal ids, rather than user ids.
423 # When creating this user, set up a principal Id for it.
424 my $principal = RT::Principal->new($self->CurrentUser);
425 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
426 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
428 # If we couldn't create a principal Id, get the fuck out.
429 unless ($principal_id) {
430 $RT::Handle->Rollback();
431 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
432 return ( 0, 'Could not create user' );
434 $self->SUPER::Create(id => $principal_id, %args);
436 #If the create failed.
438 $RT::Handle->Rollback();
439 return ( 0, 'Could not create user' ) ; #never loc this
442 my $aclstash = RT::Group->new($self->CurrentUser);
443 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
446 $RT::Handle->Rollback();
447 $RT::Logger->crit("Couldn't stash the user in groupmembers");
448 return ( 0, $self->loc('Could not create user') );
451 $RT::Handle->Commit();
453 return ( $id, 'User created' );
459 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
465 Load a user object from the database. Takes a single argument.
466 If the argument is numerical, load by the column 'id'. If a user
467 object or its subclass passed then loads the same user by id.
468 Otherwise, load by the "Name" column which is the user's textual
475 my $identifier = shift || return undef;
477 if ( $identifier !~ /\D/ ) {
478 return $self->SUPER::LoadById( $identifier );
479 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
480 return $self->SUPER::LoadById( $identifier->Id );
482 return $self->LoadByCol( "Name", $identifier );
488 Tries to load this user object from the database by the user's email address.
496 # Never load an empty address as an email address.
501 $address = $self->CanonicalizeEmailAddress($address);
503 #$RT::Logger->debug("Trying to load an email address: $address");
504 return $self->LoadByCol( "EmailAddress", $address );
507 =head2 LoadOrCreateByEmail ADDRESS
509 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
510 the provided email address and loads them. Address can be provided either as L<Email::Address> object
511 or string which is parsed using the module.
513 Returns a tuple of the user's id and a status message.
514 0 will be returned in place of the user's id in case of failure.
518 sub LoadOrCreateByEmail {
522 my ($message, $name);
523 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
524 ($email, $name) = ($email->address, $email->phrase);
526 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
529 $self->LoadByEmail( $email );
530 $self->Load( $email ) unless $self->Id;
531 $message = $self->loc('User loaded');
533 unless( $self->Id ) {
535 ($val, $message) = $self->Create(
537 EmailAddress => $email,
540 Comments => 'Autocreated when added as a watcher',
543 # Deal with the race condition of two account creations at once
544 $self->LoadByEmail( $email );
545 unless ( $self->Id ) {
547 $self->LoadByEmail( $email );
550 $RT::Logger->error("Recovered from creation failure due to race condition");
551 $message = $self->loc("User loaded");
553 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
557 return wantarray ? (0, $message) : 0 unless $self->id;
558 return wantarray ? ($self->Id, $message) : $self->Id;
561 =head2 ValidateEmailAddress ADDRESS
563 Returns true if the email address entered is not in use by another user or is
564 undef or ''. Returns false if it's in use.
568 sub ValidateEmailAddress {
572 # if the email address is null, it's always valid
573 return (1) if ( !$Value || $Value eq "" );
575 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
576 # We only allow one valid email address
577 my @addresses = Email::Address->parse($Value);
578 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
582 my $TempUser = RT::User->new(RT->SystemUser);
583 $TempUser->LoadByEmail($Value);
585 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
586 { # if we found a user with that address
587 # it's invalid to set this user's address to it
588 return ( 0, $self->loc('Email address in use') );
589 } else { #it's a valid email address
596 Check to make sure someone else isn't using this name already
604 my ( $val, $message ) = $self->ValidateName($Value);
606 return $self->_Set( Field => 'Name', Value => $Value );
609 return ( 0, $message );
613 =head2 SetEmailAddress
615 Check to make sure someone else isn't using this email address already
616 so that a better email address can be returned
620 sub SetEmailAddress {
623 $Value = '' unless defined $Value;
625 my ($val, $message) = $self->ValidateEmailAddress( $Value );
627 return $self->_Set( Field => 'EmailAddress', Value => $Value );
629 return ( 0, $message )
634 =head2 EmailFrequency
636 Takes optional Ticket argument in paramhash. Returns a string, suitable
637 for localization, describing any notable properties about email delivery
638 to the user. This includes lack of email address, ticket-level
639 squelching (if C<Ticket> is provided in the paramhash), or user email
640 delivery preferences.
642 Returns the empty string if there are no notable properties.
652 return '' unless $self->id && $self->id != RT->Nobody->id
653 && $self->id != RT->SystemUser->id;
654 return 'no email address set' # loc
655 unless my $email = $self->EmailAddress;
656 return 'email disabled for ticket' # loc
657 if $args{'Ticket'} &&
658 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
659 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
660 return 'receives daily digests' # loc
661 if $frequency =~ /daily/i;
662 return 'receives weekly digests' # loc
663 if $frequency =~ /weekly/i;
664 return 'email delivery suspended' # loc
665 if $frequency =~ /suspend/i;
669 =head2 CanonicalizeEmailAddress ADDRESS
671 CanonicalizeEmailAddress converts email addresses into canonical form.
672 it takes one email address in and returns the proper canonical
673 form. You can dump whatever your proper local config is in here. Note
674 that it may be called as a static method; in this case the first argument
675 is class name not an object.
679 sub CanonicalizeEmailAddress {
682 # Example: the following rule would treat all email
683 # coming from a subdomain as coming from second level domain
685 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
686 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
688 $email =~ s/$match/$replace/gi;
693 =head2 CanonicalizeUserInfo HASH of ARGS
695 CanonicalizeUserInfo can convert all User->Create options.
696 it takes a hashref of all the params sent to User->Create and
697 returns that same hash, by default nothing is done.
699 This function is intended to allow users to have their info looked up via
700 an outside source and modified upon creation.
704 sub CanonicalizeUserInfo {
713 =head2 Password and authentication related functions
715 =head3 SetRandomPassword
717 Takes no arguments. Returns a status code and a new password or an error message.
718 If the status is 1, the second value returned is the new password.
719 If the status is anything else, the new value returned is the error code.
723 sub SetRandomPassword {
726 unless ( $self->CurrentUserCanModify('Password') ) {
727 return ( 0, $self->loc("Permission Denied") );
731 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
732 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
734 my $pass = $self->GenerateRandomPassword( $min, $max) ;
736 # If we have "notify user on
738 my ( $val, $msg ) = $self->SetPassword($pass);
740 #If we got an error return the error.
741 return ( 0, $msg ) unless ($val);
743 #Otherwise, we changed the password, lets return it.
750 Returns status, [ERROR or new password]. Resets this user's password to
751 a randomly generated pronouncable password and emails them, using a
752 global template called "PasswordChange".
754 This function is currently unused in the UI, but available for local scripts.
761 unless ( $self->CurrentUserCanModify('Password') ) {
762 return ( 0, $self->loc("Permission Denied") );
764 my ( $status, $pass ) = $self->SetRandomPassword();
767 return ( 0, "$pass" );
770 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
771 To => $self->EmailAddress,
772 Template => 'PasswordChange',
774 NewPassword => $pass,
779 return ( 1, $self->loc('New password notification sent') );
781 return ( 0, $self->loc('Notification could not be sent') );
786 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
788 Returns a random password between MIN_LEN and MAX_LEN characters long.
792 sub GenerateRandomPassword {
793 my $self = shift; # just to drop it
794 return Text::Password::Pronounceable->generate(@_);
797 sub SafeSetPassword {
802 Confirmation => undef,
805 return (1) unless defined $args{'New'} && length $args{'New'};
807 my %cond = $self->CurrentUserRequireToSetPassword;
809 unless ( $cond{'CanSet'} ) {
810 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
814 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
815 if ( defined $args{'Current'} && length $args{'Current'} ) {
816 $error = $self->loc("Please enter your current password correctly.");
818 $error = $self->loc("Please enter your current password.");
820 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
821 $error = $self->loc("Passwords do not match.");
825 $error .= ' '. $self->loc('Password has not been set.');
829 return $self->SetPassword( $args{'New'} );
834 Takes a string. Checks the string's length and sets this user's password
841 my $password = shift;
843 unless ( $self->CurrentUserCanModify('Password') ) {
844 return ( 0, $self->loc('Password: Permission Denied') );
848 return ( 0, $self->loc("No password set") );
850 my ($val, $msg) = $self->ValidatePassword($password);
851 return ($val, $msg) if !$val;
853 my $new = !$self->HasPassword;
854 $password = $self->_GeneratePassword($password);
856 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
858 return ( 1, $self->loc("Password set") ) if $new;
859 return ( 1, $self->loc("Password changed") );
861 return ( $val, $msg );
867 sub _GeneratePassword_bcrypt {
869 my ($password, @rest) = @_;
874 # The first split is the number of rounds
877 # The salt is the first 22 characters, b64 encoded usign the
878 # special bcrypt base64.
879 $salt = Crypt::Eksblowfish::Bcrypt::de_base64( substr($rest[1], 0, 22) );
881 $rounds = RT->Config->Get('BcryptCost');
883 # Generate a random 16-octet base64 salt
885 $salt .= pack("C", int rand(256)) for 1..16;
888 my $hash = Crypt::Eksblowfish::Bcrypt::bcrypt_hash({
892 }, Digest::SHA::sha512( Encode::encode( 'UTF-8', $password) ) );
894 return join("!", "", "bcrypt", sprintf("%02d", $rounds),
895 Crypt::Eksblowfish::Bcrypt::en_base64( $salt ).
896 Crypt::Eksblowfish::Bcrypt::en_base64( $hash )
900 sub _GeneratePassword_sha512 {
902 my ($password, $salt) = @_;
904 # Generate a 16-character base64 salt
907 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
911 my $sha = Digest::SHA->new(512);
913 $sha->add(Encode::encode( 'UTF-8', $password));
914 return join("!", "", "sha512", $salt, $sha->b64digest);
917 =head3 _GeneratePassword PASSWORD [, SALT]
919 Returns a string to store in the database. This string takes the form:
923 By default, the method is currently C<bcrypt>.
927 sub _GeneratePassword {
929 return $self->_GeneratePassword_bcrypt(@_);
934 Returns true if the user has a valid password, otherwise returns false.
940 my $pwd = $self->__Value('Password');
941 return undef if !defined $pwd
943 || $pwd eq '*NO-PASSWORD*';
949 Returns true if the passed in value is this user's password.
950 Returns undef otherwise.
958 #TODO there isn't any apparent way to legitimately ACL this
960 # RT does not allow null passwords
961 if ( ( !defined($value) ) or ( $value eq '' ) ) {
965 if ( $self->PrincipalObj->Disabled ) {
967 "Disabled user " . $self->Name . " tried to log in" );
971 unless ($self->HasPassword) {
975 my $stored = $self->__Value('Password');
976 if ($stored =~ /^!/) {
977 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
978 my (undef, $method, @rest) = split /!/, $stored;
979 if ($method eq "bcrypt") {
980 return 0 unless $self->_GeneratePassword_bcrypt($value, @rest) eq $stored;
981 # Upgrade to a larger number of rounds if necessary
982 return 1 unless $rest[0] < RT->Config->Get('BcryptCost');
983 } elsif ($method eq "sha512") {
984 return 0 unless $self->_GeneratePassword_sha512($value, @rest) eq $stored;
986 $RT::Logger->warn("Unknown hash method $method");
989 } elsif (length $stored == 40) {
990 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
991 my $hash = MIME::Base64::decode_base64($stored);
992 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
993 my $salt = substr($hash, 0, 4, "");
994 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26) eq $hash;
995 } elsif (length $stored == 32) {
997 return 0 unless Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)) eq $stored;
998 } elsif (length $stored == 22) {
999 # Base64 nonsalted-md5
1000 return 0 unless Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)) eq $stored;
1001 } elsif (length $stored == 13) {
1003 return 0 unless crypt(Encode::encode( "UTF-8", $value), $stored) eq $stored;
1005 $RT::Logger->warning("Unknown password form");
1009 # We got here by validating successfully, but with a legacy
1010 # password form. Update to the most recent form.
1011 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
1012 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
1016 sub CurrentUserRequireToSetPassword {
1022 RequireCurrent => 1,
1025 if ( RT->Config->Get('WebRemoteUserAuth')
1026 && !RT->Config->Get('WebFallbackToRTLogin')
1029 $res{'Reason'} = $self->loc("External authentication enabled.");
1030 } elsif ( !$self->CurrentUser->HasPassword ) {
1031 if ( $self->CurrentUser->id == ($self->id||0) ) {
1032 # don't require current password if user has no
1033 $res{'RequireCurrent'} = 0;
1036 $res{'Reason'} = $self->loc("Your password is not set.");
1045 Returns an authentication string associated with the user. This
1046 string can be used to generate passwordless URLs to integrate
1047 RT with services and programms like callendar managers, rss
1054 my $secret = $self->_Value( AuthToken => @_ );
1055 return $secret if $secret;
1057 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1059 my $tmp = RT::User->new( RT->SystemUser );
1060 $tmp->Load( $self->id );
1061 my ($status, $msg) = $tmp->SetAuthToken( $secret );
1062 unless ( $status ) {
1063 $RT::Logger->error( "Couldn't set auth token: $msg" );
1069 =head3 GenerateAuthToken
1071 Generate a random authentication string for the user.
1075 sub GenerateAuthToken {
1077 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1078 return $self->SetAuthToken( $token );
1081 =head3 GenerateAuthString
1083 Takes a string and returns back a hex hash string. Later you can use
1084 this pair to make sure it's generated by this user using L</ValidateAuthString>
1088 sub GenerateAuthString {
1090 my $protect = shift;
1092 my $str = Encode::encode( "UTF-8", $self->AuthToken . $protect );
1094 return substr(Digest::MD5::md5_hex($str),0,16);
1097 =head3 ValidateAuthString
1099 Takes auth string and protected string. Returns true is protected string
1100 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1104 sub ValidateAuthString {
1106 my $auth_string = shift;
1107 my $protected = shift;
1109 my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected );
1111 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1116 Toggles the user's disabled flag.
1118 set, all password checks for this user will fail. All ACL checks for this
1119 user will fail. The user will appear in no user listings.
1126 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1127 return (0, $self->loc('Permission Denied'));
1130 $RT::Handle->BeginTransaction();
1131 my ($status, $msg) = $self->PrincipalObj->SetDisabled($val);
1133 $RT::Handle->Rollback();
1134 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1135 return ($status, $msg);
1137 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1139 $RT::Handle->Commit();
1142 return (1, $self->loc("User disabled"));
1144 return (1, $self->loc("User enabled"));
1151 Returns true if user is disabled or false otherwise
1157 return $self->PrincipalObj->Disabled(@_);
1162 Returns the principal object for this user. returns an empty RT::Principal
1163 if there's no principal object matching this user.
1164 The response is cached. PrincipalObj should never ever change.
1171 unless ( $self->id ) {
1172 $RT::Logger->error("Couldn't get principal for an empty user");
1176 if ( !$self->{_principal_obj} ) {
1178 my $obj = RT::Principal->new( $self->CurrentUser );
1179 $obj->LoadById( $self->id );
1181 $RT::Logger->crit( 'No principal for user #' . $self->id );
1183 } elsif ( $obj->PrincipalType ne 'User' ) {
1184 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1187 $self->{_principal_obj} = $obj;
1189 return $self->{_principal_obj};
1195 Returns this user's PrincipalId
1204 =head2 HasGroupRight
1206 Takes a paramhash which can contain
1208 GroupObj => RT::Group or Group => integer
1212 Returns 1 if this user has the right specified in the paramhash for the Group
1215 Returns undef if they don't.
1229 if ( defined $args{'Group'} ) {
1230 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1231 $args{'GroupObj'}->Load( $args{'Group'} );
1234 # Validate and load up the GroupId
1235 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1239 # Figure out whether a user has the right we're asking about.
1240 my $retval = $self->HasRight(
1241 Object => $args{'GroupObj'},
1242 Right => $args{'Right'},
1250 Returns a group collection object containing the groups of which this
1257 my $groups = RT::Groups->new($self->CurrentUser);
1258 $groups->LimitToUserDefinedGroups;
1259 $groups->WithMember(
1260 PrincipalId => $self->Id,
1268 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1274 return $self->PrincipalObj->HasRight(@_);
1277 =head2 CurrentUserCanSee [FIELD]
1279 Returns true if the current user can see the user, based on if it is
1280 public, ourself, or we have AdminUsers
1284 sub CurrentUserCanSee {
1286 my ($what, $txn) = @_;
1288 # If it's a public property, fine
1289 return 1 if $self->_Accessible( $what, 'public' );
1291 # Users can see all of their own properties
1292 return 1 if defined($self->Id) and $self->CurrentUser->Id == $self->Id;
1294 # If the user has the admin users right, that's also enough
1295 return 1 if $self->CurrentUserHasRight( 'AdminUsers' );
1297 # Transactions of public properties are visible to users with ShowUserHistory
1298 if ($what eq "Transaction" and $self->CurrentUserHasRight( 'ShowUserHistory' )) {
1299 my $type = $txn->__Value('Type');
1300 my $field = $txn->__Value('Field');
1301 return 1 if $type eq "Set" and $self->CurrentUserCanSee($field, $txn);
1303 # RT::Transaction->CurrentUserCanSee deals with ensuring we meet
1304 # the ACLs on CFs, so allow them here
1305 return 1 if $type eq "CustomField";
1311 =head2 CurrentUserCanModify RIGHT
1313 If the user has rights for this object, either because
1314 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1315 admin right) 'ModifySelf', return 1. otherwise, return undef.
1319 sub CurrentUserCanModify {
1323 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1327 #If the field is marked as an "administrators only" field,
1328 # don't let the user touch it.
1329 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1333 #If the current user is trying to modify themselves
1334 elsif ( ( $self->id == $self->CurrentUser->id )
1335 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1340 #If we don't have a good reason to grant them rights to modify
1348 =head2 CurrentUserHasRight
1350 Takes a single argument. returns 1 if $Self->CurrentUser
1351 has the requested right. returns undef otherwise
1355 sub CurrentUserHasRight {
1359 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1365 $name = ref($name).'-'.$name->Id;
1368 return 'Pref-'. $name;
1371 =head2 Preferences NAME/OBJ DEFAULT
1373 Obtain user preferences associated with given object or name.
1374 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1375 override the entries with user preferences.
1381 my $name = _PrefName(shift);
1382 my $default = shift;
1384 my ($attr) = $self->Attributes->Named( $name );
1385 my $content = $attr ? $attr->Content : undef;
1386 unless ( ref $content eq 'HASH' ) {
1387 return defined $content ? $content : $default;
1390 if (ref $default eq 'HASH') {
1391 for (keys %$default) {
1392 exists $content->{$_} or $content->{$_} = $default->{$_};
1394 } elsif (defined $default) {
1395 $RT::Logger->error("Preferences $name for user #".$self->Id." is hash but default is not");
1400 =head2 SetPreferences NAME/OBJ VALUE
1402 Set user preferences associated with given object or name.
1406 sub SetPreferences {
1408 my $name = _PrefName( shift );
1411 return (0, $self->loc("No permission to set preferences"))
1412 unless $self->CurrentUserCanModify('Preferences');
1414 my ($attr) = $self->Attributes->Named( $name );
1416 my ($ok, $msg) = $attr->SetContent( $value );
1417 return (1, "No updates made")
1418 if $msg eq "That is already the current value";
1421 return $self->AddAttribute( Name => $name, Content => $value );
1425 =head2 DeletePreferences NAME/OBJ VALUE
1427 Delete user preferences associated with given object or name.
1431 sub DeletePreferences {
1433 my $name = _PrefName( shift );
1435 return (0, $self->loc("No permission to set preferences"))
1436 unless $self->CurrentUserCanModify('Preferences');
1438 my ($attr) = $self->DeleteAttribute( $name );
1439 return (0, $self->loc("Preferences were not found"))
1447 Returns a list of valid stylesheets take from preferences.
1454 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1456 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1457 for my $root (RT::Interface::Web->StaticRoots) {
1458 if (-d "$root/css/$style") {
1464 # Fall back to the system stylesheet.
1465 return RT->Config->Get('WebDefaultStylesheet');
1468 =head2 WatchedQueues ROLE_LIST
1470 Returns a RT::Queues object containing every queue watched by the user.
1472 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1474 $user->WatchedQueues('Cc', 'AdminCc');
1481 my @roles = @_ ? @_ : ('Cc', 'AdminCc');
1483 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1485 my $watched_queues = RT::Queues->new($self->CurrentUser);
1487 my $group_alias = $watched_queues->Join(
1491 FIELD2 => 'Instance',
1494 $watched_queues->Limit(
1495 ALIAS => $group_alias,
1497 VALUE => 'RT::Queue-Role',
1498 ENTRYAGGREGATOR => 'AND',
1501 if (grep { $_ eq 'Cc' } @roles) {
1502 $watched_queues->Limit(
1503 SUBCLAUSE => 'LimitToWatchers',
1504 ALIAS => $group_alias,
1507 ENTRYAGGREGATOR => 'OR',
1510 if (grep { $_ eq 'AdminCc' } @roles) {
1511 $watched_queues->Limit(
1512 SUBCLAUSE => 'LimitToWatchers',
1513 ALIAS => $group_alias,
1516 ENTRYAGGREGATOR => 'OR',
1520 my $queues_alias = $watched_queues->Join(
1521 ALIAS1 => $group_alias,
1523 TABLE2 => 'CachedGroupMembers',
1524 FIELD2 => 'GroupId',
1526 $watched_queues->Limit(
1527 ALIAS => $queues_alias,
1528 FIELD => 'MemberId',
1529 VALUE => $self->PrincipalId,
1531 $watched_queues->Limit(
1532 ALIAS => $queues_alias,
1533 FIELD => 'Disabled',
1538 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1540 return $watched_queues;
1550 TransactionType => 'Set',
1551 RecordTransaction => 1,
1555 # Nobody is allowed to futz with RT_System or Nobody
1557 if ( ($self->Id == RT->SystemUser->Id ) ||
1558 ($self->Id == RT->Nobody->Id)) {
1559 return ( 0, $self->loc("Can not modify system users") );
1561 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1562 return ( 0, $self->loc("Permission Denied") );
1565 my $Old = $self->SUPER::_Value("$args{'Field'}");
1567 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1568 Value => $args{'Value'} );
1570 #If we can't actually set the field to the value, don't record
1571 # a transaction. instead, get out of here.
1572 if ( $ret == 0 ) { return ( 0, $msg ); }
1574 if ( $args{'RecordTransaction'} == 1 ) {
1575 if ($args{'Field'} eq "Password") {
1576 $args{'Value'} = $Old = '********';
1578 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1579 Type => $args{'TransactionType'},
1580 Field => $args{'Field'},
1581 NewValue => $args{'Value'},
1583 TimeTaken => $args{'TimeTaken'},
1585 return ( $Trans, scalar $TransObj->BriefDescription );
1587 return ( $ret, $msg );
1593 Takes the name of a table column.
1594 Returns its value as a string, if the user passes an ACL check
1603 # Defer to the abstraction above to know if the field can be read
1604 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1610 Return the friendly name
1616 return $self->RealName if defined $self->RealName and length $self->RealName;
1622 Class or object method.
1624 Returns a string describing a user in the current user's preferred format.
1626 May be invoked in three ways:
1629 RT::User->Format( User => $UserObj ); # same as above
1630 RT::User->Format( Address => $AddressObj, CurrentUser => $CurrentUserObj );
1632 Possible arguments are:
1638 An L<RT::User> object representing the user to format. Preferred to Address.
1642 An L<Email::Address> object representing the user address to format. Address
1643 will be used to lookup an L<RT::User> if possible.
1647 Required when Format is called as a class method with an Address argument.
1648 Otherwise, this argument is ignored in preference to the CurrentUser of the
1649 involved L<RT::User> object.
1653 Specifies the format to use, overriding any set from the config or current
1665 CurrentUser => undef,
1670 if (blessed($self) and $self->id) {
1671 @args{"User", "CurrentUser"} = ($self, $self->CurrentUser);
1673 elsif ($args{User} and $args{User}->id) {
1674 $args{CurrentUser} = $args{User}->CurrentUser;
1676 elsif ($args{Address} and $args{CurrentUser}) {
1677 $args{User} = RT::User->new( $args{CurrentUser} );
1678 $args{User}->LoadByEmail( $args{Address}->address );
1679 if ($args{User}->id) {
1680 delete $args{Address};
1686 RT->Logger->warning("Invalid arguments to RT::User->Format at @{[join '/', caller]}");
1690 $args{Format} ||= RT->Config->Get("UsernameFormat", $args{CurrentUser});
1691 $args{Format} =~ s/[^A-Za-z0-9_]+//g;
1693 my $method = "_FormatUser" . ucfirst lc $args{Format};
1694 my $formatter = $self->can($method);
1696 unless ($formatter) {
1698 "Either system config or user #" . $args{CurrentUser}->id .
1699 " picked UsernameFormat $args{Format}, but RT::User->$method doesn't exist"
1701 $formatter = $self->can("_FormatUserRole");
1703 return $formatter->( $self, map { $_ => $args{$_} } qw(User Address) );
1706 sub _FormatUserRole {
1710 my $user = $args{User};
1711 return $self->_FormatUserVerbose(@_)
1712 unless $user and $user->Privileged;
1714 my $name = $user->Name;
1715 $name .= " (".$user->RealName.")"
1716 if $user->RealName and lc $user->RealName ne lc $user->Name;
1720 sub _FormatUserConcise {
1723 return $args{User} ? $args{User}->FriendlyName : $args{Address}->address;
1726 sub _FormatUserVerbose {
1729 my ($user, $address) = @args{"User", "Address"};
1736 $email = $user->EmailAddress || '';
1737 $phrase = $user->RealName if $user->RealName and lc $user->RealName ne lc $email;
1738 $comment = $user->Name if lc $user->Name ne lc $email;
1740 ($email, $phrase, $comment) = (map { $address->$_ } "address", "phrase", "comment");
1743 return join " ", grep { $_ } ($phrase || $comment || ''), ($email ? "<$email>" : "");
1748 Returns the preferred key of the user. If none is set, then this will query
1749 GPG and set the preferred key to the maximally trusted key found (and then
1750 return it). Returns C<undef> if no preferred key can be found.
1757 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1759 if ( ($self->CurrentUser->Id != $self->Id ) &&
1760 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1766 my $prefkey = $self->FirstAttribute('PreferredKey');
1767 return $prefkey->Content if $prefkey;
1769 # we don't have a preferred key for this user, so now we must query GPG
1770 my %res = RT::Crypt->GetKeysForEncryption($self->EmailAddress);
1771 return undef unless defined $res{'info'};
1772 my @keys = @{ $res{'info'} };
1773 return undef if @keys == 0;
1776 $prefkey = $keys[0]->{'Fingerprint'};
1778 # prefer the maximally trusted key
1779 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1780 $prefkey = $keys[0]->{'Fingerprint'};
1783 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1791 #If the user wants to see their own values, let them.
1792 #If the user is an admin, let them.
1793 #Otherwwise, don't let them.
1795 if ( ($self->CurrentUser->Id != $self->Id ) &&
1796 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1800 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1801 return $key->Content;
1808 # Users should not be able to change their own PrivateKey values
1809 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1810 return (0, $self->loc("Permission Denied"));
1814 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1815 unless ( $status ) {
1816 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1817 return ($status, $self->loc("Couldn't unset private key"));
1819 return ($status, $self->loc("Unset private key"));
1822 # check that it's really private key
1824 my %tmp = RT::Crypt->GetKeysForSigning( Signer => $key, Protocol => 'GnuPG' );
1825 return (0, $self->loc("No such key or it's not suitable for signing"))
1826 if $tmp{'exit_code'} || !$tmp{'info'};
1829 my ($status, $msg) = $self->SetAttribute(
1830 Name => 'PrivateKey',
1833 return ($status, $self->loc("Couldn't set private key"))
1835 return ($status, $self->loc("Set private key"));
1842 unless ($self->CurrentUserCanModify('Lang')) {
1843 return (0, $self->loc("Permission Denied"));
1846 # Local hack to cause the result message to be in the _new_ language
1847 # if we're updating ourselves
1848 $self->CurrentUser->{LangHandle} = RT::I18N->get_handle( $lang )
1849 if $self->CurrentUser->id == $self->id;
1850 return $self->_Set( Field => 'Lang', Value => $lang );
1855 [ Name => 'Username' ],
1856 [ EmailAddress => 'Email' ],
1857 [ RealName => 'Name' ],
1858 [ Organization => 'Organization' ],
1864 Returns an unordered list of IDs representing the user's bookmarked tickets.
1870 my $bookmarks = $self->FirstAttribute('Bookmarks');
1871 return if !$bookmarks;
1873 $bookmarks = $bookmarks->Content;
1874 return if !$bookmarks;
1876 return keys %$bookmarks;
1879 =head2 HasBookmark TICKET
1881 Returns whether the provided ticket is bookmarked by the user.
1888 my $id = $ticket->id;
1890 # maintain bookmarks across merges
1891 my @ids = ($id, $ticket->Merged);
1893 my $bookmarks = $self->FirstAttribute('Bookmarks');
1894 $bookmarks = $bookmarks ? $bookmarks->Content : {};
1896 my @bookmarked = grep { $bookmarks->{ $_ } } @ids;
1897 return @bookmarked ? 1 : 0;
1900 =head2 ToggleBookmark TICKET
1902 Toggles whether the provided ticket is bookmarked by the user.
1906 sub ToggleBookmark {
1909 my $id = $ticket->id;
1911 # maintain bookmarks across merges
1912 my @ids = ($id, $ticket->Merged);
1914 my $bookmarks = $self->FirstAttribute('Bookmarks');
1915 $bookmarks = $bookmarks ? $bookmarks->Content : {};
1919 if ( grep { $bookmarks->{ $_ } } @ids ) {
1920 delete $bookmarks->{ $_ } foreach @ids;
1923 $bookmarks->{ $id } = 1;
1927 $self->SetAttribute(
1928 Name => 'Bookmarks',
1929 Content => $bookmarks,
1932 return $is_bookmarked;
1935 =head2 Create PARAMHASH
1937 Create takes a hash of values and creates a row in the database:
1939 varchar(200) 'Name'.
1940 varbinary(256) 'Password'.
1941 varchar(16) 'AuthToken'.
1944 varchar(120) 'EmailAddress'.
1945 text 'FreeformContactInfo'.
1946 varchar(200) 'Organization'.
1947 varchar(120) 'RealName'.
1948 varchar(16) 'NickName'.
1950 varchar(16) 'EmailEncoding'.
1951 varchar(16) 'WebEncoding'.
1952 varchar(100) 'ExternalContactInfoId'.
1953 varchar(30) 'ContactInfoSystem'.
1954 varchar(100) 'ExternalAuthId'.
1955 varchar(30) 'AuthSystem'.
1956 varchar(16) 'Gecos'.
1957 varchar(30) 'HomePhone'.
1958 varchar(30) 'WorkPhone'.
1959 varchar(30) 'MobilePhone'.
1960 varchar(30) 'PagerPhone'.
1961 varchar(200) 'Address1'.
1962 varchar(200) 'Address2'.
1963 varchar(100) 'City'.
1964 varchar(100) 'State'.
1966 varchar(50) 'Country'.
1967 varchar(50) 'Timezone'.
1977 Returns the current value of id.
1978 (In the database, id is stored as int(11).)
1986 Returns the current value of Name.
1987 (In the database, Name is stored as varchar(200).)
1991 =head2 SetName VALUE
1995 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1996 (In the database, Name will be stored as a varchar(200).)
2004 Returns the current value of Password.
2005 (In the database, Password is stored as varchar(256).)
2009 =head2 SetPassword VALUE
2012 Set Password to VALUE.
2013 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2014 (In the database, Password will be stored as a varchar(256).)
2022 Returns the current value of AuthToken.
2023 (In the database, AuthToken is stored as varchar(16).)
2027 =head2 SetAuthToken VALUE
2030 Set AuthToken to VALUE.
2031 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2032 (In the database, AuthToken will be stored as a varchar(16).)
2040 Returns the current value of Comments.
2041 (In the database, Comments is stored as text.)
2045 =head2 SetComments VALUE
2048 Set Comments to VALUE.
2049 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2050 (In the database, Comments will be stored as a text.)
2058 Returns the current value of Signature.
2059 (In the database, Signature is stored as text.)
2063 =head2 SetSignature VALUE
2066 Set Signature to VALUE.
2067 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2068 (In the database, Signature will be stored as a text.)
2076 Returns the current value of EmailAddress.
2077 (In the database, EmailAddress is stored as varchar(120).)
2081 =head2 SetEmailAddress VALUE
2084 Set EmailAddress to VALUE.
2085 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2086 (In the database, EmailAddress will be stored as a varchar(120).)
2092 =head2 FreeformContactInfo
2094 Returns the current value of FreeformContactInfo.
2095 (In the database, FreeformContactInfo is stored as text.)
2099 =head2 SetFreeformContactInfo VALUE
2102 Set FreeformContactInfo to VALUE.
2103 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2104 (In the database, FreeformContactInfo will be stored as a text.)
2112 Returns the current value of Organization.
2113 (In the database, Organization is stored as varchar(200).)
2117 =head2 SetOrganization VALUE
2120 Set Organization to VALUE.
2121 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2122 (In the database, Organization will be stored as a varchar(200).)
2130 Returns the current value of RealName.
2131 (In the database, RealName is stored as varchar(120).)
2135 =head2 SetRealName VALUE
2138 Set RealName to VALUE.
2139 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2140 (In the database, RealName will be stored as a varchar(120).)
2148 Returns the current value of NickName.
2149 (In the database, NickName is stored as varchar(16).)
2153 =head2 SetNickName VALUE
2156 Set NickName to VALUE.
2157 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2158 (In the database, NickName will be stored as a varchar(16).)
2166 Returns the current value of Lang.
2167 (In the database, Lang is stored as varchar(16).)
2171 =head2 SetLang VALUE
2175 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2176 (In the database, Lang will be stored as a varchar(16).)
2182 =head2 EmailEncoding
2184 Returns the current value of EmailEncoding.
2185 (In the database, EmailEncoding is stored as varchar(16).)
2189 =head2 SetEmailEncoding VALUE
2192 Set EmailEncoding to VALUE.
2193 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2194 (In the database, EmailEncoding will be stored as a varchar(16).)
2202 Returns the current value of WebEncoding.
2203 (In the database, WebEncoding is stored as varchar(16).)
2207 =head2 SetWebEncoding VALUE
2210 Set WebEncoding to VALUE.
2211 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2212 (In the database, WebEncoding will be stored as a varchar(16).)
2218 =head2 ExternalContactInfoId
2220 Returns the current value of ExternalContactInfoId.
2221 (In the database, ExternalContactInfoId is stored as varchar(100).)
2225 =head2 SetExternalContactInfoId VALUE
2228 Set ExternalContactInfoId to VALUE.
2229 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2230 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
2236 =head2 ContactInfoSystem
2238 Returns the current value of ContactInfoSystem.
2239 (In the database, ContactInfoSystem is stored as varchar(30).)
2243 =head2 SetContactInfoSystem VALUE
2246 Set ContactInfoSystem to VALUE.
2247 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2248 (In the database, ContactInfoSystem will be stored as a varchar(30).)
2254 =head2 ExternalAuthId
2256 Returns the current value of ExternalAuthId.
2257 (In the database, ExternalAuthId is stored as varchar(100).)
2261 =head2 SetExternalAuthId VALUE
2264 Set ExternalAuthId to VALUE.
2265 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2266 (In the database, ExternalAuthId will be stored as a varchar(100).)
2274 Returns the current value of AuthSystem.
2275 (In the database, AuthSystem is stored as varchar(30).)
2279 =head2 SetAuthSystem VALUE
2282 Set AuthSystem to VALUE.
2283 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2284 (In the database, AuthSystem will be stored as a varchar(30).)
2292 Returns the current value of Gecos.
2293 (In the database, Gecos is stored as varchar(16).)
2297 =head2 SetGecos VALUE
2301 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2302 (In the database, Gecos will be stored as a varchar(16).)
2310 Returns the current value of HomePhone.
2311 (In the database, HomePhone is stored as varchar(30).)
2315 =head2 SetHomePhone VALUE
2318 Set HomePhone to VALUE.
2319 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2320 (In the database, HomePhone will be stored as a varchar(30).)
2328 Returns the current value of WorkPhone.
2329 (In the database, WorkPhone is stored as varchar(30).)
2333 =head2 SetWorkPhone VALUE
2336 Set WorkPhone to VALUE.
2337 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2338 (In the database, WorkPhone will be stored as a varchar(30).)
2346 Returns the current value of MobilePhone.
2347 (In the database, MobilePhone is stored as varchar(30).)
2351 =head2 SetMobilePhone VALUE
2354 Set MobilePhone to VALUE.
2355 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2356 (In the database, MobilePhone will be stored as a varchar(30).)
2364 Returns the current value of PagerPhone.
2365 (In the database, PagerPhone is stored as varchar(30).)
2369 =head2 SetPagerPhone VALUE
2372 Set PagerPhone to VALUE.
2373 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2374 (In the database, PagerPhone will be stored as a varchar(30).)
2382 Returns the current value of Address1.
2383 (In the database, Address1 is stored as varchar(200).)
2387 =head2 SetAddress1 VALUE
2390 Set Address1 to VALUE.
2391 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2392 (In the database, Address1 will be stored as a varchar(200).)
2400 Returns the current value of Address2.
2401 (In the database, Address2 is stored as varchar(200).)
2405 =head2 SetAddress2 VALUE
2408 Set Address2 to VALUE.
2409 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2410 (In the database, Address2 will be stored as a varchar(200).)
2418 Returns the current value of City.
2419 (In the database, City is stored as varchar(100).)
2423 =head2 SetCity VALUE
2427 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2428 (In the database, City will be stored as a varchar(100).)
2436 Returns the current value of State.
2437 (In the database, State is stored as varchar(100).)
2441 =head2 SetState VALUE
2445 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2446 (In the database, State will be stored as a varchar(100).)
2454 Returns the current value of Zip.
2455 (In the database, Zip is stored as varchar(16).)
2463 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2464 (In the database, Zip will be stored as a varchar(16).)
2472 Returns the current value of Country.
2473 (In the database, Country is stored as varchar(50).)
2477 =head2 SetCountry VALUE
2480 Set Country to VALUE.
2481 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2482 (In the database, Country will be stored as a varchar(50).)
2490 Returns the current value of Timezone.
2491 (In the database, Timezone is stored as varchar(50).)
2495 =head2 SetTimezone VALUE
2498 Set Timezone to VALUE.
2499 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2500 (In the database, Timezone will be stored as a varchar(50).)
2508 Returns the current value of PGPKey.
2509 (In the database, PGPKey is stored as text.)
2513 =head2 SetPGPKey VALUE
2516 Set PGPKey to VALUE.
2517 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2518 (In the database, PGPKey will be stored as a text.)
2524 =head2 SMIMECertificate
2526 Returns the current value of SMIMECertificate.
2527 (In the database, SMIMECertificate is stored as text.)
2531 =head2 SetSMIMECertificate VALUE
2534 Set SMIMECertificate to VALUE.
2535 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2536 (In the database, SMIMECertificate will be stored as a text.)
2544 Returns the current value of Creator.
2545 (In the database, Creator is stored as int(11).)
2553 Returns the current value of Created.
2554 (In the database, Created is stored as datetime.)
2560 =head2 LastUpdatedBy
2562 Returns the current value of LastUpdatedBy.
2563 (In the database, LastUpdatedBy is stored as int(11).)
2571 Returns the current value of LastUpdated.
2572 (In the database, LastUpdated is stored as datetime.)
2578 # much false laziness w/Ticket.pm. now with RT 4!
2580 MemberOf => { Base => 'MemberOf',
2581 Target => 'HasMember', },
2582 RefersTo => { Base => 'RefersTo',
2583 Target => 'ReferredToBy', },
2584 DependsOn => { Base => 'DependsOn',
2585 Target => 'DependedOnBy', },
2586 MergedInto => { Base => 'MergedInto',
2587 Target => 'MergedInto', },
2591 sub LINKDIRMAP { return \%LINKDIRMAP }
2596 Delete a link. takes a paramhash of Base, Target and Type.
2597 Either Base or Target must be null. The null value will
2598 be replaced with this ticket\'s id
2611 unless ( $args{'Target'} || $args{'Base'} ) {
2612 $RT::Logger->error("Base or Target must be specified\n");
2613 return ( 0, $self->loc('Either base or target must be specified') );
2618 $right++ if $self->CurrentUserHasRight('AdminUsers');
2619 if ( !$right && $RT::StrictLinkACL ) {
2620 return ( 0, $self->loc("Permission Denied") );
2623 # # If the other URI is an RT::Ticket, we want to make sure the user
2624 # # can modify it too...
2625 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2626 # return (0, $msg) unless $status;
2627 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2630 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2631 # ( $RT::StrictLinkACL && $right < 2 ) )
2633 # return ( 0, $self->loc("Permission Denied") );
2636 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2639 $RT::Logger->debug("Couldn't find that link\n");
2643 my ($direction, $remote_link);
2645 if ( $args{'Base'} ) {
2646 $remote_link = $args{'Base'};
2647 $direction = 'Target';
2649 elsif ( $args{'Target'} ) {
2650 $remote_link = $args{'Target'};
2654 if ( $args{'Silent'} ) {
2655 return ( $val, $Msg );
2658 my $remote_uri = RT::URI->new( $self->CurrentUser );
2659 $remote_uri->FromURI( $remote_link );
2661 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2662 Type => 'DeleteLink',
2663 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2664 OldValue => $remote_uri->URI || $remote_link,
2668 if ( $remote_uri->IsLocal ) {
2670 my $OtherObj = $remote_uri->Object;
2671 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2672 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2673 : $LINKDIRMAP{$args{'Type'}}->{Target},
2674 OldValue => $self->URI,
2675 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2679 return ( $Trans, $Msg );
2685 my %args = ( Target => '',
2691 unless ( $args{'Target'} || $args{'Base'} ) {
2692 $RT::Logger->error("Base or Target must be specified\n");
2693 return ( 0, $self->loc('Either base or target must be specified') );
2697 $right++ if $self->CurrentUserHasRight('AdminUsers');
2698 if ( !$right && $RT::StrictLinkACL ) {
2699 return ( 0, $self->loc("Permission Denied") );
2702 # # If the other URI is an RT::Ticket, we want to make sure the user
2703 # # can modify it too...
2704 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2705 # return (0, $msg) unless $status;
2706 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2709 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2710 # ( $RT::StrictLinkACL && $right < 2 ) )
2712 # return ( 0, $self->loc("Permission Denied") );
2715 return $self->_AddLink(%args);
2720 Private non-acled variant of AddLink so that links can be added during create.
2726 my %args = ( Target => '',
2732 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2733 return ($val, $msg) if !$val || $exist;
2735 my ($direction, $remote_link);
2736 if ( $args{'Target'} ) {
2737 $remote_link = $args{'Target'};
2738 $direction = 'Base';
2739 } elsif ( $args{'Base'} ) {
2740 $remote_link = $args{'Base'};
2741 $direction = 'Target';
2744 # Don't write the transaction if we're doing this on create
2745 if ( $args{'Silent'} ) {
2746 return ( $val, $msg );
2749 my $remote_uri = RT::URI->new( $self->CurrentUser );
2750 $remote_uri->FromURI( $remote_link );
2752 #Write the transaction
2753 my ( $Trans, $Msg, $TransObj ) =
2754 $self->_NewTransaction(Type => 'AddLink',
2755 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2756 NewValue => $remote_uri->URI || $remote_link,
2759 if ( $remote_uri->IsLocal ) {
2761 my $OtherObj = $remote_uri->Object;
2762 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2763 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2764 : $LINKDIRMAP{$args{'Type'}}->{Target},
2765 NewValue => $self->URI,
2766 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2769 return ( $val, $Msg );
2775 sub _CoreAccessible {
2779 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2781 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2783 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2785 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2787 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2789 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2791 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2792 FreeformContactInfo =>
2793 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2795 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2797 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2799 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2801 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2803 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2805 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2806 ExternalContactInfoId =>
2807 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2808 ContactInfoSystem =>
2809 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2811 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2813 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2815 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2817 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2819 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2821 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2823 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2825 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2827 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2829 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2831 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2833 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2835 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2837 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2839 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2841 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2843 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2845 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2847 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2849 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2856 return undef unless defined $self->Name;
2857 return "@{[ref $self]}-@{[$self->Name]}";
2860 sub FindDependencies {
2862 my ($walker, $deps) = @_;
2864 $self->SUPER::FindDependencies($walker, $deps);
2866 # ACL equivalence group
2867 my $objs = RT::Groups->new( $self->CurrentUser );
2868 $objs->Limit( FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 );
2869 $objs->Limit( FIELD => 'Instance', VALUE => $self->Id );
2870 $deps->Add( in => $objs );
2872 # Memberships in SystemInternal groups
2873 $objs = RT::GroupMembers->new( $self->CurrentUser );
2874 $objs->Limit( FIELD => 'MemberId', VALUE => $self->Id );
2875 my $principals = $objs->Join(
2877 FIELD1 => 'GroupId',
2878 TABLE2 => 'Principals',
2881 my $groups = $objs->Join(
2882 ALIAS1 => $principals,
2883 FIELD1 => 'ObjectId',
2890 VALUE => 'SystemInternal',
2893 $deps->Add( in => $objs );
2895 # XXX: This ignores the myriad of "in" references from the Creator
2896 # and LastUpdatedBy columns.
2903 Dependencies => undef,
2906 my $deps = $args{'Dependencies'};
2910 $deps->_PushDependency(
2911 BaseObject => $self,
2912 Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::WIPE_AFTER,
2913 TargetObject => $self->PrincipalObj,
2914 Shredder => $args{'Shredder'}
2917 # ACL equivalence group
2918 # don't use LoadACLEquivalenceGroup cause it may not exists any more
2919 my $objs = RT::Groups->new( $self->CurrentUser );
2920 $objs->Limit( FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 );
2921 $objs->Limit( FIELD => 'Instance', VALUE => $self->Id );
2922 push( @$list, $objs );
2924 # Cleanup user's membership
2925 $objs = RT::GroupMembers->new( $self->CurrentUser );
2926 $objs->Limit( FIELD => 'MemberId', VALUE => $self->Id );
2927 push( @$list, $objs );
2929 $deps->_PushDependencies(
2930 BaseObject => $self,
2931 Flags => RT::Shredder::Constants::DEPENDS_ON,
2932 TargetObjects => $list,
2933 Shredder => $args{'Shredder'}
2936 # TODO: Almost all objects has Creator, LastUpdatedBy and etc. fields
2937 # which are references on users(Principal actualy)
2951 ObjectCustomFieldValues
2965 foreach( @OBJECTS ) {
2966 my $class = "RT::$_";
2967 foreach my $method ( qw(Creator LastUpdatedBy) ) {
2968 my $objs = $class->new( $self->CurrentUser );
2969 next unless $objs->RecordClass->_Accessible( $method => 'read' );
2970 $objs->Limit( FIELD => $method, VALUE => $self->id );
2971 push @var_objs, $objs;
2974 $deps->_PushDependencies(
2975 BaseObject => $self,
2976 Flags => RT::Shredder::Constants::DEPENDS_ON | RT::Shredder::Constants::VARIABLE,
2977 TargetObjects => \@var_objs,
2978 Shredder => $args{'Shredder'}
2981 return $self->SUPER::__DependsOn( %args );
2986 if( $self->Name =~ /^(RT_System|Nobody)$/ ) {
2987 RT::Shredder::Exception::Info->throw('SystemObject');
2989 return $self->SUPER::BeforeWipeout( @_ );
2995 Disabled => $self->PrincipalObj->Disabled,
2996 Principal => $self->PrincipalObj->UID,
2997 PrincipalId => $self->PrincipalObj->Id,
2998 $self->SUPER::Serialize(@_),
3004 my ($importer, $uid, $data) = @_;
3006 my $principal_uid = delete $data->{Principal};
3007 my $principal_id = delete $data->{PrincipalId};
3008 my $disabled = delete $data->{Disabled};
3010 my $obj = RT::User->new( RT->SystemUser );
3011 $obj->LoadByCols( Name => $data->{Name} );
3012 $obj->LoadByEmail( $data->{EmailAddress} ) unless $obj->Id;
3014 # User already exists -- merge
3016 # XXX: We might be merging a privileged user into an unpriv one,
3017 # in which case we should probably promote the unpriv user to
3018 # being privileged. Of course, we don't know if the user being
3019 # imported is privileged yet, as its group memberships show up
3020 # later in the stream...
3021 $importer->MergeValues($obj, $data);
3022 $importer->SkipTransactions( $uid );
3024 # Mark both the principal and the user object as resolved
3027 ref($obj->PrincipalObj),
3028 $obj->PrincipalObj->Id
3030 $importer->Resolve( $uid => ref($obj) => $obj->Id );
3034 # Create a principal first, so we know what ID to use
3035 my $principal = RT::Principal->new( RT->SystemUser );
3036 my ($id) = $principal->Create(
3037 PrincipalType => 'User',
3038 Disabled => $disabled,
3042 # Now we have a principal id, set the id for the user record
3045 $importer->Resolve( $principal_uid => ref($principal), $id );
3047 $importer->Postpone(
3049 uid => $principal_uid,
3050 column => "ObjectId",
3053 return $class->SUPER::PreInflate( $importer, $uid, $data );
3058 RT->InitSystemObjects if $self->Name eq "RT_System";
3061 RT::Base->_ImportOverlays();