1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
51 RT::User - RT User object
70 use base 'RT::Record';
83 use RT::Interface::Email;
85 use Text::Password::Pronounceable;
87 sub _OverlayAccessible {
90 Name => { public => 1, admin => 1 },
91 Password => { read => 0 },
92 EmailAddress => { public => 1 },
93 Organization => { public => 1, admin => 1 },
94 RealName => { public => 1 },
95 NickName => { public => 1 },
96 Lang => { public => 1 },
97 EmailEncoding => { public => 1 },
98 WebEncoding => { public => 1 },
99 ExternalContactInfoId => { public => 1, admin => 1 },
100 ContactInfoSystem => { public => 1, admin => 1 },
101 ExternalAuthId => { public => 1, admin => 1 },
102 AuthSystem => { public => 1, admin => 1 },
103 Gecos => { public => 1, admin => 1 },
104 PGPKey => { public => 1, admin => 1 },
111 =head2 Create { PARAMHASH }
124 _RecordTransaction => 1,
125 @_ # get the real argumentlist
128 # remove the value so it does not cripple SUPER::Create
129 my $record_transaction = delete $args{'_RecordTransaction'};
132 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
133 return ( 0, $self->loc('Permission Denied') );
137 unless ($self->CanonicalizeUserInfo(\%args)) {
138 return ( 0, $self->loc("Could not set user info") );
141 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
143 # if the user doesn't have a name defined, set it to the email address
144 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
148 my $privileged = delete $args{'Privileged'};
151 if ($args{'CryptedPassword'} ) {
152 $args{'Password'} = $args{'CryptedPassword'};
153 delete $args{'CryptedPassword'};
154 } elsif ( !$args{'Password'} ) {
155 $args{'Password'} = '*NO-PASSWORD*';
157 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
158 return ($ok, $msg) if !$ok;
160 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
163 #TODO Specify some sensible defaults.
165 unless ( $args{'Name'} ) {
166 return ( 0, $self->loc("Must specify 'Name' attribute") );
169 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
170 if (RT->SystemUser) { #This only works if RT::SystemUser has been defined
171 my $TempUser = RT::User->new(RT->SystemUser);
172 $TempUser->Load( $args{'Name'} );
173 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
175 my ($val, $message) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
176 return (0, $message) unless ( $val );
178 $RT::Logger->warning( "$self couldn't check for pre-existing users");
182 $RT::Handle->BeginTransaction();
183 # Groups deal with principal ids, rather than user ids.
184 # When creating this user, set up a principal Id for it.
185 my $principal = RT::Principal->new($self->CurrentUser);
186 my $principal_id = $principal->Create(PrincipalType => 'User',
187 Disabled => $args{'Disabled'},
189 # If we couldn't create a principal Id, get the fuck out.
190 unless ($principal_id) {
191 $RT::Handle->Rollback();
192 $RT::Logger->crit("Couldn't create a Principal on new user create.");
193 $RT::Logger->crit("Strange things are afoot at the circle K");
194 return ( 0, $self->loc('Could not create user') );
197 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
198 delete $args{'Disabled'};
200 $self->SUPER::Create(id => $principal_id , %args);
203 #If the create failed.
205 $RT::Handle->Rollback();
206 $RT::Logger->error("Could not create a new user - " .join('-', %args));
208 return ( 0, $self->loc('Could not create user') );
211 my $aclstash = RT::Group->new($self->CurrentUser);
212 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
215 $RT::Handle->Rollback();
216 $RT::Logger->crit("Couldn't stash the user in groupmembers");
217 return ( 0, $self->loc('Could not create user') );
221 my $everyone = RT::Group->new($self->CurrentUser);
222 $everyone->LoadSystemInternalGroup('Everyone');
223 unless ($everyone->id) {
224 $RT::Logger->crit("Could not load Everyone group on user creation.");
225 $RT::Handle->Rollback();
226 return ( 0, $self->loc('Could not create user') );
230 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
231 unless ($everyone_id) {
232 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
233 $RT::Logger->crit($everyone_msg);
234 $RT::Handle->Rollback();
235 return ( 0, $self->loc('Could not create user') );
239 my $access_class = RT::Group->new($self->CurrentUser);
241 $access_class->LoadSystemInternalGroup('Privileged');
243 $access_class->LoadSystemInternalGroup('Unprivileged');
246 unless ($access_class->id) {
247 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
248 $RT::Handle->Rollback();
249 return ( 0, $self->loc('Could not create user') );
253 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
256 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
257 $RT::Logger->crit($ac_msg);
258 $RT::Handle->Rollback();
259 return ( 0, $self->loc('Could not create user') );
263 if ( $record_transaction ) {
264 $self->_NewTransaction( Type => "Create" );
269 return ( $id, $self->loc('User created') );
272 =head2 ValidatePassword STRING
274 Returns either (0, "failure reason") or 1 depending on whether the given
279 sub ValidatePassword {
281 my $password = shift;
283 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
284 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
290 =head2 SetPrivileged BOOL
292 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
293 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
295 Returns a standard RT tuple of (val, msg);
305 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
306 return ( 0, $self->loc('Permission Denied') );
309 $self->_SetPrivileged($val);
315 my $priv = RT::Group->new($self->CurrentUser);
316 $priv->LoadSystemInternalGroup('Privileged');
318 $RT::Logger->crit("Could not find Privileged pseudogroup");
319 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
322 my $unpriv = RT::Group->new($self->CurrentUser);
323 $unpriv->LoadSystemInternalGroup('Unprivileged');
324 unless ($unpriv->Id) {
325 $RT::Logger->crit("Could not find unprivileged pseudogroup");
326 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
329 my $principal = $self->PrincipalId;
331 if ($priv->HasMember($principal)) {
332 #$RT::Logger->debug("That user is already privileged");
333 return (0,$self->loc("That user is already privileged"));
335 if ($unpriv->HasMember($principal)) {
336 $unpriv->_DeleteMember($principal);
338 # if we had layered transactions, life would be good
339 # sadly, we have to just go ahead, even if something
341 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
342 "unprivileged. something is drastically wrong.");
344 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
346 return (1, $self->loc("That user is now privileged"));
351 if ($unpriv->HasMember($principal)) {
352 #$RT::Logger->debug("That user is already unprivileged");
353 return (0,$self->loc("That user is already unprivileged"));
355 if ($priv->HasMember($principal)) {
356 $priv->_DeleteMember( $principal );
358 # if we had layered transactions, life would be good
359 # sadly, we have to just go ahead, even if something
361 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
362 "unprivileged. something is drastically wrong.");
364 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
366 return (1, $self->loc("That user is now unprivileged"));
375 Returns true if this user is privileged. Returns undef otherwise.
381 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
388 #create a user without validating _any_ data.
390 #To be used only on database init.
391 # We can't localize here because it's before we _have_ a loc framework
393 sub _BootstrapCreate {
397 $args{'Password'} = '*NO-PASSWORD*';
400 $RT::Handle->BeginTransaction();
402 # Groups deal with principal ids, rather than user ids.
403 # When creating this user, set up a principal Id for it.
404 my $principal = RT::Principal->new($self->CurrentUser);
405 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
406 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
408 # If we couldn't create a principal Id, get the fuck out.
409 unless ($principal_id) {
410 $RT::Handle->Rollback();
411 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
412 return ( 0, 'Could not create user' );
414 $self->SUPER::Create(id => $principal_id, %args);
416 #If the create failed.
418 $RT::Handle->Rollback();
419 return ( 0, 'Could not create user' ) ; #never loc this
422 my $aclstash = RT::Group->new($self->CurrentUser);
423 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
426 $RT::Handle->Rollback();
427 $RT::Logger->crit("Couldn't stash the user in groupmembers");
428 return ( 0, $self->loc('Could not create user') );
431 $RT::Handle->Commit();
433 return ( $id, 'User created' );
439 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
445 Load a user object from the database. Takes a single argument.
446 If the argument is numerical, load by the column 'id'. If a user
447 object or its subclass passed then loads the same user by id.
448 Otherwise, load by the "Name" column which is the user's textual
455 my $identifier = shift || return undef;
457 if ( $identifier !~ /\D/ ) {
458 return $self->SUPER::LoadById( $identifier );
459 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
460 return $self->SUPER::LoadById( $identifier->Id );
462 return $self->LoadByCol( "Name", $identifier );
468 Tries to load this user object from the database by the user's email address.
476 # Never load an empty address as an email address.
481 $address = $self->CanonicalizeEmailAddress($address);
483 #$RT::Logger->debug("Trying to load an email address: $address");
484 return $self->LoadByCol( "EmailAddress", $address );
487 =head2 LoadOrCreateByEmail ADDRESS
489 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
490 the provided email address and loads them. Address can be provided either as L<Email::Address> object
491 or string which is parsed using the module.
493 Returns a tuple of the user's id and a status message.
494 0 will be returned in place of the user's id in case of failure.
498 sub LoadOrCreateByEmail {
502 my ($message, $name);
503 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
504 ($email, $name) = ($email->address, $email->phrase);
506 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
509 $self->LoadByEmail( $email );
510 $self->Load( $email ) unless $self->Id;
511 $message = $self->loc('User loaded');
513 unless( $self->Id ) {
515 ($val, $message) = $self->Create(
517 EmailAddress => $email,
520 Comments => 'Autocreated when added as a watcher',
523 # Deal with the race condition of two account creations at once
524 $self->LoadByEmail( $email );
525 unless ( $self->Id ) {
527 $self->LoadByEmail( $email );
530 $RT::Logger->error("Recovered from creation failure due to race condition");
531 $message = $self->loc("User loaded");
533 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
537 return (0, $message) unless $self->id;
538 return ($self->Id, $message);
541 =head2 ValidateEmailAddress ADDRESS
543 Returns true if the email address entered is not in use by another user or is
544 undef or ''. Returns false if it's in use.
548 sub ValidateEmailAddress {
552 # if the email address is null, it's always valid
553 return (1) if ( !$Value || $Value eq "" );
555 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
556 # We only allow one valid email address
557 my @addresses = Email::Address->parse($Value);
558 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
562 my $TempUser = RT::User->new(RT->SystemUser);
563 $TempUser->LoadByEmail($Value);
565 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
566 { # if we found a user with that address
567 # it's invalid to set this user's address to it
568 return ( 0, $self->loc('Email address in use') );
569 } else { #it's a valid email address
574 =head2 SetEmailAddress
576 Check to make sure someone else isn't using this email address already
577 so that a better email address can be returned
581 sub SetEmailAddress {
584 $Value = '' unless defined $Value;
586 my ($val, $message) = $self->ValidateEmailAddress( $Value );
588 return $self->_Set( Field => 'EmailAddress', Value => $Value );
590 return ( 0, $message )
595 =head2 EmailFrequency
597 Takes optional Ticket argument in paramhash. Returns 'no email',
598 'squelched', 'daily', 'weekly' or empty string depending on
603 =item 'no email' - user has no email, so can not recieve notifications.
605 =item 'squelched' - returned only when Ticket argument is provided and
606 notifications to the user has been supressed for this ticket.
608 =item 'daily' - retruned when user recieve daily messages digest instead
609 of immediate delivery.
611 =item 'weekly' - previous, but weekly.
613 =item empty string returned otherwise.
625 return '' unless $self->id && $self->id != RT->Nobody->id
626 && $self->id != RT->SystemUser->id;
627 return 'no email address' unless my $email = $self->EmailAddress;
628 return 'email disabled for ticket' if $args{'Ticket'} &&
629 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
630 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
631 return 'daily' if $frequency =~ /daily/i;
632 return 'weekly' if $frequency =~ /weekly/i;
636 =head2 CanonicalizeEmailAddress ADDRESS
638 CanonicalizeEmailAddress converts email addresses into canonical form.
639 it takes one email address in and returns the proper canonical
640 form. You can dump whatever your proper local config is in here. Note
641 that it may be called as a static method; in this case the first argument
642 is class name not an object.
646 sub CanonicalizeEmailAddress {
649 # Example: the following rule would treat all email
650 # coming from a subdomain as coming from second level domain
652 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
653 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
655 $email =~ s/$match/$replace/gi;
660 =head2 CanonicalizeUserInfo HASH of ARGS
662 CanonicalizeUserInfo can convert all User->Create options.
663 it takes a hashref of all the params sent to User->Create and
664 returns that same hash, by default nothing is done.
666 This function is intended to allow users to have their info looked up via
667 an outside source and modified upon creation.
671 sub CanonicalizeUserInfo {
680 =head2 Password and authentication related functions
682 =head3 SetRandomPassword
684 Takes no arguments. Returns a status code and a new password or an error message.
685 If the status is 1, the second value returned is the new password.
686 If the status is anything else, the new value returned is the error code.
690 sub SetRandomPassword {
693 unless ( $self->CurrentUserCanModify('Password') ) {
694 return ( 0, $self->loc("Permission Denied") );
698 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
699 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
701 my $pass = $self->GenerateRandomPassword( $min, $max) ;
703 # If we have "notify user on
705 my ( $val, $msg ) = $self->SetPassword($pass);
707 #If we got an error return the error.
708 return ( 0, $msg ) unless ($val);
710 #Otherwise, we changed the password, lets return it.
717 Returns status, [ERROR or new password]. Resets this user's password to
718 a randomly generated pronouncable password and emails them, using a
719 global template called "PasswordChange".
721 This function is currently unused in the UI, but available for local scripts.
728 unless ( $self->CurrentUserCanModify('Password') ) {
729 return ( 0, $self->loc("Permission Denied") );
731 my ( $status, $pass ) = $self->SetRandomPassword();
734 return ( 0, "$pass" );
737 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
738 To => $self->EmailAddress,
739 Template => 'PasswordChange',
741 NewPassword => $pass,
746 return ( 1, $self->loc('New password notification sent') );
748 return ( 0, $self->loc('Notification could not be sent') );
753 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
755 Returns a random password between MIN_LEN and MAX_LEN characters long.
759 sub GenerateRandomPassword {
760 my $self = shift; # just to drop it
761 return Text::Password::Pronounceable->generate(@_);
764 sub SafeSetPassword {
769 Confirmation => undef,
772 return (1) unless defined $args{'New'} && length $args{'New'};
774 my %cond = $self->CurrentUserRequireToSetPassword;
776 unless ( $cond{'CanSet'} ) {
777 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
781 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
782 if ( defined $args{'Current'} && length $args{'Current'} ) {
783 $error = $self->loc("Please enter your current password correctly.");
785 $error = $self->loc("Please enter your current password.");
787 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
788 $error = $self->loc("Passwords do not match.");
792 $error .= ' '. $self->loc('Password has not been set.');
796 return $self->SetPassword( $args{'New'} );
801 Takes a string. Checks the string's length and sets this user's password
808 my $password = shift;
810 unless ( $self->CurrentUserCanModify('Password') ) {
811 return ( 0, $self->loc('Password: Permission Denied') );
815 return ( 0, $self->loc("No password set") );
817 my ($val, $msg) = $self->ValidatePassword($password);
818 return ($val, $msg) if !$val;
820 my $new = !$self->HasPassword;
821 $password = $self->_GeneratePassword($password);
823 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
825 return ( 1, $self->loc("Password set") ) if $new;
826 return ( 1, $self->loc("Password changed") );
828 return ( $val, $msg );
834 sub _GeneratePassword_sha512 {
836 my ($password, $salt) = @_;
838 # Generate a 16-character base64 salt
841 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
845 my $sha = Digest::SHA->new(512);
847 $sha->add(encode_utf8($password));
848 return join("!", "", "sha512", $salt, $sha->b64digest);
851 =head3 _GeneratePassword PASSWORD [, SALT]
853 Returns a string to store in the database. This string takes the form:
857 By default, the method is currently C<sha512>.
861 sub _GeneratePassword {
863 return $self->_GeneratePassword_sha512(@_);
868 Returns true if the user has a valid password, otherwise returns false.
874 my $pwd = $self->__Value('Password');
875 return undef if !defined $pwd
877 || $pwd eq '*NO-PASSWORD*';
883 Returns true if the passed in value is this user's password.
884 Returns undef otherwise.
892 #TODO there isn't any apparent way to legitimately ACL this
894 # RT does not allow null passwords
895 if ( ( !defined($value) ) or ( $value eq '' ) ) {
899 if ( $self->PrincipalObj->Disabled ) {
901 "Disabled user " . $self->Name . " tried to log in" );
905 unless ($self->HasPassword) {
909 my $stored = $self->__Value('Password');
910 if ($stored =~ /^!/) {
911 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
912 my (undef, $method, $salt, undef) = split /!/, $stored;
913 if ($method eq "sha512") {
914 return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
916 $RT::Logger->warn("Unknown hash method $method");
919 } elsif (length $stored == 40) {
920 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
921 my $hash = MIME::Base64::decode_base64($stored);
922 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
923 my $salt = substr($hash, 0, 4, "");
924 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
925 } elsif (length $stored == 32) {
927 return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
928 } elsif (length $stored == 22) {
929 # Base64 nonsalted-md5
930 return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
931 } elsif (length $stored == 13) {
933 return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
935 $RT::Logger->warn("Unknown password form");
939 # We got here by validating successfully, but with a legacy
940 # password form. Update to the most recent form.
941 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
942 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
946 sub CurrentUserRequireToSetPassword {
955 if ( RT->Config->Get('WebExternalAuth')
956 && !RT->Config->Get('WebFallbackToInternalAuth')
959 $res{'Reason'} = $self->loc("External authentication enabled.");
960 } elsif ( !$self->CurrentUser->HasPassword ) {
961 if ( $self->CurrentUser->id == ($self->id||0) ) {
962 # don't require current password if user has no
963 $res{'RequireCurrent'} = 0;
966 $res{'Reason'} = $self->loc("Your password is not set.");
975 Returns an authentication string associated with the user. This
976 string can be used to generate passwordless URLs to integrate
977 RT with services and programms like callendar managers, rss
984 my $secret = $self->_Value( AuthToken => @_ );
985 return $secret if $secret;
987 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
989 my $tmp = RT::User->new( RT->SystemUser );
990 $tmp->Load( $self->id );
991 my ($status, $msg) = $tmp->SetAuthToken( $secret );
993 $RT::Logger->error( "Couldn't set auth token: $msg" );
999 =head3 GenerateAuthToken
1001 Generate a random authentication string for the user.
1005 sub GenerateAuthToken {
1007 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1008 return $self->SetAuthToken( $token );
1011 =head3 GenerateAuthString
1013 Takes a string and returns back a hex hash string. Later you can use
1014 this pair to make sure it's generated by this user using L</ValidateAuthString>
1018 sub GenerateAuthString {
1020 my $protect = shift;
1022 my $str = $self->AuthToken . $protect;
1025 return substr(Digest::MD5::md5_hex($str),0,16);
1028 =head3 ValidateAuthString
1030 Takes auth string and protected string. Returns true is protected string
1031 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1035 sub ValidateAuthString {
1037 my $auth_string = shift;
1038 my $protected = shift;
1040 my $str = $self->AuthToken . $protected;
1041 utf8::encode( $str );
1043 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1048 Toggles the user's disabled flag.
1050 set, all password checks for this user will fail. All ACL checks for this
1051 user will fail. The user will appear in no user listings.
1058 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1059 return (0, $self->loc('Permission Denied'));
1062 $RT::Handle->BeginTransaction();
1063 my $set_err = $self->PrincipalObj->SetDisabled($val);
1065 $RT::Handle->Rollback();
1066 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1069 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1071 $RT::Handle->Commit();
1074 return (1, $self->loc("User disabled"));
1076 return (1, $self->loc("User enabled"));
1083 Returns true if user is disabled or false otherwise
1089 return $self->PrincipalObj->Disabled(@_);
1094 Returns the principal object for this user. returns an empty RT::Principal
1095 if there's no principal object matching this user.
1096 The response is cached. PrincipalObj should never ever change.
1103 unless ( $self->id ) {
1104 $RT::Logger->error("Couldn't get principal for an empty user");
1108 if ( !$self->{_principal_obj} ) {
1110 my $obj = RT::Principal->new( $self->CurrentUser );
1111 $obj->LoadById( $self->id );
1113 $RT::Logger->crit( 'No principal for user #' . $self->id );
1115 } elsif ( $obj->PrincipalType ne 'User' ) {
1116 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1119 $self->{_principal_obj} = $obj;
1121 return $self->{_principal_obj};
1127 Returns this user's PrincipalId
1136 =head2 HasGroupRight
1138 Takes a paramhash which can contain
1140 GroupObj => RT::Group or Group => integer
1144 Returns 1 if this user has the right specified in the paramhash for the Group
1147 Returns undef if they don't.
1161 if ( defined $args{'Group'} ) {
1162 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1163 $args{'GroupObj'}->Load( $args{'Group'} );
1166 # Validate and load up the GroupId
1167 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1171 # Figure out whether a user has the right we're asking about.
1172 my $retval = $self->HasRight(
1173 Object => $args{'GroupObj'},
1174 Right => $args{'Right'},
1182 Returns a group collection object containing the groups of which this
1189 my $groups = RT::Groups->new($self->CurrentUser);
1190 $groups->LimitToUserDefinedGroups;
1191 $groups->WithMember(
1192 PrincipalId => $self->Id,
1200 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1206 return $self->PrincipalObj->HasRight(@_);
1209 =head2 CurrentUserCanModify RIGHT
1211 If the user has rights for this object, either because
1212 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1213 admin right) 'ModifySelf', return 1. otherwise, return undef.
1217 sub CurrentUserCanModify {
1221 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1225 #If the field is marked as an "administrators only" field,
1226 # don't let the user touch it.
1227 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1231 #If the current user is trying to modify themselves
1232 elsif ( ( $self->id == $self->CurrentUser->id )
1233 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1238 #If we don't have a good reason to grant them rights to modify
1246 =head2 CurrentUserHasRight
1248 Takes a single argument. returns 1 if $Self->CurrentUser
1249 has the requested right. returns undef otherwise
1253 sub CurrentUserHasRight {
1257 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1263 $name = ref($name).'-'.$name->Id;
1266 return 'Pref-'.$name;
1269 =head2 Preferences NAME/OBJ DEFAULT
1271 Obtain user preferences associated with given object or name.
1272 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1273 override the entries with user preferences.
1279 my $name = _PrefName (shift);
1280 my $default = shift;
1282 my $attr = RT::Attribute->new( $self->CurrentUser );
1283 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1285 my $content = $attr->Id ? $attr->Content : undef;
1286 unless ( ref $content eq 'HASH' ) {
1287 return defined $content ? $content : $default;
1290 if (ref $default eq 'HASH') {
1291 for (keys %$default) {
1292 exists $content->{$_} or $content->{$_} = $default->{$_};
1294 } elsif (defined $default) {
1295 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1300 =head2 SetPreferences NAME/OBJ VALUE
1302 Set user preferences associated with given object or name.
1306 sub SetPreferences {
1308 my $name = _PrefName( shift );
1311 return (0, $self->loc("No permission to set preferences"))
1312 unless $self->CurrentUserCanModify('Preferences');
1314 my $attr = RT::Attribute->new( $self->CurrentUser );
1315 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1317 my ($ok, $msg) = $attr->SetContent( $value );
1318 return (1, "No updates made")
1319 if $msg eq "That is already the current value";
1322 return $self->AddAttribute( Name => $name, Content => $value );
1328 Returns a list of valid stylesheets take from preferences.
1335 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1338 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1340 for my $css_path (@css_paths) {
1341 if (-d "$css_path/$style") {
1346 # Fall back to the system stylesheet.
1347 return RT->Config->Get('WebDefaultStylesheet');
1350 =head2 WatchedQueues ROLE_LIST
1352 Returns a RT::Queues object containing every queue watched by the user.
1354 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1356 $user->WatchedQueues('Cc', 'AdminCc');
1363 my @roles = @_ || ('Cc', 'AdminCc');
1365 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1367 my $watched_queues = RT::Queues->new($self->CurrentUser);
1369 my $group_alias = $watched_queues->Join(
1373 FIELD2 => 'Instance',
1376 $watched_queues->Limit(
1377 ALIAS => $group_alias,
1379 VALUE => 'RT::Queue-Role',
1380 ENTRYAGGREGATOR => 'AND',
1382 if (grep { $_ eq 'Cc' } @roles) {
1383 $watched_queues->Limit(
1384 SUBCLAUSE => 'LimitToWatchers',
1385 ALIAS => $group_alias,
1388 ENTRYAGGREGATOR => 'OR',
1391 if (grep { $_ eq 'AdminCc' } @roles) {
1392 $watched_queues->Limit(
1393 SUBCLAUSE => 'LimitToWatchers',
1394 ALIAS => $group_alias,
1397 ENTRYAGGREGATOR => 'OR',
1401 my $queues_alias = $watched_queues->Join(
1402 ALIAS1 => $group_alias,
1404 TABLE2 => 'CachedGroupMembers',
1405 FIELD2 => 'GroupId',
1407 $watched_queues->Limit(
1408 ALIAS => $queues_alias,
1409 FIELD => 'MemberId',
1410 VALUE => $self->PrincipalId,
1413 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1415 return $watched_queues;
1425 TransactionType => 'Set',
1426 RecordTransaction => 1,
1430 # Nobody is allowed to futz with RT_System or Nobody
1432 if ( ($self->Id == RT->SystemUser->Id ) ||
1433 ($self->Id == RT->Nobody->Id)) {
1434 return ( 0, $self->loc("Can not modify system users") );
1436 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1437 return ( 0, $self->loc("Permission Denied") );
1440 my $Old = $self->SUPER::_Value("$args{'Field'}");
1442 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1443 Value => $args{'Value'} );
1445 #If we can't actually set the field to the value, don't record
1446 # a transaction. instead, get out of here.
1447 if ( $ret == 0 ) { return ( 0, $msg ); }
1449 if ( $args{'RecordTransaction'} == 1 ) {
1451 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1452 Type => $args{'TransactionType'},
1453 Field => $args{'Field'},
1454 NewValue => $args{'Value'},
1456 TimeTaken => $args{'TimeTaken'},
1458 return ( $Trans, scalar $TransObj->BriefDescription );
1460 return ( $ret, $msg );
1466 Takes the name of a table column.
1467 Returns its value as a string, if the user passes an ACL check
1476 #if the field is public, return it.
1477 if ( $self->_Accessible( $field, 'public' ) ) {
1478 return ( $self->SUPER::_Value($field) );
1482 #If the user wants to see their own values, let them
1483 # TODO figure ouyt a better way to deal with this
1484 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
1485 return ( $self->SUPER::_Value($field) );
1488 #If the user has the admin users right, return the field
1489 elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1490 return ( $self->SUPER::_Value($field) );
1499 Return the friendly name
1505 return $self->RealName if defined($self->RealName);
1506 return $self->Name if defined($self->Name);
1512 Returns the preferred key of the user. If none is set, then this will query
1513 GPG and set the preferred key to the maximally trusted key found (and then
1514 return it). Returns C<undef> if no preferred key can be found.
1521 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1523 if ( ($self->CurrentUser->Id != $self->Id ) &&
1524 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1530 my $prefkey = $self->FirstAttribute('PreferredKey');
1531 return $prefkey->Content if $prefkey;
1533 # we don't have a preferred key for this user, so now we must query GPG
1534 require RT::Crypt::GnuPG;
1535 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1536 return undef unless defined $res{'info'};
1537 my @keys = @{ $res{'info'} };
1538 return undef if @keys == 0;
1541 $prefkey = $keys[0]->{'Fingerprint'};
1543 # prefer the maximally trusted key
1544 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1545 $prefkey = $keys[0]->{'Fingerprint'};
1548 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1556 #If the user wants to see their own values, let them.
1557 #If the user is an admin, let them.
1558 #Otherwwise, don't let them.
1560 if ( ($self->CurrentUser->Id != $self->Id ) &&
1561 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1565 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1566 return $key->Content;
1573 unless ($self->CurrentUserCanModify('PrivateKey')) {
1574 return (0, $self->loc("Permission Denied"));
1578 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1579 unless ( $status ) {
1580 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1581 return ($status, $self->loc("Couldn't unset private key"));
1583 return ($status, $self->loc("Unset private key"));
1586 # check that it's really private key
1588 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1589 return (0, $self->loc("No such key or it's not suitable for signing"))
1590 if $tmp{'exit_code'} || !$tmp{'info'};
1593 my ($status, $msg) = $self->SetAttribute(
1594 Name => 'PrivateKey',
1597 return ($status, $self->loc("Couldn't set private key"))
1599 return ($status, $self->loc("Set private key"));
1604 [ Name => 'Username' ],
1605 [ EmailAddress => 'Email' ],
1606 [ RealName => 'Name' ],
1607 [ Organization => 'Organization' ],
1611 =head2 Create PARAMHASH
1613 Create takes a hash of values and creates a row in the database:
1615 varchar(200) 'Name'.
1616 varbinary(256) 'Password'.
1617 varchar(16) 'AuthToken'.
1620 varchar(120) 'EmailAddress'.
1621 text 'FreeformContactInfo'.
1622 varchar(200) 'Organization'.
1623 varchar(120) 'RealName'.
1624 varchar(16) 'NickName'.
1626 varchar(16) 'EmailEncoding'.
1627 varchar(16) 'WebEncoding'.
1628 varchar(100) 'ExternalContactInfoId'.
1629 varchar(30) 'ContactInfoSystem'.
1630 varchar(100) 'ExternalAuthId'.
1631 varchar(30) 'AuthSystem'.
1632 varchar(16) 'Gecos'.
1633 varchar(30) 'HomePhone'.
1634 varchar(30) 'WorkPhone'.
1635 varchar(30) 'MobilePhone'.
1636 varchar(30) 'PagerPhone'.
1637 varchar(200) 'Address1'.
1638 varchar(200) 'Address2'.
1639 varchar(100) 'City'.
1640 varchar(100) 'State'.
1642 varchar(50) 'Country'.
1643 varchar(50) 'Timezone'.
1653 Returns the current value of id.
1654 (In the database, id is stored as int(11).)
1662 Returns the current value of Name.
1663 (In the database, Name is stored as varchar(200).)
1667 =head2 SetName VALUE
1671 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1672 (In the database, Name will be stored as a varchar(200).)
1680 Returns the current value of Password.
1681 (In the database, Password is stored as varchar(256).)
1685 =head2 SetPassword VALUE
1688 Set Password to VALUE.
1689 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1690 (In the database, Password will be stored as a varchar(256).)
1698 Returns the current value of AuthToken.
1699 (In the database, AuthToken is stored as varchar(16).)
1703 =head2 SetAuthToken VALUE
1706 Set AuthToken to VALUE.
1707 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1708 (In the database, AuthToken will be stored as a varchar(16).)
1716 Returns the current value of Comments.
1717 (In the database, Comments is stored as text.)
1721 =head2 SetComments VALUE
1724 Set Comments to VALUE.
1725 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1726 (In the database, Comments will be stored as a text.)
1734 Returns the current value of Signature.
1735 (In the database, Signature is stored as text.)
1739 =head2 SetSignature VALUE
1742 Set Signature to VALUE.
1743 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1744 (In the database, Signature will be stored as a text.)
1752 Returns the current value of EmailAddress.
1753 (In the database, EmailAddress is stored as varchar(120).)
1757 =head2 SetEmailAddress VALUE
1760 Set EmailAddress to VALUE.
1761 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1762 (In the database, EmailAddress will be stored as a varchar(120).)
1768 =head2 FreeformContactInfo
1770 Returns the current value of FreeformContactInfo.
1771 (In the database, FreeformContactInfo is stored as text.)
1775 =head2 SetFreeformContactInfo VALUE
1778 Set FreeformContactInfo to VALUE.
1779 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1780 (In the database, FreeformContactInfo will be stored as a text.)
1788 Returns the current value of Organization.
1789 (In the database, Organization is stored as varchar(200).)
1793 =head2 SetOrganization VALUE
1796 Set Organization to VALUE.
1797 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1798 (In the database, Organization will be stored as a varchar(200).)
1806 Returns the current value of RealName.
1807 (In the database, RealName is stored as varchar(120).)
1811 =head2 SetRealName VALUE
1814 Set RealName to VALUE.
1815 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1816 (In the database, RealName will be stored as a varchar(120).)
1824 Returns the current value of NickName.
1825 (In the database, NickName is stored as varchar(16).)
1829 =head2 SetNickName VALUE
1832 Set NickName to VALUE.
1833 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1834 (In the database, NickName will be stored as a varchar(16).)
1842 Returns the current value of Lang.
1843 (In the database, Lang is stored as varchar(16).)
1847 =head2 SetLang VALUE
1851 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1852 (In the database, Lang will be stored as a varchar(16).)
1858 =head2 EmailEncoding
1860 Returns the current value of EmailEncoding.
1861 (In the database, EmailEncoding is stored as varchar(16).)
1865 =head2 SetEmailEncoding VALUE
1868 Set EmailEncoding to VALUE.
1869 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1870 (In the database, EmailEncoding will be stored as a varchar(16).)
1878 Returns the current value of WebEncoding.
1879 (In the database, WebEncoding is stored as varchar(16).)
1883 =head2 SetWebEncoding VALUE
1886 Set WebEncoding to VALUE.
1887 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1888 (In the database, WebEncoding will be stored as a varchar(16).)
1894 =head2 ExternalContactInfoId
1896 Returns the current value of ExternalContactInfoId.
1897 (In the database, ExternalContactInfoId is stored as varchar(100).)
1901 =head2 SetExternalContactInfoId VALUE
1904 Set ExternalContactInfoId to VALUE.
1905 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1906 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
1912 =head2 ContactInfoSystem
1914 Returns the current value of ContactInfoSystem.
1915 (In the database, ContactInfoSystem is stored as varchar(30).)
1919 =head2 SetContactInfoSystem VALUE
1922 Set ContactInfoSystem to VALUE.
1923 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1924 (In the database, ContactInfoSystem will be stored as a varchar(30).)
1930 =head2 ExternalAuthId
1932 Returns the current value of ExternalAuthId.
1933 (In the database, ExternalAuthId is stored as varchar(100).)
1937 =head2 SetExternalAuthId VALUE
1940 Set ExternalAuthId to VALUE.
1941 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1942 (In the database, ExternalAuthId will be stored as a varchar(100).)
1950 Returns the current value of AuthSystem.
1951 (In the database, AuthSystem is stored as varchar(30).)
1955 =head2 SetAuthSystem VALUE
1958 Set AuthSystem to VALUE.
1959 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1960 (In the database, AuthSystem will be stored as a varchar(30).)
1968 Returns the current value of Gecos.
1969 (In the database, Gecos is stored as varchar(16).)
1973 =head2 SetGecos VALUE
1977 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1978 (In the database, Gecos will be stored as a varchar(16).)
1986 Returns the current value of HomePhone.
1987 (In the database, HomePhone is stored as varchar(30).)
1991 =head2 SetHomePhone VALUE
1994 Set HomePhone to VALUE.
1995 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1996 (In the database, HomePhone will be stored as a varchar(30).)
2004 Returns the current value of WorkPhone.
2005 (In the database, WorkPhone is stored as varchar(30).)
2009 =head2 SetWorkPhone VALUE
2012 Set WorkPhone to VALUE.
2013 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2014 (In the database, WorkPhone will be stored as a varchar(30).)
2022 Returns the current value of MobilePhone.
2023 (In the database, MobilePhone is stored as varchar(30).)
2027 =head2 SetMobilePhone VALUE
2030 Set MobilePhone to VALUE.
2031 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2032 (In the database, MobilePhone will be stored as a varchar(30).)
2040 Returns the current value of PagerPhone.
2041 (In the database, PagerPhone is stored as varchar(30).)
2045 =head2 SetPagerPhone VALUE
2048 Set PagerPhone to VALUE.
2049 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2050 (In the database, PagerPhone will be stored as a varchar(30).)
2058 Returns the current value of Address1.
2059 (In the database, Address1 is stored as varchar(200).)
2063 =head2 SetAddress1 VALUE
2066 Set Address1 to VALUE.
2067 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2068 (In the database, Address1 will be stored as a varchar(200).)
2076 Returns the current value of Address2.
2077 (In the database, Address2 is stored as varchar(200).)
2081 =head2 SetAddress2 VALUE
2084 Set Address2 to VALUE.
2085 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2086 (In the database, Address2 will be stored as a varchar(200).)
2094 Returns the current value of City.
2095 (In the database, City is stored as varchar(100).)
2099 =head2 SetCity VALUE
2103 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2104 (In the database, City will be stored as a varchar(100).)
2112 Returns the current value of State.
2113 (In the database, State is stored as varchar(100).)
2117 =head2 SetState VALUE
2121 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2122 (In the database, State will be stored as a varchar(100).)
2130 Returns the current value of Zip.
2131 (In the database, Zip is stored as varchar(16).)
2139 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2140 (In the database, Zip will be stored as a varchar(16).)
2148 Returns the current value of Country.
2149 (In the database, Country is stored as varchar(50).)
2153 =head2 SetCountry VALUE
2156 Set Country to VALUE.
2157 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2158 (In the database, Country will be stored as a varchar(50).)
2166 Returns the current value of Timezone.
2167 (In the database, Timezone is stored as varchar(50).)
2171 =head2 SetTimezone VALUE
2174 Set Timezone to VALUE.
2175 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2176 (In the database, Timezone will be stored as a varchar(50).)
2184 Returns the current value of PGPKey.
2185 (In the database, PGPKey is stored as text.)
2189 =head2 SetPGPKey VALUE
2192 Set PGPKey to VALUE.
2193 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2194 (In the database, PGPKey will be stored as a text.)
2202 Returns the current value of Creator.
2203 (In the database, Creator is stored as int(11).)
2211 Returns the current value of Created.
2212 (In the database, Created is stored as datetime.)
2218 =head2 LastUpdatedBy
2220 Returns the current value of LastUpdatedBy.
2221 (In the database, LastUpdatedBy is stored as int(11).)
2229 Returns the current value of LastUpdated.
2230 (In the database, LastUpdated is stored as datetime.)
2236 # much false laziness w/Ticket.pm. now with RT 4!
2238 MemberOf => { Base => 'MemberOf',
2239 Target => 'HasMember', },
2240 RefersTo => { Base => 'RefersTo',
2241 Target => 'ReferredToBy', },
2242 DependsOn => { Base => 'DependsOn',
2243 Target => 'DependedOnBy', },
2244 MergedInto => { Base => 'MergedInto',
2245 Target => 'MergedInto', },
2249 sub LINKDIRMAP { return \%LINKDIRMAP }
2254 Delete a link. takes a paramhash of Base, Target and Type.
2255 Either Base or Target must be null. The null value will
2256 be replaced with this ticket\'s id
2269 unless ( $args{'Target'} || $args{'Base'} ) {
2270 $RT::Logger->error("Base or Target must be specified\n");
2271 return ( 0, $self->loc('Either base or target must be specified') );
2276 $right++ if $self->CurrentUserHasRight('AdminUsers');
2277 if ( !$right && $RT::StrictLinkACL ) {
2278 return ( 0, $self->loc("Permission Denied") );
2281 # # If the other URI is an RT::Ticket, we want to make sure the user
2282 # # can modify it too...
2283 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2284 # return (0, $msg) unless $status;
2285 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2288 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2289 # ( $RT::StrictLinkACL && $right < 2 ) )
2291 # return ( 0, $self->loc("Permission Denied") );
2294 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2297 $RT::Logger->debug("Couldn't find that link\n");
2301 my ($direction, $remote_link);
2303 if ( $args{'Base'} ) {
2304 $remote_link = $args{'Base'};
2305 $direction = 'Target';
2307 elsif ( $args{'Target'} ) {
2308 $remote_link = $args{'Target'};
2312 if ( $args{'Silent'} ) {
2313 return ( $val, $Msg );
2316 my $remote_uri = RT::URI->new( $self->CurrentUser );
2317 $remote_uri->FromURI( $remote_link );
2319 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2320 Type => 'DeleteLink',
2321 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2322 OldValue => $remote_uri->URI || $remote_link,
2326 if ( $remote_uri->IsLocal ) {
2328 my $OtherObj = $remote_uri->Object;
2329 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2330 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2331 : $LINKDIRMAP{$args{'Type'}}->{Target},
2332 OldValue => $self->URI,
2333 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2337 return ( $Trans, $Msg );
2343 my %args = ( Target => '',
2349 unless ( $args{'Target'} || $args{'Base'} ) {
2350 $RT::Logger->error("Base or Target must be specified\n");
2351 return ( 0, $self->loc('Either base or target must be specified') );
2355 $right++ if $self->CurrentUserHasRight('AdminUsers');
2356 if ( !$right && $RT::StrictLinkACL ) {
2357 return ( 0, $self->loc("Permission Denied") );
2360 # # If the other URI is an RT::Ticket, we want to make sure the user
2361 # # can modify it too...
2362 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2363 # return (0, $msg) unless $status;
2364 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2367 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2368 # ( $RT::StrictLinkACL && $right < 2 ) )
2370 # return ( 0, $self->loc("Permission Denied") );
2373 return $self->_AddLink(%args);
2378 Private non-acled variant of AddLink so that links can be added during create.
2384 my %args = ( Target => '',
2390 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2391 return ($val, $msg) if !$val || $exist;
2393 my ($direction, $remote_link);
2394 if ( $args{'Target'} ) {
2395 $remote_link = $args{'Target'};
2396 $direction = 'Base';
2397 } elsif ( $args{'Base'} ) {
2398 $remote_link = $args{'Base'};
2399 $direction = 'Target';
2402 # Don't write the transaction if we're doing this on create
2403 if ( $args{'Silent'} ) {
2404 return ( $val, $msg );
2407 my $remote_uri = RT::URI->new( $self->CurrentUser );
2408 $remote_uri->FromURI( $remote_link );
2410 #Write the transaction
2411 my ( $Trans, $Msg, $TransObj ) =
2412 $self->_NewTransaction(Type => 'AddLink',
2413 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2414 NewValue => $remote_uri->URI || $remote_link,
2417 if ( $remote_uri->IsLocal ) {
2419 my $OtherObj = $remote_uri->Object;
2420 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2421 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2422 : $LINKDIRMAP{$args{'Type'}}->{Target},
2423 NewValue => $self->URI,
2424 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2427 return ( $val, $Msg );
2433 sub _CoreAccessible {
2437 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2439 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2441 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2443 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2445 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2447 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2449 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2450 FreeformContactInfo =>
2451 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2453 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2455 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2457 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2459 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2461 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2463 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2464 ExternalContactInfoId =>
2465 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2466 ContactInfoSystem =>
2467 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2469 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2471 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2473 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2475 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2477 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2479 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2481 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2483 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2485 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2487 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2489 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2491 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2493 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2495 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2497 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2499 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2501 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2503 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2505 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2510 RT::Base->_ImportOverlays();