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 },
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 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
171 if (RT->SystemUser) { #This only works if RT::SystemUser has been defined
172 my $TempUser = RT::User->new(RT->SystemUser);
173 $TempUser->Load( $args{'Name'} );
174 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
176 my ($val, $message) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
177 return (0, $message) unless ( $val );
179 $RT::Logger->warning( "$self couldn't check for pre-existing users");
183 $RT::Handle->BeginTransaction();
184 # Groups deal with principal ids, rather than user ids.
185 # When creating this user, set up a principal Id for it.
186 my $principal = RT::Principal->new($self->CurrentUser);
187 my $principal_id = $principal->Create(PrincipalType => 'User',
188 Disabled => $args{'Disabled'},
190 # If we couldn't create a principal Id, get the fuck out.
191 unless ($principal_id) {
192 $RT::Handle->Rollback();
193 $RT::Logger->crit("Couldn't create a Principal on new user create.");
194 $RT::Logger->crit("Strange things are afoot at the circle K");
195 return ( 0, $self->loc('Could not create user') );
198 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
199 delete $args{'Disabled'};
201 $self->SUPER::Create(id => $principal_id , %args);
204 #If the create failed.
206 $RT::Handle->Rollback();
207 $RT::Logger->error("Could not create a new user - " .join('-', %args));
209 return ( 0, $self->loc('Could not create user') );
212 my $aclstash = RT::Group->new($self->CurrentUser);
213 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
216 $RT::Handle->Rollback();
217 $RT::Logger->crit("Couldn't stash the user in groupmembers");
218 return ( 0, $self->loc('Could not create user') );
222 my $everyone = RT::Group->new($self->CurrentUser);
223 $everyone->LoadSystemInternalGroup('Everyone');
224 unless ($everyone->id) {
225 $RT::Logger->crit("Could not load Everyone group on user creation.");
226 $RT::Handle->Rollback();
227 return ( 0, $self->loc('Could not create user') );
231 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
232 unless ($everyone_id) {
233 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
234 $RT::Logger->crit($everyone_msg);
235 $RT::Handle->Rollback();
236 return ( 0, $self->loc('Could not create user') );
240 my $access_class = RT::Group->new($self->CurrentUser);
242 $access_class->LoadSystemInternalGroup('Privileged');
244 $access_class->LoadSystemInternalGroup('Unprivileged');
247 unless ($access_class->id) {
248 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
249 $RT::Handle->Rollback();
250 return ( 0, $self->loc('Could not create user') );
254 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
257 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
258 $RT::Logger->crit($ac_msg);
259 $RT::Handle->Rollback();
260 return ( 0, $self->loc('Could not create user') );
264 if ( $record_transaction ) {
265 $self->_NewTransaction( Type => "Create" );
270 return ( $id, $self->loc('User created') );
273 =head2 ValidatePassword STRING
275 Returns either (0, "failure reason") or 1 depending on whether the given
280 sub ValidatePassword {
282 my $password = shift;
284 if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
285 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
291 =head2 SetPrivileged BOOL
293 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
294 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
296 Returns a standard RT tuple of (val, msg);
306 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
307 return ( 0, $self->loc('Permission Denied') );
310 $self->_SetPrivileged($val);
316 my $priv = RT::Group->new($self->CurrentUser);
317 $priv->LoadSystemInternalGroup('Privileged');
319 $RT::Logger->crit("Could not find Privileged pseudogroup");
320 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
323 my $unpriv = RT::Group->new($self->CurrentUser);
324 $unpriv->LoadSystemInternalGroup('Unprivileged');
325 unless ($unpriv->Id) {
326 $RT::Logger->crit("Could not find unprivileged pseudogroup");
327 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
330 my $principal = $self->PrincipalId;
332 if ($priv->HasMember($principal)) {
333 #$RT::Logger->debug("That user is already privileged");
334 return (0,$self->loc("That user is already privileged"));
336 if ($unpriv->HasMember($principal)) {
337 $unpriv->_DeleteMember($principal);
339 # if we had layered transactions, life would be good
340 # sadly, we have to just go ahead, even if something
342 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
343 "unprivileged. something is drastically wrong.");
345 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
347 return (1, $self->loc("That user is now privileged"));
352 if ($unpriv->HasMember($principal)) {
353 #$RT::Logger->debug("That user is already unprivileged");
354 return (0,$self->loc("That user is already unprivileged"));
356 if ($priv->HasMember($principal)) {
357 $priv->_DeleteMember( $principal );
359 # if we had layered transactions, life would be good
360 # sadly, we have to just go ahead, even if something
362 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
363 "unprivileged. something is drastically wrong.");
365 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
367 return (1, $self->loc("That user is now unprivileged"));
376 Returns true if this user is privileged. Returns undef otherwise.
382 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
389 #create a user without validating _any_ data.
391 #To be used only on database init.
392 # We can't localize here because it's before we _have_ a loc framework
394 sub _BootstrapCreate {
398 $args{'Password'} = '*NO-PASSWORD*';
401 $RT::Handle->BeginTransaction();
403 # Groups deal with principal ids, rather than user ids.
404 # When creating this user, set up a principal Id for it.
405 my $principal = RT::Principal->new($self->CurrentUser);
406 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
407 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
409 # If we couldn't create a principal Id, get the fuck out.
410 unless ($principal_id) {
411 $RT::Handle->Rollback();
412 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
413 return ( 0, 'Could not create user' );
415 $self->SUPER::Create(id => $principal_id, %args);
417 #If the create failed.
419 $RT::Handle->Rollback();
420 return ( 0, 'Could not create user' ) ; #never loc this
423 my $aclstash = RT::Group->new($self->CurrentUser);
424 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
427 $RT::Handle->Rollback();
428 $RT::Logger->crit("Couldn't stash the user in groupmembers");
429 return ( 0, $self->loc('Could not create user') );
432 $RT::Handle->Commit();
434 return ( $id, 'User created' );
440 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
446 Load a user object from the database. Takes a single argument.
447 If the argument is numerical, load by the column 'id'. If a user
448 object or its subclass passed then loads the same user by id.
449 Otherwise, load by the "Name" column which is the user's textual
456 my $identifier = shift || return undef;
458 if ( $identifier !~ /\D/ ) {
459 return $self->SUPER::LoadById( $identifier );
460 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
461 return $self->SUPER::LoadById( $identifier->Id );
463 return $self->LoadByCol( "Name", $identifier );
469 Tries to load this user object from the database by the user's email address.
477 # Never load an empty address as an email address.
482 $address = $self->CanonicalizeEmailAddress($address);
484 #$RT::Logger->debug("Trying to load an email address: $address");
485 return $self->LoadByCol( "EmailAddress", $address );
488 =head2 LoadOrCreateByEmail ADDRESS
490 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
491 the provided email address and loads them. Address can be provided either as L<Email::Address> object
492 or string which is parsed using the module.
494 Returns a tuple of the user's id and a status message.
495 0 will be returned in place of the user's id in case of failure.
499 sub LoadOrCreateByEmail {
503 my ($message, $name);
504 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
505 ($email, $name) = ($email->address, $email->phrase);
507 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
510 $self->LoadByEmail( $email );
511 $self->Load( $email ) unless $self->Id;
512 $message = $self->loc('User loaded');
514 unless( $self->Id ) {
516 ($val, $message) = $self->Create(
518 EmailAddress => $email,
521 Comments => 'Autocreated when added as a watcher',
524 # Deal with the race condition of two account creations at once
525 $self->LoadByEmail( $email );
526 unless ( $self->Id ) {
528 $self->LoadByEmail( $email );
531 $RT::Logger->error("Recovered from creation failure due to race condition");
532 $message = $self->loc("User loaded");
534 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
538 return (0, $message) unless $self->id;
539 return ($self->Id, $message);
542 =head2 ValidateEmailAddress ADDRESS
544 Returns true if the email address entered is not in use by another user or is
545 undef or ''. Returns false if it's in use.
549 sub ValidateEmailAddress {
553 # if the email address is null, it's always valid
554 return (1) if ( !$Value || $Value eq "" );
556 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
557 # We only allow one valid email address
558 my @addresses = Email::Address->parse($Value);
559 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
563 my $TempUser = RT::User->new(RT->SystemUser);
564 $TempUser->LoadByEmail($Value);
566 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
567 { # if we found a user with that address
568 # it's invalid to set this user's address to it
569 return ( 0, $self->loc('Email address in use') );
570 } else { #it's a valid email address
575 =head2 SetEmailAddress
577 Check to make sure someone else isn't using this email address already
578 so that a better email address can be returned
582 sub SetEmailAddress {
585 $Value = '' unless defined $Value;
587 my ($val, $message) = $self->ValidateEmailAddress( $Value );
589 return $self->_Set( Field => 'EmailAddress', Value => $Value );
591 return ( 0, $message )
596 =head2 EmailFrequency
598 Takes optional Ticket argument in paramhash. Returns 'no email',
599 'squelched', 'daily', 'weekly' or empty string depending on
604 =item 'no email' - user has no email, so can not recieve notifications.
606 =item 'squelched' - returned only when Ticket argument is provided and
607 notifications to the user has been supressed for this ticket.
609 =item 'daily' - retruned when user recieve daily messages digest instead
610 of immediate delivery.
612 =item 'weekly' - previous, but weekly.
614 =item empty string returned otherwise.
626 return '' unless $self->id && $self->id != RT->Nobody->id
627 && $self->id != RT->SystemUser->id;
628 return 'no email address' unless my $email = $self->EmailAddress;
629 return 'email disabled for ticket' if $args{'Ticket'} &&
630 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
631 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
632 return 'daily' if $frequency =~ /daily/i;
633 return 'weekly' if $frequency =~ /weekly/i;
637 =head2 CanonicalizeEmailAddress ADDRESS
639 CanonicalizeEmailAddress converts email addresses into canonical form.
640 it takes one email address in and returns the proper canonical
641 form. You can dump whatever your proper local config is in here. Note
642 that it may be called as a static method; in this case the first argument
643 is class name not an object.
647 sub CanonicalizeEmailAddress {
650 # Example: the following rule would treat all email
651 # coming from a subdomain as coming from second level domain
653 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
654 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
656 $email =~ s/$match/$replace/gi;
661 =head2 CanonicalizeUserInfo HASH of ARGS
663 CanonicalizeUserInfo can convert all User->Create options.
664 it takes a hashref of all the params sent to User->Create and
665 returns that same hash, by default nothing is done.
667 This function is intended to allow users to have their info looked up via
668 an outside source and modified upon creation.
672 sub CanonicalizeUserInfo {
681 =head2 Password and authentication related functions
683 =head3 SetRandomPassword
685 Takes no arguments. Returns a status code and a new password or an error message.
686 If the status is 1, the second value returned is the new password.
687 If the status is anything else, the new value returned is the error code.
691 sub SetRandomPassword {
694 unless ( $self->CurrentUserCanModify('Password') ) {
695 return ( 0, $self->loc("Permission Denied") );
699 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
700 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
702 my $pass = $self->GenerateRandomPassword( $min, $max) ;
704 # If we have "notify user on
706 my ( $val, $msg ) = $self->SetPassword($pass);
708 #If we got an error return the error.
709 return ( 0, $msg ) unless ($val);
711 #Otherwise, we changed the password, lets return it.
718 Returns status, [ERROR or new password]. Resets this user's password to
719 a randomly generated pronouncable password and emails them, using a
720 global template called "PasswordChange".
722 This function is currently unused in the UI, but available for local scripts.
729 unless ( $self->CurrentUserCanModify('Password') ) {
730 return ( 0, $self->loc("Permission Denied") );
732 my ( $status, $pass ) = $self->SetRandomPassword();
735 return ( 0, "$pass" );
738 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
739 To => $self->EmailAddress,
740 Template => 'PasswordChange',
742 NewPassword => $pass,
747 return ( 1, $self->loc('New password notification sent') );
749 return ( 0, $self->loc('Notification could not be sent') );
754 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
756 Returns a random password between MIN_LEN and MAX_LEN characters long.
760 sub GenerateRandomPassword {
761 my $self = shift; # just to drop it
762 return Text::Password::Pronounceable->generate(@_);
765 sub SafeSetPassword {
770 Confirmation => undef,
773 return (1) unless defined $args{'New'} && length $args{'New'};
775 my %cond = $self->CurrentUserRequireToSetPassword;
777 unless ( $cond{'CanSet'} ) {
778 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
782 if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
783 if ( defined $args{'Current'} && length $args{'Current'} ) {
784 $error = $self->loc("Please enter your current password correctly.");
786 $error = $self->loc("Please enter your current password.");
788 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
789 $error = $self->loc("Passwords do not match.");
793 $error .= ' '. $self->loc('Password has not been set.');
797 return $self->SetPassword( $args{'New'} );
802 Takes a string. Checks the string's length and sets this user's password
809 my $password = shift;
811 unless ( $self->CurrentUserCanModify('Password') ) {
812 return ( 0, $self->loc('Password: Permission Denied') );
816 return ( 0, $self->loc("No password set") );
818 my ($val, $msg) = $self->ValidatePassword($password);
819 return ($val, $msg) if !$val;
821 my $new = !$self->HasPassword;
822 $password = $self->_GeneratePassword($password);
824 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
826 return ( 1, $self->loc("Password set") ) if $new;
827 return ( 1, $self->loc("Password changed") );
829 return ( $val, $msg );
835 sub _GeneratePassword_sha512 {
837 my ($password, $salt) = @_;
839 # Generate a 16-character base64 salt
842 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
846 my $sha = Digest::SHA->new(512);
848 $sha->add(encode_utf8($password));
849 return join("!", "", "sha512", $salt, $sha->b64digest);
852 =head3 _GeneratePassword PASSWORD [, SALT]
854 Returns a string to store in the database. This string takes the form:
858 By default, the method is currently C<sha512>.
862 sub _GeneratePassword {
864 return $self->_GeneratePassword_sha512(@_);
869 Returns true if the user has a valid password, otherwise returns false.
875 my $pwd = $self->__Value('Password');
876 return undef if !defined $pwd
878 || $pwd eq '*NO-PASSWORD*';
884 Returns true if the passed in value is this user's password.
885 Returns undef otherwise.
893 #TODO there isn't any apparent way to legitimately ACL this
895 # RT does not allow null passwords
896 if ( ( !defined($value) ) or ( $value eq '' ) ) {
900 if ( $self->PrincipalObj->Disabled ) {
902 "Disabled user " . $self->Name . " tried to log in" );
906 unless ($self->HasPassword) {
910 my $stored = $self->__Value('Password');
911 if ($stored =~ /^!/) {
912 # If it's a new-style (>= RT 4.0) password, it starts with a '!'
913 my (undef, $method, $salt, undef) = split /!/, $stored;
914 if ($method eq "sha512") {
915 return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
917 $RT::Logger->warn("Unknown hash method $method");
920 } elsif (length $stored == 40) {
921 # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
922 my $hash = MIME::Base64::decode_base64($stored);
923 # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26)
924 my $salt = substr($hash, 0, 4, "");
925 return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
926 } elsif (length $stored == 32) {
928 return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
929 } elsif (length $stored == 22) {
930 # Base64 nonsalted-md5
931 return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
932 } elsif (length $stored == 13) {
934 return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
936 $RT::Logger->warning("Unknown password form");
940 # We got here by validating successfully, but with a legacy
941 # password form. Update to the most recent form.
942 my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
943 $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
947 sub CurrentUserRequireToSetPassword {
956 if ( RT->Config->Get('WebExternalAuth')
957 && !RT->Config->Get('WebFallbackToInternalAuth')
960 $res{'Reason'} = $self->loc("External authentication enabled.");
961 } elsif ( !$self->CurrentUser->HasPassword ) {
962 if ( $self->CurrentUser->id == ($self->id||0) ) {
963 # don't require current password if user has no
964 $res{'RequireCurrent'} = 0;
967 $res{'Reason'} = $self->loc("Your password is not set.");
976 Returns an authentication string associated with the user. This
977 string can be used to generate passwordless URLs to integrate
978 RT with services and programms like callendar managers, rss
985 my $secret = $self->_Value( AuthToken => @_ );
986 return $secret if $secret;
988 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
990 my $tmp = RT::User->new( RT->SystemUser );
991 $tmp->Load( $self->id );
992 my ($status, $msg) = $tmp->SetAuthToken( $secret );
994 $RT::Logger->error( "Couldn't set auth token: $msg" );
1000 =head3 GenerateAuthToken
1002 Generate a random authentication string for the user.
1006 sub GenerateAuthToken {
1008 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1009 return $self->SetAuthToken( $token );
1012 =head3 GenerateAuthString
1014 Takes a string and returns back a hex hash string. Later you can use
1015 this pair to make sure it's generated by this user using L</ValidateAuthString>
1019 sub GenerateAuthString {
1021 my $protect = shift;
1023 my $str = $self->AuthToken . $protect;
1026 return substr(Digest::MD5::md5_hex($str),0,16);
1029 =head3 ValidateAuthString
1031 Takes auth string and protected string. Returns true is protected string
1032 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1036 sub ValidateAuthString {
1038 my $auth_string = shift;
1039 my $protected = shift;
1041 my $str = $self->AuthToken . $protected;
1042 utf8::encode( $str );
1044 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1049 Toggles the user's disabled flag.
1051 set, all password checks for this user will fail. All ACL checks for this
1052 user will fail. The user will appear in no user listings.
1059 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1060 return (0, $self->loc('Permission Denied'));
1063 $RT::Handle->BeginTransaction();
1064 my $set_err = $self->PrincipalObj->SetDisabled($val);
1066 $RT::Handle->Rollback();
1067 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1070 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1072 $RT::Handle->Commit();
1075 return (1, $self->loc("User disabled"));
1077 return (1, $self->loc("User enabled"));
1084 Returns true if user is disabled or false otherwise
1090 return $self->PrincipalObj->Disabled(@_);
1095 Returns the principal object for this user. returns an empty RT::Principal
1096 if there's no principal object matching this user.
1097 The response is cached. PrincipalObj should never ever change.
1104 unless ( $self->id ) {
1105 $RT::Logger->error("Couldn't get principal for an empty user");
1109 if ( !$self->{_principal_obj} ) {
1111 my $obj = RT::Principal->new( $self->CurrentUser );
1112 $obj->LoadById( $self->id );
1114 $RT::Logger->crit( 'No principal for user #' . $self->id );
1116 } elsif ( $obj->PrincipalType ne 'User' ) {
1117 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1120 $self->{_principal_obj} = $obj;
1122 return $self->{_principal_obj};
1128 Returns this user's PrincipalId
1137 =head2 HasGroupRight
1139 Takes a paramhash which can contain
1141 GroupObj => RT::Group or Group => integer
1145 Returns 1 if this user has the right specified in the paramhash for the Group
1148 Returns undef if they don't.
1162 if ( defined $args{'Group'} ) {
1163 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1164 $args{'GroupObj'}->Load( $args{'Group'} );
1167 # Validate and load up the GroupId
1168 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1172 # Figure out whether a user has the right we're asking about.
1173 my $retval = $self->HasRight(
1174 Object => $args{'GroupObj'},
1175 Right => $args{'Right'},
1183 Returns a group collection object containing the groups of which this
1190 my $groups = RT::Groups->new($self->CurrentUser);
1191 $groups->LimitToUserDefinedGroups;
1192 $groups->WithMember(
1193 PrincipalId => $self->Id,
1201 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1207 return $self->PrincipalObj->HasRight(@_);
1210 =head2 CurrentUserCanSee [FIELD]
1212 Returns true if the current user can see the user, based on if it is
1213 public, ourself, or we have AdminUsers
1217 sub CurrentUserCanSee {
1221 # If it's public, fine. Note that $what may be "transaction", which
1222 # doesn't have an Accessible value, and thus falls through below.
1223 if ( $self->_Accessible( $what, 'public' ) ) {
1227 # Users can see their own properties
1228 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
1232 # If the user has the admin users right, that's also enough
1233 elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
1241 =head2 CurrentUserCanModify RIGHT
1243 If the user has rights for this object, either because
1244 he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1245 admin right) 'ModifySelf', return 1. otherwise, return undef.
1249 sub CurrentUserCanModify {
1253 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1257 #If the field is marked as an "administrators only" field,
1258 # don't let the user touch it.
1259 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1263 #If the current user is trying to modify themselves
1264 elsif ( ( $self->id == $self->CurrentUser->id )
1265 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1270 #If we don't have a good reason to grant them rights to modify
1278 =head2 CurrentUserHasRight
1280 Takes a single argument. returns 1 if $Self->CurrentUser
1281 has the requested right. returns undef otherwise
1285 sub CurrentUserHasRight {
1289 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1295 $name = ref($name).'-'.$name->Id;
1298 return 'Pref-'.$name;
1301 =head2 Preferences NAME/OBJ DEFAULT
1303 Obtain user preferences associated with given object or name.
1304 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1305 override the entries with user preferences.
1311 my $name = _PrefName (shift);
1312 my $default = shift;
1314 my $attr = RT::Attribute->new( $self->CurrentUser );
1315 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1317 my $content = $attr->Id ? $attr->Content : undef;
1318 unless ( ref $content eq 'HASH' ) {
1319 return defined $content ? $content : $default;
1322 if (ref $default eq 'HASH') {
1323 for (keys %$default) {
1324 exists $content->{$_} or $content->{$_} = $default->{$_};
1326 } elsif (defined $default) {
1327 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1332 =head2 SetPreferences NAME/OBJ VALUE
1334 Set user preferences associated with given object or name.
1338 sub SetPreferences {
1340 my $name = _PrefName( shift );
1343 return (0, $self->loc("No permission to set preferences"))
1344 unless $self->CurrentUserCanModify('Preferences');
1346 my $attr = RT::Attribute->new( $self->CurrentUser );
1347 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1349 my ($ok, $msg) = $attr->SetContent( $value );
1350 return (1, "No updates made")
1351 if $msg eq "That is already the current value";
1354 return $self->AddAttribute( Name => $name, Content => $value );
1360 Returns a list of valid stylesheets take from preferences.
1367 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1369 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1370 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1372 for my $css_path (@css_paths) {
1373 if (-d "$css_path/$style") {
1379 # Fall back to the system stylesheet.
1380 return RT->Config->Get('WebDefaultStylesheet');
1383 =head2 WatchedQueues ROLE_LIST
1385 Returns a RT::Queues object containing every queue watched by the user.
1387 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1389 $user->WatchedQueues('Cc', 'AdminCc');
1396 my @roles = @_ || ('Cc', 'AdminCc');
1398 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1400 my $watched_queues = RT::Queues->new($self->CurrentUser);
1402 my $group_alias = $watched_queues->Join(
1406 FIELD2 => 'Instance',
1409 $watched_queues->Limit(
1410 ALIAS => $group_alias,
1412 VALUE => 'RT::Queue-Role',
1413 ENTRYAGGREGATOR => 'AND',
1415 if (grep { $_ eq 'Cc' } @roles) {
1416 $watched_queues->Limit(
1417 SUBCLAUSE => 'LimitToWatchers',
1418 ALIAS => $group_alias,
1421 ENTRYAGGREGATOR => 'OR',
1424 if (grep { $_ eq 'AdminCc' } @roles) {
1425 $watched_queues->Limit(
1426 SUBCLAUSE => 'LimitToWatchers',
1427 ALIAS => $group_alias,
1430 ENTRYAGGREGATOR => 'OR',
1434 my $queues_alias = $watched_queues->Join(
1435 ALIAS1 => $group_alias,
1437 TABLE2 => 'CachedGroupMembers',
1438 FIELD2 => 'GroupId',
1440 $watched_queues->Limit(
1441 ALIAS => $queues_alias,
1442 FIELD => 'MemberId',
1443 VALUE => $self->PrincipalId,
1445 $watched_queues->Limit(
1446 ALIAS => $queues_alias,
1447 FIELD => 'Disabled',
1452 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1454 return $watched_queues;
1464 TransactionType => 'Set',
1465 RecordTransaction => 1,
1469 # Nobody is allowed to futz with RT_System or Nobody
1471 if ( ($self->Id == RT->SystemUser->Id ) ||
1472 ($self->Id == RT->Nobody->Id)) {
1473 return ( 0, $self->loc("Can not modify system users") );
1475 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1476 return ( 0, $self->loc("Permission Denied") );
1479 my $Old = $self->SUPER::_Value("$args{'Field'}");
1481 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1482 Value => $args{'Value'} );
1484 #If we can't actually set the field to the value, don't record
1485 # a transaction. instead, get out of here.
1486 if ( $ret == 0 ) { return ( 0, $msg ); }
1488 if ( $args{'RecordTransaction'} == 1 ) {
1489 if ($args{'Field'} eq "Password") {
1490 $args{'Value'} = $Old = '********';
1492 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1493 Type => $args{'TransactionType'},
1494 Field => $args{'Field'},
1495 NewValue => $args{'Value'},
1497 TimeTaken => $args{'TimeTaken'},
1499 return ( $Trans, scalar $TransObj->BriefDescription );
1501 return ( $ret, $msg );
1507 Takes the name of a table column.
1508 Returns its value as a string, if the user passes an ACL check
1517 # Defer to the abstraction above to know if the field can be read
1518 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1524 Return the friendly name
1530 return $self->RealName if defined($self->RealName);
1531 return $self->Name if defined($self->Name);
1537 Returns the preferred key of the user. If none is set, then this will query
1538 GPG and set the preferred key to the maximally trusted key found (and then
1539 return it). Returns C<undef> if no preferred key can be found.
1546 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1548 if ( ($self->CurrentUser->Id != $self->Id ) &&
1549 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1555 my $prefkey = $self->FirstAttribute('PreferredKey');
1556 return $prefkey->Content if $prefkey;
1558 # we don't have a preferred key for this user, so now we must query GPG
1559 require RT::Crypt::GnuPG;
1560 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1561 return undef unless defined $res{'info'};
1562 my @keys = @{ $res{'info'} };
1563 return undef if @keys == 0;
1566 $prefkey = $keys[0]->{'Fingerprint'};
1568 # prefer the maximally trusted key
1569 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1570 $prefkey = $keys[0]->{'Fingerprint'};
1573 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1581 #If the user wants to see their own values, let them.
1582 #If the user is an admin, let them.
1583 #Otherwwise, don't let them.
1585 if ( ($self->CurrentUser->Id != $self->Id ) &&
1586 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1590 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1591 return $key->Content;
1598 unless ($self->CurrentUserCanModify('PrivateKey')) {
1599 return (0, $self->loc("Permission Denied"));
1603 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1604 unless ( $status ) {
1605 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1606 return ($status, $self->loc("Couldn't unset private key"));
1608 return ($status, $self->loc("Unset private key"));
1611 # check that it's really private key
1613 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1614 return (0, $self->loc("No such key or it's not suitable for signing"))
1615 if $tmp{'exit_code'} || !$tmp{'info'};
1618 my ($status, $msg) = $self->SetAttribute(
1619 Name => 'PrivateKey',
1622 return ($status, $self->loc("Couldn't set private key"))
1624 return ($status, $self->loc("Set private key"));
1629 [ Name => 'Username' ],
1630 [ EmailAddress => 'Email' ],
1631 [ RealName => 'Name' ],
1632 [ Organization => 'Organization' ],
1636 =head2 Create PARAMHASH
1638 Create takes a hash of values and creates a row in the database:
1640 varchar(200) 'Name'.
1641 varbinary(256) 'Password'.
1642 varchar(16) 'AuthToken'.
1645 varchar(120) 'EmailAddress'.
1646 text 'FreeformContactInfo'.
1647 varchar(200) 'Organization'.
1648 varchar(120) 'RealName'.
1649 varchar(16) 'NickName'.
1651 varchar(16) 'EmailEncoding'.
1652 varchar(16) 'WebEncoding'.
1653 varchar(100) 'ExternalContactInfoId'.
1654 varchar(30) 'ContactInfoSystem'.
1655 varchar(100) 'ExternalAuthId'.
1656 varchar(30) 'AuthSystem'.
1657 varchar(16) 'Gecos'.
1658 varchar(30) 'HomePhone'.
1659 varchar(30) 'WorkPhone'.
1660 varchar(30) 'MobilePhone'.
1661 varchar(30) 'PagerPhone'.
1662 varchar(200) 'Address1'.
1663 varchar(200) 'Address2'.
1664 varchar(100) 'City'.
1665 varchar(100) 'State'.
1667 varchar(50) 'Country'.
1668 varchar(50) 'Timezone'.
1678 Returns the current value of id.
1679 (In the database, id is stored as int(11).)
1687 Returns the current value of Name.
1688 (In the database, Name is stored as varchar(200).)
1692 =head2 SetName VALUE
1696 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1697 (In the database, Name will be stored as a varchar(200).)
1705 Returns the current value of Password.
1706 (In the database, Password is stored as varchar(256).)
1710 =head2 SetPassword VALUE
1713 Set Password to VALUE.
1714 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1715 (In the database, Password will be stored as a varchar(256).)
1723 Returns the current value of AuthToken.
1724 (In the database, AuthToken is stored as varchar(16).)
1728 =head2 SetAuthToken VALUE
1731 Set AuthToken to VALUE.
1732 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1733 (In the database, AuthToken will be stored as a varchar(16).)
1741 Returns the current value of Comments.
1742 (In the database, Comments is stored as text.)
1746 =head2 SetComments VALUE
1749 Set Comments to VALUE.
1750 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1751 (In the database, Comments will be stored as a text.)
1759 Returns the current value of Signature.
1760 (In the database, Signature is stored as text.)
1764 =head2 SetSignature VALUE
1767 Set Signature to VALUE.
1768 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1769 (In the database, Signature will be stored as a text.)
1777 Returns the current value of EmailAddress.
1778 (In the database, EmailAddress is stored as varchar(120).)
1782 =head2 SetEmailAddress VALUE
1785 Set EmailAddress to VALUE.
1786 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1787 (In the database, EmailAddress will be stored as a varchar(120).)
1793 =head2 FreeformContactInfo
1795 Returns the current value of FreeformContactInfo.
1796 (In the database, FreeformContactInfo is stored as text.)
1800 =head2 SetFreeformContactInfo VALUE
1803 Set FreeformContactInfo to VALUE.
1804 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1805 (In the database, FreeformContactInfo will be stored as a text.)
1813 Returns the current value of Organization.
1814 (In the database, Organization is stored as varchar(200).)
1818 =head2 SetOrganization VALUE
1821 Set Organization to VALUE.
1822 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1823 (In the database, Organization will be stored as a varchar(200).)
1831 Returns the current value of RealName.
1832 (In the database, RealName is stored as varchar(120).)
1836 =head2 SetRealName VALUE
1839 Set RealName to VALUE.
1840 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1841 (In the database, RealName will be stored as a varchar(120).)
1849 Returns the current value of NickName.
1850 (In the database, NickName is stored as varchar(16).)
1854 =head2 SetNickName VALUE
1857 Set NickName to VALUE.
1858 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1859 (In the database, NickName will be stored as a varchar(16).)
1867 Returns the current value of Lang.
1868 (In the database, Lang is stored as varchar(16).)
1872 =head2 SetLang VALUE
1876 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1877 (In the database, Lang will be stored as a varchar(16).)
1883 =head2 EmailEncoding
1885 Returns the current value of EmailEncoding.
1886 (In the database, EmailEncoding is stored as varchar(16).)
1890 =head2 SetEmailEncoding VALUE
1893 Set EmailEncoding to VALUE.
1894 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1895 (In the database, EmailEncoding will be stored as a varchar(16).)
1903 Returns the current value of WebEncoding.
1904 (In the database, WebEncoding is stored as varchar(16).)
1908 =head2 SetWebEncoding VALUE
1911 Set WebEncoding to VALUE.
1912 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1913 (In the database, WebEncoding will be stored as a varchar(16).)
1919 =head2 ExternalContactInfoId
1921 Returns the current value of ExternalContactInfoId.
1922 (In the database, ExternalContactInfoId is stored as varchar(100).)
1926 =head2 SetExternalContactInfoId VALUE
1929 Set ExternalContactInfoId to VALUE.
1930 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1931 (In the database, ExternalContactInfoId will be stored as a varchar(100).)
1937 =head2 ContactInfoSystem
1939 Returns the current value of ContactInfoSystem.
1940 (In the database, ContactInfoSystem is stored as varchar(30).)
1944 =head2 SetContactInfoSystem VALUE
1947 Set ContactInfoSystem to VALUE.
1948 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1949 (In the database, ContactInfoSystem will be stored as a varchar(30).)
1955 =head2 ExternalAuthId
1957 Returns the current value of ExternalAuthId.
1958 (In the database, ExternalAuthId is stored as varchar(100).)
1962 =head2 SetExternalAuthId VALUE
1965 Set ExternalAuthId to VALUE.
1966 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1967 (In the database, ExternalAuthId will be stored as a varchar(100).)
1975 Returns the current value of AuthSystem.
1976 (In the database, AuthSystem is stored as varchar(30).)
1980 =head2 SetAuthSystem VALUE
1983 Set AuthSystem to VALUE.
1984 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1985 (In the database, AuthSystem will be stored as a varchar(30).)
1993 Returns the current value of Gecos.
1994 (In the database, Gecos is stored as varchar(16).)
1998 =head2 SetGecos VALUE
2002 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2003 (In the database, Gecos will be stored as a varchar(16).)
2011 Returns the current value of HomePhone.
2012 (In the database, HomePhone is stored as varchar(30).)
2016 =head2 SetHomePhone VALUE
2019 Set HomePhone to VALUE.
2020 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2021 (In the database, HomePhone will be stored as a varchar(30).)
2029 Returns the current value of WorkPhone.
2030 (In the database, WorkPhone is stored as varchar(30).)
2034 =head2 SetWorkPhone VALUE
2037 Set WorkPhone to VALUE.
2038 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2039 (In the database, WorkPhone will be stored as a varchar(30).)
2047 Returns the current value of MobilePhone.
2048 (In the database, MobilePhone is stored as varchar(30).)
2052 =head2 SetMobilePhone VALUE
2055 Set MobilePhone to VALUE.
2056 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2057 (In the database, MobilePhone will be stored as a varchar(30).)
2065 Returns the current value of PagerPhone.
2066 (In the database, PagerPhone is stored as varchar(30).)
2070 =head2 SetPagerPhone VALUE
2073 Set PagerPhone to VALUE.
2074 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2075 (In the database, PagerPhone will be stored as a varchar(30).)
2083 Returns the current value of Address1.
2084 (In the database, Address1 is stored as varchar(200).)
2088 =head2 SetAddress1 VALUE
2091 Set Address1 to VALUE.
2092 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2093 (In the database, Address1 will be stored as a varchar(200).)
2101 Returns the current value of Address2.
2102 (In the database, Address2 is stored as varchar(200).)
2106 =head2 SetAddress2 VALUE
2109 Set Address2 to VALUE.
2110 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2111 (In the database, Address2 will be stored as a varchar(200).)
2119 Returns the current value of City.
2120 (In the database, City is stored as varchar(100).)
2124 =head2 SetCity VALUE
2128 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2129 (In the database, City will be stored as a varchar(100).)
2137 Returns the current value of State.
2138 (In the database, State is stored as varchar(100).)
2142 =head2 SetState VALUE
2146 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2147 (In the database, State will be stored as a varchar(100).)
2155 Returns the current value of Zip.
2156 (In the database, Zip is stored as varchar(16).)
2164 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2165 (In the database, Zip will be stored as a varchar(16).)
2173 Returns the current value of Country.
2174 (In the database, Country is stored as varchar(50).)
2178 =head2 SetCountry VALUE
2181 Set Country to VALUE.
2182 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2183 (In the database, Country will be stored as a varchar(50).)
2191 Returns the current value of Timezone.
2192 (In the database, Timezone is stored as varchar(50).)
2196 =head2 SetTimezone VALUE
2199 Set Timezone to VALUE.
2200 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2201 (In the database, Timezone will be stored as a varchar(50).)
2209 Returns the current value of PGPKey.
2210 (In the database, PGPKey is stored as text.)
2214 =head2 SetPGPKey VALUE
2217 Set PGPKey to VALUE.
2218 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2219 (In the database, PGPKey will be stored as a text.)
2227 Returns the current value of Creator.
2228 (In the database, Creator is stored as int(11).)
2236 Returns the current value of Created.
2237 (In the database, Created is stored as datetime.)
2243 =head2 LastUpdatedBy
2245 Returns the current value of LastUpdatedBy.
2246 (In the database, LastUpdatedBy is stored as int(11).)
2254 Returns the current value of LastUpdated.
2255 (In the database, LastUpdated is stored as datetime.)
2261 # much false laziness w/Ticket.pm. now with RT 4!
2263 MemberOf => { Base => 'MemberOf',
2264 Target => 'HasMember', },
2265 RefersTo => { Base => 'RefersTo',
2266 Target => 'ReferredToBy', },
2267 DependsOn => { Base => 'DependsOn',
2268 Target => 'DependedOnBy', },
2269 MergedInto => { Base => 'MergedInto',
2270 Target => 'MergedInto', },
2274 sub LINKDIRMAP { return \%LINKDIRMAP }
2279 Delete a link. takes a paramhash of Base, Target and Type.
2280 Either Base or Target must be null. The null value will
2281 be replaced with this ticket\'s id
2294 unless ( $args{'Target'} || $args{'Base'} ) {
2295 $RT::Logger->error("Base or Target must be specified\n");
2296 return ( 0, $self->loc('Either base or target must be specified') );
2301 $right++ if $self->CurrentUserHasRight('AdminUsers');
2302 if ( !$right && $RT::StrictLinkACL ) {
2303 return ( 0, $self->loc("Permission Denied") );
2306 # # If the other URI is an RT::Ticket, we want to make sure the user
2307 # # can modify it too...
2308 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2309 # return (0, $msg) unless $status;
2310 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2313 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2314 # ( $RT::StrictLinkACL && $right < 2 ) )
2316 # return ( 0, $self->loc("Permission Denied") );
2319 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
2322 $RT::Logger->debug("Couldn't find that link\n");
2326 my ($direction, $remote_link);
2328 if ( $args{'Base'} ) {
2329 $remote_link = $args{'Base'};
2330 $direction = 'Target';
2332 elsif ( $args{'Target'} ) {
2333 $remote_link = $args{'Target'};
2337 if ( $args{'Silent'} ) {
2338 return ( $val, $Msg );
2341 my $remote_uri = RT::URI->new( $self->CurrentUser );
2342 $remote_uri->FromURI( $remote_link );
2344 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2345 Type => 'DeleteLink',
2346 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2347 OldValue => $remote_uri->URI || $remote_link,
2351 if ( $remote_uri->IsLocal ) {
2353 my $OtherObj = $remote_uri->Object;
2354 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
2355 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2356 : $LINKDIRMAP{$args{'Type'}}->{Target},
2357 OldValue => $self->URI,
2358 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2362 return ( $Trans, $Msg );
2368 my %args = ( Target => '',
2374 unless ( $args{'Target'} || $args{'Base'} ) {
2375 $RT::Logger->error("Base or Target must be specified\n");
2376 return ( 0, $self->loc('Either base or target must be specified') );
2380 $right++ if $self->CurrentUserHasRight('AdminUsers');
2381 if ( !$right && $RT::StrictLinkACL ) {
2382 return ( 0, $self->loc("Permission Denied") );
2385 # # If the other URI is an RT::Ticket, we want to make sure the user
2386 # # can modify it too...
2387 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
2388 # return (0, $msg) unless $status;
2389 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
2392 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
2393 # ( $RT::StrictLinkACL && $right < 2 ) )
2395 # return ( 0, $self->loc("Permission Denied") );
2398 return $self->_AddLink(%args);
2403 Private non-acled variant of AddLink so that links can be added during create.
2409 my %args = ( Target => '',
2415 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
2416 return ($val, $msg) if !$val || $exist;
2418 my ($direction, $remote_link);
2419 if ( $args{'Target'} ) {
2420 $remote_link = $args{'Target'};
2421 $direction = 'Base';
2422 } elsif ( $args{'Base'} ) {
2423 $remote_link = $args{'Base'};
2424 $direction = 'Target';
2427 # Don't write the transaction if we're doing this on create
2428 if ( $args{'Silent'} ) {
2429 return ( $val, $msg );
2432 my $remote_uri = RT::URI->new( $self->CurrentUser );
2433 $remote_uri->FromURI( $remote_link );
2435 #Write the transaction
2436 my ( $Trans, $Msg, $TransObj ) =
2437 $self->_NewTransaction(Type => 'AddLink',
2438 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
2439 NewValue => $remote_uri->URI || $remote_link,
2442 if ( $remote_uri->IsLocal ) {
2444 my $OtherObj = $remote_uri->Object;
2445 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
2446 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
2447 : $LINKDIRMAP{$args{'Type'}}->{Target},
2448 NewValue => $self->URI,
2449 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
2452 return ( $val, $Msg );
2458 sub _CoreAccessible {
2462 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2464 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2466 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2468 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2470 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2472 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2474 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2475 FreeformContactInfo =>
2476 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2478 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2480 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2482 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2484 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2486 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2488 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2489 ExternalContactInfoId =>
2490 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2491 ContactInfoSystem =>
2492 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2494 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2496 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2498 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2500 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2502 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2504 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2506 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2508 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2510 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2512 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2514 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2516 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2518 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2520 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2522 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2524 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2526 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2528 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2530 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2535 RT::Base->_ImportOverlays();