1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2012 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
70 use base 'RT::Record';
83 use RT::Interface::Email;
85 use Text::Password::Pronounceable;
87 sub _OverlayAccessible {
90 Name => { public => 1, admin => 1 },
91 Password => { read => 0 },
92 EmailAddress => { public => 1 },
93 Organization => { public => 1, admin => 1 },
94 RealName => { public => 1 },
95 NickName => { public => 1 },
96 Lang => { public => 1 },
97 EmailEncoding => { public => 1 },
98 WebEncoding => { public => 1 },
99 ExternalContactInfoId => { public => 1, admin => 1 },
100 ContactInfoSystem => { public => 1, admin => 1 },
101 ExternalAuthId => { public => 1, admin => 1 },
102 AuthSystem => { public => 1, admin => 1 },
103 Gecos => { public => 1, admin => 1 },
104 PGPKey => { public => 1, admin => 1 },
111 =head2 Create { PARAMHASH }
124 _RecordTransaction => 1,
125 @_ # get the real argumentlist
128 # remove the value so it does not cripple SUPER::Create
129 my $record_transaction = delete $args{'_RecordTransaction'};
132 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
133 return ( 0, $self->loc('Permission Denied') );
137 unless ($self->CanonicalizeUserInfo(\%args)) {
138 return ( 0, $self->loc("Could not set user info") );
141 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
143 # if the user doesn't have a name defined, set it to the email address
144 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
148 my $privileged = delete $args{'Privileged'};
151 if ($args{'CryptedPassword'} ) {
152 $args{'Password'} = $args{'CryptedPassword'};
153 delete $args{'CryptedPassword'};
154 } elsif ( !$args{'Password'} ) {
155 $args{'Password'} = '*NO-PASSWORD*';
157 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
158 return ($ok, $msg) if !$ok;
160 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
163 #TODO Specify some sensible defaults.
165 unless ( $args{'Name'} ) {
166 return ( 0, $self->loc("Must specify 'Name' attribute") );
169 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
170 if (RT->SystemUser) { #This only works if RT::SystemUser has been defined
171 my $TempUser = RT::User->new(RT->SystemUser);
172 $TempUser->Load( $args{'Name'} );
173 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
175 my ($val, $message) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
176 return (0, $message) unless ( $val );
178 $RT::Logger->warning( "$self couldn't check for pre-existing users");
182 $RT::Handle->BeginTransaction();
183 # Groups deal with principal ids, rather than user ids.
184 # When creating this user, set up a principal Id for it.
185 my $principal = RT::Principal->new($self->CurrentUser);
186 my $principal_id = $principal->Create(PrincipalType => 'User',
187 Disabled => $args{'Disabled'},
189 # If we couldn't create a principal Id, get the fuck out.
190 unless ($principal_id) {
191 $RT::Handle->Rollback();
192 $RT::Logger->crit("Couldn't create a Principal on new user create.");
193 $RT::Logger->crit("Strange things are afoot at the circle K");
194 return ( 0, $self->loc('Could not create user') );
197 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
198 delete $args{'Disabled'};
200 $self->SUPER::Create(id => $principal_id , %args);
203 #If the create failed.
205 $RT::Handle->Rollback();
206 $RT::Logger->error("Could not create a new user - " .join('-', %args));
208 return ( 0, $self->loc('Could not create user') );
211 my $aclstash = RT::Group->new($self->CurrentUser);
212 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
215 $RT::Handle->Rollback();
216 $RT::Logger->crit("Couldn't stash the user in groupmembers");
217 return ( 0, $self->loc('Could not create user') );
221 my $everyone = RT::Group->new($self->CurrentUser);
222 $everyone->LoadSystemInternalGroup('Everyone');
223 unless ($everyone->id) {
224 $RT::Logger->crit("Could not load Everyone group on user creation.");
225 $RT::Handle->Rollback();
226 return ( 0, $self->loc('Could not create user') );
230 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
231 unless ($everyone_id) {
232 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
233 $RT::Logger->crit($everyone_msg);
234 $RT::Handle->Rollback();
235 return ( 0, $self->loc('Could not create user') );
239 my $access_class = RT::Group->new($self->CurrentUser);
241 $access_class->LoadSystemInternalGroup('Privileged');
243 $access_class->LoadSystemInternalGroup('Unprivileged');
246 unless ($access_class->id) {
247 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
248 $RT::Handle->Rollback();
249 return ( 0, $self->loc('Could not create user') );
253 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
256 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
257 $RT::Logger->crit($ac_msg);
258 $RT::Handle->Rollback();
259 return ( 0, $self->loc('Could not create user') );
263 if ( $record_transaction ) {
264 $self->_NewTransaction( Type => "Create" );
269 return ( $id, $self->loc('User created') );
272 =head2 ValidatePassword STRING
274 Returns either (0, "failure reason") or 1 depending on whether the given
279 sub ValidatePassword {
281 my $password = shift;
283 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
284 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
290 =head2 SetPrivileged BOOL
292 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
293 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
295 Returns a standard RT tuple of (val, msg);
305 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
306 return ( 0, $self->loc('Permission Denied') );
309 $self->_SetPrivileged($val);
315 my $priv = RT::Group->new($self->CurrentUser);
316 $priv->LoadSystemInternalGroup('Privileged');
318 $RT::Logger->crit("Could not find Privileged pseudogroup");
319 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
322 my $unpriv = RT::Group->new($self->CurrentUser);
323 $unpriv->LoadSystemInternalGroup('Unprivileged');
324 unless ($unpriv->Id) {
325 $RT::Logger->crit("Could not find unprivileged pseudogroup");
326 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
329 my $principal = $self->PrincipalId;
331 if ($priv->HasMember($principal)) {
332 #$RT::Logger->debug("That user is already privileged");
333 return (0,$self->loc("That user is already privileged"));
335 if ($unpriv->HasMember($principal)) {
336 $unpriv->_DeleteMember($principal);
338 # if we had layered transactions, life would be good
339 # sadly, we have to just go ahead, even if something
341 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
342 "unprivileged. something is drastically wrong.");
344 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
346 return (1, $self->loc("That user is now privileged"));
351 if ($unpriv->HasMember($principal)) {
352 #$RT::Logger->debug("That user is already unprivileged");
353 return (0,$self->loc("That user is already unprivileged"));
355 if ($priv->HasMember($principal)) {
356 $priv->_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) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
366 return (1, $self->loc("That user is now unprivileged"));
375 Returns true if this user is privileged. Returns undef otherwise.
381 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
388 #create a user without validating _any_ data.
390 #To be used only on database init.
391 # We can't localize here because it's before we _have_ a loc framework
393 sub _BootstrapCreate {
397 $args{'Password'} = '*NO-PASSWORD*';
400 $RT::Handle->BeginTransaction();
402 # Groups deal with principal ids, rather than user ids.
403 # When creating this user, set up a principal Id for it.
404 my $principal = RT::Principal->new($self->CurrentUser);
405 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
406 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
408 # If we couldn't create a principal Id, get the fuck out.
409 unless ($principal_id) {
410 $RT::Handle->Rollback();
411 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
412 return ( 0, 'Could not create user' );
414 $self->SUPER::Create(id => $principal_id, %args);
416 #If the create failed.
418 $RT::Handle->Rollback();
419 return ( 0, 'Could not create user' ) ; #never loc this
422 my $aclstash = RT::Group->new($self->CurrentUser);
423 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
426 $RT::Handle->Rollback();
427 $RT::Logger->crit("Couldn't stash the user in groupmembers");
428 return ( 0, $self->loc('Could not create user') );
431 $RT::Handle->Commit();
433 return ( $id, 'User created' );
439 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
445 Load a user object from the database. Takes a single argument.
446 If the argument is numerical, load by the column 'id'. If a user
447 object or its subclass passed then loads the same user by id.
448 Otherwise, load by the "Name" column which is the user's textual
455 my $identifier = shift || return undef;
457 if ( $identifier !~ /\D/ ) {
458 return $self->SUPER::LoadById( $identifier );
459 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
460 return $self->SUPER::LoadById( $identifier->Id );
462 return $self->LoadByCol( "Name", $identifier );
468 Tries to load this user object from the database by the user's email address.
476 # Never load an empty address as an email address.
481 $address = $self->CanonicalizeEmailAddress($address);
483 #$RT::Logger->debug("Trying to load an email address: $address");
484 return $self->LoadByCol( "EmailAddress", $address );
487 =head2 LoadOrCreateByEmail ADDRESS
489 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
490 the provided email address and loads them. Address can be provided either as L<Email::Address> object
491 or string which is parsed using the module.
493 Returns a tuple of the user's id and a status message.
494 0 will be returned in place of the user's id in case of failure.
498 sub LoadOrCreateByEmail {
502 my ($message, $name);
503 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
504 ($email, $name) = ($email->address, $email->phrase);
506 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
509 $self->LoadByEmail( $email );
510 $self->Load( $email ) unless $self->Id;
511 $message = $self->loc('User loaded');
513 unless( $self->Id ) {
515 ($val, $message) = $self->Create(
517 EmailAddress => $email,
520 Comments => 'Autocreated when added as a watcher',
523 # Deal with the race condition of two account creations at once
524 $self->LoadByEmail( $email );
525 unless ( $self->Id ) {
527 $self->LoadByEmail( $email );
530 $RT::Logger->error("Recovered from creation failure due to race condition");
531 $message = $self->loc("User loaded");
533 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
537 return (0, $message) unless $self->id;
538 return ($self->Id, $message);
541 =head2 ValidateEmailAddress ADDRESS
543 Returns true if the email address entered is not in use by another user or is
544 undef or ''. Returns false if it's in use.
548 sub ValidateEmailAddress {
552 # if the email address is null, it's always valid
553 return (1) if ( !$Value || $Value eq "" );
555 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
556 # We only allow one valid email address
557 my @addresses = Email::Address->parse($Value);
558 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
562 my $TempUser = RT::User->new(RT->SystemUser);
563 $TempUser->LoadByEmail($Value);
565 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
566 { # if we found a user with that address
567 # it's invalid to set this user's address to it
568 return ( 0, $self->loc('Email address in use') );
569 } else { #it's a valid email address
574 =head2 SetEmailAddress
576 Check to make sure someone else isn't using this email address already
577 so that a better email address can be returned
581 sub SetEmailAddress {
584 $Value = '' unless defined $Value;
586 my ($val, $message) = $self->ValidateEmailAddress( $Value );
588 return $self->_Set( Field => 'EmailAddress', Value => $Value );
590 return ( 0, $message )
595 =head2 EmailFrequency
597 Takes optional Ticket argument in paramhash. Returns 'no email',
598 'squelched', 'daily', 'weekly' or empty string depending on
603 =item 'no email' - user has no email, so can not recieve notifications.
605 =item 'squelched' - returned only when Ticket argument is provided and
606 notifications to the user has been supressed for this ticket.
608 =item 'daily' - retruned when user recieve daily messages digest instead
609 of immediate delivery.
611 =item 'weekly' - previous, but weekly.
613 =item empty string returned otherwise.
625 return '' unless $self->id && $self->id != RT->Nobody->id
626 && $self->id != RT->SystemUser->id;
627 return 'no email address' unless my $email = $self->EmailAddress;
628 return 'email disabled for ticket' if $args{'Ticket'} &&
629 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
630 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
631 return 'daily' if $frequency =~ /daily/i;
632 return 'weekly' if $frequency =~ /weekly/i;
636 =head2 CanonicalizeEmailAddress ADDRESS
638 CanonicalizeEmailAddress converts email addresses into canonical form.
639 it takes one email address in and returns the proper canonical
640 form. You can dump whatever your proper local config is in here. Note
641 that it may be called as a static method; in this case the first argument
642 is class name not an object.
646 sub CanonicalizeEmailAddress {
649 # Example: the following rule would treat all email
650 # coming from a subdomain as coming from second level domain
652 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
653 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
655 $email =~ s/$match/$replace/gi;
660 =head2 CanonicalizeUserInfo HASH of ARGS
662 CanonicalizeUserInfo can convert all User->Create options.
663 it takes a hashref of all the params sent to User->Create and
664 returns that same hash, by default nothing is done.
666 This function is intended to allow users to have their info looked up via
667 an outside source and modified upon creation.
671 sub CanonicalizeUserInfo {
680 =head2 Password and authentication related functions
682 =head3 SetRandomPassword
684 Takes no arguments. Returns a status code and a new password or an error message.
685 If the status is 1, the second value returned is the new password.
686 If the status is anything else, the new value returned is the error code.
690 sub SetRandomPassword {
693 unless ( $self->CurrentUserCanModify('Password') ) {
694 return ( 0, $self->loc("Permission Denied") );
698 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
699 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
701 my $pass = $self->GenerateRandomPassword( $min, $max) ;
703 # If we have "notify user on
705 my ( $val, $msg ) = $self->SetPassword($pass);
707 #If we got an error return the error.
708 return ( 0, $msg ) unless ($val);
710 #Otherwise, we changed the password, lets return it.
717 Returns status, [ERROR or new password]. Resets this user's password to
718 a randomly generated pronouncable password and emails them, using a
719 global template called "PasswordChange".
721 This function is currently unused in the UI, but available for local scripts.
728 unless ( $self->CurrentUserCanModify('Password') ) {
729 return ( 0, $self->loc("Permission Denied") );
731 my ( $status, $pass ) = $self->SetRandomPassword();
734 return ( 0, "$pass" );
737 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
738 To => $self->EmailAddress,
739 Template => 'PasswordChange',
741 NewPassword => $pass,
746 return ( 1, $self->loc('New password notification sent') );
748 return ( 0, $self->loc('Notification could not be sent') );
753 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
755 Returns a random password between MIN_LEN and MAX_LEN characters long.
759 sub GenerateRandomPassword {
760 my $self = shift; # just to drop it
761 return Text::Password::Pronounceable->generate(@_);
764 sub SafeSetPassword {
769 Confirmation => undef,
772 return (1) unless defined $args{'New'} && length $args{'New'};
774 my %cond = $self->CurrentUserRequireToSetPassword;
776 unless ( $cond{'CanSet'} ) {
777 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
781 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
782 if ( defined $args{'Current'} && length $args{'Current'} ) {
783 $error = $self->loc("Please enter your current password correctly.");
785 $error = $self->loc("Please enter your current password.");
787 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
788 $error = $self->loc("Passwords do not match.");
792 $error .= ' '. $self->loc('Password has not been set.');
796 return $self->SetPassword( $args{'New'} );
801 Takes a string. Checks the string's length and sets this user's password
808 my $password = shift;
810 unless ( $self->CurrentUserCanModify('Password') ) {
811 return ( 0, $self->loc('Password: Permission Denied') );
815 return ( 0, $self->loc("No password set") );
817 my ($val, $msg) = $self->ValidatePassword($password);
818 return ($val, $msg) if !$val;
820 my $new = !$self->HasPassword;
821 $password = $self->_GeneratePassword($password);
823 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
825 return ( 1, $self->loc("Password set") ) if $new;
826 return ( 1, $self->loc("Password changed") );
828 return ( $val, $msg );
834 sub _GeneratePassword_sha512 {
836 my ($password, $salt) = @_;
838 # Generate a 16-character base64 salt
841 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
845 my $sha = Digest::SHA->new(512);
847 $sha->add(encode_utf8($password));
848 return join("!", "", "sha512", $salt, $sha->b64digest);
851 =head3 _GeneratePassword PASSWORD [, SALT]
853 Returns a string to store in the database. This string takes the form:
857 By default, the method is currently C<sha512>.
861 sub _GeneratePassword {
863 return $self->_GeneratePassword_sha512(@_);
868 Returns true if the user has a valid password, otherwise returns false.
874 my $pwd = $self->__Value('Password');
875 return undef if !defined $pwd
877 || $pwd eq '*NO-PASSWORD*';
883 Returns true if the passed in value is this user's password.
884 Returns undef otherwise.
892 #TODO there isn't any apparent way to legitimately ACL this
894 # RT does not allow null passwords
895 if ( ( !defined($value) ) or ( $value eq '' ) ) {
899 if ( $self->PrincipalObj->Disabled ) {
901 "Disabled user " . $self->Name . " tried to log in" );
905 unless ($self->HasPassword) {
909 my $stored = $self->__Value('Password');
910 if ($stored =~ /^!/) {
911 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
912 my (undef, $method, $salt, undef) = split /!/, $stored;
913 if ($method eq "sha512") {
914 return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
916 $RT::Logger->warn("Unknown hash method $method");
919 } elsif (length $stored == 40) {
920 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
921 my $hash = MIME::Base64::decode_base64($stored);
922 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
923 my $salt = substr($hash, 0, 4, "");
924 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
925 } elsif (length $stored == 32) {
927 return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
928 } elsif (length $stored == 22) {
929 # Base64 nonsalted-md5
930 return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
931 } elsif (length $stored == 13) {
933 return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
935 $RT::Logger->warning("Unknown password form");
939 # We got here by validating successfully, but with a legacy
940 # password form. Update to the most recent form.
941 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
942 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
946 sub CurrentUserRequireToSetPassword {
955 if ( RT->Config->Get('WebExternalAuth')
956 && !RT->Config->Get('WebFallbackToInternalAuth')
959 $res{'Reason'} = $self->loc("External authentication enabled.");
960 } elsif ( !$self->CurrentUser->HasPassword ) {
961 if ( $self->CurrentUser->id == ($self->id||0) ) {
962 # don't require current password if user has no
963 $res{'RequireCurrent'} = 0;
966 $res{'Reason'} = $self->loc("Your password is not set.");
975 Returns an authentication string associated with the user. This
976 string can be used to generate passwordless URLs to integrate
977 RT with services and programms like callendar managers, rss
984 my $secret = $self->_Value( AuthToken => @_ );
985 return $secret if $secret;
987 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
989 my $tmp = RT::User->new( RT->SystemUser );
990 $tmp->Load( $self->id );
991 my ($status, $msg) = $tmp->SetAuthToken( $secret );
993 $RT::Logger->error( "Couldn't set auth token: $msg" );
999 =head3 GenerateAuthToken
1001 Generate a random authentication string for the user.
1005 sub GenerateAuthToken {
1007 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1008 return $self->SetAuthToken( $token );
1011 =head3 GenerateAuthString
1013 Takes a string and returns back a hex hash string. Later you can use
1014 this pair to make sure it's generated by this user using L</ValidateAuthString>
1018 sub GenerateAuthString {
1020 my $protect = shift;
1022 my $str = $self->AuthToken . $protect;
1025 return substr(Digest::MD5::md5_hex($str),0,16);
1028 =head3 ValidateAuthString
1030 Takes auth string and protected string. Returns true is protected string
1031 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1035 sub ValidateAuthString {
1037 my $auth_string = shift;
1038 my $protected = shift;
1040 my $str = $self->AuthToken . $protected;
1041 utf8::encode( $str );
1043 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1048 Toggles the user's disabled flag.
1050 set, all password checks for this user will fail. All ACL checks for this
1051 user will fail. The user will appear in no user listings.
1058 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1059 return (0, $self->loc('Permission Denied'));
1062 $RT::Handle->BeginTransaction();
1063 my $set_err = $self->PrincipalObj->SetDisabled($val);
1065 $RT::Handle->Rollback();
1066 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1069 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1071 $RT::Handle->Commit();
1074 return (1, $self->loc("User disabled"));
1076 return (1, $self->loc("User enabled"));
1083 Returns true if user is disabled or false otherwise
1089 return $self->PrincipalObj->Disabled(@_);
1094 Returns the principal object for this user. returns an empty RT::Principal
1095 if there's no principal object matching this user.
1096 The response is cached. PrincipalObj should never ever change.
1103 unless ( $self->id ) {
1104 $RT::Logger->error("Couldn't get principal for an empty user");
1108 if ( !$self->{_principal_obj} ) {
1110 my $obj = RT::Principal->new( $self->CurrentUser );
1111 $obj->LoadById( $self->id );
1113 $RT::Logger->crit( 'No principal for user #' . $self->id );
1115 } elsif ( $obj->PrincipalType ne 'User' ) {
1116 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1119 $self->{_principal_obj} = $obj;
1121 return $self->{_principal_obj};
1127 Returns this user's PrincipalId
1136 =head2 HasGroupRight
1138 Takes a paramhash which can contain
1140 GroupObj => RT::Group or Group => integer
1144 Returns 1 if this user has the right specified in the paramhash for the Group
1147 Returns undef if they don't.
1161 if ( defined $args{'Group'} ) {
1162 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1163 $args{'GroupObj'}->Load( $args{'Group'} );
1166 # Validate and load up the GroupId
1167 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1171 # Figure out whether a user has the right we're asking about.
1172 my $retval = $self->HasRight(
1173 Object => $args{'GroupObj'},
1174 Right => $args{'Right'},
1182 Returns a group collection object containing the groups of which this
1189 my $groups = RT::Groups->new($self->CurrentUser);
1190 $groups->LimitToUserDefinedGroups;
1191 $groups->WithMember(
1192 PrincipalId => $self->Id,
1200 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1206 return $self->PrincipalObj->HasRight(@_);
1209 =head2 CurrentUserCanSee [FIELD]
1211 Returns true if the current user can see the user, based on if it is
1212 public, ourself, or we have AdminUsers
1216 sub CurrentUserCanSee {
1220 # If it's public, fine. Note that $what may be "transaction", which
1221 # doesn't have an Accessible value, and thus falls through below.
1222 if ( $self->_Accessible( $what, 'public' ) ) {
1226 # Users can see their own properties
1227 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
1231 # If the user has the admin users right, that's also enough
1232 elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
1240 =head2 CurrentUserCanModify RIGHT
1242 If the user has rights for this object, either because
1243 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1244 admin right) 'ModifySelf', return 1. otherwise, return undef.
1248 sub CurrentUserCanModify {
1252 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1256 #If the field is marked as an "administrators only" field,
1257 # don't let the user touch it.
1258 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1262 #If the current user is trying to modify themselves
1263 elsif ( ( $self->id == $self->CurrentUser->id )
1264 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1269 #If we don't have a good reason to grant them rights to modify
1277 =head2 CurrentUserHasRight
1279 Takes a single argument. returns 1 if $Self->CurrentUser
1280 has the requested right. returns undef otherwise
1284 sub CurrentUserHasRight {
1288 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1294 $name = ref($name).'-'.$name->Id;
1297 return 'Pref-'.$name;
1300 =head2 Preferences NAME/OBJ DEFAULT
1302 Obtain user preferences associated with given object or name.
1303 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1304 override the entries with user preferences.
1310 my $name = _PrefName (shift);
1311 my $default = shift;
1313 my $attr = RT::Attribute->new( $self->CurrentUser );
1314 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1316 my $content = $attr->Id ? $attr->Content : undef;
1317 unless ( ref $content eq 'HASH' ) {
1318 return defined $content ? $content : $default;
1321 if (ref $default eq 'HASH') {
1322 for (keys %$default) {
1323 exists $content->{$_} or $content->{$_} = $default->{$_};
1325 } elsif (defined $default) {
1326 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1331 =head2 SetPreferences NAME/OBJ VALUE
1333 Set user preferences associated with given object or name.
1337 sub SetPreferences {
1339 my $name = _PrefName( shift );
1342 return (0, $self->loc("No permission to set preferences"))
1343 unless $self->CurrentUserCanModify('Preferences');
1345 my $attr = RT::Attribute->new( $self->CurrentUser );
1346 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1348 my ($ok, $msg) = $attr->SetContent( $value );
1349 return (1, "No updates made")
1350 if $msg eq "That is already the current value";
1353 return $self->AddAttribute( Name => $name, Content => $value );
1359 Returns a list of valid stylesheets take from preferences.
1366 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1368 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1369 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1371 for my $css_path (@css_paths) {
1372 if (-d "$css_path/$style") {
1378 # Fall back to the system stylesheet.
1379 return RT->Config->Get('WebDefaultStylesheet');
1382 =head2 WatchedQueues ROLE_LIST
1384 Returns a RT::Queues object containing every queue watched by the user.
1386 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1388 $user->WatchedQueues('Cc', 'AdminCc');
1395 my @roles = @_ || ('Cc', 'AdminCc');
1397 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1399 my $watched_queues = RT::Queues->new($self->CurrentUser);
1401 my $group_alias = $watched_queues->Join(
1405 FIELD2 => 'Instance',
1408 $watched_queues->Limit(
1409 ALIAS => $group_alias,
1411 VALUE => 'RT::Queue-Role',
1412 ENTRYAGGREGATOR => 'AND',
1414 if (grep { $_ eq 'Cc' } @roles) {
1415 $watched_queues->Limit(
1416 SUBCLAUSE => 'LimitToWatchers',
1417 ALIAS => $group_alias,
1420 ENTRYAGGREGATOR => 'OR',
1423 if (grep { $_ eq 'AdminCc' } @roles) {
1424 $watched_queues->Limit(
1425 SUBCLAUSE => 'LimitToWatchers',
1426 ALIAS => $group_alias,
1429 ENTRYAGGREGATOR => 'OR',
1433 my $queues_alias = $watched_queues->Join(
1434 ALIAS1 => $group_alias,
1436 TABLE2 => 'CachedGroupMembers',
1437 FIELD2 => 'GroupId',
1439 $watched_queues->Limit(
1440 ALIAS => $queues_alias,
1441 FIELD => 'MemberId',
1442 VALUE => $self->PrincipalId,
1444 $watched_queues->Limit(
1445 ALIAS => $queues_alias,
1446 FIELD => 'Disabled',
1451 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1453 return $watched_queues;
1463 TransactionType => 'Set',
1464 RecordTransaction => 1,
1468 # Nobody is allowed to futz with RT_System or Nobody
1470 if ( ($self->Id == RT->SystemUser->Id ) ||
1471 ($self->Id == RT->Nobody->Id)) {
1472 return ( 0, $self->loc("Can not modify system users") );
1474 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1475 return ( 0, $self->loc("Permission Denied") );
1478 my $Old = $self->SUPER::_Value("$args{'Field'}");
1480 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1481 Value => $args{'Value'} );
1483 #If we can't actually set the field to the value, don't record
1484 # a transaction. instead, get out of here.
1485 if ( $ret == 0 ) { return ( 0, $msg ); }
1487 if ( $args{'RecordTransaction'} == 1 ) {
1488 if ($args{'Field'} eq "Password") {
1489 $args{'Value'} = $Old = '********';
1491 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1492 Type => $args{'TransactionType'},
1493 Field => $args{'Field'},
1494 NewValue => $args{'Value'},
1496 TimeTaken => $args{'TimeTaken'},
1498 return ( $Trans, scalar $TransObj->BriefDescription );
1500 return ( $ret, $msg );
1506 Takes the name of a table column.
1507 Returns its value as a string, if the user passes an ACL check
1516 # Defer to the abstraction above to know if the field can be read
1517 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1523 Return the friendly name
1529 return $self->RealName if defined($self->RealName);
1530 return $self->Name if defined($self->Name);
1536 Returns the preferred key of the user. If none is set, then this will query
1537 GPG and set the preferred key to the maximally trusted key found (and then
1538 return it). Returns C<undef> if no preferred key can be found.
1545 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1547 if ( ($self->CurrentUser->Id != $self->Id ) &&
1548 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1554 my $prefkey = $self->FirstAttribute('PreferredKey');
1555 return $prefkey->Content if $prefkey;
1557 # we don't have a preferred key for this user, so now we must query GPG
1558 require RT::Crypt::GnuPG;
1559 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1560 return undef unless defined $res{'info'};
1561 my @keys = @{ $res{'info'} };
1562 return undef if @keys == 0;
1565 $prefkey = $keys[0]->{'Fingerprint'};
1567 # prefer the maximally trusted key
1568 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1569 $prefkey = $keys[0]->{'Fingerprint'};
1572 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1580 #If the user wants to see their own values, let them.
1581 #If the user is an admin, let them.
1582 #Otherwwise, don't let them.
1584 if ( ($self->CurrentUser->Id != $self->Id ) &&
1585 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1589 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1590 return $key->Content;
1597 unless ($self->CurrentUserCanModify('PrivateKey')) {
1598 return (0, $self->loc("Permission Denied"));
1602 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1603 unless ( $status ) {
1604 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1605 return ($status, $self->loc("Couldn't unset private key"));
1607 return ($status, $self->loc("Unset private key"));
1610 # check that it's really private key
1612 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1613 return (0, $self->loc("No such key or it's not suitable for signing"))
1614 if $tmp{'exit_code'} || !$tmp{'info'};
1617 my ($status, $msg) = $self->SetAttribute(
1618 Name => 'PrivateKey',
1621 return ($status, $self->loc("Couldn't set private key"))
1623 return ($status, $self->loc("Set private key"));
1628 [ Name => 'Username' ],
1629 [ EmailAddress => 'Email' ],
1630 [ RealName => 'Name' ],
1631 [ Organization => 'Organization' ],
1635 =head2 Create PARAMHASH
1637 Create takes a hash of values and creates a row in the database:
1639 varchar(200) 'Name'.
1640 varbinary(256) 'Password'.
1641 varchar(16) 'AuthToken'.
1644 varchar(120) 'EmailAddress'.
1645 text 'FreeformContactInfo'.
1646 varchar(200) 'Organization'.
1647 varchar(120) 'RealName'.
1648 varchar(16) 'NickName'.
1650 varchar(16) 'EmailEncoding'.
1651 varchar(16) 'WebEncoding'.
1652 varchar(100) 'ExternalContactInfoId'.
1653 varchar(30) 'ContactInfoSystem'.
1654 varchar(100) 'ExternalAuthId'.
1655 varchar(30) 'AuthSystem'.
1656 varchar(16) 'Gecos'.
1657 varchar(30) 'HomePhone'.
1658 varchar(30) 'WorkPhone'.
1659 varchar(30) 'MobilePhone'.
1660 varchar(30) 'PagerPhone'.
1661 varchar(200) 'Address1'.
1662 varchar(200) 'Address2'.
1663 varchar(100) 'City'.
1664 varchar(100) 'State'.
1666 varchar(50) 'Country'.
1667 varchar(50) 'Timezone'.
1677 Returns the current value of id.
1678 (In the database, id is stored as int(11).)
1686 Returns the current value of Name.
1687 (In the database, Name is stored as varchar(200).)
1691 =head2 SetName VALUE
1695 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1696 (In the database, Name will be stored as a varchar(200).)
1704 Returns the current value of Password.
1705 (In the database, Password is stored as varchar(256).)
1709 =head2 SetPassword VALUE
1712 Set Password to VALUE.
1713 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1714 (In the database, Password will be stored as a varchar(256).)
1722 Returns the current value of AuthToken.
1723 (In the database, AuthToken is stored as varchar(16).)
1727 =head2 SetAuthToken VALUE
1730 Set AuthToken to VALUE.
1731 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1732 (In the database, AuthToken will be stored as a varchar(16).)
1740 Returns the current value of Comments.
1741 (In the database, Comments is stored as text.)
1745 =head2 SetComments VALUE
1748 Set Comments to VALUE.
1749 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1750 (In the database, Comments will be stored as a text.)
1758 Returns the current value of Signature.
1759 (In the database, Signature is stored as text.)
1763 =head2 SetSignature VALUE
1766 Set Signature to VALUE.
1767 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1768 (In the database, Signature will be stored as a text.)
1776 Returns the current value of EmailAddress.
1777 (In the database, EmailAddress is stored as varchar(120).)
1781 =head2 SetEmailAddress VALUE
1784 Set EmailAddress to VALUE.
1785 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1786 (In the database, EmailAddress will be stored as a varchar(120).)
1792 =head2 FreeformContactInfo
1794 Returns the current value of FreeformContactInfo.
1795 (In the database, FreeformContactInfo is stored as text.)
1799 =head2 SetFreeformContactInfo VALUE
1802 Set FreeformContactInfo to VALUE.
1803 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1804 (In the database, FreeformContactInfo will be stored as a text.)
1812 Returns the current value of Organization.
1813 (In the database, Organization is stored as varchar(200).)
1817 =head2 SetOrganization VALUE
1820 Set Organization to VALUE.
1821 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1822 (In the database, Organization will be stored as a varchar(200).)
1830 Returns the current value of RealName.
1831 (In the database, RealName is stored as varchar(120).)
1835 =head2 SetRealName VALUE
1838 Set RealName to VALUE.
1839 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1840 (In the database, RealName will be stored as a varchar(120).)
1848 Returns the current value of NickName.
1849 (In the database, NickName is stored as varchar(16).)
1853 =head2 SetNickName VALUE
1856 Set NickName to VALUE.
1857 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1858 (In the database, NickName will be stored as a varchar(16).)
1866 Returns the current value of Lang.
1867 (In the database, Lang is stored as varchar(16).)
1871 =head2 SetLang VALUE
1875 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1876 (In the database, Lang will be stored as a varchar(16).)
1882 =head2 EmailEncoding
1884 Returns the current value of EmailEncoding.
1885 (In the database, EmailEncoding is stored as varchar(16).)
1889 =head2 SetEmailEncoding VALUE
1892 Set EmailEncoding to VALUE.
1893 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1894 (In the database, EmailEncoding will be stored as a varchar(16).)
1902 Returns the current value of WebEncoding.
1903 (In the database, WebEncoding is stored as varchar(16).)
1907 =head2 SetWebEncoding VALUE
1910 Set WebEncoding to VALUE.
1911 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1912 (In the database, WebEncoding will be stored as a varchar(16).)
1918 =head2 ExternalContactInfoId
1920 Returns the current value of ExternalContactInfoId.
1921 (In the database, ExternalContactInfoId is stored as varchar(100).)
1925 =head2 SetExternalContactInfoId VALUE
1928 Set ExternalContactInfoId to VALUE.
1929 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1930 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
1936 =head2 ContactInfoSystem
1938 Returns the current value of ContactInfoSystem.
1939 (In the database, ContactInfoSystem is stored as varchar(30).)
1943 =head2 SetContactInfoSystem VALUE
1946 Set ContactInfoSystem to VALUE.
1947 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1948 (In the database, ContactInfoSystem will be stored as a varchar(30).)
1954 =head2 ExternalAuthId
1956 Returns the current value of ExternalAuthId.
1957 (In the database, ExternalAuthId is stored as varchar(100).)
1961 =head2 SetExternalAuthId VALUE
1964 Set ExternalAuthId to VALUE.
1965 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1966 (In the database, ExternalAuthId will be stored as a varchar(100).)
1974 Returns the current value of AuthSystem.
1975 (In the database, AuthSystem is stored as varchar(30).)
1979 =head2 SetAuthSystem VALUE
1982 Set AuthSystem to VALUE.
1983 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1984 (In the database, AuthSystem will be stored as a varchar(30).)
1992 Returns the current value of Gecos.
1993 (In the database, Gecos is stored as varchar(16).)
1997 =head2 SetGecos VALUE
2001 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2002 (In the database, Gecos will be stored as a varchar(16).)
2010 Returns the current value of HomePhone.
2011 (In the database, HomePhone is stored as varchar(30).)
2015 =head2 SetHomePhone VALUE
2018 Set HomePhone to VALUE.
2019 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2020 (In the database, HomePhone will be stored as a varchar(30).)
2028 Returns the current value of WorkPhone.
2029 (In the database, WorkPhone is stored as varchar(30).)
2033 =head2 SetWorkPhone VALUE
2036 Set WorkPhone to VALUE.
2037 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2038 (In the database, WorkPhone will be stored as a varchar(30).)
2046 Returns the current value of MobilePhone.
2047 (In the database, MobilePhone is stored as varchar(30).)
2051 =head2 SetMobilePhone VALUE
2054 Set MobilePhone to VALUE.
2055 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2056 (In the database, MobilePhone will be stored as a varchar(30).)
2064 Returns the current value of PagerPhone.
2065 (In the database, PagerPhone is stored as varchar(30).)
2069 =head2 SetPagerPhone VALUE
2072 Set PagerPhone to VALUE.
2073 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2074 (In the database, PagerPhone will be stored as a varchar(30).)
2082 Returns the current value of Address1.
2083 (In the database, Address1 is stored as varchar(200).)
2087 =head2 SetAddress1 VALUE
2090 Set Address1 to VALUE.
2091 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2092 (In the database, Address1 will be stored as a varchar(200).)
2100 Returns the current value of Address2.
2101 (In the database, Address2 is stored as varchar(200).)
2105 =head2 SetAddress2 VALUE
2108 Set Address2 to VALUE.
2109 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2110 (In the database, Address2 will be stored as a varchar(200).)
2118 Returns the current value of City.
2119 (In the database, City is stored as varchar(100).)
2123 =head2 SetCity VALUE
2127 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2128 (In the database, City will be stored as a varchar(100).)
2136 Returns the current value of State.
2137 (In the database, State is stored as varchar(100).)
2141 =head2 SetState VALUE
2145 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2146 (In the database, State will be stored as a varchar(100).)
2154 Returns the current value of Zip.
2155 (In the database, Zip is stored as varchar(16).)
2163 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2164 (In the database, Zip will be stored as a varchar(16).)
2172 Returns the current value of Country.
2173 (In the database, Country is stored as varchar(50).)
2177 =head2 SetCountry VALUE
2180 Set Country to VALUE.
2181 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2182 (In the database, Country will be stored as a varchar(50).)
2190 Returns the current value of Timezone.
2191 (In the database, Timezone is stored as varchar(50).)
2195 =head2 SetTimezone VALUE
2198 Set Timezone to VALUE.
2199 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2200 (In the database, Timezone will be stored as a varchar(50).)
2208 Returns the current value of PGPKey.
2209 (In the database, PGPKey is stored as text.)
2213 =head2 SetPGPKey VALUE
2216 Set PGPKey to VALUE.
2217 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2218 (In the database, PGPKey will be stored as a text.)
2226 Returns the current value of Creator.
2227 (In the database, Creator is stored as int(11).)
2235 Returns the current value of Created.
2236 (In the database, Created is stored as datetime.)
2242 =head2 LastUpdatedBy
2244 Returns the current value of LastUpdatedBy.
2245 (In the database, LastUpdatedBy is stored as int(11).)
2253 Returns the current value of LastUpdated.
2254 (In the database, LastUpdated is stored as datetime.)
2260 # much false laziness w/Ticket.pm. now with RT 4!
2262 MemberOf => { Base => 'MemberOf',
2263 Target => 'HasMember', },
2264 RefersTo => { Base => 'RefersTo',
2265 Target => 'ReferredToBy', },
2266 DependsOn => { Base => 'DependsOn',
2267 Target => 'DependedOnBy', },
2268 MergedInto => { Base => 'MergedInto',
2269 Target => 'MergedInto', },
2273 sub LINKDIRMAP { return \%LINKDIRMAP }
2278 Delete a link. takes a paramhash of Base, Target and Type.
2279 Either Base or Target must be null. The null value will
2280 be replaced with this ticket\'s id
2293 unless ( $args{'Target'} || $args{'Base'} ) {
2294 $RT::Logger->error("Base or Target must be specified\n");
2295 return ( 0, $self->loc('Either base or target must be specified') );
2300 $right++ if $self->CurrentUserHasRight('AdminUsers');
2301 if ( !$right && $RT::StrictLinkACL ) {
2302 return ( 0, $self->loc("Permission Denied") );
2305 # # If the other URI is an RT::Ticket, we want to make sure the user
2306 # # can modify it too...
2307 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2308 # return (0, $msg) unless $status;
2309 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2312 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2313 # ( $RT::StrictLinkACL && $right < 2 ) )
2315 # return ( 0, $self->loc("Permission Denied") );
2318 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2321 $RT::Logger->debug("Couldn't find that link\n");
2325 my ($direction, $remote_link);
2327 if ( $args{'Base'} ) {
2328 $remote_link = $args{'Base'};
2329 $direction = 'Target';
2331 elsif ( $args{'Target'} ) {
2332 $remote_link = $args{'Target'};
2336 if ( $args{'Silent'} ) {
2337 return ( $val, $Msg );
2340 my $remote_uri = RT::URI->new( $self->CurrentUser );
2341 $remote_uri->FromURI( $remote_link );
2343 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2344 Type => 'DeleteLink',
2345 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2346 OldValue => $remote_uri->URI || $remote_link,
2350 if ( $remote_uri->IsLocal ) {
2352 my $OtherObj = $remote_uri->Object;
2353 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2354 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2355 : $LINKDIRMAP{$args{'Type'}}->{Target},
2356 OldValue => $self->URI,
2357 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2361 return ( $Trans, $Msg );
2367 my %args = ( Target => '',
2373 unless ( $args{'Target'} || $args{'Base'} ) {
2374 $RT::Logger->error("Base or Target must be specified\n");
2375 return ( 0, $self->loc('Either base or target must be specified') );
2379 $right++ if $self->CurrentUserHasRight('AdminUsers');
2380 if ( !$right && $RT::StrictLinkACL ) {
2381 return ( 0, $self->loc("Permission Denied") );
2384 # # If the other URI is an RT::Ticket, we want to make sure the user
2385 # # can modify it too...
2386 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2387 # return (0, $msg) unless $status;
2388 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2391 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2392 # ( $RT::StrictLinkACL && $right < 2 ) )
2394 # return ( 0, $self->loc("Permission Denied") );
2397 return $self->_AddLink(%args);
2402 Private non-acled variant of AddLink so that links can be added during create.
2408 my %args = ( Target => '',
2414 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2415 return ($val, $msg) if !$val || $exist;
2417 my ($direction, $remote_link);
2418 if ( $args{'Target'} ) {
2419 $remote_link = $args{'Target'};
2420 $direction = 'Base';
2421 } elsif ( $args{'Base'} ) {
2422 $remote_link = $args{'Base'};
2423 $direction = 'Target';
2426 # Don't write the transaction if we're doing this on create
2427 if ( $args{'Silent'} ) {
2428 return ( $val, $msg );
2431 my $remote_uri = RT::URI->new( $self->CurrentUser );
2432 $remote_uri->FromURI( $remote_link );
2434 #Write the transaction
2435 my ( $Trans, $Msg, $TransObj ) =
2436 $self->_NewTransaction(Type => 'AddLink',
2437 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2438 NewValue => $remote_uri->URI || $remote_link,
2441 if ( $remote_uri->IsLocal ) {
2443 my $OtherObj = $remote_uri->Object;
2444 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2445 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2446 : $LINKDIRMAP{$args{'Type'}}->{Target},
2447 NewValue => $self->URI,
2448 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2451 return ( $val, $Msg );
2457 sub _CoreAccessible {
2461 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2463 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2465 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2467 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2469 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2471 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2473 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2474 FreeformContactInfo =>
2475 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2477 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2479 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2481 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2483 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2485 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2487 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2488 ExternalContactInfoId =>
2489 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2490 ContactInfoSystem =>
2491 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2493 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2495 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2497 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2499 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2501 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2503 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2505 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2507 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2509 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2511 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2513 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2515 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2517 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2519 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2521 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2523 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2525 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2527 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2529 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2534 RT::Base->_ImportOverlays();