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
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 },
105 PrivateKey => { admin => 1 },
112 =head2 Create { PARAMHASH }
125 _RecordTransaction => 1,
126 @_ # get the real argumentlist
129 # remove the value so it does not cripple SUPER::Create
130 my $record_transaction = delete $args{'_RecordTransaction'};
133 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
134 return ( 0, $self->loc('Permission Denied') );
138 unless ($self->CanonicalizeUserInfo(\%args)) {
139 return ( 0, $self->loc("Could not set user info") );
142 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
144 # if the user doesn't have a name defined, set it to the email address
145 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
149 my $privileged = delete $args{'Privileged'};
152 if ($args{'CryptedPassword'} ) {
153 $args{'Password'} = $args{'CryptedPassword'};
154 delete $args{'CryptedPassword'};
155 } elsif ( !$args{'Password'} ) {
156 $args{'Password'} = '*NO-PASSWORD*';
158 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
159 return ($ok, $msg) if !$ok;
161 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
164 #TODO Specify some sensible defaults.
166 unless ( $args{'Name'} ) {
167 return ( 0, $self->loc("Must specify 'Name' attribute") );
170 my ( $val, $msg ) = $self->ValidateName( $args{'Name'} );
171 return ( 0, $msg ) unless $val;
172 ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
173 return ( 0, $msg ) unless ($val);
175 $RT::Handle->BeginTransaction();
176 # Groups deal with principal ids, rather than user ids.
177 # When creating this user, set up a principal Id for it.
178 my $principal = RT::Principal->new($self->CurrentUser);
179 my $principal_id = $principal->Create(PrincipalType => 'User',
180 Disabled => $args{'Disabled'},
182 # If we couldn't create a principal Id, get the fuck out.
183 unless ($principal_id) {
184 $RT::Handle->Rollback();
185 $RT::Logger->crit("Couldn't create a Principal on new user create.");
186 $RT::Logger->crit("Strange things are afoot at the circle K");
187 return ( 0, $self->loc('Could not create user') );
190 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
191 delete $args{'Disabled'};
193 $self->SUPER::Create(id => $principal_id , %args);
196 #If the create failed.
198 $RT::Handle->Rollback();
199 $RT::Logger->error("Could not create a new user - " .join('-', %args));
201 return ( 0, $self->loc('Could not create user') );
204 my $aclstash = RT::Group->new($self->CurrentUser);
205 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
208 $RT::Handle->Rollback();
209 $RT::Logger->crit("Couldn't stash the user in groupmembers");
210 return ( 0, $self->loc('Could not create user') );
214 my $everyone = RT::Group->new($self->CurrentUser);
215 $everyone->LoadSystemInternalGroup('Everyone');
216 unless ($everyone->id) {
217 $RT::Logger->crit("Could not load Everyone group on user creation.");
218 $RT::Handle->Rollback();
219 return ( 0, $self->loc('Could not create user') );
223 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
224 unless ($everyone_id) {
225 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
226 $RT::Logger->crit($everyone_msg);
227 $RT::Handle->Rollback();
228 return ( 0, $self->loc('Could not create user') );
232 my $access_class = RT::Group->new($self->CurrentUser);
234 $access_class->LoadSystemInternalGroup('Privileged');
236 $access_class->LoadSystemInternalGroup('Unprivileged');
239 unless ($access_class->id) {
240 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
241 $RT::Handle->Rollback();
242 return ( 0, $self->loc('Could not create user') );
246 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
249 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
250 $RT::Logger->crit($ac_msg);
251 $RT::Handle->Rollback();
252 return ( 0, $self->loc('Could not create user') );
256 if ( $record_transaction ) {
257 $self->_NewTransaction( Type => "Create" );
262 return ( $id, $self->loc('User created') );
265 =head2 ValidateName STRING
267 Returns either (0, "failure reason") or 1 depending on whether the given
276 return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
278 my $TempUser = RT::User->new( RT->SystemUser );
279 $TempUser->Load($name);
281 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) {
282 return ( 0, $self->loc('Name in use') );
289 =head2 ValidatePassword STRING
291 Returns either (0, "failure reason") or 1 depending on whether the given
296 sub ValidatePassword {
298 my $password = shift;
300 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
301 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
307 =head2 SetPrivileged BOOL
309 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
310 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
312 Returns a standard RT tuple of (val, msg);
322 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
323 return ( 0, $self->loc('Permission Denied') );
326 $self->_SetPrivileged($val);
332 my $priv = RT::Group->new($self->CurrentUser);
333 $priv->LoadSystemInternalGroup('Privileged');
335 $RT::Logger->crit("Could not find Privileged pseudogroup");
336 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
339 my $unpriv = RT::Group->new($self->CurrentUser);
340 $unpriv->LoadSystemInternalGroup('Unprivileged');
341 unless ($unpriv->Id) {
342 $RT::Logger->crit("Could not find unprivileged pseudogroup");
343 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
346 my $principal = $self->PrincipalId;
348 if ($priv->HasMember($principal)) {
349 #$RT::Logger->debug("That user is already privileged");
350 return (0,$self->loc("That user is already privileged"));
352 if ($unpriv->HasMember($principal)) {
353 $unpriv->_DeleteMember($principal);
355 # if we had layered transactions, life would be good
356 # sadly, we have to just go ahead, even if something
358 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
359 "unprivileged. something is drastically wrong.");
361 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
363 return (1, $self->loc("That user is now privileged"));
368 if ($unpriv->HasMember($principal)) {
369 #$RT::Logger->debug("That user is already unprivileged");
370 return (0,$self->loc("That user is already unprivileged"));
372 if ($priv->HasMember($principal)) {
373 $priv->_DeleteMember( $principal );
375 # if we had layered transactions, life would be good
376 # sadly, we have to just go ahead, even if something
378 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
379 "unprivileged. something is drastically wrong.");
381 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
383 return (1, $self->loc("That user is now unprivileged"));
392 Returns true if this user is privileged. Returns undef otherwise.
398 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
405 #create a user without validating _any_ data.
407 #To be used only on database init.
408 # We can't localize here because it's before we _have_ a loc framework
410 sub _BootstrapCreate {
414 $args{'Password'} = '*NO-PASSWORD*';
417 $RT::Handle->BeginTransaction();
419 # Groups deal with principal ids, rather than user ids.
420 # When creating this user, set up a principal Id for it.
421 my $principal = RT::Principal->new($self->CurrentUser);
422 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
423 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
425 # If we couldn't create a principal Id, get the fuck out.
426 unless ($principal_id) {
427 $RT::Handle->Rollback();
428 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
429 return ( 0, 'Could not create user' );
431 $self->SUPER::Create(id => $principal_id, %args);
433 #If the create failed.
435 $RT::Handle->Rollback();
436 return ( 0, 'Could not create user' ) ; #never loc this
439 my $aclstash = RT::Group->new($self->CurrentUser);
440 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
443 $RT::Handle->Rollback();
444 $RT::Logger->crit("Couldn't stash the user in groupmembers");
445 return ( 0, $self->loc('Could not create user') );
448 $RT::Handle->Commit();
450 return ( $id, 'User created' );
456 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
462 Load a user object from the database. Takes a single argument.
463 If the argument is numerical, load by the column 'id'. If a user
464 object or its subclass passed then loads the same user by id.
465 Otherwise, load by the "Name" column which is the user's textual
472 my $identifier = shift || return undef;
474 if ( $identifier !~ /\D/ ) {
475 return $self->SUPER::LoadById( $identifier );
476 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
477 return $self->SUPER::LoadById( $identifier->Id );
479 return $self->LoadByCol( "Name", $identifier );
485 Tries to load this user object from the database by the user's email address.
493 # Never load an empty address as an email address.
498 $address = $self->CanonicalizeEmailAddress($address);
500 #$RT::Logger->debug("Trying to load an email address: $address");
501 return $self->LoadByCol( "EmailAddress", $address );
504 =head2 LoadOrCreateByEmail ADDRESS
506 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
507 the provided email address and loads them. Address can be provided either as L<Email::Address> object
508 or string which is parsed using the module.
510 Returns a tuple of the user's id and a status message.
511 0 will be returned in place of the user's id in case of failure.
515 sub LoadOrCreateByEmail {
519 my ($message, $name);
520 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
521 ($email, $name) = ($email->address, $email->phrase);
523 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
526 $self->LoadByEmail( $email );
527 $self->Load( $email ) unless $self->Id;
528 $message = $self->loc('User loaded');
530 unless( $self->Id ) {
532 ($val, $message) = $self->Create(
534 EmailAddress => $email,
537 Comments => 'Autocreated when added as a watcher',
540 # Deal with the race condition of two account creations at once
541 $self->LoadByEmail( $email );
542 unless ( $self->Id ) {
544 $self->LoadByEmail( $email );
547 $RT::Logger->error("Recovered from creation failure due to race condition");
548 $message = $self->loc("User loaded");
550 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
554 return (0, $message) unless $self->id;
555 return ($self->Id, $message);
558 =head2 ValidateEmailAddress ADDRESS
560 Returns true if the email address entered is not in use by another user or is
561 undef or ''. Returns false if it's in use.
565 sub ValidateEmailAddress {
569 # if the email address is null, it's always valid
570 return (1) if ( !$Value || $Value eq "" );
572 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
573 # We only allow one valid email address
574 my @addresses = Email::Address->parse($Value);
575 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
579 my $TempUser = RT::User->new(RT->SystemUser);
580 $TempUser->LoadByEmail($Value);
582 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
583 { # if we found a user with that address
584 # it's invalid to set this user's address to it
585 return ( 0, $self->loc('Email address in use') );
586 } else { #it's a valid email address
593 Check to make sure someone else isn't using this name already
601 my ( $val, $message ) = $self->ValidateName($Value);
603 return $self->_Set( Field => 'Name', Value => $Value );
606 return ( 0, $message );
610 =head2 SetEmailAddress
612 Check to make sure someone else isn't using this email address already
613 so that a better email address can be returned
617 sub SetEmailAddress {
620 $Value = '' unless defined $Value;
622 my ($val, $message) = $self->ValidateEmailAddress( $Value );
624 return $self->_Set( Field => 'EmailAddress', Value => $Value );
626 return ( 0, $message )
631 =head2 EmailFrequency
633 Takes optional Ticket argument in paramhash. Returns 'no email',
634 'squelched', 'daily', 'weekly' or empty string depending on
639 =item 'no email' - user has no email, so can not recieve notifications.
641 =item 'squelched' - returned only when Ticket argument is provided and
642 notifications to the user has been supressed for this ticket.
644 =item 'daily' - retruned when user recieve daily messages digest instead
645 of immediate delivery.
647 =item 'weekly' - previous, but weekly.
649 =item empty string returned otherwise.
661 return '' unless $self->id && $self->id != RT->Nobody->id
662 && $self->id != RT->SystemUser->id;
663 return 'no email address' unless my $email = $self->EmailAddress;
664 return 'email disabled for ticket' if $args{'Ticket'} &&
665 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
666 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
667 return 'daily' if $frequency =~ /daily/i;
668 return 'weekly' if $frequency =~ /weekly/i;
672 =head2 CanonicalizeEmailAddress ADDRESS
674 CanonicalizeEmailAddress converts email addresses into canonical form.
675 it takes one email address in and returns the proper canonical
676 form. You can dump whatever your proper local config is in here. Note
677 that it may be called as a static method; in this case the first argument
678 is class name not an object.
682 sub CanonicalizeEmailAddress {
685 # Example: the following rule would treat all email
686 # coming from a subdomain as coming from second level domain
688 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
689 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
691 $email =~ s/$match/$replace/gi;
696 =head2 CanonicalizeUserInfo HASH of ARGS
698 CanonicalizeUserInfo can convert all User->Create options.
699 it takes a hashref of all the params sent to User->Create and
700 returns that same hash, by default nothing is done.
702 This function is intended to allow users to have their info looked up via
703 an outside source and modified upon creation.
707 sub CanonicalizeUserInfo {
716 =head2 Password and authentication related functions
718 =head3 SetRandomPassword
720 Takes no arguments. Returns a status code and a new password or an error message.
721 If the status is 1, the second value returned is the new password.
722 If the status is anything else, the new value returned is the error code.
726 sub SetRandomPassword {
729 unless ( $self->CurrentUserCanModify('Password') ) {
730 return ( 0, $self->loc("Permission Denied") );
734 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
735 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
737 my $pass = $self->GenerateRandomPassword( $min, $max) ;
739 # If we have "notify user on
741 my ( $val, $msg ) = $self->SetPassword($pass);
743 #If we got an error return the error.
744 return ( 0, $msg ) unless ($val);
746 #Otherwise, we changed the password, lets return it.
753 Returns status, [ERROR or new password]. Resets this user's password to
754 a randomly generated pronouncable password and emails them, using a
755 global template called "PasswordChange".
757 This function is currently unused in the UI, but available for local scripts.
764 unless ( $self->CurrentUserCanModify('Password') ) {
765 return ( 0, $self->loc("Permission Denied") );
767 my ( $status, $pass ) = $self->SetRandomPassword();
770 return ( 0, "$pass" );
773 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
774 To => $self->EmailAddress,
775 Template => 'PasswordChange',
777 NewPassword => $pass,
782 return ( 1, $self->loc('New password notification sent') );
784 return ( 0, $self->loc('Notification could not be sent') );
789 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
791 Returns a random password between MIN_LEN and MAX_LEN characters long.
795 sub GenerateRandomPassword {
796 my $self = shift; # just to drop it
797 return Text::Password::Pronounceable->generate(@_);
800 sub SafeSetPassword {
805 Confirmation => undef,
808 return (1) unless defined $args{'New'} && length $args{'New'};
810 my %cond = $self->CurrentUserRequireToSetPassword;
812 unless ( $cond{'CanSet'} ) {
813 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
817 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
818 if ( defined $args{'Current'} && length $args{'Current'} ) {
819 $error = $self->loc("Please enter your current password correctly.");
821 $error = $self->loc("Please enter your current password.");
823 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
824 $error = $self->loc("Passwords do not match.");
828 $error .= ' '. $self->loc('Password has not been set.');
832 return $self->SetPassword( $args{'New'} );
837 Takes a string. Checks the string's length and sets this user's password
844 my $password = shift;
846 unless ( $self->CurrentUserCanModify('Password') ) {
847 return ( 0, $self->loc('Password: Permission Denied') );
851 return ( 0, $self->loc("No password set") );
853 my ($val, $msg) = $self->ValidatePassword($password);
854 return ($val, $msg) if !$val;
856 my $new = !$self->HasPassword;
857 $password = $self->_GeneratePassword($password);
859 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
861 return ( 1, $self->loc("Password set") ) if $new;
862 return ( 1, $self->loc("Password changed") );
864 return ( $val, $msg );
870 sub _GeneratePassword_sha512 {
872 my ($password, $salt) = @_;
874 # Generate a 16-character base64 salt
877 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
881 my $sha = Digest::SHA->new(512);
883 $sha->add(encode_utf8($password));
884 return join("!", "", "sha512", $salt, $sha->b64digest);
887 =head3 _GeneratePassword PASSWORD [, SALT]
889 Returns a string to store in the database. This string takes the form:
893 By default, the method is currently C<sha512>.
897 sub _GeneratePassword {
899 return $self->_GeneratePassword_sha512(@_);
904 Returns true if the user has a valid password, otherwise returns false.
910 my $pwd = $self->__Value('Password');
911 return undef if !defined $pwd
913 || $pwd eq '*NO-PASSWORD*';
919 Returns true if the passed in value is this user's password.
920 Returns undef otherwise.
928 #TODO there isn't any apparent way to legitimately ACL this
930 # RT does not allow null passwords
931 if ( ( !defined($value) ) or ( $value eq '' ) ) {
935 if ( $self->PrincipalObj->Disabled ) {
937 "Disabled user " . $self->Name . " tried to log in" );
941 unless ($self->HasPassword) {
945 my $stored = $self->__Value('Password');
946 if ($stored =~ /^!/) {
947 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
948 my (undef, $method, $salt, undef) = split /!/, $stored;
949 if ($method eq "sha512") {
950 return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
952 $RT::Logger->warn("Unknown hash method $method");
955 } elsif (length $stored == 40) {
956 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
957 my $hash = MIME::Base64::decode_base64($stored);
958 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
959 my $salt = substr($hash, 0, 4, "");
960 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
961 } elsif (length $stored == 32) {
963 return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
964 } elsif (length $stored == 22) {
965 # Base64 nonsalted-md5
966 return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
967 } elsif (length $stored == 13) {
969 return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
971 $RT::Logger->warning("Unknown password form");
975 # We got here by validating successfully, but with a legacy
976 # password form. Update to the most recent form.
977 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
978 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
982 sub CurrentUserRequireToSetPassword {
991 if ( RT->Config->Get('WebExternalAuth')
992 && !RT->Config->Get('WebFallbackToInternalAuth')
995 $res{'Reason'} = $self->loc("External authentication enabled.");
996 } elsif ( !$self->CurrentUser->HasPassword ) {
997 if ( $self->CurrentUser->id == ($self->id||0) ) {
998 # don't require current password if user has no
999 $res{'RequireCurrent'} = 0;
1002 $res{'Reason'} = $self->loc("Your password is not set.");
1011 Returns an authentication string associated with the user. This
1012 string can be used to generate passwordless URLs to integrate
1013 RT with services and programms like callendar managers, rss
1020 my $secret = $self->_Value( AuthToken => @_ );
1021 return $secret if $secret;
1023 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1025 my $tmp = RT::User->new( RT->SystemUser );
1026 $tmp->Load( $self->id );
1027 my ($status, $msg) = $tmp->SetAuthToken( $secret );
1028 unless ( $status ) {
1029 $RT::Logger->error( "Couldn't set auth token: $msg" );
1035 =head3 GenerateAuthToken
1037 Generate a random authentication string for the user.
1041 sub GenerateAuthToken {
1043 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1044 return $self->SetAuthToken( $token );
1047 =head3 GenerateAuthString
1049 Takes a string and returns back a hex hash string. Later you can use
1050 this pair to make sure it's generated by this user using L</ValidateAuthString>
1054 sub GenerateAuthString {
1056 my $protect = shift;
1058 my $str = $self->AuthToken . $protect;
1061 return substr(Digest::MD5::md5_hex($str),0,16);
1064 =head3 ValidateAuthString
1066 Takes auth string and protected string. Returns true is protected string
1067 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1071 sub ValidateAuthString {
1073 my $auth_string = shift;
1074 my $protected = shift;
1076 my $str = $self->AuthToken . $protected;
1077 utf8::encode( $str );
1079 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1084 Toggles the user's disabled flag.
1086 set, all password checks for this user will fail. All ACL checks for this
1087 user will fail. The user will appear in no user listings.
1094 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1095 return (0, $self->loc('Permission Denied'));
1098 $RT::Handle->BeginTransaction();
1099 my $set_err = $self->PrincipalObj->SetDisabled($val);
1101 $RT::Handle->Rollback();
1102 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1105 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1107 $RT::Handle->Commit();
1110 return (1, $self->loc("User disabled"));
1112 return (1, $self->loc("User enabled"));
1119 Returns true if user is disabled or false otherwise
1125 return $self->PrincipalObj->Disabled(@_);
1130 Returns the principal object for this user. returns an empty RT::Principal
1131 if there's no principal object matching this user.
1132 The response is cached. PrincipalObj should never ever change.
1139 unless ( $self->id ) {
1140 $RT::Logger->error("Couldn't get principal for an empty user");
1144 if ( !$self->{_principal_obj} ) {
1146 my $obj = RT::Principal->new( $self->CurrentUser );
1147 $obj->LoadById( $self->id );
1149 $RT::Logger->crit( 'No principal for user #' . $self->id );
1151 } elsif ( $obj->PrincipalType ne 'User' ) {
1152 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1155 $self->{_principal_obj} = $obj;
1157 return $self->{_principal_obj};
1163 Returns this user's PrincipalId
1172 =head2 HasGroupRight
1174 Takes a paramhash which can contain
1176 GroupObj => RT::Group or Group => integer
1180 Returns 1 if this user has the right specified in the paramhash for the Group
1183 Returns undef if they don't.
1197 if ( defined $args{'Group'} ) {
1198 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1199 $args{'GroupObj'}->Load( $args{'Group'} );
1202 # Validate and load up the GroupId
1203 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1207 # Figure out whether a user has the right we're asking about.
1208 my $retval = $self->HasRight(
1209 Object => $args{'GroupObj'},
1210 Right => $args{'Right'},
1218 Returns a group collection object containing the groups of which this
1225 my $groups = RT::Groups->new($self->CurrentUser);
1226 $groups->LimitToUserDefinedGroups;
1227 $groups->WithMember(
1228 PrincipalId => $self->Id,
1236 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1242 return $self->PrincipalObj->HasRight(@_);
1245 =head2 CurrentUserCanSee [FIELD]
1247 Returns true if the current user can see the user, based on if it is
1248 public, ourself, or we have AdminUsers
1252 sub CurrentUserCanSee {
1256 # If it's public, fine. Note that $what may be "transaction", which
1257 # doesn't have an Accessible value, and thus falls through below.
1258 if ( $self->_Accessible( $what, 'public' ) ) {
1262 # Users can see their own properties
1263 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
1267 # If the user has the admin users right, that's also enough
1268 elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
1276 =head2 CurrentUserCanModify RIGHT
1278 If the user has rights for this object, either because
1279 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1280 admin right) 'ModifySelf', return 1. otherwise, return undef.
1284 sub CurrentUserCanModify {
1288 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1292 #If the field is marked as an "administrators only" field,
1293 # don't let the user touch it.
1294 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1298 #If the current user is trying to modify themselves
1299 elsif ( ( $self->id == $self->CurrentUser->id )
1300 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1305 #If we don't have a good reason to grant them rights to modify
1313 =head2 CurrentUserHasRight
1315 Takes a single argument. returns 1 if $Self->CurrentUser
1316 has the requested right. returns undef otherwise
1320 sub CurrentUserHasRight {
1324 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1330 $name = ref($name).'-'.$name->Id;
1333 return 'Pref-'.$name;
1336 =head2 Preferences NAME/OBJ DEFAULT
1338 Obtain user preferences associated with given object or name.
1339 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1340 override the entries with user preferences.
1346 my $name = _PrefName (shift);
1347 my $default = shift;
1349 my $attr = RT::Attribute->new( $self->CurrentUser );
1350 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1352 my $content = $attr->Id ? $attr->Content : undef;
1353 unless ( ref $content eq 'HASH' ) {
1354 return defined $content ? $content : $default;
1357 if (ref $default eq 'HASH') {
1358 for (keys %$default) {
1359 exists $content->{$_} or $content->{$_} = $default->{$_};
1361 } elsif (defined $default) {
1362 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1367 =head2 SetPreferences NAME/OBJ VALUE
1369 Set user preferences associated with given object or name.
1373 sub SetPreferences {
1375 my $name = _PrefName( shift );
1378 return (0, $self->loc("No permission to set preferences"))
1379 unless $self->CurrentUserCanModify('Preferences');
1381 my $attr = RT::Attribute->new( $self->CurrentUser );
1382 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1384 my ($ok, $msg) = $attr->SetContent( $value );
1385 return (1, "No updates made")
1386 if $msg eq "That is already the current value";
1389 return $self->AddAttribute( Name => $name, Content => $value );
1395 Returns a list of valid stylesheets take from preferences.
1402 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1404 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1405 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1407 for my $css_path (@css_paths) {
1408 if (-d "$css_path/$style") {
1414 # Fall back to the system stylesheet.
1415 return RT->Config->Get('WebDefaultStylesheet');
1418 =head2 WatchedQueues ROLE_LIST
1420 Returns a RT::Queues object containing every queue watched by the user.
1422 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1424 $user->WatchedQueues('Cc', 'AdminCc');
1431 my @roles = @_ ? @_ : ('Cc', 'AdminCc');
1433 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1435 my $watched_queues = RT::Queues->new($self->CurrentUser);
1437 my $group_alias = $watched_queues->Join(
1441 FIELD2 => 'Instance',
1444 $watched_queues->Limit(
1445 ALIAS => $group_alias,
1447 VALUE => 'RT::Queue-Role',
1448 ENTRYAGGREGATOR => 'AND',
1450 if (grep { $_ eq 'Cc' } @roles) {
1451 $watched_queues->Limit(
1452 SUBCLAUSE => 'LimitToWatchers',
1453 ALIAS => $group_alias,
1456 ENTRYAGGREGATOR => 'OR',
1459 if (grep { $_ eq 'AdminCc' } @roles) {
1460 $watched_queues->Limit(
1461 SUBCLAUSE => 'LimitToWatchers',
1462 ALIAS => $group_alias,
1465 ENTRYAGGREGATOR => 'OR',
1469 my $queues_alias = $watched_queues->Join(
1470 ALIAS1 => $group_alias,
1472 TABLE2 => 'CachedGroupMembers',
1473 FIELD2 => 'GroupId',
1475 $watched_queues->Limit(
1476 ALIAS => $queues_alias,
1477 FIELD => 'MemberId',
1478 VALUE => $self->PrincipalId,
1480 $watched_queues->Limit(
1481 ALIAS => $queues_alias,
1482 FIELD => 'Disabled',
1487 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1489 return $watched_queues;
1499 TransactionType => 'Set',
1500 RecordTransaction => 1,
1504 # Nobody is allowed to futz with RT_System or Nobody
1506 if ( ($self->Id == RT->SystemUser->Id ) ||
1507 ($self->Id == RT->Nobody->Id)) {
1508 return ( 0, $self->loc("Can not modify system users") );
1510 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1511 return ( 0, $self->loc("Permission Denied") );
1514 my $Old = $self->SUPER::_Value("$args{'Field'}");
1516 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1517 Value => $args{'Value'} );
1519 #If we can't actually set the field to the value, don't record
1520 # a transaction. instead, get out of here.
1521 if ( $ret == 0 ) { return ( 0, $msg ); }
1523 if ( $args{'RecordTransaction'} == 1 ) {
1524 if ($args{'Field'} eq "Password") {
1525 $args{'Value'} = $Old = '********';
1527 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1528 Type => $args{'TransactionType'},
1529 Field => $args{'Field'},
1530 NewValue => $args{'Value'},
1532 TimeTaken => $args{'TimeTaken'},
1534 return ( $Trans, scalar $TransObj->BriefDescription );
1536 return ( $ret, $msg );
1542 Takes the name of a table column.
1543 Returns its value as a string, if the user passes an ACL check
1552 # Defer to the abstraction above to know if the field can be read
1553 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1559 Return the friendly name
1565 return $self->RealName if defined($self->RealName);
1566 return $self->Name if defined($self->Name);
1572 Returns the preferred key of the user. If none is set, then this will query
1573 GPG and set the preferred key to the maximally trusted key found (and then
1574 return it). Returns C<undef> if no preferred key can be found.
1581 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1583 if ( ($self->CurrentUser->Id != $self->Id ) &&
1584 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1590 my $prefkey = $self->FirstAttribute('PreferredKey');
1591 return $prefkey->Content if $prefkey;
1593 # we don't have a preferred key for this user, so now we must query GPG
1594 require RT::Crypt::GnuPG;
1595 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1596 return undef unless defined $res{'info'};
1597 my @keys = @{ $res{'info'} };
1598 return undef if @keys == 0;
1601 $prefkey = $keys[0]->{'Fingerprint'};
1603 # prefer the maximally trusted key
1604 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1605 $prefkey = $keys[0]->{'Fingerprint'};
1608 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1616 #If the user wants to see their own values, let them.
1617 #If the user is an admin, let them.
1618 #Otherwwise, don't let them.
1620 if ( ($self->CurrentUser->Id != $self->Id ) &&
1621 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1625 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1626 return $key->Content;
1633 unless ($self->CurrentUserCanModify('PrivateKey')) {
1634 return (0, $self->loc("Permission Denied"));
1638 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1639 unless ( $status ) {
1640 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1641 return ($status, $self->loc("Couldn't unset private key"));
1643 return ($status, $self->loc("Unset private key"));
1646 # check that it's really private key
1648 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1649 return (0, $self->loc("No such key or it's not suitable for signing"))
1650 if $tmp{'exit_code'} || !$tmp{'info'};
1653 my ($status, $msg) = $self->SetAttribute(
1654 Name => 'PrivateKey',
1657 return ($status, $self->loc("Couldn't set private key"))
1659 return ($status, $self->loc("Set private key"));
1664 [ Name => 'Username' ],
1665 [ EmailAddress => 'Email' ],
1666 [ RealName => 'Name' ],
1667 [ Organization => 'Organization' ],
1671 =head2 Create PARAMHASH
1673 Create takes a hash of values and creates a row in the database:
1675 varchar(200) 'Name'.
1676 varbinary(256) 'Password'.
1677 varchar(16) 'AuthToken'.
1680 varchar(120) 'EmailAddress'.
1681 text 'FreeformContactInfo'.
1682 varchar(200) 'Organization'.
1683 varchar(120) 'RealName'.
1684 varchar(16) 'NickName'.
1686 varchar(16) 'EmailEncoding'.
1687 varchar(16) 'WebEncoding'.
1688 varchar(100) 'ExternalContactInfoId'.
1689 varchar(30) 'ContactInfoSystem'.
1690 varchar(100) 'ExternalAuthId'.
1691 varchar(30) 'AuthSystem'.
1692 varchar(16) 'Gecos'.
1693 varchar(30) 'HomePhone'.
1694 varchar(30) 'WorkPhone'.
1695 varchar(30) 'MobilePhone'.
1696 varchar(30) 'PagerPhone'.
1697 varchar(200) 'Address1'.
1698 varchar(200) 'Address2'.
1699 varchar(100) 'City'.
1700 varchar(100) 'State'.
1702 varchar(50) 'Country'.
1703 varchar(50) 'Timezone'.
1713 Returns the current value of id.
1714 (In the database, id is stored as int(11).)
1722 Returns the current value of Name.
1723 (In the database, Name is stored as varchar(200).)
1727 =head2 SetName VALUE
1731 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1732 (In the database, Name will be stored as a varchar(200).)
1740 Returns the current value of Password.
1741 (In the database, Password is stored as varchar(256).)
1745 =head2 SetPassword VALUE
1748 Set Password to VALUE.
1749 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1750 (In the database, Password will be stored as a varchar(256).)
1758 Returns the current value of AuthToken.
1759 (In the database, AuthToken is stored as varchar(16).)
1763 =head2 SetAuthToken VALUE
1766 Set AuthToken to VALUE.
1767 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1768 (In the database, AuthToken will be stored as a varchar(16).)
1776 Returns the current value of Comments.
1777 (In the database, Comments is stored as text.)
1781 =head2 SetComments VALUE
1784 Set Comments to VALUE.
1785 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1786 (In the database, Comments will be stored as a text.)
1794 Returns the current value of Signature.
1795 (In the database, Signature is stored as text.)
1799 =head2 SetSignature VALUE
1802 Set Signature to VALUE.
1803 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1804 (In the database, Signature will be stored as a text.)
1812 Returns the current value of EmailAddress.
1813 (In the database, EmailAddress is stored as varchar(120).)
1817 =head2 SetEmailAddress VALUE
1820 Set EmailAddress to VALUE.
1821 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1822 (In the database, EmailAddress will be stored as a varchar(120).)
1828 =head2 FreeformContactInfo
1830 Returns the current value of FreeformContactInfo.
1831 (In the database, FreeformContactInfo is stored as text.)
1835 =head2 SetFreeformContactInfo VALUE
1838 Set FreeformContactInfo to VALUE.
1839 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1840 (In the database, FreeformContactInfo will be stored as a text.)
1848 Returns the current value of Organization.
1849 (In the database, Organization is stored as varchar(200).)
1853 =head2 SetOrganization VALUE
1856 Set Organization to VALUE.
1857 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1858 (In the database, Organization will be stored as a varchar(200).)
1866 Returns the current value of RealName.
1867 (In the database, RealName is stored as varchar(120).)
1871 =head2 SetRealName VALUE
1874 Set RealName to VALUE.
1875 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1876 (In the database, RealName will be stored as a varchar(120).)
1884 Returns the current value of NickName.
1885 (In the database, NickName is stored as varchar(16).)
1889 =head2 SetNickName VALUE
1892 Set NickName to VALUE.
1893 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1894 (In the database, NickName will be stored as a varchar(16).)
1902 Returns the current value of Lang.
1903 (In the database, Lang is stored as varchar(16).)
1907 =head2 SetLang VALUE
1911 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1912 (In the database, Lang will be stored as a varchar(16).)
1918 =head2 EmailEncoding
1920 Returns the current value of EmailEncoding.
1921 (In the database, EmailEncoding is stored as varchar(16).)
1925 =head2 SetEmailEncoding VALUE
1928 Set EmailEncoding to VALUE.
1929 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1930 (In the database, EmailEncoding will be stored as a varchar(16).)
1938 Returns the current value of WebEncoding.
1939 (In the database, WebEncoding is stored as varchar(16).)
1943 =head2 SetWebEncoding VALUE
1946 Set WebEncoding to VALUE.
1947 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1948 (In the database, WebEncoding will be stored as a varchar(16).)
1954 =head2 ExternalContactInfoId
1956 Returns the current value of ExternalContactInfoId.
1957 (In the database, ExternalContactInfoId is stored as varchar(100).)
1961 =head2 SetExternalContactInfoId VALUE
1964 Set ExternalContactInfoId to VALUE.
1965 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1966 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
1972 =head2 ContactInfoSystem
1974 Returns the current value of ContactInfoSystem.
1975 (In the database, ContactInfoSystem is stored as varchar(30).)
1979 =head2 SetContactInfoSystem VALUE
1982 Set ContactInfoSystem to VALUE.
1983 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1984 (In the database, ContactInfoSystem will be stored as a varchar(30).)
1990 =head2 ExternalAuthId
1992 Returns the current value of ExternalAuthId.
1993 (In the database, ExternalAuthId is stored as varchar(100).)
1997 =head2 SetExternalAuthId VALUE
2000 Set ExternalAuthId to VALUE.
2001 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2002 (In the database, ExternalAuthId will be stored as a varchar(100).)
2010 Returns the current value of AuthSystem.
2011 (In the database, AuthSystem is stored as varchar(30).)
2015 =head2 SetAuthSystem VALUE
2018 Set AuthSystem to VALUE.
2019 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2020 (In the database, AuthSystem will be stored as a varchar(30).)
2028 Returns the current value of Gecos.
2029 (In the database, Gecos is stored as varchar(16).)
2033 =head2 SetGecos VALUE
2037 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2038 (In the database, Gecos will be stored as a varchar(16).)
2046 Returns the current value of HomePhone.
2047 (In the database, HomePhone is stored as varchar(30).)
2051 =head2 SetHomePhone VALUE
2054 Set HomePhone to VALUE.
2055 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2056 (In the database, HomePhone will be stored as a varchar(30).)
2064 Returns the current value of WorkPhone.
2065 (In the database, WorkPhone is stored as varchar(30).)
2069 =head2 SetWorkPhone VALUE
2072 Set WorkPhone to VALUE.
2073 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2074 (In the database, WorkPhone will be stored as a varchar(30).)
2082 Returns the current value of MobilePhone.
2083 (In the database, MobilePhone is stored as varchar(30).)
2087 =head2 SetMobilePhone VALUE
2090 Set MobilePhone to VALUE.
2091 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2092 (In the database, MobilePhone will be stored as a varchar(30).)
2100 Returns the current value of PagerPhone.
2101 (In the database, PagerPhone is stored as varchar(30).)
2105 =head2 SetPagerPhone VALUE
2108 Set PagerPhone to VALUE.
2109 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2110 (In the database, PagerPhone will be stored as a varchar(30).)
2118 Returns the current value of Address1.
2119 (In the database, Address1 is stored as varchar(200).)
2123 =head2 SetAddress1 VALUE
2126 Set Address1 to VALUE.
2127 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2128 (In the database, Address1 will be stored as a varchar(200).)
2136 Returns the current value of Address2.
2137 (In the database, Address2 is stored as varchar(200).)
2141 =head2 SetAddress2 VALUE
2144 Set Address2 to VALUE.
2145 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2146 (In the database, Address2 will be stored as a varchar(200).)
2154 Returns the current value of City.
2155 (In the database, City is stored as varchar(100).)
2159 =head2 SetCity VALUE
2163 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2164 (In the database, City will be stored as a varchar(100).)
2172 Returns the current value of State.
2173 (In the database, State is stored as varchar(100).)
2177 =head2 SetState VALUE
2181 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2182 (In the database, State will be stored as a varchar(100).)
2190 Returns the current value of Zip.
2191 (In the database, Zip is stored as varchar(16).)
2199 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2200 (In the database, Zip will be stored as a varchar(16).)
2208 Returns the current value of Country.
2209 (In the database, Country is stored as varchar(50).)
2213 =head2 SetCountry VALUE
2216 Set Country to VALUE.
2217 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2218 (In the database, Country will be stored as a varchar(50).)
2226 Returns the current value of Timezone.
2227 (In the database, Timezone is stored as varchar(50).)
2231 =head2 SetTimezone VALUE
2234 Set Timezone to VALUE.
2235 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2236 (In the database, Timezone will be stored as a varchar(50).)
2244 Returns the current value of PGPKey.
2245 (In the database, PGPKey is stored as text.)
2249 =head2 SetPGPKey VALUE
2252 Set PGPKey to VALUE.
2253 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2254 (In the database, PGPKey will be stored as a text.)
2262 Returns the current value of Creator.
2263 (In the database, Creator is stored as int(11).)
2271 Returns the current value of Created.
2272 (In the database, Created is stored as datetime.)
2278 =head2 LastUpdatedBy
2280 Returns the current value of LastUpdatedBy.
2281 (In the database, LastUpdatedBy is stored as int(11).)
2289 Returns the current value of LastUpdated.
2290 (In the database, LastUpdated is stored as datetime.)
2296 # much false laziness w/Ticket.pm. now with RT 4!
2298 MemberOf => { Base => 'MemberOf',
2299 Target => 'HasMember', },
2300 RefersTo => { Base => 'RefersTo',
2301 Target => 'ReferredToBy', },
2302 DependsOn => { Base => 'DependsOn',
2303 Target => 'DependedOnBy', },
2304 MergedInto => { Base => 'MergedInto',
2305 Target => 'MergedInto', },
2309 sub LINKDIRMAP { return \%LINKDIRMAP }
2314 Delete a link. takes a paramhash of Base, Target and Type.
2315 Either Base or Target must be null. The null value will
2316 be replaced with this ticket\'s id
2329 unless ( $args{'Target'} || $args{'Base'} ) {
2330 $RT::Logger->error("Base or Target must be specified\n");
2331 return ( 0, $self->loc('Either base or target must be specified') );
2336 $right++ if $self->CurrentUserHasRight('AdminUsers');
2337 if ( !$right && $RT::StrictLinkACL ) {
2338 return ( 0, $self->loc("Permission Denied") );
2341 # # If the other URI is an RT::Ticket, we want to make sure the user
2342 # # can modify it too...
2343 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2344 # return (0, $msg) unless $status;
2345 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2348 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2349 # ( $RT::StrictLinkACL && $right < 2 ) )
2351 # return ( 0, $self->loc("Permission Denied") );
2354 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2357 $RT::Logger->debug("Couldn't find that link\n");
2361 my ($direction, $remote_link);
2363 if ( $args{'Base'} ) {
2364 $remote_link = $args{'Base'};
2365 $direction = 'Target';
2367 elsif ( $args{'Target'} ) {
2368 $remote_link = $args{'Target'};
2372 if ( $args{'Silent'} ) {
2373 return ( $val, $Msg );
2376 my $remote_uri = RT::URI->new( $self->CurrentUser );
2377 $remote_uri->FromURI( $remote_link );
2379 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2380 Type => 'DeleteLink',
2381 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2382 OldValue => $remote_uri->URI || $remote_link,
2386 if ( $remote_uri->IsLocal ) {
2388 my $OtherObj = $remote_uri->Object;
2389 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2390 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2391 : $LINKDIRMAP{$args{'Type'}}->{Target},
2392 OldValue => $self->URI,
2393 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2397 return ( $Trans, $Msg );
2403 my %args = ( Target => '',
2409 unless ( $args{'Target'} || $args{'Base'} ) {
2410 $RT::Logger->error("Base or Target must be specified\n");
2411 return ( 0, $self->loc('Either base or target must be specified') );
2415 $right++ if $self->CurrentUserHasRight('AdminUsers');
2416 if ( !$right && $RT::StrictLinkACL ) {
2417 return ( 0, $self->loc("Permission Denied") );
2420 # # If the other URI is an RT::Ticket, we want to make sure the user
2421 # # can modify it too...
2422 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2423 # return (0, $msg) unless $status;
2424 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2427 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2428 # ( $RT::StrictLinkACL && $right < 2 ) )
2430 # return ( 0, $self->loc("Permission Denied") );
2433 return $self->_AddLink(%args);
2438 Private non-acled variant of AddLink so that links can be added during create.
2444 my %args = ( Target => '',
2450 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2451 return ($val, $msg) if !$val || $exist;
2453 my ($direction, $remote_link);
2454 if ( $args{'Target'} ) {
2455 $remote_link = $args{'Target'};
2456 $direction = 'Base';
2457 } elsif ( $args{'Base'} ) {
2458 $remote_link = $args{'Base'};
2459 $direction = 'Target';
2462 # Don't write the transaction if we're doing this on create
2463 if ( $args{'Silent'} ) {
2464 return ( $val, $msg );
2467 my $remote_uri = RT::URI->new( $self->CurrentUser );
2468 $remote_uri->FromURI( $remote_link );
2470 #Write the transaction
2471 my ( $Trans, $Msg, $TransObj ) =
2472 $self->_NewTransaction(Type => 'AddLink',
2473 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2474 NewValue => $remote_uri->URI || $remote_link,
2477 if ( $remote_uri->IsLocal ) {
2479 my $OtherObj = $remote_uri->Object;
2480 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2481 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2482 : $LINKDIRMAP{$args{'Type'}}->{Target},
2483 NewValue => $self->URI,
2484 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2487 return ( $val, $Msg );
2493 sub _CoreAccessible {
2497 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2499 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2501 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2503 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2505 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2507 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2509 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2510 FreeformContactInfo =>
2511 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2513 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2515 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2517 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2519 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2521 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2523 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2524 ExternalContactInfoId =>
2525 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2526 ContactInfoSystem =>
2527 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2529 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2531 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2533 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2535 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2537 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2539 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2541 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2543 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2545 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2547 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2549 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2551 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2553 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2555 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2557 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2559 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2561 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2563 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2565 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2570 RT::Base->_ImportOverlays();