1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2014 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;
87 use Text::Password::Pronounceable;
89 sub _OverlayAccessible {
92 Name => { public => 1, admin => 1 }, # loc_left_pair
93 Password => { read => 0 },
94 EmailAddress => { public => 1 }, # loc_left_pair
95 Organization => { public => 1, admin => 1 }, # loc_left_pair
96 RealName => { public => 1 }, # loc_left_pair
97 NickName => { public => 1 }, # loc_left_pair
98 Lang => { public => 1 }, # loc_left_pair
99 EmailEncoding => { public => 1 },
100 WebEncoding => { public => 1 },
101 ExternalContactInfoId => { public => 1, admin => 1 },
102 ContactInfoSystem => { public => 1, admin => 1 },
103 ExternalAuthId => { public => 1, admin => 1 },
104 AuthSystem => { public => 1, admin => 1 },
105 Gecos => { public => 1, admin => 1 }, # loc_left_pair
106 PGPKey => { public => 1, admin => 1 }, # loc_left_pair
107 SMIMECertificate => { public => 1, admin => 1 }, # loc_left_pair
108 PrivateKey => { admin => 1 },
109 City => { public => 1 }, # loc_left_pair
110 Country => { public => 1 }, # loc_left_pair
111 Timezone => { public => 1 }, # loc_left_pair
117 =head2 Create { PARAMHASH }
130 _RecordTransaction => 1,
131 @_ # get the real argumentlist
134 # remove the value so it does not cripple SUPER::Create
135 my $record_transaction = delete $args{'_RecordTransaction'};
138 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
139 return ( 0, $self->loc('Permission Denied') );
143 unless ($self->CanonicalizeUserInfo(\%args)) {
144 return ( 0, $self->loc("Could not set user info") );
147 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
149 # if the user doesn't have a name defined, set it to the email address
150 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
154 my $privileged = delete $args{'Privileged'};
157 if ($args{'CryptedPassword'} ) {
158 $args{'Password'} = $args{'CryptedPassword'};
159 delete $args{'CryptedPassword'};
160 } elsif ( !$args{'Password'} ) {
161 $args{'Password'} = '*NO-PASSWORD*';
163 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
164 return ($ok, $msg) if !$ok;
166 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
169 #TODO Specify some sensible defaults.
171 unless ( $args{'Name'} ) {
172 return ( 0, $self->loc("Must specify 'Name' attribute") );
175 my ( $val, $msg ) = $self->ValidateName( $args{'Name'} );
176 return ( 0, $msg ) unless $val;
177 ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
178 return ( 0, $msg ) unless ($val);
180 $RT::Handle->BeginTransaction();
181 # Groups deal with principal ids, rather than user ids.
182 # When creating this user, set up a principal Id for it.
183 my $principal = RT::Principal->new($self->CurrentUser);
184 my $principal_id = $principal->Create(PrincipalType => 'User',
185 Disabled => $args{'Disabled'},
187 # If we couldn't create a principal Id, get the fuck out.
188 unless ($principal_id) {
189 $RT::Handle->Rollback();
190 $RT::Logger->crit("Couldn't create a Principal on new user create.");
191 $RT::Logger->crit("Strange things are afoot at the circle K");
192 return ( 0, $self->loc('Could not create user') );
195 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
196 delete $args{'Disabled'};
198 $self->SUPER::Create(id => $principal_id , %args);
201 #If the create failed.
203 $RT::Handle->Rollback();
204 $RT::Logger->error("Could not create a new user - " .join('-', %args));
206 return ( 0, $self->loc('Could not create user') );
209 my $aclstash = RT::Group->new($self->CurrentUser);
210 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
213 $RT::Handle->Rollback();
214 $RT::Logger->crit("Couldn't stash the user in groupmembers");
215 return ( 0, $self->loc('Could not create user') );
219 my $everyone = RT::Group->new($self->CurrentUser);
220 $everyone->LoadSystemInternalGroup('Everyone');
221 unless ($everyone->id) {
222 $RT::Logger->crit("Could not load Everyone group on user creation.");
223 $RT::Handle->Rollback();
224 return ( 0, $self->loc('Could not create user') );
228 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
229 unless ($everyone_id) {
230 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
231 $RT::Logger->crit($everyone_msg);
232 $RT::Handle->Rollback();
233 return ( 0, $self->loc('Could not create user') );
237 my $access_class = RT::Group->new($self->CurrentUser);
239 $access_class->LoadSystemInternalGroup('Privileged');
241 $access_class->LoadSystemInternalGroup('Unprivileged');
244 unless ($access_class->id) {
245 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
246 $RT::Handle->Rollback();
247 return ( 0, $self->loc('Could not create user') );
251 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
254 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
255 $RT::Logger->crit($ac_msg);
256 $RT::Handle->Rollback();
257 return ( 0, $self->loc('Could not create user') );
261 if ( $record_transaction ) {
262 $self->_NewTransaction( Type => "Create" );
267 return ( $id, $self->loc('User created') );
270 =head2 ValidateName STRING
272 Returns either (0, "failure reason") or 1 depending on whether the given
281 return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
283 my $TempUser = RT::User->new( RT->SystemUser );
284 $TempUser->Load($name);
286 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) {
287 return ( 0, $self->loc('Name in use') );
294 =head2 ValidatePassword STRING
296 Returns either (0, "failure reason") or 1 depending on whether the given
301 sub ValidatePassword {
303 my $password = shift;
305 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
306 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
312 =head2 SetPrivileged BOOL
314 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
315 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
317 Returns a standard RT tuple of (val, msg);
327 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
328 return ( 0, $self->loc('Permission Denied') );
331 $self->_SetPrivileged($val);
337 my $priv = RT::Group->new($self->CurrentUser);
338 $priv->LoadSystemInternalGroup('Privileged');
340 $RT::Logger->crit("Could not find Privileged pseudogroup");
341 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
344 my $unpriv = RT::Group->new($self->CurrentUser);
345 $unpriv->LoadSystemInternalGroup('Unprivileged');
346 unless ($unpriv->Id) {
347 $RT::Logger->crit("Could not find unprivileged pseudogroup");
348 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
351 my $principal = $self->PrincipalId;
353 if ($priv->HasMember($principal)) {
354 #$RT::Logger->debug("That user is already privileged");
355 return (0,$self->loc("That user is already privileged"));
357 if ($unpriv->HasMember($principal)) {
358 $unpriv->_DeleteMember($principal);
360 # if we had layered transactions, life would be good
361 # sadly, we have to just go ahead, even if something
363 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
364 "unprivileged. something is drastically wrong.");
366 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
368 return (1, $self->loc("That user is now privileged"));
373 if ($unpriv->HasMember($principal)) {
374 #$RT::Logger->debug("That user is already unprivileged");
375 return (0,$self->loc("That user is already unprivileged"));
377 if ($priv->HasMember($principal)) {
378 $priv->_DeleteMember( $principal );
380 # if we had layered transactions, life would be good
381 # sadly, we have to just go ahead, even if something
383 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
384 "unprivileged. something is drastically wrong.");
386 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
388 return (1, $self->loc("That user is now unprivileged"));
397 Returns true if this user is privileged. Returns undef otherwise.
403 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
410 #create a user without validating _any_ data.
412 #To be used only on database init.
413 # We can't localize here because it's before we _have_ a loc framework
415 sub _BootstrapCreate {
419 $args{'Password'} = '*NO-PASSWORD*';
422 $RT::Handle->BeginTransaction();
424 # Groups deal with principal ids, rather than user ids.
425 # When creating this user, set up a principal Id for it.
426 my $principal = RT::Principal->new($self->CurrentUser);
427 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
428 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
430 # If we couldn't create a principal Id, get the fuck out.
431 unless ($principal_id) {
432 $RT::Handle->Rollback();
433 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
434 return ( 0, 'Could not create user' );
436 $self->SUPER::Create(id => $principal_id, %args);
438 #If the create failed.
440 $RT::Handle->Rollback();
441 return ( 0, 'Could not create user' ) ; #never loc this
444 my $aclstash = RT::Group->new($self->CurrentUser);
445 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
448 $RT::Handle->Rollback();
449 $RT::Logger->crit("Couldn't stash the user in groupmembers");
450 return ( 0, $self->loc('Could not create user') );
453 $RT::Handle->Commit();
455 return ( $id, 'User created' );
461 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
467 Load a user object from the database. Takes a single argument.
468 If the argument is numerical, load by the column 'id'. If a user
469 object or its subclass passed then loads the same user by id.
470 Otherwise, load by the "Name" column which is the user's textual
477 my $identifier = shift || return undef;
479 if ( $identifier !~ /\D/ ) {
480 return $self->SUPER::LoadById( $identifier );
481 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
482 return $self->SUPER::LoadById( $identifier->Id );
484 return $self->LoadByCol( "Name", $identifier );
490 Tries to load this user object from the database by the user's email address.
498 # Never load an empty address as an email address.
503 $address = $self->CanonicalizeEmailAddress($address);
505 #$RT::Logger->debug("Trying to load an email address: $address");
506 return $self->LoadByCol( "EmailAddress", $address );
509 =head2 LoadOrCreateByEmail ADDRESS
511 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
512 the provided email address and loads them. Address can be provided either as L<Email::Address> object
513 or string which is parsed using the module.
515 Returns a tuple of the user's id and a status message.
516 0 will be returned in place of the user's id in case of failure.
520 sub LoadOrCreateByEmail {
524 my ($message, $name);
525 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
526 ($email, $name) = ($email->address, $email->phrase);
528 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
531 $self->LoadByEmail( $email );
532 $self->Load( $email ) unless $self->Id;
533 $message = $self->loc('User loaded');
535 unless( $self->Id ) {
537 ($val, $message) = $self->Create(
539 EmailAddress => $email,
542 Comments => 'Autocreated when added as a watcher',
545 # Deal with the race condition of two account creations at once
546 $self->LoadByEmail( $email );
547 unless ( $self->Id ) {
549 $self->LoadByEmail( $email );
552 $RT::Logger->error("Recovered from creation failure due to race condition");
553 $message = $self->loc("User loaded");
555 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
559 return wantarray ? (0, $message) : 0 unless $self->id;
560 return wantarray ? ($self->Id, $message) : $self->Id;
563 =head2 ValidateEmailAddress ADDRESS
565 Returns true if the email address entered is not in use by another user or is
566 undef or ''. Returns false if it's in use.
570 sub ValidateEmailAddress {
574 # if the email address is null, it's always valid
575 return (1) if ( !$Value || $Value eq "" );
577 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
578 # We only allow one valid email address
579 my @addresses = Email::Address->parse($Value);
580 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
584 my $TempUser = RT::User->new(RT->SystemUser);
585 $TempUser->LoadByEmail($Value);
587 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
588 { # if we found a user with that address
589 # it's invalid to set this user's address to it
590 return ( 0, $self->loc('Email address in use') );
591 } else { #it's a valid email address
598 Check to make sure someone else isn't using this name already
606 my ( $val, $message ) = $self->ValidateName($Value);
608 return $self->_Set( Field => 'Name', Value => $Value );
611 return ( 0, $message );
615 =head2 SetEmailAddress
617 Check to make sure someone else isn't using this email address already
618 so that a better email address can be returned
622 sub SetEmailAddress {
625 $Value = '' unless defined $Value;
627 my ($val, $message) = $self->ValidateEmailAddress( $Value );
629 return $self->_Set( Field => 'EmailAddress', Value => $Value );
631 return ( 0, $message )
636 =head2 EmailFrequency
638 Takes optional Ticket argument in paramhash. Returns 'no email',
639 'squelched', 'daily', 'weekly' or empty string depending on
644 =item 'no email' - user has no email, so can not recieve notifications.
646 =item 'squelched' - returned only when Ticket argument is provided and
647 notifications to the user has been supressed for this ticket.
649 =item 'daily' - retruned when user recieve daily messages digest instead
650 of immediate delivery.
652 =item 'weekly' - previous, but weekly.
654 =item empty string returned otherwise.
666 return '' unless $self->id && $self->id != RT->Nobody->id
667 && $self->id != RT->SystemUser->id;
668 return 'no email address' unless my $email = $self->EmailAddress;
669 return 'email disabled for ticket' if $args{'Ticket'} &&
670 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
671 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
672 return 'daily' if $frequency =~ /daily/i;
673 return 'weekly' if $frequency =~ /weekly/i;
677 =head2 CanonicalizeEmailAddress ADDRESS
679 CanonicalizeEmailAddress converts email addresses into canonical form.
680 it takes one email address in and returns the proper canonical
681 form. You can dump whatever your proper local config is in here. Note
682 that it may be called as a static method; in this case the first argument
683 is class name not an object.
687 sub CanonicalizeEmailAddress {
690 # Example: the following rule would treat all email
691 # coming from a subdomain as coming from second level domain
693 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
694 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
696 $email =~ s/$match/$replace/gi;
701 =head2 CanonicalizeUserInfo HASH of ARGS
703 CanonicalizeUserInfo can convert all User->Create options.
704 it takes a hashref of all the params sent to User->Create and
705 returns that same hash, by default nothing is done.
707 This function is intended to allow users to have their info looked up via
708 an outside source and modified upon creation.
712 sub CanonicalizeUserInfo {
721 =head2 Password and authentication related functions
723 =head3 SetRandomPassword
725 Takes no arguments. Returns a status code and a new password or an error message.
726 If the status is 1, the second value returned is the new password.
727 If the status is anything else, the new value returned is the error code.
731 sub SetRandomPassword {
734 unless ( $self->CurrentUserCanModify('Password') ) {
735 return ( 0, $self->loc("Permission Denied") );
739 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
740 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
742 my $pass = $self->GenerateRandomPassword( $min, $max) ;
744 # If we have "notify user on
746 my ( $val, $msg ) = $self->SetPassword($pass);
748 #If we got an error return the error.
749 return ( 0, $msg ) unless ($val);
751 #Otherwise, we changed the password, lets return it.
758 Returns status, [ERROR or new password]. Resets this user's password to
759 a randomly generated pronouncable password and emails them, using a
760 global template called "PasswordChange".
762 This function is currently unused in the UI, but available for local scripts.
769 unless ( $self->CurrentUserCanModify('Password') ) {
770 return ( 0, $self->loc("Permission Denied") );
772 my ( $status, $pass ) = $self->SetRandomPassword();
775 return ( 0, "$pass" );
778 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
779 To => $self->EmailAddress,
780 Template => 'PasswordChange',
782 NewPassword => $pass,
787 return ( 1, $self->loc('New password notification sent') );
789 return ( 0, $self->loc('Notification could not be sent') );
794 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
796 Returns a random password between MIN_LEN and MAX_LEN characters long.
800 sub GenerateRandomPassword {
801 my $self = shift; # just to drop it
802 return Text::Password::Pronounceable->generate(@_);
805 sub SafeSetPassword {
810 Confirmation => undef,
813 return (1) unless defined $args{'New'} && length $args{'New'};
815 my %cond = $self->CurrentUserRequireToSetPassword;
817 unless ( $cond{'CanSet'} ) {
818 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
822 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
823 if ( defined $args{'Current'} && length $args{'Current'} ) {
824 $error = $self->loc("Please enter your current password correctly.");
826 $error = $self->loc("Please enter your current password.");
828 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
829 $error = $self->loc("Passwords do not match.");
833 $error .= ' '. $self->loc('Password has not been set.');
837 return $self->SetPassword( $args{'New'} );
842 Takes a string. Checks the string's length and sets this user's password
849 my $password = shift;
851 unless ( $self->CurrentUserCanModify('Password') ) {
852 return ( 0, $self->loc('Password: Permission Denied') );
856 return ( 0, $self->loc("No password set") );
858 my ($val, $msg) = $self->ValidatePassword($password);
859 return ($val, $msg) if !$val;
861 my $new = !$self->HasPassword;
862 $password = $self->_GeneratePassword($password);
864 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
866 return ( 1, $self->loc("Password set") ) if $new;
867 return ( 1, $self->loc("Password changed") );
869 return ( $val, $msg );
875 sub _GeneratePassword_bcrypt {
877 my ($password, @rest) = @_;
882 # The first split is the number of rounds
885 # The salt is the first 22 characters, b64 encoded usign the
886 # special bcrypt base64.
887 $salt = Crypt::Eksblowfish::Bcrypt::de_base64( substr($rest[1], 0, 22) );
889 $rounds = RT->Config->Get('BcryptCost');
891 # Generate a random 16-octet base64 salt
893 $salt .= pack("C", int rand(256)) for 1..16;
896 my $hash = Crypt::Eksblowfish::Bcrypt::bcrypt_hash({
900 }, Digest::SHA::sha512( encode_utf8($password) ) );
902 return join("!", "", "bcrypt", sprintf("%02d", $rounds),
903 Crypt::Eksblowfish::Bcrypt::en_base64( $salt ).
904 Crypt::Eksblowfish::Bcrypt::en_base64( $hash )
908 sub _GeneratePassword_sha512 {
910 my ($password, $salt) = @_;
912 # Generate a 16-character base64 salt
915 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
919 my $sha = Digest::SHA->new(512);
921 $sha->add(encode_utf8($password));
922 return join("!", "", "sha512", $salt, $sha->b64digest);
925 =head3 _GeneratePassword PASSWORD [, SALT]
927 Returns a string to store in the database. This string takes the form:
931 By default, the method is currently C<bcrypt>.
935 sub _GeneratePassword {
937 return $self->_GeneratePassword_bcrypt(@_);
942 Returns true if the user has a valid password, otherwise returns false.
948 my $pwd = $self->__Value('Password');
949 return undef if !defined $pwd
951 || $pwd eq '*NO-PASSWORD*';
957 Returns true if the passed in value is this user's password.
958 Returns undef otherwise.
966 #TODO there isn't any apparent way to legitimately ACL this
968 # RT does not allow null passwords
969 if ( ( !defined($value) ) or ( $value eq '' ) ) {
973 if ( $self->PrincipalObj->Disabled ) {
975 "Disabled user " . $self->Name . " tried to log in" );
979 unless ($self->HasPassword) {
983 my $stored = $self->__Value('Password');
984 if ($stored =~ /^!/) {
985 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
986 my (undef, $method, @rest) = split /!/, $stored;
987 if ($method eq "bcrypt") {
988 return 0 unless $self->_GeneratePassword_bcrypt($value, @rest) eq $stored;
989 # Upgrade to a larger number of rounds if necessary
990 return 1 unless $rest[0] < RT->Config->Get('BcryptCost');
991 } elsif ($method eq "sha512") {
992 return 0 unless $self->_GeneratePassword_sha512($value, @rest) eq $stored;
994 $RT::Logger->warn("Unknown hash method $method");
997 } elsif (length $stored == 40) {
998 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
999 my $hash = MIME::Base64::decode_base64($stored);
1000 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
1001 my $salt = substr($hash, 0, 4, "");
1002 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
1003 } elsif (length $stored == 32) {
1005 return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
1006 } elsif (length $stored == 22) {
1007 # Base64 nonsalted-md5
1008 return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
1009 } elsif (length $stored == 13) {
1011 return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
1013 $RT::Logger->warning("Unknown password form");
1017 # We got here by validating successfully, but with a legacy
1018 # password form. Update to the most recent form.
1019 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
1020 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
1024 sub CurrentUserRequireToSetPassword {
1030 RequireCurrent => 1,
1033 if ( RT->Config->Get('WebRemoteUserAuth')
1034 && !RT->Config->Get('WebFallbackToRTLogin')
1037 $res{'Reason'} = $self->loc("External authentication enabled.");
1038 } elsif ( !$self->CurrentUser->HasPassword ) {
1039 if ( $self->CurrentUser->id == ($self->id||0) ) {
1040 # don't require current password if user has no
1041 $res{'RequireCurrent'} = 0;
1044 $res{'Reason'} = $self->loc("Your password is not set.");
1053 Returns an authentication string associated with the user. This
1054 string can be used to generate passwordless URLs to integrate
1055 RT with services and programms like callendar managers, rss
1062 my $secret = $self->_Value( AuthToken => @_ );
1063 return $secret if $secret;
1065 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1067 my $tmp = RT::User->new( RT->SystemUser );
1068 $tmp->Load( $self->id );
1069 my ($status, $msg) = $tmp->SetAuthToken( $secret );
1070 unless ( $status ) {
1071 $RT::Logger->error( "Couldn't set auth token: $msg" );
1077 =head3 GenerateAuthToken
1079 Generate a random authentication string for the user.
1083 sub GenerateAuthToken {
1085 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1086 return $self->SetAuthToken( $token );
1089 =head3 GenerateAuthString
1091 Takes a string and returns back a hex hash string. Later you can use
1092 this pair to make sure it's generated by this user using L</ValidateAuthString>
1096 sub GenerateAuthString {
1098 my $protect = shift;
1100 my $str = $self->AuthToken . $protect;
1103 return substr(Digest::MD5::md5_hex($str),0,16);
1106 =head3 ValidateAuthString
1108 Takes auth string and protected string. Returns true is protected string
1109 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1113 sub ValidateAuthString {
1115 my $auth_string = shift;
1116 my $protected = shift;
1118 my $str = $self->AuthToken . $protected;
1119 utf8::encode( $str );
1121 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1126 Toggles the user's disabled flag.
1128 set, all password checks for this user will fail. All ACL checks for this
1129 user will fail. The user will appear in no user listings.
1136 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1137 return (0, $self->loc('Permission Denied'));
1140 $RT::Handle->BeginTransaction();
1141 my ($status, $msg) = $self->PrincipalObj->SetDisabled($val);
1143 $RT::Handle->Rollback();
1144 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1145 return ($status, $msg);
1147 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1149 $RT::Handle->Commit();
1152 return (1, $self->loc("User disabled"));
1154 return (1, $self->loc("User enabled"));
1161 Returns true if user is disabled or false otherwise
1167 return $self->PrincipalObj->Disabled(@_);
1172 Returns the principal object for this user. returns an empty RT::Principal
1173 if there's no principal object matching this user.
1174 The response is cached. PrincipalObj should never ever change.
1181 unless ( $self->id ) {
1182 $RT::Logger->error("Couldn't get principal for an empty user");
1186 if ( !$self->{_principal_obj} ) {
1188 my $obj = RT::Principal->new( $self->CurrentUser );
1189 $obj->LoadById( $self->id );
1191 $RT::Logger->crit( 'No principal for user #' . $self->id );
1193 } elsif ( $obj->PrincipalType ne 'User' ) {
1194 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1197 $self->{_principal_obj} = $obj;
1199 return $self->{_principal_obj};
1205 Returns this user's PrincipalId
1214 =head2 HasGroupRight
1216 Takes a paramhash which can contain
1218 GroupObj => RT::Group or Group => integer
1222 Returns 1 if this user has the right specified in the paramhash for the Group
1225 Returns undef if they don't.
1239 if ( defined $args{'Group'} ) {
1240 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1241 $args{'GroupObj'}->Load( $args{'Group'} );
1244 # Validate and load up the GroupId
1245 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1249 # Figure out whether a user has the right we're asking about.
1250 my $retval = $self->HasRight(
1251 Object => $args{'GroupObj'},
1252 Right => $args{'Right'},
1260 Returns a group collection object containing the groups of which this
1267 my $groups = RT::Groups->new($self->CurrentUser);
1268 $groups->LimitToUserDefinedGroups;
1269 $groups->WithMember(
1270 PrincipalId => $self->Id,
1278 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1284 return $self->PrincipalObj->HasRight(@_);
1287 =head2 CurrentUserCanSee [FIELD]
1289 Returns true if the current user can see the user, based on if it is
1290 public, ourself, or we have AdminUsers
1294 sub CurrentUserCanSee {
1296 my ($what, $txn) = @_;
1298 # If it's a public property, fine
1299 return 1 if $self->_Accessible( $what, 'public' );
1301 # Users can see all of their own properties
1302 return 1 if defined($self->Id) and $self->CurrentUser->Id == $self->Id;
1304 # If the user has the admin users right, that's also enough
1305 return 1 if $self->CurrentUserHasRight( 'AdminUsers' );
1307 # Transactions of public properties are visible to users with ShowUserHistory
1308 if ($what eq "Transaction" and $self->CurrentUserHasRight( 'ShowUserHistory' )) {
1309 my $type = $txn->__Value('Type');
1310 my $field = $txn->__Value('Field');
1311 return 1 if $type eq "Set" and $self->CurrentUserCanSee($field, $txn);
1313 # RT::Transaction->CurrentUserCanSee deals with ensuring we meet
1314 # the ACLs on CFs, so allow them here
1315 return 1 if $type eq "CustomField";
1321 =head2 CurrentUserCanModify RIGHT
1323 If the user has rights for this object, either because
1324 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1325 admin right) 'ModifySelf', return 1. otherwise, return undef.
1329 sub CurrentUserCanModify {
1333 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1337 #If the field is marked as an "administrators only" field,
1338 # don't let the user touch it.
1339 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1343 #If the current user is trying to modify themselves
1344 elsif ( ( $self->id == $self->CurrentUser->id )
1345 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1350 #If we don't have a good reason to grant them rights to modify
1358 =head2 CurrentUserHasRight
1360 Takes a single argument. returns 1 if $Self->CurrentUser
1361 has the requested right. returns undef otherwise
1365 sub CurrentUserHasRight {
1369 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1375 $name = ref($name).'-'.$name->Id;
1378 return 'Pref-'. $name;
1381 =head2 Preferences NAME/OBJ DEFAULT
1383 Obtain user preferences associated with given object or name.
1384 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1385 override the entries with user preferences.
1389 our %PREFERENCES_CACHE = ();
1393 my $name = _PrefName(shift);
1394 my $default = shift;
1397 if ( exists $PREFERENCES_CACHE{ $self->id }{ $name } ) {
1398 $content = $PREFERENCES_CACHE{ $self->id }{ $name };
1401 my $attr = RT::Attribute->new( $self->CurrentUser );
1402 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1403 $PREFERENCES_CACHE{ $self->id }{ $name } = $content
1404 = $attr->Id ? $attr->Content : undef;
1407 unless ( ref $content eq 'HASH' ) {
1408 return defined $content ? $content : $default;
1411 if (ref $default eq 'HASH') {
1412 for (keys %$default) {
1413 exists $content->{$_} or $content->{$_} = $default->{$_};
1415 } elsif (defined $default) {
1416 $RT::Logger->error("Preferences $name for user #".$self->Id." is hash but default is not");
1421 =head2 SetPreferences NAME/OBJ VALUE
1423 Set user preferences associated with given object or name.
1427 sub SetPreferences {
1429 my $name = _PrefName( shift );
1432 return (0, $self->loc("No permission to set preferences"))
1433 unless $self->CurrentUserCanModify('Preferences');
1435 # we clear cache in RT::Attribute
1437 my $attr = RT::Attribute->new( $self->CurrentUser );
1438 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1440 my ($ok, $msg) = $attr->SetContent( $value );
1441 return (1, "No updates made")
1442 if $msg eq "That is already the current value";
1445 return $self->AddAttribute( Name => $name, Content => $value );
1451 Returns a list of valid stylesheets take from preferences.
1458 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1460 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1461 for my $root (RT::Interface::Web->StaticRoots) {
1462 if (-d "$root/css/$style") {
1468 # Fall back to the system stylesheet.
1469 return RT->Config->Get('WebDefaultStylesheet');
1472 =head2 WatchedQueues ROLE_LIST
1474 Returns a RT::Queues object containing every queue watched by the user.
1476 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1478 $user->WatchedQueues('Cc', 'AdminCc');
1485 my @roles = @_ ? @_ : ('Cc', 'AdminCc');
1487 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1489 my $watched_queues = RT::Queues->new($self->CurrentUser);
1491 my $group_alias = $watched_queues->Join(
1495 FIELD2 => 'Instance',
1498 $watched_queues->Limit(
1499 ALIAS => $group_alias,
1501 VALUE => 'RT::Queue-Role',
1502 ENTRYAGGREGATOR => 'AND',
1505 if (grep { $_ eq 'Cc' } @roles) {
1506 $watched_queues->Limit(
1507 SUBCLAUSE => 'LimitToWatchers',
1508 ALIAS => $group_alias,
1511 ENTRYAGGREGATOR => 'OR',
1514 if (grep { $_ eq 'AdminCc' } @roles) {
1515 $watched_queues->Limit(
1516 SUBCLAUSE => 'LimitToWatchers',
1517 ALIAS => $group_alias,
1520 ENTRYAGGREGATOR => 'OR',
1524 my $queues_alias = $watched_queues->Join(
1525 ALIAS1 => $group_alias,
1527 TABLE2 => 'CachedGroupMembers',
1528 FIELD2 => 'GroupId',
1530 $watched_queues->Limit(
1531 ALIAS => $queues_alias,
1532 FIELD => 'MemberId',
1533 VALUE => $self->PrincipalId,
1535 $watched_queues->Limit(
1536 ALIAS => $queues_alias,
1537 FIELD => 'Disabled',
1542 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1544 return $watched_queues;
1554 TransactionType => 'Set',
1555 RecordTransaction => 1,
1559 # Nobody is allowed to futz with RT_System or Nobody
1561 if ( ($self->Id == RT->SystemUser->Id ) ||
1562 ($self->Id == RT->Nobody->Id)) {
1563 return ( 0, $self->loc("Can not modify system users") );
1565 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1566 return ( 0, $self->loc("Permission Denied") );
1569 my $Old = $self->SUPER::_Value("$args{'Field'}");
1571 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1572 Value => $args{'Value'} );
1574 #If we can't actually set the field to the value, don't record
1575 # a transaction. instead, get out of here.
1576 if ( $ret == 0 ) { return ( 0, $msg ); }
1578 if ( $args{'RecordTransaction'} == 1 ) {
1579 if ($args{'Field'} eq "Password") {
1580 $args{'Value'} = $Old = '********';
1582 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1583 Type => $args{'TransactionType'},
1584 Field => $args{'Field'},
1585 NewValue => $args{'Value'},
1587 TimeTaken => $args{'TimeTaken'},
1589 return ( $Trans, scalar $TransObj->BriefDescription );
1591 return ( $ret, $msg );
1597 Takes the name of a table column.
1598 Returns its value as a string, if the user passes an ACL check
1607 # Defer to the abstraction above to know if the field can be read
1608 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1614 Return the friendly name
1620 return $self->RealName if defined $self->RealName and length $self->RealName;
1626 Class or object method.
1628 Returns a string describing a user in the current user's preferred format.
1630 May be invoked in three ways:
1633 RT::User->Format( User => $UserObj ); # same as above
1634 RT::User->Format( Address => $AddressObj, CurrentUser => $CurrentUserObj );
1636 Possible arguments are:
1642 An L<RT::User> object representing the user to format. Preferred to Address.
1646 An L<Email::Address> object representing the user address to format. Address
1647 will be used to lookup an L<RT::User> if possible.
1651 Required when Format is called as a class method with an Address argument.
1652 Otherwise, this argument is ignored in preference to the CurrentUser of the
1653 involved L<RT::User> object.
1657 Specifies the format to use, overriding any set from the config or current
1669 CurrentUser => undef,
1674 if (blessed($self) and $self->id) {
1675 @args{"User", "CurrentUser"} = ($self, $self->CurrentUser);
1677 elsif ($args{User} and $args{User}->id) {
1678 $args{CurrentUser} = $args{User}->CurrentUser;
1680 elsif ($args{Address} and $args{CurrentUser}) {
1681 $args{User} = RT::User->new( $args{CurrentUser} );
1682 $args{User}->LoadByEmail( $args{Address}->address );
1683 if ($args{User}->id) {
1684 delete $args{Address};
1690 RT->Logger->warning("Invalid arguments to RT::User->Format at @{[join '/', caller]}");
1694 $args{Format} ||= RT->Config->Get("UsernameFormat", $args{CurrentUser});
1695 $args{Format} =~ s/[^A-Za-z0-9_]+//g;
1697 my $method = "_FormatUser" . ucfirst lc $args{Format};
1698 my $formatter = $self->can($method);
1700 unless ($formatter) {
1702 "Either system config or user #" . $args{CurrentUser}->id .
1703 " picked UsernameFormat $args{Format}, but RT::User->$method doesn't exist"
1705 $formatter = $self->can("_FormatUserRole");
1707 return $formatter->( $self, map { $_ => $args{$_} } qw(User Address) );
1710 sub _FormatUserRole {
1714 my $user = $args{User};
1715 return $self->_FormatUserVerbose(@_)
1716 unless $user and $user->Privileged;
1718 my $name = $user->Name;
1719 $name .= " (".$user->RealName.")"
1720 if $user->RealName and lc $user->RealName ne lc $user->Name;
1724 sub _FormatUserConcise {
1727 return $args{User} ? $args{User}->FriendlyName : $args{Address}->address;
1730 sub _FormatUserVerbose {
1733 my ($user, $address) = @args{"User", "Address"};
1740 $email = $user->EmailAddress || '';
1741 $phrase = $user->RealName if $user->RealName and lc $user->RealName ne lc $email;
1742 $comment = $user->Name if lc $user->Name ne lc $email;
1744 ($email, $phrase, $comment) = (map { $address->$_ } "address", "phrase", "comment");
1747 return join " ", grep { $_ } ($phrase || $comment || ''), ($email ? "<$email>" : "");
1752 Returns the preferred key of the user. If none is set, then this will query
1753 GPG and set the preferred key to the maximally trusted key found (and then
1754 return it). Returns C<undef> if no preferred key can be found.
1761 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1763 if ( ($self->CurrentUser->Id != $self->Id ) &&
1764 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1770 my $prefkey = $self->FirstAttribute('PreferredKey');
1771 return $prefkey->Content if $prefkey;
1773 # we don't have a preferred key for this user, so now we must query GPG
1774 my %res = RT::Crypt->GetKeysForEncryption($self->EmailAddress);
1775 return undef unless defined $res{'info'};
1776 my @keys = @{ $res{'info'} };
1777 return undef if @keys == 0;
1780 $prefkey = $keys[0]->{'id'} || $keys[0]->{'Fingerprint'};
1782 # prefer the maximally trusted key
1783 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1784 $prefkey = $keys[0]->{'id'} || $keys[0]->{'Fingerprint'};
1787 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1795 #If the user wants to see their own values, let them.
1796 #If the user is an admin, let them.
1797 #Otherwwise, don't let them.
1799 if ( ($self->CurrentUser->Id != $self->Id ) &&
1800 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1804 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1805 return $key->Content;
1812 unless ($self->CurrentUserCanModify('PrivateKey')) {
1813 return (0, $self->loc("Permission Denied"));
1817 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1818 unless ( $status ) {
1819 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1820 return ($status, $self->loc("Couldn't unset private key"));
1822 return ($status, $self->loc("Unset private key"));
1825 # check that it's really private key
1827 my %tmp = RT::Crypt->GetKeysForSigning( Signer => $key, Protocol => 'GnuPG' );
1828 return (0, $self->loc("No such key or it's not suitable for signing"))
1829 if $tmp{'exit_code'} || !$tmp{'info'};
1832 my ($status, $msg) = $self->SetAttribute(
1833 Name => 'PrivateKey',
1836 return ($status, $self->loc("Couldn't set private key"))
1838 return ($status, $self->loc("Set private key"));
1845 unless ($self->CurrentUserCanModify('Lang')) {
1846 return (0, $self->loc("Permission Denied"));
1849 # Local hack to cause the result message to be in the _new_ language
1850 # if we're updating ourselves
1851 $self->CurrentUser->{LangHandle} = RT::I18N->get_handle( $lang )
1852 if $self->CurrentUser->id == $self->id;
1853 return $self->_Set( Field => 'Lang', Value => $lang );
1858 [ Name => 'Username' ],
1859 [ EmailAddress => 'Email' ],
1860 [ RealName => 'Name' ],
1861 [ Organization => 'Organization' ],
1867 Returns an unordered list of IDs representing the user's bookmarked tickets.
1873 my $bookmarks = $self->FirstAttribute('Bookmarks');
1874 return if !$bookmarks;
1876 $bookmarks = $bookmarks->Content;
1877 return if !$bookmarks;
1879 return keys %$bookmarks;
1882 =head2 HasBookmark TICKET
1884 Returns whether the provided ticket is bookmarked by the user.
1891 my $id = $ticket->id;
1893 # maintain bookmarks across merges
1894 my @ids = ($id, $ticket->Merged);
1896 my $bookmarks = $self->FirstAttribute('Bookmarks');
1897 $bookmarks = $bookmarks ? $bookmarks->Content : {};
1899 my @bookmarked = grep { $bookmarks->{ $_ } } @ids;
1900 return @bookmarked ? 1 : 0;
1903 =head2 ToggleBookmark TICKET
1905 Toggles whether the provided ticket is bookmarked by the user.
1909 sub ToggleBookmark {
1912 my $id = $ticket->id;
1914 # maintain bookmarks across merges
1915 my @ids = ($id, $ticket->Merged);
1917 my $bookmarks = $self->FirstAttribute('Bookmarks');
1918 $bookmarks = $bookmarks ? $bookmarks->Content : {};
1922 if ( grep { $bookmarks->{ $_ } } @ids ) {
1923 delete $bookmarks->{ $_ } foreach @ids;
1926 $bookmarks->{ $id } = 1;
1930 $self->SetAttribute(
1931 Name => 'Bookmarks',
1932 Content => $bookmarks,
1935 return $is_bookmarked;
1938 =head2 Create PARAMHASH
1940 Create takes a hash of values and creates a row in the database:
1942 varchar(200) 'Name'.
1943 varbinary(256) 'Password'.
1944 varchar(16) 'AuthToken'.
1947 varchar(120) 'EmailAddress'.
1948 text 'FreeformContactInfo'.
1949 varchar(200) 'Organization'.
1950 varchar(120) 'RealName'.
1951 varchar(16) 'NickName'.
1953 varchar(16) 'EmailEncoding'.
1954 varchar(16) 'WebEncoding'.
1955 varchar(100) 'ExternalContactInfoId'.
1956 varchar(30) 'ContactInfoSystem'.
1957 varchar(100) 'ExternalAuthId'.
1958 varchar(30) 'AuthSystem'.
1959 varchar(16) 'Gecos'.
1960 varchar(30) 'HomePhone'.
1961 varchar(30) 'WorkPhone'.
1962 varchar(30) 'MobilePhone'.
1963 varchar(30) 'PagerPhone'.
1964 varchar(200) 'Address1'.
1965 varchar(200) 'Address2'.
1966 varchar(100) 'City'.
1967 varchar(100) 'State'.
1969 varchar(50) 'Country'.
1970 varchar(50) 'Timezone'.
1980 Returns the current value of id.
1981 (In the database, id is stored as int(11).)
1989 Returns the current value of Name.
1990 (In the database, Name is stored as varchar(200).)
1994 =head2 SetName VALUE
1998 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1999 (In the database, Name will be stored as a varchar(200).)
2007 Returns the current value of Password.
2008 (In the database, Password is stored as varchar(256).)
2012 =head2 SetPassword VALUE
2015 Set Password to VALUE.
2016 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2017 (In the database, Password will be stored as a varchar(256).)
2025 Returns the current value of AuthToken.
2026 (In the database, AuthToken is stored as varchar(16).)
2030 =head2 SetAuthToken VALUE
2033 Set AuthToken to VALUE.
2034 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2035 (In the database, AuthToken will be stored as a varchar(16).)
2043 Returns the current value of Comments.
2044 (In the database, Comments is stored as text.)
2048 =head2 SetComments VALUE
2051 Set Comments to VALUE.
2052 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2053 (In the database, Comments will be stored as a text.)
2061 Returns the current value of Signature.
2062 (In the database, Signature is stored as text.)
2066 =head2 SetSignature VALUE
2069 Set Signature to VALUE.
2070 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2071 (In the database, Signature will be stored as a text.)
2079 Returns the current value of EmailAddress.
2080 (In the database, EmailAddress is stored as varchar(120).)
2084 =head2 SetEmailAddress VALUE
2087 Set EmailAddress to VALUE.
2088 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2089 (In the database, EmailAddress will be stored as a varchar(120).)
2095 =head2 FreeformContactInfo
2097 Returns the current value of FreeformContactInfo.
2098 (In the database, FreeformContactInfo is stored as text.)
2102 =head2 SetFreeformContactInfo VALUE
2105 Set FreeformContactInfo to VALUE.
2106 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2107 (In the database, FreeformContactInfo will be stored as a text.)
2115 Returns the current value of Organization.
2116 (In the database, Organization is stored as varchar(200).)
2120 =head2 SetOrganization VALUE
2123 Set Organization to VALUE.
2124 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2125 (In the database, Organization will be stored as a varchar(200).)
2133 Returns the current value of RealName.
2134 (In the database, RealName is stored as varchar(120).)
2138 =head2 SetRealName VALUE
2141 Set RealName to VALUE.
2142 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2143 (In the database, RealName will be stored as a varchar(120).)
2151 Returns the current value of NickName.
2152 (In the database, NickName is stored as varchar(16).)
2156 =head2 SetNickName VALUE
2159 Set NickName to VALUE.
2160 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2161 (In the database, NickName will be stored as a varchar(16).)
2169 Returns the current value of Lang.
2170 (In the database, Lang is stored as varchar(16).)
2174 =head2 SetLang VALUE
2178 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2179 (In the database, Lang will be stored as a varchar(16).)
2185 =head2 EmailEncoding
2187 Returns the current value of EmailEncoding.
2188 (In the database, EmailEncoding is stored as varchar(16).)
2192 =head2 SetEmailEncoding VALUE
2195 Set EmailEncoding to VALUE.
2196 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2197 (In the database, EmailEncoding will be stored as a varchar(16).)
2205 Returns the current value of WebEncoding.
2206 (In the database, WebEncoding is stored as varchar(16).)
2210 =head2 SetWebEncoding VALUE
2213 Set WebEncoding to VALUE.
2214 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2215 (In the database, WebEncoding will be stored as a varchar(16).)
2221 =head2 ExternalContactInfoId
2223 Returns the current value of ExternalContactInfoId.
2224 (In the database, ExternalContactInfoId is stored as varchar(100).)
2228 =head2 SetExternalContactInfoId VALUE
2231 Set ExternalContactInfoId to VALUE.
2232 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2233 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
2239 =head2 ContactInfoSystem
2241 Returns the current value of ContactInfoSystem.
2242 (In the database, ContactInfoSystem is stored as varchar(30).)
2246 =head2 SetContactInfoSystem VALUE
2249 Set ContactInfoSystem to VALUE.
2250 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2251 (In the database, ContactInfoSystem will be stored as a varchar(30).)
2257 =head2 ExternalAuthId
2259 Returns the current value of ExternalAuthId.
2260 (In the database, ExternalAuthId is stored as varchar(100).)
2264 =head2 SetExternalAuthId VALUE
2267 Set ExternalAuthId to VALUE.
2268 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2269 (In the database, ExternalAuthId will be stored as a varchar(100).)
2277 Returns the current value of AuthSystem.
2278 (In the database, AuthSystem is stored as varchar(30).)
2282 =head2 SetAuthSystem VALUE
2285 Set AuthSystem to VALUE.
2286 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2287 (In the database, AuthSystem will be stored as a varchar(30).)
2295 Returns the current value of Gecos.
2296 (In the database, Gecos is stored as varchar(16).)
2300 =head2 SetGecos VALUE
2304 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2305 (In the database, Gecos will be stored as a varchar(16).)
2313 Returns the current value of HomePhone.
2314 (In the database, HomePhone is stored as varchar(30).)
2318 =head2 SetHomePhone VALUE
2321 Set HomePhone to VALUE.
2322 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2323 (In the database, HomePhone will be stored as a varchar(30).)
2331 Returns the current value of WorkPhone.
2332 (In the database, WorkPhone is stored as varchar(30).)
2336 =head2 SetWorkPhone VALUE
2339 Set WorkPhone to VALUE.
2340 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2341 (In the database, WorkPhone will be stored as a varchar(30).)
2349 Returns the current value of MobilePhone.
2350 (In the database, MobilePhone is stored as varchar(30).)
2354 =head2 SetMobilePhone VALUE
2357 Set MobilePhone to VALUE.
2358 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2359 (In the database, MobilePhone will be stored as a varchar(30).)
2367 Returns the current value of PagerPhone.
2368 (In the database, PagerPhone is stored as varchar(30).)
2372 =head2 SetPagerPhone VALUE
2375 Set PagerPhone to VALUE.
2376 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2377 (In the database, PagerPhone will be stored as a varchar(30).)
2385 Returns the current value of Address1.
2386 (In the database, Address1 is stored as varchar(200).)
2390 =head2 SetAddress1 VALUE
2393 Set Address1 to VALUE.
2394 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2395 (In the database, Address1 will be stored as a varchar(200).)
2403 Returns the current value of Address2.
2404 (In the database, Address2 is stored as varchar(200).)
2408 =head2 SetAddress2 VALUE
2411 Set Address2 to VALUE.
2412 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2413 (In the database, Address2 will be stored as a varchar(200).)
2421 Returns the current value of City.
2422 (In the database, City is stored as varchar(100).)
2426 =head2 SetCity VALUE
2430 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2431 (In the database, City will be stored as a varchar(100).)
2439 Returns the current value of State.
2440 (In the database, State is stored as varchar(100).)
2444 =head2 SetState VALUE
2448 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2449 (In the database, State will be stored as a varchar(100).)
2457 Returns the current value of Zip.
2458 (In the database, Zip is stored as varchar(16).)
2466 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2467 (In the database, Zip will be stored as a varchar(16).)
2475 Returns the current value of Country.
2476 (In the database, Country is stored as varchar(50).)
2480 =head2 SetCountry VALUE
2483 Set Country to VALUE.
2484 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2485 (In the database, Country will be stored as a varchar(50).)
2493 Returns the current value of Timezone.
2494 (In the database, Timezone is stored as varchar(50).)
2498 =head2 SetTimezone VALUE
2501 Set Timezone to VALUE.
2502 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2503 (In the database, Timezone will be stored as a varchar(50).)
2511 Returns the current value of PGPKey.
2512 (In the database, PGPKey is stored as text.)
2516 =head2 SetPGPKey VALUE
2519 Set PGPKey to VALUE.
2520 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2521 (In the database, PGPKey will be stored as a text.)
2527 =head2 SMIMECertificate
2529 Returns the current value of SMIMECertificate.
2530 (In the database, SMIMECertificate is stored as text.)
2534 =head2 SetSMIMECertificate VALUE
2537 Set SMIMECertificate to VALUE.
2538 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2539 (In the database, SMIMECertificate will be stored as a text.)
2547 Returns the current value of Creator.
2548 (In the database, Creator is stored as int(11).)
2556 Returns the current value of Created.
2557 (In the database, Created is stored as datetime.)
2563 =head2 LastUpdatedBy
2565 Returns the current value of LastUpdatedBy.
2566 (In the database, LastUpdatedBy is stored as int(11).)
2574 Returns the current value of LastUpdated.
2575 (In the database, LastUpdated is stored as datetime.)
2582 sub _CoreAccessible {
2586 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2588 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2590 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2592 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2594 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2596 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2598 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2599 FreeformContactInfo =>
2600 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2602 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2604 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2606 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2608 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2610 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2612 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2613 ExternalContactInfoId =>
2614 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2615 ContactInfoSystem =>
2616 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2618 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2620 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2622 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2624 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2626 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2628 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2630 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2632 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2634 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2636 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2638 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2640 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2642 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2644 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2646 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2648 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2650 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2652 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2654 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2656 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2663 return undef unless defined $self->Name;
2664 return "@{[ref $self]}-@{[$self->Name]}";
2667 sub FindDependencies {
2669 my ($walker, $deps) = @_;
2671 $self->SUPER::FindDependencies($walker, $deps);
2673 # ACL equivalence group
2674 my $objs = RT::Groups->new( $self->CurrentUser );
2675 $objs->Limit( FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 );
2676 $objs->Limit( FIELD => 'Instance', VALUE => $self->Id );
2677 $deps->Add( in => $objs );
2679 # Memberships in SystemInternal groups
2680 $objs = RT::GroupMembers->new( $self->CurrentUser );
2681 $objs->Limit( FIELD => 'MemberId', VALUE => $self->Id );
2682 my $principals = $objs->Join(
2684 FIELD1 => 'GroupId',
2685 TABLE2 => 'Principals',
2688 my $groups = $objs->Join(
2689 ALIAS1 => $principals,
2690 FIELD1 => 'ObjectId',
2697 VALUE => 'SystemInternal',
2700 $deps->Add( in => $objs );
2702 # XXX: This ignores the myriad of "in" references from the Creator
2703 # and LastUpdatedBy columns.
2709 Disabled => $self->PrincipalObj->Disabled,
2710 Principal => $self->PrincipalObj->UID,
2711 PrincipalId => $self->PrincipalObj->Id,
2712 $self->SUPER::Serialize(@_),
2718 my ($importer, $uid, $data) = @_;
2720 my $principal_uid = delete $data->{Principal};
2721 my $principal_id = delete $data->{PrincipalId};
2722 my $disabled = delete $data->{Disabled};
2724 my $obj = RT::User->new( RT->SystemUser );
2725 $obj->LoadByCols( Name => $data->{Name} );
2726 $obj->LoadByEmail( $data->{EmailAddress} ) unless $obj->Id;
2728 # User already exists -- merge
2730 # XXX: We might be merging a privileged user into an unpriv one,
2731 # in which case we should probably promote the unpriv user to
2732 # being privileged. Of course, we don't know if the user being
2733 # imported is privileged yet, as its group memberships show up
2734 # later in the stream...
2735 $importer->MergeValues($obj, $data);
2736 $importer->SkipTransactions( $uid );
2738 # Mark both the principal and the user object as resolved
2741 ref($obj->PrincipalObj),
2742 $obj->PrincipalObj->Id
2744 $importer->Resolve( $uid => ref($obj) => $obj->Id );
2748 # Create a principal first, so we know what ID to use
2749 my $principal = RT::Principal->new( RT->SystemUser );
2750 my ($id) = $principal->Create(
2751 PrincipalType => 'User',
2752 Disabled => $disabled,
2756 # Now we have a principal id, set the id for the user record
2759 $importer->Resolve( $principal_uid => ref($principal), $id );
2761 $importer->Postpone(
2763 uid => $principal_uid,
2764 column => "ObjectId",
2767 return $class->SUPER::PreInflate( $importer, $uid, $data );
2772 RT->InitSystemObjects if $self->Name eq "RT_System";
2775 RT::Base->_ImportOverlays();