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(encode_utf8($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 );
1393 =head2 DeletePreferences NAME/OBJ VALUE
1395 Delete user preferences associated with given object or name.
1399 sub DeletePreferences {
1401 my $name = _PrefName( shift );
1403 return (0, $self->loc("No permission to set preferences"))
1404 unless $self->CurrentUserCanModify('Preferences');
1406 my $attr = RT::Attribute->new( $self->CurrentUser );
1407 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1409 return $attr->Delete;
1412 return (0, $self->loc("Preferences were not found"));
1417 Returns a list of valid stylesheets take from preferences.
1424 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1426 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1427 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1429 for my $css_path (@css_paths) {
1430 if (-d "$css_path/$style") {
1436 # Fall back to the system stylesheet.
1437 return RT->Config->Get('WebDefaultStylesheet');
1440 =head2 WatchedQueues ROLE_LIST
1442 Returns a RT::Queues object containing every queue watched by the user.
1444 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1446 $user->WatchedQueues('Cc', 'AdminCc');
1453 my @roles = @_ ? @_ : ('Cc', 'AdminCc');
1455 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1457 my $watched_queues = RT::Queues->new($self->CurrentUser);
1459 my $group_alias = $watched_queues->Join(
1463 FIELD2 => 'Instance',
1466 $watched_queues->Limit(
1467 ALIAS => $group_alias,
1469 VALUE => 'RT::Queue-Role',
1470 ENTRYAGGREGATOR => 'AND',
1472 if (grep { $_ eq 'Cc' } @roles) {
1473 $watched_queues->Limit(
1474 SUBCLAUSE => 'LimitToWatchers',
1475 ALIAS => $group_alias,
1478 ENTRYAGGREGATOR => 'OR',
1481 if (grep { $_ eq 'AdminCc' } @roles) {
1482 $watched_queues->Limit(
1483 SUBCLAUSE => 'LimitToWatchers',
1484 ALIAS => $group_alias,
1487 ENTRYAGGREGATOR => 'OR',
1491 my $queues_alias = $watched_queues->Join(
1492 ALIAS1 => $group_alias,
1494 TABLE2 => 'CachedGroupMembers',
1495 FIELD2 => 'GroupId',
1497 $watched_queues->Limit(
1498 ALIAS => $queues_alias,
1499 FIELD => 'MemberId',
1500 VALUE => $self->PrincipalId,
1502 $watched_queues->Limit(
1503 ALIAS => $queues_alias,
1504 FIELD => 'Disabled',
1509 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1511 return $watched_queues;
1521 TransactionType => 'Set',
1522 RecordTransaction => 1,
1526 # Nobody is allowed to futz with RT_System or Nobody
1528 if ( ($self->Id == RT->SystemUser->Id ) ||
1529 ($self->Id == RT->Nobody->Id)) {
1530 return ( 0, $self->loc("Can not modify system users") );
1532 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1533 return ( 0, $self->loc("Permission Denied") );
1536 my $Old = $self->SUPER::_Value("$args{'Field'}");
1538 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1539 Value => $args{'Value'} );
1541 #If we can't actually set the field to the value, don't record
1542 # a transaction. instead, get out of here.
1543 if ( $ret == 0 ) { return ( 0, $msg ); }
1545 if ( $args{'RecordTransaction'} == 1 ) {
1546 if ($args{'Field'} eq "Password") {
1547 $args{'Value'} = $Old = '********';
1549 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1550 Type => $args{'TransactionType'},
1551 Field => $args{'Field'},
1552 NewValue => $args{'Value'},
1554 TimeTaken => $args{'TimeTaken'},
1556 return ( $Trans, scalar $TransObj->BriefDescription );
1558 return ( $ret, $msg );
1564 Takes the name of a table column.
1565 Returns its value as a string, if the user passes an ACL check
1574 # Defer to the abstraction above to know if the field can be read
1575 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1581 Return the friendly name
1587 return $self->RealName if defined($self->RealName);
1588 return $self->Name if defined($self->Name);
1594 Returns the preferred key of the user. If none is set, then this will query
1595 GPG and set the preferred key to the maximally trusted key found (and then
1596 return it). Returns C<undef> if no preferred key can be found.
1603 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1605 if ( ($self->CurrentUser->Id != $self->Id ) &&
1606 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1612 my $prefkey = $self->FirstAttribute('PreferredKey');
1613 return $prefkey->Content if $prefkey;
1615 # we don't have a preferred key for this user, so now we must query GPG
1616 require RT::Crypt::GnuPG;
1617 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1618 return undef unless defined $res{'info'};
1619 my @keys = @{ $res{'info'} };
1620 return undef if @keys == 0;
1623 $prefkey = $keys[0]->{'Fingerprint'};
1625 # prefer the maximally trusted key
1626 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1627 $prefkey = $keys[0]->{'Fingerprint'};
1630 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1638 #If the user wants to see their own values, let them.
1639 #If the user is an admin, let them.
1640 #Otherwwise, don't let them.
1642 if ( ($self->CurrentUser->Id != $self->Id ) &&
1643 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1647 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1648 return $key->Content;
1655 unless ($self->CurrentUserCanModify('PrivateKey')) {
1656 return (0, $self->loc("Permission Denied"));
1660 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1661 unless ( $status ) {
1662 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1663 return ($status, $self->loc("Couldn't unset private key"));
1665 return ($status, $self->loc("Unset private key"));
1668 # check that it's really private key
1670 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1671 return (0, $self->loc("No such key or it's not suitable for signing"))
1672 if $tmp{'exit_code'} || !$tmp{'info'};
1675 my ($status, $msg) = $self->SetAttribute(
1676 Name => 'PrivateKey',
1679 return ($status, $self->loc("Couldn't set private key"))
1681 return ($status, $self->loc("Set private key"));
1686 [ Name => 'Username' ],
1687 [ EmailAddress => 'Email' ],
1688 [ RealName => 'Name' ],
1689 [ Organization => 'Organization' ],
1693 =head2 Create PARAMHASH
1695 Create takes a hash of values and creates a row in the database:
1697 varchar(200) 'Name'.
1698 varbinary(256) 'Password'.
1699 varchar(16) 'AuthToken'.
1702 varchar(120) 'EmailAddress'.
1703 text 'FreeformContactInfo'.
1704 varchar(200) 'Organization'.
1705 varchar(120) 'RealName'.
1706 varchar(16) 'NickName'.
1708 varchar(16) 'EmailEncoding'.
1709 varchar(16) 'WebEncoding'.
1710 varchar(100) 'ExternalContactInfoId'.
1711 varchar(30) 'ContactInfoSystem'.
1712 varchar(100) 'ExternalAuthId'.
1713 varchar(30) 'AuthSystem'.
1714 varchar(16) 'Gecos'.
1715 varchar(30) 'HomePhone'.
1716 varchar(30) 'WorkPhone'.
1717 varchar(30) 'MobilePhone'.
1718 varchar(30) 'PagerPhone'.
1719 varchar(200) 'Address1'.
1720 varchar(200) 'Address2'.
1721 varchar(100) 'City'.
1722 varchar(100) 'State'.
1724 varchar(50) 'Country'.
1725 varchar(50) 'Timezone'.
1735 Returns the current value of id.
1736 (In the database, id is stored as int(11).)
1744 Returns the current value of Name.
1745 (In the database, Name is stored as varchar(200).)
1749 =head2 SetName VALUE
1753 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1754 (In the database, Name will be stored as a varchar(200).)
1762 Returns the current value of Password.
1763 (In the database, Password is stored as varchar(256).)
1767 =head2 SetPassword VALUE
1770 Set Password to VALUE.
1771 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1772 (In the database, Password will be stored as a varchar(256).)
1780 Returns the current value of AuthToken.
1781 (In the database, AuthToken is stored as varchar(16).)
1785 =head2 SetAuthToken VALUE
1788 Set AuthToken to VALUE.
1789 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1790 (In the database, AuthToken will be stored as a varchar(16).)
1798 Returns the current value of Comments.
1799 (In the database, Comments is stored as text.)
1803 =head2 SetComments VALUE
1806 Set Comments to VALUE.
1807 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1808 (In the database, Comments will be stored as a text.)
1816 Returns the current value of Signature.
1817 (In the database, Signature is stored as text.)
1821 =head2 SetSignature VALUE
1824 Set Signature to VALUE.
1825 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1826 (In the database, Signature will be stored as a text.)
1834 Returns the current value of EmailAddress.
1835 (In the database, EmailAddress is stored as varchar(120).)
1839 =head2 SetEmailAddress VALUE
1842 Set EmailAddress to VALUE.
1843 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1844 (In the database, EmailAddress will be stored as a varchar(120).)
1850 =head2 FreeformContactInfo
1852 Returns the current value of FreeformContactInfo.
1853 (In the database, FreeformContactInfo is stored as text.)
1857 =head2 SetFreeformContactInfo VALUE
1860 Set FreeformContactInfo to VALUE.
1861 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1862 (In the database, FreeformContactInfo will be stored as a text.)
1870 Returns the current value of Organization.
1871 (In the database, Organization is stored as varchar(200).)
1875 =head2 SetOrganization VALUE
1878 Set Organization to VALUE.
1879 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1880 (In the database, Organization will be stored as a varchar(200).)
1888 Returns the current value of RealName.
1889 (In the database, RealName is stored as varchar(120).)
1893 =head2 SetRealName VALUE
1896 Set RealName to VALUE.
1897 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1898 (In the database, RealName will be stored as a varchar(120).)
1906 Returns the current value of NickName.
1907 (In the database, NickName is stored as varchar(16).)
1911 =head2 SetNickName VALUE
1914 Set NickName to VALUE.
1915 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1916 (In the database, NickName will be stored as a varchar(16).)
1924 Returns the current value of Lang.
1925 (In the database, Lang is stored as varchar(16).)
1929 =head2 SetLang VALUE
1933 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1934 (In the database, Lang will be stored as a varchar(16).)
1940 =head2 EmailEncoding
1942 Returns the current value of EmailEncoding.
1943 (In the database, EmailEncoding is stored as varchar(16).)
1947 =head2 SetEmailEncoding VALUE
1950 Set EmailEncoding to VALUE.
1951 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1952 (In the database, EmailEncoding will be stored as a varchar(16).)
1960 Returns the current value of WebEncoding.
1961 (In the database, WebEncoding is stored as varchar(16).)
1965 =head2 SetWebEncoding VALUE
1968 Set WebEncoding to VALUE.
1969 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1970 (In the database, WebEncoding will be stored as a varchar(16).)
1976 =head2 ExternalContactInfoId
1978 Returns the current value of ExternalContactInfoId.
1979 (In the database, ExternalContactInfoId is stored as varchar(100).)
1983 =head2 SetExternalContactInfoId VALUE
1986 Set ExternalContactInfoId to VALUE.
1987 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1988 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
1994 =head2 ContactInfoSystem
1996 Returns the current value of ContactInfoSystem.
1997 (In the database, ContactInfoSystem is stored as varchar(30).)
2001 =head2 SetContactInfoSystem VALUE
2004 Set ContactInfoSystem to VALUE.
2005 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2006 (In the database, ContactInfoSystem will be stored as a varchar(30).)
2012 =head2 ExternalAuthId
2014 Returns the current value of ExternalAuthId.
2015 (In the database, ExternalAuthId is stored as varchar(100).)
2019 =head2 SetExternalAuthId VALUE
2022 Set ExternalAuthId to VALUE.
2023 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2024 (In the database, ExternalAuthId will be stored as a varchar(100).)
2032 Returns the current value of AuthSystem.
2033 (In the database, AuthSystem is stored as varchar(30).)
2037 =head2 SetAuthSystem VALUE
2040 Set AuthSystem to VALUE.
2041 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2042 (In the database, AuthSystem will be stored as a varchar(30).)
2050 Returns the current value of Gecos.
2051 (In the database, Gecos is stored as varchar(16).)
2055 =head2 SetGecos VALUE
2059 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2060 (In the database, Gecos will be stored as a varchar(16).)
2068 Returns the current value of HomePhone.
2069 (In the database, HomePhone is stored as varchar(30).)
2073 =head2 SetHomePhone VALUE
2076 Set HomePhone to VALUE.
2077 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2078 (In the database, HomePhone will be stored as a varchar(30).)
2086 Returns the current value of WorkPhone.
2087 (In the database, WorkPhone is stored as varchar(30).)
2091 =head2 SetWorkPhone VALUE
2094 Set WorkPhone to VALUE.
2095 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2096 (In the database, WorkPhone will be stored as a varchar(30).)
2104 Returns the current value of MobilePhone.
2105 (In the database, MobilePhone is stored as varchar(30).)
2109 =head2 SetMobilePhone VALUE
2112 Set MobilePhone to VALUE.
2113 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2114 (In the database, MobilePhone will be stored as a varchar(30).)
2122 Returns the current value of PagerPhone.
2123 (In the database, PagerPhone is stored as varchar(30).)
2127 =head2 SetPagerPhone VALUE
2130 Set PagerPhone to VALUE.
2131 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2132 (In the database, PagerPhone will be stored as a varchar(30).)
2140 Returns the current value of Address1.
2141 (In the database, Address1 is stored as varchar(200).)
2145 =head2 SetAddress1 VALUE
2148 Set Address1 to VALUE.
2149 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2150 (In the database, Address1 will be stored as a varchar(200).)
2158 Returns the current value of Address2.
2159 (In the database, Address2 is stored as varchar(200).)
2163 =head2 SetAddress2 VALUE
2166 Set Address2 to VALUE.
2167 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2168 (In the database, Address2 will be stored as a varchar(200).)
2176 Returns the current value of City.
2177 (In the database, City is stored as varchar(100).)
2181 =head2 SetCity VALUE
2185 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2186 (In the database, City will be stored as a varchar(100).)
2194 Returns the current value of State.
2195 (In the database, State is stored as varchar(100).)
2199 =head2 SetState VALUE
2203 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2204 (In the database, State will be stored as a varchar(100).)
2212 Returns the current value of Zip.
2213 (In the database, Zip is stored as varchar(16).)
2221 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2222 (In the database, Zip will be stored as a varchar(16).)
2230 Returns the current value of Country.
2231 (In the database, Country is stored as varchar(50).)
2235 =head2 SetCountry VALUE
2238 Set Country to VALUE.
2239 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2240 (In the database, Country will be stored as a varchar(50).)
2248 Returns the current value of Timezone.
2249 (In the database, Timezone is stored as varchar(50).)
2253 =head2 SetTimezone VALUE
2256 Set Timezone to VALUE.
2257 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2258 (In the database, Timezone will be stored as a varchar(50).)
2266 Returns the current value of PGPKey.
2267 (In the database, PGPKey is stored as text.)
2271 =head2 SetPGPKey VALUE
2274 Set PGPKey to VALUE.
2275 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2276 (In the database, PGPKey will be stored as a text.)
2284 Returns the current value of Creator.
2285 (In the database, Creator is stored as int(11).)
2293 Returns the current value of Created.
2294 (In the database, Created is stored as datetime.)
2300 =head2 LastUpdatedBy
2302 Returns the current value of LastUpdatedBy.
2303 (In the database, LastUpdatedBy is stored as int(11).)
2311 Returns the current value of LastUpdated.
2312 (In the database, LastUpdated is stored as datetime.)
2318 # much false laziness w/Ticket.pm. now with RT 4!
2320 MemberOf => { Base => 'MemberOf',
2321 Target => 'HasMember', },
2322 RefersTo => { Base => 'RefersTo',
2323 Target => 'ReferredToBy', },
2324 DependsOn => { Base => 'DependsOn',
2325 Target => 'DependedOnBy', },
2326 MergedInto => { Base => 'MergedInto',
2327 Target => 'MergedInto', },
2331 sub LINKDIRMAP { return \%LINKDIRMAP }
2336 Delete a link. takes a paramhash of Base, Target and Type.
2337 Either Base or Target must be null. The null value will
2338 be replaced with this ticket\'s id
2351 unless ( $args{'Target'} || $args{'Base'} ) {
2352 $RT::Logger->error("Base or Target must be specified\n");
2353 return ( 0, $self->loc('Either base or target must be specified') );
2358 $right++ if $self->CurrentUserHasRight('AdminUsers');
2359 if ( !$right && $RT::StrictLinkACL ) {
2360 return ( 0, $self->loc("Permission Denied") );
2363 # # If the other URI is an RT::Ticket, we want to make sure the user
2364 # # can modify it too...
2365 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2366 # return (0, $msg) unless $status;
2367 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2370 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2371 # ( $RT::StrictLinkACL && $right < 2 ) )
2373 # return ( 0, $self->loc("Permission Denied") );
2376 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2379 $RT::Logger->debug("Couldn't find that link\n");
2383 my ($direction, $remote_link);
2385 if ( $args{'Base'} ) {
2386 $remote_link = $args{'Base'};
2387 $direction = 'Target';
2389 elsif ( $args{'Target'} ) {
2390 $remote_link = $args{'Target'};
2394 if ( $args{'Silent'} ) {
2395 return ( $val, $Msg );
2398 my $remote_uri = RT::URI->new( $self->CurrentUser );
2399 $remote_uri->FromURI( $remote_link );
2401 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2402 Type => 'DeleteLink',
2403 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2404 OldValue => $remote_uri->URI || $remote_link,
2408 if ( $remote_uri->IsLocal ) {
2410 my $OtherObj = $remote_uri->Object;
2411 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2412 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2413 : $LINKDIRMAP{$args{'Type'}}->{Target},
2414 OldValue => $self->URI,
2415 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2419 return ( $Trans, $Msg );
2425 my %args = ( Target => '',
2431 unless ( $args{'Target'} || $args{'Base'} ) {
2432 $RT::Logger->error("Base or Target must be specified\n");
2433 return ( 0, $self->loc('Either base or target must be specified') );
2437 $right++ if $self->CurrentUserHasRight('AdminUsers');
2438 if ( !$right && $RT::StrictLinkACL ) {
2439 return ( 0, $self->loc("Permission Denied") );
2442 # # If the other URI is an RT::Ticket, we want to make sure the user
2443 # # can modify it too...
2444 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2445 # return (0, $msg) unless $status;
2446 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2449 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2450 # ( $RT::StrictLinkACL && $right < 2 ) )
2452 # return ( 0, $self->loc("Permission Denied") );
2455 return $self->_AddLink(%args);
2460 Private non-acled variant of AddLink so that links can be added during create.
2466 my %args = ( Target => '',
2472 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2473 return ($val, $msg) if !$val || $exist;
2475 my ($direction, $remote_link);
2476 if ( $args{'Target'} ) {
2477 $remote_link = $args{'Target'};
2478 $direction = 'Base';
2479 } elsif ( $args{'Base'} ) {
2480 $remote_link = $args{'Base'};
2481 $direction = 'Target';
2484 # Don't write the transaction if we're doing this on create
2485 if ( $args{'Silent'} ) {
2486 return ( $val, $msg );
2489 my $remote_uri = RT::URI->new( $self->CurrentUser );
2490 $remote_uri->FromURI( $remote_link );
2492 #Write the transaction
2493 my ( $Trans, $Msg, $TransObj ) =
2494 $self->_NewTransaction(Type => 'AddLink',
2495 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2496 NewValue => $remote_uri->URI || $remote_link,
2499 if ( $remote_uri->IsLocal ) {
2501 my $OtherObj = $remote_uri->Object;
2502 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2503 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2504 : $LINKDIRMAP{$args{'Type'}}->{Target},
2505 NewValue => $self->URI,
2506 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2509 return ( $val, $Msg );
2515 sub _CoreAccessible {
2519 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2521 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2523 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2525 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2527 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2529 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2531 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2532 FreeformContactInfo =>
2533 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2535 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2537 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2539 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2541 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2543 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2545 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2546 ExternalContactInfoId =>
2547 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2548 ContactInfoSystem =>
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 => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', 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 => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2557 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2559 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2561 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2563 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2565 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2567 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2569 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2571 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2573 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2575 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2577 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2579 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2581 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2583 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2585 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2587 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2592 RT::Base->_ImportOverlays();