1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 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;
84 use Text::Password::Pronounceable;
86 sub _OverlayAccessible {
89 Name => { public => 1, admin => 1 },
90 Password => { read => 0 },
91 EmailAddress => { public => 1 },
92 Organization => { public => 1, admin => 1 },
93 RealName => { public => 1 },
94 NickName => { public => 1 },
95 Lang => { public => 1 },
96 EmailEncoding => { public => 1 },
97 WebEncoding => { public => 1 },
98 ExternalContactInfoId => { public => 1, admin => 1 },
99 ContactInfoSystem => { public => 1, admin => 1 },
100 ExternalAuthId => { public => 1, admin => 1 },
101 AuthSystem => { public => 1, admin => 1 },
102 Gecos => { public => 1, admin => 1 },
103 PGPKey => { public => 1, admin => 1 },
110 =head2 Create { PARAMHASH }
123 _RecordTransaction => 1,
124 @_ # get the real argumentlist
127 # remove the value so it does not cripple SUPER::Create
128 my $record_transaction = delete $args{'_RecordTransaction'};
131 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
132 return ( 0, $self->loc('Permission Denied') );
136 unless ($self->CanonicalizeUserInfo(\%args)) {
137 return ( 0, $self->loc("Could not set user info") );
140 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
142 # if the user doesn't have a name defined, set it to the email address
143 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
147 my $privileged = delete $args{'Privileged'};
150 if ($args{'CryptedPassword'} ) {
151 $args{'Password'} = $args{'CryptedPassword'};
152 delete $args{'CryptedPassword'};
153 } elsif ( !$args{'Password'} ) {
154 $args{'Password'} = '*NO-PASSWORD*';
156 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
157 return ($ok, $msg) if !$ok;
159 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
162 #TODO Specify some sensible defaults.
164 unless ( $args{'Name'} ) {
165 return ( 0, $self->loc("Must specify 'Name' attribute") );
168 my ( $val, $msg ) = $self->ValidateName( $args{'Name'} );
169 return ( 0, $msg ) unless $val;
170 ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
171 return ( 0, $msg ) unless ($val);
173 $RT::Handle->BeginTransaction();
174 # Groups deal with principal ids, rather than user ids.
175 # When creating this user, set up a principal Id for it.
176 my $principal = RT::Principal->new($self->CurrentUser);
177 my $principal_id = $principal->Create(PrincipalType => 'User',
178 Disabled => $args{'Disabled'},
180 # If we couldn't create a principal Id, get the fuck out.
181 unless ($principal_id) {
182 $RT::Handle->Rollback();
183 $RT::Logger->crit("Couldn't create a Principal on new user create.");
184 $RT::Logger->crit("Strange things are afoot at the circle K");
185 return ( 0, $self->loc('Could not create user') );
188 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
189 delete $args{'Disabled'};
191 $self->SUPER::Create(id => $principal_id , %args);
194 #If the create failed.
196 $RT::Handle->Rollback();
197 $RT::Logger->error("Could not create a new user - " .join('-', %args));
199 return ( 0, $self->loc('Could not create user') );
202 my $aclstash = RT::Group->new($self->CurrentUser);
203 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
206 $RT::Handle->Rollback();
207 $RT::Logger->crit("Couldn't stash the user in groupmembers");
208 return ( 0, $self->loc('Could not create user') );
212 my $everyone = RT::Group->new($self->CurrentUser);
213 $everyone->LoadSystemInternalGroup('Everyone');
214 unless ($everyone->id) {
215 $RT::Logger->crit("Could not load Everyone group on user creation.");
216 $RT::Handle->Rollback();
217 return ( 0, $self->loc('Could not create user') );
221 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
222 unless ($everyone_id) {
223 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
224 $RT::Logger->crit($everyone_msg);
225 $RT::Handle->Rollback();
226 return ( 0, $self->loc('Could not create user') );
230 my $access_class = RT::Group->new($self->CurrentUser);
232 $access_class->LoadSystemInternalGroup('Privileged');
234 $access_class->LoadSystemInternalGroup('Unprivileged');
237 unless ($access_class->id) {
238 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
239 $RT::Handle->Rollback();
240 return ( 0, $self->loc('Could not create user') );
244 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
247 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
248 $RT::Logger->crit($ac_msg);
249 $RT::Handle->Rollback();
250 return ( 0, $self->loc('Could not create user') );
254 if ( $record_transaction ) {
255 $self->_NewTransaction( Type => "Create" );
260 return ( $id, $self->loc('User created') );
263 =head2 ValidateName STRING
265 Returns either (0, "failure reason") or 1 depending on whether the given
274 return ( 0, $self->loc('empty name') ) unless defined $name && length $name;
276 my $TempUser = RT::User->new( RT->SystemUser );
277 $TempUser->Load($name);
279 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) {
280 return ( 0, $self->loc('Name in use') );
287 =head2 ValidatePassword STRING
289 Returns either (0, "failure reason") or 1 depending on whether the given
294 sub ValidatePassword {
296 my $password = shift;
298 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
299 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
305 =head2 SetPrivileged BOOL
307 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
308 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
310 Returns a standard RT tuple of (val, msg);
320 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
321 return ( 0, $self->loc('Permission Denied') );
324 $self->_SetPrivileged($val);
330 my $priv = RT::Group->new($self->CurrentUser);
331 $priv->LoadSystemInternalGroup('Privileged');
333 $RT::Logger->crit("Could not find Privileged pseudogroup");
334 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
337 my $unpriv = RT::Group->new($self->CurrentUser);
338 $unpriv->LoadSystemInternalGroup('Unprivileged');
339 unless ($unpriv->Id) {
340 $RT::Logger->crit("Could not find unprivileged pseudogroup");
341 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
344 my $principal = $self->PrincipalId;
346 if ($priv->HasMember($principal)) {
347 #$RT::Logger->debug("That user is already privileged");
348 return (0,$self->loc("That user is already privileged"));
350 if ($unpriv->HasMember($principal)) {
351 $unpriv->_DeleteMember($principal);
353 # if we had layered transactions, life would be good
354 # sadly, we have to just go ahead, even if something
356 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
357 "unprivileged. something is drastically wrong.");
359 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
361 return (1, $self->loc("That user is now privileged"));
366 if ($unpriv->HasMember($principal)) {
367 #$RT::Logger->debug("That user is already unprivileged");
368 return (0,$self->loc("That user is already unprivileged"));
370 if ($priv->HasMember($principal)) {
371 $priv->_DeleteMember( $principal );
373 # if we had layered transactions, life would be good
374 # sadly, we have to just go ahead, even if something
376 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
377 "unprivileged. something is drastically wrong.");
379 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
381 return (1, $self->loc("That user is now unprivileged"));
390 Returns true if this user is privileged. Returns undef otherwise.
396 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
403 #create a user without validating _any_ data.
405 #To be used only on database init.
406 # We can't localize here because it's before we _have_ a loc framework
408 sub _BootstrapCreate {
412 $args{'Password'} = '*NO-PASSWORD*';
415 $RT::Handle->BeginTransaction();
417 # Groups deal with principal ids, rather than user ids.
418 # When creating this user, set up a principal Id for it.
419 my $principal = RT::Principal->new($self->CurrentUser);
420 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
421 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
423 # If we couldn't create a principal Id, get the fuck out.
424 unless ($principal_id) {
425 $RT::Handle->Rollback();
426 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
427 return ( 0, 'Could not create user' );
429 $self->SUPER::Create(id => $principal_id, %args);
431 #If the create failed.
433 $RT::Handle->Rollback();
434 return ( 0, 'Could not create user' ) ; #never loc this
437 my $aclstash = RT::Group->new($self->CurrentUser);
438 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
441 $RT::Handle->Rollback();
442 $RT::Logger->crit("Couldn't stash the user in groupmembers");
443 return ( 0, $self->loc('Could not create user') );
446 $RT::Handle->Commit();
448 return ( $id, 'User created' );
454 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
460 Load a user object from the database. Takes a single argument.
461 If the argument is numerical, load by the column 'id'. If a user
462 object or its subclass passed then loads the same user by id.
463 Otherwise, load by the "Name" column which is the user's textual
470 my $identifier = shift || return undef;
472 if ( $identifier !~ /\D/ ) {
473 return $self->SUPER::LoadById( $identifier );
474 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
475 return $self->SUPER::LoadById( $identifier->Id );
477 return $self->LoadByCol( "Name", $identifier );
483 Tries to load this user object from the database by the user's email address.
491 # Never load an empty address as an email address.
496 $address = $self->CanonicalizeEmailAddress($address);
498 #$RT::Logger->debug("Trying to load an email address: $address");
499 return $self->LoadByCol( "EmailAddress", $address );
502 =head2 LoadOrCreateByEmail ADDRESS
504 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
505 the provided email address and loads them. Address can be provided either as L<Email::Address> object
506 or string which is parsed using the module.
508 Returns a tuple of the user's id and a status message.
509 0 will be returned in place of the user's id in case of failure.
513 sub LoadOrCreateByEmail {
517 my ($message, $name);
518 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
519 ($email, $name) = ($email->address, $email->phrase);
521 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
524 $self->LoadByEmail( $email );
525 $self->Load( $email ) unless $self->Id;
526 $message = $self->loc('User loaded');
528 unless( $self->Id ) {
530 ($val, $message) = $self->Create(
532 EmailAddress => $email,
535 Comments => 'Autocreated when added as a watcher',
538 # Deal with the race condition of two account creations at once
539 $self->LoadByEmail( $email );
540 unless ( $self->Id ) {
542 $self->LoadByEmail( $email );
545 $RT::Logger->error("Recovered from creation failure due to race condition");
546 $message = $self->loc("User loaded");
548 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
552 return (0, $message) unless $self->id;
553 return ($self->Id, $message);
556 =head2 ValidateEmailAddress ADDRESS
558 Returns true if the email address entered is not in use by another user or is
559 undef or ''. Returns false if it's in use.
563 sub ValidateEmailAddress {
567 # if the email address is null, it's always valid
568 return (1) if ( !$Value || $Value eq "" );
570 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
571 # We only allow one valid email address
572 my @addresses = Email::Address->parse($Value);
573 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
577 my $TempUser = RT::User->new(RT->SystemUser);
578 $TempUser->LoadByEmail($Value);
580 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
581 { # if we found a user with that address
582 # it's invalid to set this user's address to it
583 return ( 0, $self->loc('Email address in use') );
584 } else { #it's a valid email address
591 Check to make sure someone else isn't using this name already
599 my ( $val, $message ) = $self->ValidateName($Value);
601 return $self->_Set( Field => 'Name', Value => $Value );
604 return ( 0, $message );
608 =head2 SetEmailAddress
610 Check to make sure someone else isn't using this email address already
611 so that a better email address can be returned
615 sub SetEmailAddress {
618 $Value = '' unless defined $Value;
620 my ($val, $message) = $self->ValidateEmailAddress( $Value );
622 return $self->_Set( Field => 'EmailAddress', Value => $Value );
624 return ( 0, $message )
629 =head2 EmailFrequency
631 Takes optional Ticket argument in paramhash. Returns 'no email',
632 'squelched', 'daily', 'weekly' or empty string depending on
637 =item 'no email' - user has no email, so can not recieve notifications.
639 =item 'squelched' - returned only when Ticket argument is provided and
640 notifications to the user has been supressed for this ticket.
642 =item 'daily' - retruned when user recieve daily messages digest instead
643 of immediate delivery.
645 =item 'weekly' - previous, but weekly.
647 =item empty string returned otherwise.
659 return '' unless $self->id && $self->id != RT->Nobody->id
660 && $self->id != RT->SystemUser->id;
661 return 'no email address' unless my $email = $self->EmailAddress;
662 return 'email disabled for ticket' if $args{'Ticket'} &&
663 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
664 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
665 return 'daily' if $frequency =~ /daily/i;
666 return 'weekly' if $frequency =~ /weekly/i;
670 =head2 CanonicalizeEmailAddress ADDRESS
672 CanonicalizeEmailAddress converts email addresses into canonical form.
673 it takes one email address in and returns the proper canonical
674 form. You can dump whatever your proper local config is in here. Note
675 that it may be called as a static method; in this case the first argument
676 is class name not an object.
680 sub CanonicalizeEmailAddress {
683 # Example: the following rule would treat all email
684 # coming from a subdomain as coming from second level domain
686 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
687 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
689 $email =~ s/$match/$replace/gi;
694 =head2 CanonicalizeUserInfo HASH of ARGS
696 CanonicalizeUserInfo can convert all User->Create options.
697 it takes a hashref of all the params sent to User->Create and
698 returns that same hash, by default nothing is done.
700 This function is intended to allow users to have their info looked up via
701 an outside source and modified upon creation.
705 sub CanonicalizeUserInfo {
714 =head2 Password and authentication related functions
716 =head3 SetRandomPassword
718 Takes no arguments. Returns a status code and a new password or an error message.
719 If the status is 1, the second value returned is the new password.
720 If the status is anything else, the new value returned is the error code.
724 sub SetRandomPassword {
727 unless ( $self->CurrentUserCanModify('Password') ) {
728 return ( 0, $self->loc("Permission Denied") );
732 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
733 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
735 my $pass = $self->GenerateRandomPassword( $min, $max) ;
737 # If we have "notify user on
739 my ( $val, $msg ) = $self->SetPassword($pass);
741 #If we got an error return the error.
742 return ( 0, $msg ) unless ($val);
744 #Otherwise, we changed the password, lets return it.
751 Returns status, [ERROR or new password]. Resets this user's password to
752 a randomly generated pronouncable password and emails them, using a
753 global template called "PasswordChange".
755 This function is currently unused in the UI, but available for local scripts.
762 unless ( $self->CurrentUserCanModify('Password') ) {
763 return ( 0, $self->loc("Permission Denied") );
765 my ( $status, $pass ) = $self->SetRandomPassword();
768 return ( 0, "$pass" );
771 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
772 To => $self->EmailAddress,
773 Template => 'PasswordChange',
775 NewPassword => $pass,
780 return ( 1, $self->loc('New password notification sent') );
782 return ( 0, $self->loc('Notification could not be sent') );
787 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
789 Returns a random password between MIN_LEN and MAX_LEN characters long.
793 sub GenerateRandomPassword {
794 my $self = shift; # just to drop it
795 return Text::Password::Pronounceable->generate(@_);
798 sub SafeSetPassword {
803 Confirmation => undef,
806 return (1) unless defined $args{'New'} && length $args{'New'};
808 my %cond = $self->CurrentUserRequireToSetPassword;
810 unless ( $cond{'CanSet'} ) {
811 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
815 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
816 if ( defined $args{'Current'} && length $args{'Current'} ) {
817 $error = $self->loc("Please enter your current password correctly.");
819 $error = $self->loc("Please enter your current password.");
821 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
822 $error = $self->loc("Passwords do not match.");
826 $error .= ' '. $self->loc('Password has not been set.');
830 return $self->SetPassword( $args{'New'} );
835 Takes a string. Checks the string's length and sets this user's password
842 my $password = shift;
844 unless ( $self->CurrentUserCanModify('Password') ) {
845 return ( 0, $self->loc('Password: Permission Denied') );
849 return ( 0, $self->loc("No password set") );
851 my ($val, $msg) = $self->ValidatePassword($password);
852 return ($val, $msg) if !$val;
854 my $new = !$self->HasPassword;
855 $password = $self->_GeneratePassword($password);
857 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
859 return ( 1, $self->loc("Password set") ) if $new;
860 return ( 1, $self->loc("Password changed") );
862 return ( $val, $msg );
868 sub _GeneratePassword_sha512 {
870 my ($password, $salt) = @_;
872 # Generate a 16-character base64 salt
875 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
879 my $sha = Digest::SHA->new(512);
881 $sha->add(Encode::encode( 'UTF-8', $password));
882 return join("!", "", "sha512", $salt, $sha->b64digest);
885 =head3 _GeneratePassword PASSWORD [, SALT]
887 Returns a string to store in the database. This string takes the form:
891 By default, the method is currently C<sha512>.
895 sub _GeneratePassword {
897 return $self->_GeneratePassword_sha512(@_);
902 Returns true if the user has a valid password, otherwise returns false.
908 my $pwd = $self->__Value('Password');
909 return undef if !defined $pwd
911 || $pwd eq '*NO-PASSWORD*';
917 Returns true if the passed in value is this user's password.
918 Returns undef otherwise.
926 #TODO there isn't any apparent way to legitimately ACL this
928 # RT does not allow null passwords
929 if ( ( !defined($value) ) or ( $value eq '' ) ) {
933 if ( $self->PrincipalObj->Disabled ) {
935 "Disabled user " . $self->Name . " tried to log in" );
939 unless ($self->HasPassword) {
943 my $stored = $self->__Value('Password');
944 if ($stored =~ /^!/) {
945 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
946 my (undef, $method, $salt, undef) = split /!/, $stored;
947 if ($method eq "sha512") {
948 return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
950 $RT::Logger->warn("Unknown hash method $method");
953 } elsif (length $stored == 40) {
954 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
955 my $hash = MIME::Base64::decode_base64($stored);
956 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
957 my $salt = substr($hash, 0, 4, "");
958 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26) eq $hash;
959 } elsif (length $stored == 32) {
961 return 0 unless Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)) eq $stored;
962 } elsif (length $stored == 22) {
963 # Base64 nonsalted-md5
964 return 0 unless Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)) eq $stored;
965 } elsif (length $stored == 13) {
967 return 0 unless crypt(Encode::encode( "UTF-8", $value), $stored) eq $stored;
969 $RT::Logger->warning("Unknown password form");
973 # We got here by validating successfully, but with a legacy
974 # password form. Update to the most recent form.
975 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
976 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
980 sub CurrentUserRequireToSetPassword {
989 if ( RT->Config->Get('WebExternalAuth')
990 && !RT->Config->Get('WebFallbackToInternalAuth')
993 $res{'Reason'} = $self->loc("External authentication enabled.");
994 } elsif ( !$self->CurrentUser->HasPassword ) {
995 if ( $self->CurrentUser->id == ($self->id||0) ) {
996 # don't require current password if user has no
997 $res{'RequireCurrent'} = 0;
1000 $res{'Reason'} = $self->loc("Your password is not set.");
1009 Returns an authentication string associated with the user. This
1010 string can be used to generate passwordless URLs to integrate
1011 RT with services and programms like callendar managers, rss
1018 my $secret = $self->_Value( AuthToken => @_ );
1019 return $secret if $secret;
1021 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1023 my $tmp = RT::User->new( RT->SystemUser );
1024 $tmp->Load( $self->id );
1025 my ($status, $msg) = $tmp->SetAuthToken( $secret );
1026 unless ( $status ) {
1027 $RT::Logger->error( "Couldn't set auth token: $msg" );
1033 =head3 GenerateAuthToken
1035 Generate a random authentication string for the user.
1039 sub GenerateAuthToken {
1041 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1042 return $self->SetAuthToken( $token );
1045 =head3 GenerateAuthString
1047 Takes a string and returns back a hex hash string. Later you can use
1048 this pair to make sure it's generated by this user using L</ValidateAuthString>
1052 sub GenerateAuthString {
1054 my $protect = shift;
1056 my $str = Encode::encode( "UTF-8", $self->AuthToken . $protect );
1058 return substr(Digest::MD5::md5_hex($str),0,16);
1061 =head3 ValidateAuthString
1063 Takes auth string and protected string. Returns true is protected string
1064 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1068 sub ValidateAuthString {
1070 my $auth_string = shift;
1071 my $protected = shift;
1073 my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected );
1075 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1080 Toggles the user's disabled flag.
1082 set, all password checks for this user will fail. All ACL checks for this
1083 user will fail. The user will appear in no user listings.
1090 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1091 return (0, $self->loc('Permission Denied'));
1094 $RT::Handle->BeginTransaction();
1095 my $set_err = $self->PrincipalObj->SetDisabled($val);
1097 $RT::Handle->Rollback();
1098 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1101 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1103 $RT::Handle->Commit();
1106 return (1, $self->loc("User disabled"));
1108 return (1, $self->loc("User enabled"));
1115 Returns true if user is disabled or false otherwise
1121 return $self->PrincipalObj->Disabled(@_);
1126 Returns the principal object for this user. returns an empty RT::Principal
1127 if there's no principal object matching this user.
1128 The response is cached. PrincipalObj should never ever change.
1135 unless ( $self->id ) {
1136 $RT::Logger->error("Couldn't get principal for an empty user");
1140 if ( !$self->{_principal_obj} ) {
1142 my $obj = RT::Principal->new( $self->CurrentUser );
1143 $obj->LoadById( $self->id );
1145 $RT::Logger->crit( 'No principal for user #' . $self->id );
1147 } elsif ( $obj->PrincipalType ne 'User' ) {
1148 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1151 $self->{_principal_obj} = $obj;
1153 return $self->{_principal_obj};
1159 Returns this user's PrincipalId
1168 =head2 HasGroupRight
1170 Takes a paramhash which can contain
1172 GroupObj => RT::Group or Group => integer
1176 Returns 1 if this user has the right specified in the paramhash for the Group
1179 Returns undef if they don't.
1193 if ( defined $args{'Group'} ) {
1194 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1195 $args{'GroupObj'}->Load( $args{'Group'} );
1198 # Validate and load up the GroupId
1199 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1203 # Figure out whether a user has the right we're asking about.
1204 my $retval = $self->HasRight(
1205 Object => $args{'GroupObj'},
1206 Right => $args{'Right'},
1214 Returns a group collection object containing the groups of which this
1221 my $groups = RT::Groups->new($self->CurrentUser);
1222 $groups->LimitToUserDefinedGroups;
1223 $groups->WithMember(
1224 PrincipalId => $self->Id,
1232 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1238 return $self->PrincipalObj->HasRight(@_);
1241 =head2 CurrentUserCanSee [FIELD]
1243 Returns true if the current user can see the user, based on if it is
1244 public, ourself, or we have AdminUsers
1248 sub CurrentUserCanSee {
1252 # If it's public, fine. Note that $what may be "transaction", which
1253 # doesn't have an Accessible value, and thus falls through below.
1254 if ( $self->_Accessible( $what, 'public' ) ) {
1258 # Users can see their own properties
1259 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
1263 # If the user has the admin users right, that's also enough
1264 elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
1272 =head2 CurrentUserCanModify RIGHT
1274 If the user has rights for this object, either because
1275 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1276 admin right) 'ModifySelf', return 1. otherwise, return undef.
1280 sub CurrentUserCanModify {
1284 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1288 #If the field is marked as an "administrators only" field,
1289 # don't let the user touch it.
1290 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1294 #If the current user is trying to modify themselves
1295 elsif ( ( $self->id == $self->CurrentUser->id )
1296 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1301 #If we don't have a good reason to grant them rights to modify
1309 =head2 CurrentUserHasRight
1311 Takes a single argument. returns 1 if $Self->CurrentUser
1312 has the requested right. returns undef otherwise
1316 sub CurrentUserHasRight {
1320 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1326 $name = ref($name).'-'.$name->Id;
1329 return 'Pref-'.$name;
1332 =head2 Preferences NAME/OBJ DEFAULT
1334 Obtain user preferences associated with given object or name.
1335 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1336 override the entries with user preferences.
1342 my $name = _PrefName (shift);
1343 my $default = shift;
1345 my ($attr) = $self->Attributes->Named( $name );
1346 my $content = $attr ? $attr->Content : undef;
1347 unless ( ref $content eq 'HASH' ) {
1348 return defined $content ? $content : $default;
1351 if (ref $default eq 'HASH') {
1352 for (keys %$default) {
1353 exists $content->{$_} or $content->{$_} = $default->{$_};
1355 } elsif (defined $default) {
1356 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1361 =head2 SetPreferences NAME/OBJ VALUE
1363 Set user preferences associated with given object or name.
1367 sub SetPreferences {
1369 my $name = _PrefName( shift );
1372 return (0, $self->loc("No permission to set preferences"))
1373 unless $self->CurrentUserCanModify('Preferences');
1375 my ($attr) = $self->Attributes->Named( $name );
1377 my ($ok, $msg) = $attr->SetContent( $value );
1378 return (1, "No updates made")
1379 if $msg eq "That is already the current value";
1382 return $self->AddAttribute( Name => $name, Content => $value );
1386 =head2 DeletePreferences NAME/OBJ VALUE
1388 Delete user preferences associated with given object or name.
1392 sub DeletePreferences {
1394 my $name = _PrefName( shift );
1396 return (0, $self->loc("No permission to set preferences"))
1397 unless $self->CurrentUserCanModify('Preferences');
1399 my ($attr) = $self->DeleteAttribute( $name );
1400 return (0, $self->loc("Preferences were not found"))
1408 Returns a list of valid stylesheets take from preferences.
1415 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1417 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1418 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1420 for my $css_path (@css_paths) {
1421 if (-d "$css_path/$style") {
1427 # Fall back to the system stylesheet.
1428 return RT->Config->Get('WebDefaultStylesheet');
1431 =head2 WatchedQueues ROLE_LIST
1433 Returns a RT::Queues object containing every queue watched by the user.
1435 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1437 $user->WatchedQueues('Cc', 'AdminCc');
1444 my @roles = @_ ? @_ : ('Cc', 'AdminCc');
1446 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1448 my $watched_queues = RT::Queues->new($self->CurrentUser);
1450 my $group_alias = $watched_queues->Join(
1454 FIELD2 => 'Instance',
1457 $watched_queues->Limit(
1458 ALIAS => $group_alias,
1460 VALUE => 'RT::Queue-Role',
1461 ENTRYAGGREGATOR => 'AND',
1463 if (grep { $_ eq 'Cc' } @roles) {
1464 $watched_queues->Limit(
1465 SUBCLAUSE => 'LimitToWatchers',
1466 ALIAS => $group_alias,
1469 ENTRYAGGREGATOR => 'OR',
1472 if (grep { $_ eq 'AdminCc' } @roles) {
1473 $watched_queues->Limit(
1474 SUBCLAUSE => 'LimitToWatchers',
1475 ALIAS => $group_alias,
1478 ENTRYAGGREGATOR => 'OR',
1482 my $queues_alias = $watched_queues->Join(
1483 ALIAS1 => $group_alias,
1485 TABLE2 => 'CachedGroupMembers',
1486 FIELD2 => 'GroupId',
1488 $watched_queues->Limit(
1489 ALIAS => $queues_alias,
1490 FIELD => 'MemberId',
1491 VALUE => $self->PrincipalId,
1493 $watched_queues->Limit(
1494 ALIAS => $queues_alias,
1495 FIELD => 'Disabled',
1500 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1502 return $watched_queues;
1512 TransactionType => 'Set',
1513 RecordTransaction => 1,
1517 # Nobody is allowed to futz with RT_System or Nobody
1519 if ( ($self->Id == RT->SystemUser->Id ) ||
1520 ($self->Id == RT->Nobody->Id)) {
1521 return ( 0, $self->loc("Can not modify system users") );
1523 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1524 return ( 0, $self->loc("Permission Denied") );
1527 my $Old = $self->SUPER::_Value("$args{'Field'}");
1529 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1530 Value => $args{'Value'} );
1532 #If we can't actually set the field to the value, don't record
1533 # a transaction. instead, get out of here.
1534 if ( $ret == 0 ) { return ( 0, $msg ); }
1536 if ( $args{'RecordTransaction'} == 1 ) {
1537 if ($args{'Field'} eq "Password") {
1538 $args{'Value'} = $Old = '********';
1540 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1541 Type => $args{'TransactionType'},
1542 Field => $args{'Field'},
1543 NewValue => $args{'Value'},
1545 TimeTaken => $args{'TimeTaken'},
1547 return ( $Trans, scalar $TransObj->BriefDescription );
1549 return ( $ret, $msg );
1555 Takes the name of a table column.
1556 Returns its value as a string, if the user passes an ACL check
1565 # Defer to the abstraction above to know if the field can be read
1566 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1572 Return the friendly name
1578 return $self->RealName if defined($self->RealName);
1579 return $self->Name if defined($self->Name);
1585 Returns the preferred key of the user. If none is set, then this will query
1586 GPG and set the preferred key to the maximally trusted key found (and then
1587 return it). Returns C<undef> if no preferred key can be found.
1594 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1596 if ( ($self->CurrentUser->Id != $self->Id ) &&
1597 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1603 my $prefkey = $self->FirstAttribute('PreferredKey');
1604 return $prefkey->Content if $prefkey;
1606 # we don't have a preferred key for this user, so now we must query GPG
1607 require RT::Crypt::GnuPG;
1608 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1609 return undef unless defined $res{'info'};
1610 my @keys = @{ $res{'info'} };
1611 return undef if @keys == 0;
1614 $prefkey = $keys[0]->{'Fingerprint'};
1616 # prefer the maximally trusted key
1617 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1618 $prefkey = $keys[0]->{'Fingerprint'};
1621 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1629 #If the user wants to see their own values, let them.
1630 #If the user is an admin, let them.
1631 #Otherwwise, don't let them.
1633 if ( ($self->CurrentUser->Id != $self->Id ) &&
1634 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1638 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1639 return $key->Content;
1646 # Users should not be able to change their own PrivateKey values
1647 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1648 return (0, $self->loc("Permission Denied"));
1652 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1653 unless ( $status ) {
1654 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1655 return ($status, $self->loc("Couldn't unset private key"));
1657 return ($status, $self->loc("Unset private key"));
1660 # check that it's really private key
1662 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1663 return (0, $self->loc("No such key or it's not suitable for signing"))
1664 if $tmp{'exit_code'} || !$tmp{'info'};
1667 my ($status, $msg) = $self->SetAttribute(
1668 Name => 'PrivateKey',
1671 return ($status, $self->loc("Couldn't set private key"))
1673 return ($status, $self->loc("Set private key"));
1678 [ Name => 'Username' ],
1679 [ EmailAddress => 'Email' ],
1680 [ RealName => 'Name' ],
1681 [ Organization => 'Organization' ],
1685 =head2 Create PARAMHASH
1687 Create takes a hash of values and creates a row in the database:
1689 varchar(200) 'Name'.
1690 varbinary(256) 'Password'.
1691 varchar(16) 'AuthToken'.
1694 varchar(120) 'EmailAddress'.
1695 text 'FreeformContactInfo'.
1696 varchar(200) 'Organization'.
1697 varchar(120) 'RealName'.
1698 varchar(16) 'NickName'.
1700 varchar(16) 'EmailEncoding'.
1701 varchar(16) 'WebEncoding'.
1702 varchar(100) 'ExternalContactInfoId'.
1703 varchar(30) 'ContactInfoSystem'.
1704 varchar(100) 'ExternalAuthId'.
1705 varchar(30) 'AuthSystem'.
1706 varchar(16) 'Gecos'.
1707 varchar(30) 'HomePhone'.
1708 varchar(30) 'WorkPhone'.
1709 varchar(30) 'MobilePhone'.
1710 varchar(30) 'PagerPhone'.
1711 varchar(200) 'Address1'.
1712 varchar(200) 'Address2'.
1713 varchar(100) 'City'.
1714 varchar(100) 'State'.
1716 varchar(50) 'Country'.
1717 varchar(50) 'Timezone'.
1727 Returns the current value of id.
1728 (In the database, id is stored as int(11).)
1736 Returns the current value of Name.
1737 (In the database, Name is stored as varchar(200).)
1741 =head2 SetName VALUE
1745 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1746 (In the database, Name will be stored as a varchar(200).)
1754 Returns the current value of Password.
1755 (In the database, Password is stored as varchar(256).)
1759 =head2 SetPassword VALUE
1762 Set Password to VALUE.
1763 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1764 (In the database, Password will be stored as a varchar(256).)
1772 Returns the current value of AuthToken.
1773 (In the database, AuthToken is stored as varchar(16).)
1777 =head2 SetAuthToken VALUE
1780 Set AuthToken to VALUE.
1781 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1782 (In the database, AuthToken will be stored as a varchar(16).)
1790 Returns the current value of Comments.
1791 (In the database, Comments is stored as text.)
1795 =head2 SetComments VALUE
1798 Set Comments to VALUE.
1799 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1800 (In the database, Comments will be stored as a text.)
1808 Returns the current value of Signature.
1809 (In the database, Signature is stored as text.)
1813 =head2 SetSignature VALUE
1816 Set Signature to VALUE.
1817 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1818 (In the database, Signature will be stored as a text.)
1826 Returns the current value of EmailAddress.
1827 (In the database, EmailAddress is stored as varchar(120).)
1831 =head2 SetEmailAddress VALUE
1834 Set EmailAddress to VALUE.
1835 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1836 (In the database, EmailAddress will be stored as a varchar(120).)
1842 =head2 FreeformContactInfo
1844 Returns the current value of FreeformContactInfo.
1845 (In the database, FreeformContactInfo is stored as text.)
1849 =head2 SetFreeformContactInfo VALUE
1852 Set FreeformContactInfo to VALUE.
1853 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1854 (In the database, FreeformContactInfo will be stored as a text.)
1862 Returns the current value of Organization.
1863 (In the database, Organization is stored as varchar(200).)
1867 =head2 SetOrganization VALUE
1870 Set Organization to VALUE.
1871 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1872 (In the database, Organization will be stored as a varchar(200).)
1880 Returns the current value of RealName.
1881 (In the database, RealName is stored as varchar(120).)
1885 =head2 SetRealName VALUE
1888 Set RealName to VALUE.
1889 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1890 (In the database, RealName will be stored as a varchar(120).)
1898 Returns the current value of NickName.
1899 (In the database, NickName is stored as varchar(16).)
1903 =head2 SetNickName VALUE
1906 Set NickName to VALUE.
1907 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1908 (In the database, NickName will be stored as a varchar(16).)
1916 Returns the current value of Lang.
1917 (In the database, Lang is stored as varchar(16).)
1921 =head2 SetLang VALUE
1925 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1926 (In the database, Lang will be stored as a varchar(16).)
1932 =head2 EmailEncoding
1934 Returns the current value of EmailEncoding.
1935 (In the database, EmailEncoding is stored as varchar(16).)
1939 =head2 SetEmailEncoding VALUE
1942 Set EmailEncoding to VALUE.
1943 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1944 (In the database, EmailEncoding will be stored as a varchar(16).)
1952 Returns the current value of WebEncoding.
1953 (In the database, WebEncoding is stored as varchar(16).)
1957 =head2 SetWebEncoding VALUE
1960 Set WebEncoding to VALUE.
1961 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1962 (In the database, WebEncoding will be stored as a varchar(16).)
1968 =head2 ExternalContactInfoId
1970 Returns the current value of ExternalContactInfoId.
1971 (In the database, ExternalContactInfoId is stored as varchar(100).)
1975 =head2 SetExternalContactInfoId VALUE
1978 Set ExternalContactInfoId to VALUE.
1979 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1980 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
1986 =head2 ContactInfoSystem
1988 Returns the current value of ContactInfoSystem.
1989 (In the database, ContactInfoSystem is stored as varchar(30).)
1993 =head2 SetContactInfoSystem VALUE
1996 Set ContactInfoSystem to VALUE.
1997 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1998 (In the database, ContactInfoSystem will be stored as a varchar(30).)
2004 =head2 ExternalAuthId
2006 Returns the current value of ExternalAuthId.
2007 (In the database, ExternalAuthId is stored as varchar(100).)
2011 =head2 SetExternalAuthId VALUE
2014 Set ExternalAuthId to VALUE.
2015 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2016 (In the database, ExternalAuthId will be stored as a varchar(100).)
2024 Returns the current value of AuthSystem.
2025 (In the database, AuthSystem is stored as varchar(30).)
2029 =head2 SetAuthSystem VALUE
2032 Set AuthSystem to VALUE.
2033 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2034 (In the database, AuthSystem will be stored as a varchar(30).)
2042 Returns the current value of Gecos.
2043 (In the database, Gecos is stored as varchar(16).)
2047 =head2 SetGecos VALUE
2051 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2052 (In the database, Gecos will be stored as a varchar(16).)
2060 Returns the current value of HomePhone.
2061 (In the database, HomePhone is stored as varchar(30).)
2065 =head2 SetHomePhone VALUE
2068 Set HomePhone to VALUE.
2069 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2070 (In the database, HomePhone will be stored as a varchar(30).)
2078 Returns the current value of WorkPhone.
2079 (In the database, WorkPhone is stored as varchar(30).)
2083 =head2 SetWorkPhone VALUE
2086 Set WorkPhone to VALUE.
2087 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2088 (In the database, WorkPhone will be stored as a varchar(30).)
2096 Returns the current value of MobilePhone.
2097 (In the database, MobilePhone is stored as varchar(30).)
2101 =head2 SetMobilePhone VALUE
2104 Set MobilePhone to VALUE.
2105 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2106 (In the database, MobilePhone will be stored as a varchar(30).)
2114 Returns the current value of PagerPhone.
2115 (In the database, PagerPhone is stored as varchar(30).)
2119 =head2 SetPagerPhone VALUE
2122 Set PagerPhone to VALUE.
2123 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2124 (In the database, PagerPhone will be stored as a varchar(30).)
2132 Returns the current value of Address1.
2133 (In the database, Address1 is stored as varchar(200).)
2137 =head2 SetAddress1 VALUE
2140 Set Address1 to VALUE.
2141 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2142 (In the database, Address1 will be stored as a varchar(200).)
2150 Returns the current value of Address2.
2151 (In the database, Address2 is stored as varchar(200).)
2155 =head2 SetAddress2 VALUE
2158 Set Address2 to VALUE.
2159 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2160 (In the database, Address2 will be stored as a varchar(200).)
2168 Returns the current value of City.
2169 (In the database, City is stored as varchar(100).)
2173 =head2 SetCity VALUE
2177 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2178 (In the database, City will be stored as a varchar(100).)
2186 Returns the current value of State.
2187 (In the database, State is stored as varchar(100).)
2191 =head2 SetState VALUE
2195 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2196 (In the database, State will be stored as a varchar(100).)
2204 Returns the current value of Zip.
2205 (In the database, Zip is stored as varchar(16).)
2213 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2214 (In the database, Zip will be stored as a varchar(16).)
2222 Returns the current value of Country.
2223 (In the database, Country is stored as varchar(50).)
2227 =head2 SetCountry VALUE
2230 Set Country to VALUE.
2231 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2232 (In the database, Country will be stored as a varchar(50).)
2240 Returns the current value of Timezone.
2241 (In the database, Timezone is stored as varchar(50).)
2245 =head2 SetTimezone VALUE
2248 Set Timezone to VALUE.
2249 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2250 (In the database, Timezone will be stored as a varchar(50).)
2258 Returns the current value of PGPKey.
2259 (In the database, PGPKey is stored as text.)
2263 =head2 SetPGPKey VALUE
2266 Set PGPKey to VALUE.
2267 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2268 (In the database, PGPKey will be stored as a text.)
2276 Returns the current value of Creator.
2277 (In the database, Creator is stored as int(11).)
2285 Returns the current value of Created.
2286 (In the database, Created is stored as datetime.)
2292 =head2 LastUpdatedBy
2294 Returns the current value of LastUpdatedBy.
2295 (In the database, LastUpdatedBy is stored as int(11).)
2303 Returns the current value of LastUpdated.
2304 (In the database, LastUpdated is stored as datetime.)
2310 # much false laziness w/Ticket.pm. now with RT 4!
2312 MemberOf => { Base => 'MemberOf',
2313 Target => 'HasMember', },
2314 RefersTo => { Base => 'RefersTo',
2315 Target => 'ReferredToBy', },
2316 DependsOn => { Base => 'DependsOn',
2317 Target => 'DependedOnBy', },
2318 MergedInto => { Base => 'MergedInto',
2319 Target => 'MergedInto', },
2323 sub LINKDIRMAP { return \%LINKDIRMAP }
2328 Delete a link. takes a paramhash of Base, Target and Type.
2329 Either Base or Target must be null. The null value will
2330 be replaced with this ticket\'s id
2343 unless ( $args{'Target'} || $args{'Base'} ) {
2344 $RT::Logger->error("Base or Target must be specified\n");
2345 return ( 0, $self->loc('Either base or target must be specified') );
2350 $right++ if $self->CurrentUserHasRight('AdminUsers');
2351 if ( !$right && $RT::StrictLinkACL ) {
2352 return ( 0, $self->loc("Permission Denied") );
2355 # # If the other URI is an RT::Ticket, we want to make sure the user
2356 # # can modify it too...
2357 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2358 # return (0, $msg) unless $status;
2359 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2362 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2363 # ( $RT::StrictLinkACL && $right < 2 ) )
2365 # return ( 0, $self->loc("Permission Denied") );
2368 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2371 $RT::Logger->debug("Couldn't find that link\n");
2375 my ($direction, $remote_link);
2377 if ( $args{'Base'} ) {
2378 $remote_link = $args{'Base'};
2379 $direction = 'Target';
2381 elsif ( $args{'Target'} ) {
2382 $remote_link = $args{'Target'};
2386 if ( $args{'Silent'} ) {
2387 return ( $val, $Msg );
2390 my $remote_uri = RT::URI->new( $self->CurrentUser );
2391 $remote_uri->FromURI( $remote_link );
2393 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2394 Type => 'DeleteLink',
2395 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2396 OldValue => $remote_uri->URI || $remote_link,
2400 if ( $remote_uri->IsLocal ) {
2402 my $OtherObj = $remote_uri->Object;
2403 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2404 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2405 : $LINKDIRMAP{$args{'Type'}}->{Target},
2406 OldValue => $self->URI,
2407 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2411 return ( $Trans, $Msg );
2417 my %args = ( Target => '',
2423 unless ( $args{'Target'} || $args{'Base'} ) {
2424 $RT::Logger->error("Base or Target must be specified\n");
2425 return ( 0, $self->loc('Either base or target must be specified') );
2429 $right++ if $self->CurrentUserHasRight('AdminUsers');
2430 if ( !$right && $RT::StrictLinkACL ) {
2431 return ( 0, $self->loc("Permission Denied") );
2434 # # If the other URI is an RT::Ticket, we want to make sure the user
2435 # # can modify it too...
2436 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2437 # return (0, $msg) unless $status;
2438 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2441 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2442 # ( $RT::StrictLinkACL && $right < 2 ) )
2444 # return ( 0, $self->loc("Permission Denied") );
2447 return $self->_AddLink(%args);
2452 Private non-acled variant of AddLink so that links can be added during create.
2458 my %args = ( Target => '',
2464 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2465 return ($val, $msg) if !$val || $exist;
2467 my ($direction, $remote_link);
2468 if ( $args{'Target'} ) {
2469 $remote_link = $args{'Target'};
2470 $direction = 'Base';
2471 } elsif ( $args{'Base'} ) {
2472 $remote_link = $args{'Base'};
2473 $direction = 'Target';
2476 # Don't write the transaction if we're doing this on create
2477 if ( $args{'Silent'} ) {
2478 return ( $val, $msg );
2481 my $remote_uri = RT::URI->new( $self->CurrentUser );
2482 $remote_uri->FromURI( $remote_link );
2484 #Write the transaction
2485 my ( $Trans, $Msg, $TransObj ) =
2486 $self->_NewTransaction(Type => 'AddLink',
2487 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2488 NewValue => $remote_uri->URI || $remote_link,
2491 if ( $remote_uri->IsLocal ) {
2493 my $OtherObj = $remote_uri->Object;
2494 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2495 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2496 : $LINKDIRMAP{$args{'Type'}}->{Target},
2497 NewValue => $self->URI,
2498 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2501 return ( $val, $Msg );
2507 sub _CoreAccessible {
2511 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', 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 => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', 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 => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2521 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2523 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2524 FreeformContactInfo =>
2525 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2527 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2529 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2531 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', 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 => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2537 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2538 ExternalContactInfoId =>
2539 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2540 ContactInfoSystem =>
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 => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2545 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2547 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2549 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2551 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2553 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2555 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2557 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2559 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2561 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2563 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2565 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2567 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2569 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2571 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2573 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2575 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2577 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2579 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2584 RT::Base->_ImportOverlays();