]> git.uio.no Git - usit-rt.git/blame - lib/RT/User.pm
Removed LDAP-lookup loop for new external users.
[usit-rt.git] / lib / RT / User.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
5# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6# <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
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
16# from www.gnu.org.
17#
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.
22#
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.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
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.)
37#
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.
46#
47# END BPS TAGGED BLOCK }}}
48
49=head1 NAME
50
51 RT::User - RT User object
52
53=head1 SYNOPSIS
54
55 use RT::User;
56
57=head1 DESCRIPTION
58
59=head1 METHODS
60
61=cut
62
63
64package RT::User;
65
66use strict;
67use warnings;
68
69
70use base 'RT::Record';
71
72sub Table {'Users'}
73
74
75
76
77
78
79use Digest::SHA;
80use Digest::MD5;
81use RT::Principals;
82use RT::ACE;
83use RT::Interface::Email;
84use Encode;
85use Text::Password::Pronounceable;
86
87sub _OverlayAccessible {
88 {
89
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
106 }
107}
108
109
110
111=head2 Create { PARAMHASH }
112
113
114
115=cut
116
117
118sub Create {
119 my $self = shift;
120 my %args = (
121 Privileged => 0,
122 Disabled => 0,
123 EmailAddress => '',
124 _RecordTransaction => 1,
125 @_ # get the real argumentlist
126 );
127
128 # remove the value so it does not cripple SUPER::Create
129 my $record_transaction = delete $args{'_RecordTransaction'};
130
131 #Check the ACL
132 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
133 return ( 0, $self->loc('Permission Denied') );
134 }
135
136
137 unless ($self->CanonicalizeUserInfo(\%args)) {
138 return ( 0, $self->loc("Could not set user info") );
139 }
140
141 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
142
143 # if the user doesn't have a name defined, set it to the email address
144 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
145
146
147
148 my $privileged = delete $args{'Privileged'};
149
150
151 if ($args{'CryptedPassword'} ) {
152 $args{'Password'} = $args{'CryptedPassword'};
153 delete $args{'CryptedPassword'};
154 } elsif ( !$args{'Password'} ) {
155 $args{'Password'} = '*NO-PASSWORD*';
156 } else {
157 my ($ok, $msg) = $self->ValidatePassword($args{'Password'});
158 return ($ok, $msg) if !$ok;
159
160 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
161 }
162
163 #TODO Specify some sensible defaults.
164
165 unless ( $args{'Name'} ) {
166 return ( 0, $self->loc("Must specify 'Name' attribute") );
167 }
168
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 );
174
175 my ($val, $message) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
176 return (0, $message) unless ( $val );
177 } else {
178 $RT::Logger->warning( "$self couldn't check for pre-existing users");
179 }
180
181
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'},
188 ObjectId => '0');
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') );
195 }
196
197 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
198 delete $args{'Disabled'};
199
200 $self->SUPER::Create(id => $principal_id , %args);
201 my $id = $self->Id;
202
203 #If the create failed.
204 unless ($id) {
205 $RT::Handle->Rollback();
206 $RT::Logger->error("Could not create a new user - " .join('-', %args));
207
208 return ( 0, $self->loc('Could not create user') );
209 }
210
211 my $aclstash = RT::Group->new($self->CurrentUser);
212 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
213
214 unless ($stash_id) {
215 $RT::Handle->Rollback();
216 $RT::Logger->crit("Couldn't stash the user in groupmembers");
217 return ( 0, $self->loc('Could not create user') );
218 }
219
220
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') );
227 }
228
229
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') );
236 }
237
238
239 my $access_class = RT::Group->new($self->CurrentUser);
240 if ($privileged) {
241 $access_class->LoadSystemInternalGroup('Privileged');
242 } else {
243 $access_class->LoadSystemInternalGroup('Unprivileged');
244 }
245
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') );
250 }
251
252
253 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
254
255 unless ($ac_id) {
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') );
260 }
261
262
263 if ( $record_transaction ) {
264 $self->_NewTransaction( Type => "Create" );
265 }
266
267 $RT::Handle->Commit;
268
269 return ( $id, $self->loc('User created') );
270}
271
272=head2 ValidatePassword STRING
273
274Returns either (0, "failure reason") or 1 depending on whether the given
275password is valid.
276
277=cut
278
279sub ValidatePassword {
280 my $self = shift;
281 my $password = shift;
282
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')) );
285 }
286
287 return 1;
288}
289
290=head2 SetPrivileged BOOL
291
292If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
293Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
294
295Returns a standard RT tuple of (val, msg);
296
297
298=cut
299
300sub SetPrivileged {
301 my $self = shift;
302 my $val = shift;
303
304 #Check the ACL
305 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
306 return ( 0, $self->loc('Permission Denied') );
307 }
308
309 $self->_SetPrivileged($val);
310}
311
312sub _SetPrivileged {
313 my $self = shift;
314 my $val = shift;
315 my $priv = RT::Group->new($self->CurrentUser);
316 $priv->LoadSystemInternalGroup('Privileged');
317 unless ($priv->Id) {
318 $RT::Logger->crit("Could not find Privileged pseudogroup");
319 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
320 }
321
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"));
327 }
328
329 my $principal = $self->PrincipalId;
330 if ($val) {
331 if ($priv->HasMember($principal)) {
332 #$RT::Logger->debug("That user is already privileged");
333 return (0,$self->loc("That user is already privileged"));
334 }
335 if ($unpriv->HasMember($principal)) {
336 $unpriv->_DeleteMember($principal);
337 } else {
338 # if we had layered transactions, life would be good
339 # sadly, we have to just go ahead, even if something
340 # bogus happened
341 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
342 "unprivileged. something is drastically wrong.");
343 }
344 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
345 if ($status) {
346 return (1, $self->loc("That user is now privileged"));
347 } else {
348 return (0, $msg);
349 }
350 } else {
351 if ($unpriv->HasMember($principal)) {
352 #$RT::Logger->debug("That user is already unprivileged");
353 return (0,$self->loc("That user is already unprivileged"));
354 }
355 if ($priv->HasMember($principal)) {
356 $priv->_DeleteMember( $principal );
357 } else {
358 # if we had layered transactions, life would be good
359 # sadly, we have to just go ahead, even if something
360 # bogus happened
361 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
362 "unprivileged. something is drastically wrong.");
363 }
364 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
365 if ($status) {
366 return (1, $self->loc("That user is now unprivileged"));
367 } else {
368 return (0, $msg);
369 }
370 }
371}
372
373=head2 Privileged
374
375Returns true if this user is privileged. Returns undef otherwise.
376
377=cut
378
379sub Privileged {
380 my $self = shift;
381 if ( RT->PrivilegedUsers->HasMember( $self->id ) ) {
382 return(1);
383 } else {
384 return(undef);
385 }
386}
387
388#create a user without validating _any_ data.
389
390#To be used only on database init.
391# We can't localize here because it's before we _have_ a loc framework
392
393sub _BootstrapCreate {
394 my $self = shift;
395 my %args = (@_);
396
397 $args{'Password'} = '*NO-PASSWORD*';
398
399
400 $RT::Handle->BeginTransaction();
401
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);
407
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' );
413 }
414 $self->SUPER::Create(id => $principal_id, %args);
415 my $id = $self->Id;
416 #If the create failed.
417 unless ($id) {
418 $RT::Handle->Rollback();
419 return ( 0, 'Could not create user' ) ; #never loc this
420 }
421
422 my $aclstash = RT::Group->new($self->CurrentUser);
423 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
424
425 unless ($stash_id) {
426 $RT::Handle->Rollback();
427 $RT::Logger->crit("Couldn't stash the user in groupmembers");
428 return ( 0, $self->loc('Could not create user') );
429 }
430
431 $RT::Handle->Commit();
432
433 return ( $id, 'User created' );
434}
435
436sub Delete {
437 my $self = shift;
438
439 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
440
441}
442
443=head2 Load
444
445Load a user object from the database. Takes a single argument.
446If the argument is numerical, load by the column 'id'. If a user
447object or its subclass passed then loads the same user by id.
448Otherwise, load by the "Name" column which is the user's textual
449username.
450
451=cut
452
453sub Load {
454 my $self = shift;
455 my $identifier = shift || return undef;
456
457 if ( $identifier !~ /\D/ ) {
458 return $self->SUPER::LoadById( $identifier );
459 } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
460 return $self->SUPER::LoadById( $identifier->Id );
461 } else {
462 return $self->LoadByCol( "Name", $identifier );
463 }
464}
465
466=head2 LoadByEmail
467
468Tries to load this user object from the database by the user's email address.
469
470=cut
471
472sub LoadByEmail {
473 my $self = shift;
474 my $address = shift;
475
476 # Never load an empty address as an email address.
477 unless ($address) {
478 return (undef);
479 }
480
481 $address = $self->CanonicalizeEmailAddress($address);
482
483 #$RT::Logger->debug("Trying to load an email address: $address");
484 return $self->LoadByCol( "EmailAddress", $address );
485}
486
487=head2 LoadOrCreateByEmail ADDRESS
488
489Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
490the provided email address and loads them. Address can be provided either as L<Email::Address> object
491or string which is parsed using the module.
492
493Returns a tuple of the user's id and a status message.
4940 will be returned in place of the user's id in case of failure.
495
496=cut
497
498sub LoadOrCreateByEmail {
499 my $self = shift;
500 my $email = shift;
501
502 my ($message, $name);
503 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
504 ($email, $name) = ($email->address, $email->phrase);
505 } else {
506 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
507 }
508
509 $self->LoadByEmail( $email );
510 $self->Load( $email ) unless $self->Id;
511 $message = $self->loc('User loaded');
512
513 unless( $self->Id ) {
514 my $val;
515 ($val, $message) = $self->Create(
516 Name => $email,
517 EmailAddress => $email,
518 RealName => $name,
519 Privileged => 0,
520 Comments => 'Autocreated when added as a watcher',
521 );
522 unless ( $val ) {
523 # Deal with the race condition of two account creations at once
524 $self->LoadByEmail( $email );
525 unless ( $self->Id ) {
526 sleep 5;
527 $self->LoadByEmail( $email );
528 }
529 if ( $self->Id ) {
530 $RT::Logger->error("Recovered from creation failure due to race condition");
531 $message = $self->loc("User loaded");
532 } else {
533 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
534 }
535 }
536 }
537 return (0, $message) unless $self->id;
538 return ($self->Id, $message);
539}
540
541=head2 ValidateEmailAddress ADDRESS
542
543Returns true if the email address entered is not in use by another user or is
544undef or ''. Returns false if it's in use.
545
546=cut
547
548sub ValidateEmailAddress {
549 my $self = shift;
550 my $Value = shift;
551
552 # if the email address is null, it's always valid
553 return (1) if ( !$Value || $Value eq "" );
554
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 ) );
559 }
560
561
562 my $TempUser = RT::User->new(RT->SystemUser);
563 $TempUser->LoadByEmail($Value);
564
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
570 return (1);
571 }
572}
573
574=head2 SetEmailAddress
575
576Check to make sure someone else isn't using this email address already
577so that a better email address can be returned
578
579=cut
580
581sub SetEmailAddress {
582 my $self = shift;
583 my $Value = shift;
584 $Value = '' unless defined $Value;
585
586 my ($val, $message) = $self->ValidateEmailAddress( $Value );
587 if ( $val ) {
588 return $self->_Set( Field => 'EmailAddress', Value => $Value );
589 } else {
590 return ( 0, $message )
591 }
592
593}
594
595=head2 EmailFrequency
596
597Takes optional Ticket argument in paramhash. Returns 'no email',
598'squelched', 'daily', 'weekly' or empty string depending on
599user preferences.
600
601=over 4
602
603=item 'no email' - user has no email, so can not recieve notifications.
604
605=item 'squelched' - returned only when Ticket argument is provided and
606notifications to the user has been supressed for this ticket.
607
608=item 'daily' - retruned when user recieve daily messages digest instead
609of immediate delivery.
610
611=item 'weekly' - previous, but weekly.
612
613=item empty string returned otherwise.
614
615=back
616
617=cut
618
619sub EmailFrequency {
620 my $self = shift;
621 my %args = (
622 Ticket => undef,
623 @_
624 );
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;
633 return '';
634}
635
636=head2 CanonicalizeEmailAddress ADDRESS
637
638CanonicalizeEmailAddress converts email addresses into canonical form.
639it takes one email address in and returns the proper canonical
640form. You can dump whatever your proper local config is in here. Note
641that it may be called as a static method; in this case the first argument
642is class name not an object.
643
644=cut
645
646sub CanonicalizeEmailAddress {
647 my $self = shift;
648 my $email = shift;
649 # Example: the following rule would treat all email
650 # coming from a subdomain as coming from second level domain
651 # foo.com
652 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
653 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
654 {
655 $email =~ s/$match/$replace/gi;
656 }
657 return ($email);
658}
659
660=head2 CanonicalizeUserInfo HASH of ARGS
661
662CanonicalizeUserInfo can convert all User->Create options.
663it takes a hashref of all the params sent to User->Create and
664returns that same hash, by default nothing is done.
665
666This function is intended to allow users to have their info looked up via
667an outside source and modified upon creation.
668
669=cut
670
671sub CanonicalizeUserInfo {
672 my $self = shift;
673 my $args = shift;
674 my $success = 1;
675
676 return ($success);
677}
678
679
680=head2 Password and authentication related functions
681
682=head3 SetRandomPassword
683
684Takes no arguments. Returns a status code and a new password or an error message.
685If the status is 1, the second value returned is the new password.
686If the status is anything else, the new value returned is the error code.
687
688=cut
689
690sub SetRandomPassword {
691 my $self = shift;
692
693 unless ( $self->CurrentUserCanModify('Password') ) {
694 return ( 0, $self->loc("Permission Denied") );
695 }
696
697
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);
700
701 my $pass = $self->GenerateRandomPassword( $min, $max) ;
702
703 # If we have "notify user on
704
705 my ( $val, $msg ) = $self->SetPassword($pass);
706
707 #If we got an error return the error.
708 return ( 0, $msg ) unless ($val);
709
710 #Otherwise, we changed the password, lets return it.
711 return ( 1, $pass );
712
713}
714
715=head3 ResetPassword
716
717Returns status, [ERROR or new password]. Resets this user's password to
718a randomly generated pronouncable password and emails them, using a
719global template called "PasswordChange".
720
721This function is currently unused in the UI, but available for local scripts.
722
723=cut
724
725sub ResetPassword {
726 my $self = shift;
727
728 unless ( $self->CurrentUserCanModify('Password') ) {
729 return ( 0, $self->loc("Permission Denied") );
730 }
731 my ( $status, $pass ) = $self->SetRandomPassword();
732
733 unless ($status) {
734 return ( 0, "$pass" );
735 }
736
737 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
738 To => $self->EmailAddress,
739 Template => 'PasswordChange',
740 Arguments => {
741 NewPassword => $pass,
742 },
743 );
744
745 if ($ret) {
746 return ( 1, $self->loc('New password notification sent') );
747 } else {
748 return ( 0, $self->loc('Notification could not be sent') );
749 }
750
751}
752
753=head3 GenerateRandomPassword MIN_LEN and MAX_LEN
754
755Returns a random password between MIN_LEN and MAX_LEN characters long.
756
757=cut
758
759sub GenerateRandomPassword {
760 my $self = shift; # just to drop it
761 return Text::Password::Pronounceable->generate(@_);
762}
763
764sub SafeSetPassword {
765 my $self = shift;
766 my %args = (
767 Current => undef,
768 New => undef,
769 Confirmation => undef,
770 @_,
771 );
772 return (1) unless defined $args{'New'} && length $args{'New'};
773
774 my %cond = $self->CurrentUserRequireToSetPassword;
775
776 unless ( $cond{'CanSet'} ) {
777 return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
778 }
779
780 my $error = '';
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.");
784 } else {
785 $error = $self->loc("Please enter your current password.");
786 }
787 } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
788 $error = $self->loc("Passwords do not match.");
789 }
790
791 if ( $error ) {
792 $error .= ' '. $self->loc('Password has not been set.');
793 return (0, $error);
794 }
795
796 return $self->SetPassword( $args{'New'} );
797}
798
799=head3 SetPassword
800
801Takes a string. Checks the string's length and sets this user's password
802to that string.
803
804=cut
805
806sub SetPassword {
807 my $self = shift;
808 my $password = shift;
809
810 unless ( $self->CurrentUserCanModify('Password') ) {
811 return ( 0, $self->loc('Password: Permission Denied') );
812 }
813
814 if ( !$password ) {
815 return ( 0, $self->loc("No password set") );
816 } else {
817 my ($val, $msg) = $self->ValidatePassword($password);
818 return ($val, $msg) if !$val;
819
820 my $new = !$self->HasPassword;
821 $password = $self->_GeneratePassword($password);
822
823 ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password);
824 if ($val) {
825 return ( 1, $self->loc("Password set") ) if $new;
826 return ( 1, $self->loc("Password changed") );
827 } else {
828 return ( $val, $msg );
829 }
830 }
831
832}
833
834sub _GeneratePassword_sha512 {
835 my $self = shift;
836 my ($password, $salt) = @_;
837
838 # Generate a 16-character base64 salt
839 unless ($salt) {
840 $salt = "";
841 $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64]
842 for 1..16;
843 }
844
845 my $sha = Digest::SHA->new(512);
846 $sha->add($salt);
847 $sha->add(encode_utf8($password));
848 return join("!", "", "sha512", $salt, $sha->b64digest);
849}
850
851=head3 _GeneratePassword PASSWORD [, SALT]
852
853Returns a string to store in the database. This string takes the form:
854
855 !method!salt!hash
856
857By default, the method is currently C<sha512>.
858
859=cut
860
861sub _GeneratePassword {
862 my $self = shift;
863 return $self->_GeneratePassword_sha512(@_);
864}
865
866=head3 HasPassword
867
868Returns true if the user has a valid password, otherwise returns false.
869
870=cut
871
872sub HasPassword {
873 my $self = shift;
874 my $pwd = $self->__Value('Password');
875 return undef if !defined $pwd
876 || $pwd eq ''
877 || $pwd eq '*NO-PASSWORD*';
878 return 1;
879}
880
881=head3 IsPassword
882
883Returns true if the passed in value is this user's password.
884Returns undef otherwise.
885
886=cut
887
888sub IsPassword {
889 my $self = shift;
890 my $value = shift;
891
892 #TODO there isn't any apparent way to legitimately ACL this
893
894 # RT does not allow null passwords
895 if ( ( !defined($value) ) or ( $value eq '' ) ) {
896 return (undef);
897 }
898
899 if ( $self->PrincipalObj->Disabled ) {
900 $RT::Logger->info(
901 "Disabled user " . $self->Name . " tried to log in" );
902 return (undef);
903 }
904
905 unless ($self->HasPassword) {
906 return(undef);
907 }
908
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;
915 } else {
916 $RT::Logger->warn("Unknown hash method $method");
917 return 0;
918 }
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) {
926 # Hex nonsalted-md5
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) {
932 # crypt() output
933 return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
934 } else {
935 $RT::Logger->warn("Unknown password form");
936 return 0;
937 }
938
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) );
943 return 1;
944}
945
946sub CurrentUserRequireToSetPassword {
947 my $self = shift;
948
949 my %res = (
950 CanSet => 1,
951 Reason => '',
952 RequireCurrent => 1,
953 );
954
955 if ( RT->Config->Get('WebExternalAuth')
956 && !RT->Config->Get('WebFallbackToInternalAuth')
957 ) {
958 $res{'CanSet'} = 0;
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;
964 } else {
965 $res{'CanSet'} = 0;
966 $res{'Reason'} = $self->loc("Your password is not set.");
967 }
968 }
969
970 return %res;
971}
972
973=head3 AuthToken
974
975Returns an authentication string associated with the user. This
976string can be used to generate passwordless URLs to integrate
977RT with services and programms like callendar managers, rss
978readers and other.
979
980=cut
981
982sub AuthToken {
983 my $self = shift;
984 my $secret = $self->_Value( AuthToken => @_ );
985 return $secret if $secret;
986
987 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
988
989 my $tmp = RT::User->new( RT->SystemUser );
990 $tmp->Load( $self->id );
991 my ($status, $msg) = $tmp->SetAuthToken( $secret );
992 unless ( $status ) {
993 $RT::Logger->error( "Couldn't set auth token: $msg" );
994 return undef;
995 }
996 return $secret;
997}
998
999=head3 GenerateAuthToken
1000
1001Generate a random authentication string for the user.
1002
1003=cut
1004
1005sub GenerateAuthToken {
1006 my $self = shift;
1007 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1008 return $self->SetAuthToken( $token );
1009}
1010
1011=head3 GenerateAuthString
1012
1013Takes a string and returns back a hex hash string. Later you can use
1014this pair to make sure it's generated by this user using L</ValidateAuthString>
1015
1016=cut
1017
1018sub GenerateAuthString {
1019 my $self = shift;
1020 my $protect = shift;
1021
1022 my $str = $self->AuthToken . $protect;
1023 utf8::encode($str);
1024
1025 return substr(Digest::MD5::md5_hex($str),0,16);
1026}
1027
1028=head3 ValidateAuthString
1029
1030Takes auth string and protected string. Returns true is protected string
1031has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1032
1033=cut
1034
1035sub ValidateAuthString {
1036 my $self = shift;
1037 my $auth_string = shift;
1038 my $protected = shift;
1039
1040 my $str = $self->AuthToken . $protected;
1041 utf8::encode( $str );
1042
1043 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1044}
1045
1046=head2 SetDisabled
1047
1048Toggles the user's disabled flag.
1049If this flag is
1050set, all password checks for this user will fail. All ACL checks for this
1051user will fail. The user will appear in no user listings.
1052
1053=cut
1054
1055sub SetDisabled {
1056 my $self = shift;
1057 my $val = shift;
1058 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1059 return (0, $self->loc('Permission Denied'));
1060 }
1061
1062 $RT::Handle->BeginTransaction();
1063 my $set_err = $self->PrincipalObj->SetDisabled($val);
1064 unless ($set_err) {
1065 $RT::Handle->Rollback();
1066 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1067 return (undef);
1068 }
1069 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1070
1071 $RT::Handle->Commit();
1072
1073 if ( $val == 1 ) {
1074 return (1, $self->loc("User disabled"));
1075 } else {
1076 return (1, $self->loc("User enabled"));
1077 }
1078
1079}
1080
1081=head2 Disabled
1082
1083Returns true if user is disabled or false otherwise
1084
1085=cut
1086
1087sub Disabled {
1088 my $self = shift;
1089 return $self->PrincipalObj->Disabled(@_);
1090}
1091
1092=head2 PrincipalObj
1093
1094Returns the principal object for this user. returns an empty RT::Principal
1095if there's no principal object matching this user.
1096The response is cached. PrincipalObj should never ever change.
1097
1098=cut
1099
1100sub PrincipalObj {
1101 my $self = shift;
1102
1103 unless ( $self->id ) {
1104 $RT::Logger->error("Couldn't get principal for an empty user");
1105 return undef;
1106 }
1107
1108 if ( !$self->{_principal_obj} ) {
1109
1110 my $obj = RT::Principal->new( $self->CurrentUser );
1111 $obj->LoadById( $self->id );
1112 if (! $obj->id ) {
1113 $RT::Logger->crit( 'No principal for user #' . $self->id );
1114 return undef;
1115 } elsif ( $obj->PrincipalType ne 'User' ) {
1116 $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' );
1117 return undef;
1118 }
1119 $self->{_principal_obj} = $obj;
1120 }
1121 return $self->{_principal_obj};
1122}
1123
1124
1125=head2 PrincipalId
1126
1127Returns this user's PrincipalId
1128
1129=cut
1130
1131sub PrincipalId {
1132 my $self = shift;
1133 return $self->Id;
1134}
1135
1136=head2 HasGroupRight
1137
1138Takes a paramhash which can contain
1139these items:
1140 GroupObj => RT::Group or Group => integer
1141 Right => 'Right'
1142
1143
1144Returns 1 if this user has the right specified in the paramhash for the Group
1145passed in.
1146
1147Returns undef if they don't.
1148
1149=cut
1150
1151sub HasGroupRight {
1152 my $self = shift;
1153 my %args = (
1154 GroupObj => undef,
1155 Group => undef,
1156 Right => undef,
1157 @_
1158 );
1159
1160
1161 if ( defined $args{'Group'} ) {
1162 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1163 $args{'GroupObj'}->Load( $args{'Group'} );
1164 }
1165
1166 # Validate and load up the GroupId
1167 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1168 return undef;
1169 }
1170
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'},
1175 );
1176
1177 return ($retval);
1178}
1179
1180=head2 OwnGroups
1181
1182Returns a group collection object containing the groups of which this
1183user is a member.
1184
1185=cut
1186
1187sub OwnGroups {
1188 my $self = shift;
1189 my $groups = RT::Groups->new($self->CurrentUser);
1190 $groups->LimitToUserDefinedGroups;
1191 $groups->WithMember(
1192 PrincipalId => $self->Id,
1193 Recursively => 1
1194 );
1195 return $groups;
1196}
1197
1198=head2 HasRight
1199
1200Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1201
1202=cut
1203
1204sub HasRight {
1205 my $self = shift;
1206 return $self->PrincipalObj->HasRight(@_);
1207}
1208
1209=head2 CurrentUserCanSee [FIELD]
1210
1211Returns true if the current user can see the user, based on if it is
1212public, ourself, or we have AdminUsers
1213
1214=cut
1215
1216sub CurrentUserCanSee {
1217 my $self = shift;
1218 my ($what) = @_;
1219
1220 # If it's public, fine. Note that $what may be "transaction", which
1221 # doesn't have an Accessible value, and thus falls through below.
1222 if ( $self->_Accessible( $what, 'public' ) ) {
1223 return 1;
1224 }
1225
1226 # Users can see their own properties
1227 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
1228 return 1;
1229 }
1230
1231 # If the user has the admin users right, that's also enough
1232 elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
1233 return 1;
1234 }
1235 else {
1236 return 0;
1237 }
1238}
1239
1240=head2 CurrentUserCanModify RIGHT
1241
1242If the user has rights for this object, either because
1243he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an
1244admin right) 'ModifySelf', return 1. otherwise, return undef.
1245
1246=cut
1247
1248sub CurrentUserCanModify {
1249 my $self = shift;
1250 my $field = shift;
1251
1252 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1253 return (1);
1254 }
1255
1256 #If the field is marked as an "administrators only" field,
1257 # don't let the user touch it.
1258 elsif ( $self->_Accessible( $field, 'admin' ) ) {
1259 return (undef);
1260 }
1261
1262 #If the current user is trying to modify themselves
1263 elsif ( ( $self->id == $self->CurrentUser->id )
1264 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1265 {
1266 return (1);
1267 }
1268
1269 #If we don't have a good reason to grant them rights to modify
1270 # by now, they lose
1271 else {
1272 return (undef);
1273 }
1274
1275}
1276
1277=head2 CurrentUserHasRight
1278
1279Takes a single argument. returns 1 if $Self->CurrentUser
1280has the requested right. returns undef otherwise
1281
1282=cut
1283
1284sub CurrentUserHasRight {
1285 my $self = shift;
1286 my $right = shift;
1287
1288 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1289}
1290
1291sub _PrefName {
1292 my $name = shift;
1293 if (ref $name) {
1294 $name = ref($name).'-'.$name->Id;
1295 }
1296
1297 return 'Pref-'.$name;
1298}
1299
1300=head2 Preferences NAME/OBJ DEFAULT
1301
1302Obtain user preferences associated with given object or name.
1303Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1304override the entries with user preferences.
1305
1306=cut
1307
1308sub Preferences {
1309 my $self = shift;
1310 my $name = _PrefName (shift);
1311 my $default = shift;
1312
1313 my $attr = RT::Attribute->new( $self->CurrentUser );
1314 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1315
1316 my $content = $attr->Id ? $attr->Content : undef;
1317 unless ( ref $content eq 'HASH' ) {
1318 return defined $content ? $content : $default;
1319 }
1320
1321 if (ref $default eq 'HASH') {
1322 for (keys %$default) {
1323 exists $content->{$_} or $content->{$_} = $default->{$_};
1324 }
1325 } elsif (defined $default) {
1326 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1327 }
1328 return $content;
1329}
1330
1331=head2 SetPreferences NAME/OBJ VALUE
1332
1333Set user preferences associated with given object or name.
1334
1335=cut
1336
1337sub SetPreferences {
1338 my $self = shift;
1339 my $name = _PrefName( shift );
1340 my $value = shift;
1341
1342 return (0, $self->loc("No permission to set preferences"))
1343 unless $self->CurrentUserCanModify('Preferences');
1344
1345 my $attr = RT::Attribute->new( $self->CurrentUser );
1346 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1347 if ( $attr->Id ) {
1348 my ($ok, $msg) = $attr->SetContent( $value );
1349 return (1, "No updates made")
1350 if $msg eq "That is already the current value";
1351 return ($ok, $msg);
1352 } else {
1353 return $self->AddAttribute( Name => $name, Content => $value );
1354 }
1355}
1356
1357=head2 Stylesheet
1358
1359Returns a list of valid stylesheets take from preferences.
1360
1361=cut
1362
1363sub Stylesheet {
1364 my $self = shift;
1365
1366 my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
1367
1368 if (RT::Interface::Web->ComponentPathIsSafe($style)) {
1369 my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
1370
1371 for my $css_path (@css_paths) {
1372 if (-d "$css_path/$style") {
1373 return $style
1374 }
1375 }
1376 }
1377
1378 # Fall back to the system stylesheet.
1379 return RT->Config->Get('WebDefaultStylesheet');
1380}
1381
1382=head2 WatchedQueues ROLE_LIST
1383
1384Returns a RT::Queues object containing every queue watched by the user.
1385
1386Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1387
1388$user->WatchedQueues('Cc', 'AdminCc');
1389
1390=cut
1391
1392sub WatchedQueues {
1393
1394 my $self = shift;
1395 my @roles = @_ || ('Cc', 'AdminCc');
1396
1397 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1398
1399 my $watched_queues = RT::Queues->new($self->CurrentUser);
1400
1401 my $group_alias = $watched_queues->Join(
1402 ALIAS1 => 'main',
1403 FIELD1 => 'id',
1404 TABLE2 => 'Groups',
1405 FIELD2 => 'Instance',
1406 );
1407
1408 $watched_queues->Limit(
1409 ALIAS => $group_alias,
1410 FIELD => 'Domain',
1411 VALUE => 'RT::Queue-Role',
1412 ENTRYAGGREGATOR => 'AND',
1413 );
1414 if (grep { $_ eq 'Cc' } @roles) {
1415 $watched_queues->Limit(
1416 SUBCLAUSE => 'LimitToWatchers',
1417 ALIAS => $group_alias,
1418 FIELD => 'Type',
1419 VALUE => 'Cc',
1420 ENTRYAGGREGATOR => 'OR',
1421 );
1422 }
1423 if (grep { $_ eq 'AdminCc' } @roles) {
1424 $watched_queues->Limit(
1425 SUBCLAUSE => 'LimitToWatchers',
1426 ALIAS => $group_alias,
1427 FIELD => 'Type',
1428 VALUE => 'AdminCc',
1429 ENTRYAGGREGATOR => 'OR',
1430 );
1431 }
1432
1433 my $queues_alias = $watched_queues->Join(
1434 ALIAS1 => $group_alias,
1435 FIELD1 => 'id',
1436 TABLE2 => 'CachedGroupMembers',
1437 FIELD2 => 'GroupId',
1438 );
1439 $watched_queues->Limit(
1440 ALIAS => $queues_alias,
1441 FIELD => 'MemberId',
1442 VALUE => $self->PrincipalId,
1443 );
1444 $watched_queues->Limit(
1445 ALIAS => $queues_alias,
1446 FIELD => 'Disabled',
1447 VALUE => 0,
1448 );
1449
1450
1451 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1452
1453 return $watched_queues;
1454
1455}
1456
1457sub _Set {
1458 my $self = shift;
1459
1460 my %args = (
1461 Field => undef,
1462 Value => undef,
1463 TransactionType => 'Set',
1464 RecordTransaction => 1,
1465 @_
1466 );
1467
1468 # Nobody is allowed to futz with RT_System or Nobody
1469
1470 if ( ($self->Id == RT->SystemUser->Id ) ||
1471 ($self->Id == RT->Nobody->Id)) {
1472 return ( 0, $self->loc("Can not modify system users") );
1473 }
1474 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1475 return ( 0, $self->loc("Permission Denied") );
1476 }
1477
1478 my $Old = $self->SUPER::_Value("$args{'Field'}");
1479
1480 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1481 Value => $args{'Value'} );
1482
1483 #If we can't actually set the field to the value, don't record
1484 # a transaction. instead, get out of here.
1485 if ( $ret == 0 ) { return ( 0, $msg ); }
1486
1487 if ( $args{'RecordTransaction'} == 1 ) {
1488 if ($args{'Field'} eq "Password") {
1489 $args{'Value'} = $Old = '********';
1490 }
1491 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1492 Type => $args{'TransactionType'},
1493 Field => $args{'Field'},
1494 NewValue => $args{'Value'},
1495 OldValue => $Old,
1496 TimeTaken => $args{'TimeTaken'},
1497 );
1498 return ( $Trans, scalar $TransObj->BriefDescription );
1499 } else {
1500 return ( $ret, $msg );
1501 }
1502}
1503
1504=head2 _Value
1505
1506Takes the name of a table column.
1507Returns its value as a string, if the user passes an ACL check
1508
1509=cut
1510
1511sub _Value {
1512
1513 my $self = shift;
1514 my $field = shift;
1515
1516 # Defer to the abstraction above to know if the field can be read
1517 return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
1518 return undef;
1519}
1520
1521=head2 FriendlyName
1522
1523Return the friendly name
1524
1525=cut
1526
1527sub FriendlyName {
1528 my $self = shift;
1529 return $self->RealName if defined($self->RealName);
1530 return $self->Name if defined($self->Name);
1531 return "";
1532}
1533
1534=head2 PreferredKey
1535
1536Returns the preferred key of the user. If none is set, then this will query
1537GPG and set the preferred key to the maximally trusted key found (and then
1538return it). Returns C<undef> if no preferred key can be found.
1539
1540=cut
1541
1542sub PreferredKey
1543{
1544 my $self = shift;
1545 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
1546
1547 if ( ($self->CurrentUser->Id != $self->Id ) &&
1548 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1549 return undef;
1550 }
1551
1552
1553
1554 my $prefkey = $self->FirstAttribute('PreferredKey');
1555 return $prefkey->Content if $prefkey;
1556
1557 # we don't have a preferred key for this user, so now we must query GPG
1558 require RT::Crypt::GnuPG;
1559 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
1560 return undef unless defined $res{'info'};
1561 my @keys = @{ $res{'info'} };
1562 return undef if @keys == 0;
1563
1564 if (@keys == 1) {
1565 $prefkey = $keys[0]->{'Fingerprint'};
1566 } else {
1567 # prefer the maximally trusted key
1568 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
1569 $prefkey = $keys[0]->{'Fingerprint'};
1570 }
1571
1572 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
1573 return $prefkey;
1574}
1575
1576sub PrivateKey {
1577 my $self = shift;
1578
1579
1580 #If the user wants to see their own values, let them.
1581 #If the user is an admin, let them.
1582 #Otherwwise, don't let them.
1583 #
1584 if ( ($self->CurrentUser->Id != $self->Id ) &&
1585 !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1586 return undef;
1587 }
1588
1589 my $key = $self->FirstAttribute('PrivateKey') or return undef;
1590 return $key->Content;
1591}
1592
1593sub SetPrivateKey {
1594 my $self = shift;
1595 my $key = shift;
1596
1597 unless ($self->CurrentUserCanModify('PrivateKey')) {
1598 return (0, $self->loc("Permission Denied"));
1599 }
1600
1601 unless ( $key ) {
1602 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
1603 unless ( $status ) {
1604 $RT::Logger->error( "Couldn't delete attribute: $msg" );
1605 return ($status, $self->loc("Couldn't unset private key"));
1606 }
1607 return ($status, $self->loc("Unset private key"));
1608 }
1609
1610 # check that it's really private key
1611 {
1612 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
1613 return (0, $self->loc("No such key or it's not suitable for signing"))
1614 if $tmp{'exit_code'} || !$tmp{'info'};
1615 }
1616
1617 my ($status, $msg) = $self->SetAttribute(
1618 Name => 'PrivateKey',
1619 Content => $key,
1620 );
1621 return ($status, $self->loc("Couldn't set private key"))
1622 unless $status;
1623 return ($status, $self->loc("Set private key"));
1624}
1625
1626sub BasicColumns {
1627 (
1628 [ Name => 'Username' ],
1629 [ EmailAddress => 'Email' ],
1630 [ RealName => 'Name' ],
1631 [ Organization => 'Organization' ],
1632 );
1633}
1634
1635=head2 Create PARAMHASH
1636
1637Create takes a hash of values and creates a row in the database:
1638
1639 varchar(200) 'Name'.
1640 varbinary(256) 'Password'.
1641 varchar(16) 'AuthToken'.
1642 text 'Comments'.
1643 text 'Signature'.
1644 varchar(120) 'EmailAddress'.
1645 text 'FreeformContactInfo'.
1646 varchar(200) 'Organization'.
1647 varchar(120) 'RealName'.
1648 varchar(16) 'NickName'.
1649 varchar(16) 'Lang'.
1650 varchar(16) 'EmailEncoding'.
1651 varchar(16) 'WebEncoding'.
1652 varchar(100) 'ExternalContactInfoId'.
1653 varchar(30) 'ContactInfoSystem'.
1654 varchar(100) 'ExternalAuthId'.
1655 varchar(30) 'AuthSystem'.
1656 varchar(16) 'Gecos'.
1657 varchar(30) 'HomePhone'.
1658 varchar(30) 'WorkPhone'.
1659 varchar(30) 'MobilePhone'.
1660 varchar(30) 'PagerPhone'.
1661 varchar(200) 'Address1'.
1662 varchar(200) 'Address2'.
1663 varchar(100) 'City'.
1664 varchar(100) 'State'.
1665 varchar(16) 'Zip'.
1666 varchar(50) 'Country'.
1667 varchar(50) 'Timezone'.
1668 text 'PGPKey'.
1669
1670=cut
1671
1672
1673
1674
1675=head2 id
1676
1677Returns the current value of id.
1678(In the database, id is stored as int(11).)
1679
1680
1681=cut
1682
1683
1684=head2 Name
1685
1686Returns the current value of Name.
1687(In the database, Name is stored as varchar(200).)
1688
1689
1690
1691=head2 SetName VALUE
1692
1693
1694Set Name to VALUE.
1695Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1696(In the database, Name will be stored as a varchar(200).)
1697
1698
1699=cut
1700
1701
1702=head2 Password
1703
1704Returns the current value of Password.
1705(In the database, Password is stored as varchar(256).)
1706
1707
1708
1709=head2 SetPassword VALUE
1710
1711
1712Set Password to VALUE.
1713Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1714(In the database, Password will be stored as a varchar(256).)
1715
1716
1717=cut
1718
1719
1720=head2 AuthToken
1721
1722Returns the current value of AuthToken.
1723(In the database, AuthToken is stored as varchar(16).)
1724
1725
1726
1727=head2 SetAuthToken VALUE
1728
1729
1730Set AuthToken to VALUE.
1731Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1732(In the database, AuthToken will be stored as a varchar(16).)
1733
1734
1735=cut
1736
1737
1738=head2 Comments
1739
1740Returns the current value of Comments.
1741(In the database, Comments is stored as text.)
1742
1743
1744
1745=head2 SetComments VALUE
1746
1747
1748Set Comments to VALUE.
1749Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1750(In the database, Comments will be stored as a text.)
1751
1752
1753=cut
1754
1755
1756=head2 Signature
1757
1758Returns the current value of Signature.
1759(In the database, Signature is stored as text.)
1760
1761
1762
1763=head2 SetSignature VALUE
1764
1765
1766Set Signature to VALUE.
1767Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1768(In the database, Signature will be stored as a text.)
1769
1770
1771=cut
1772
1773
1774=head2 EmailAddress
1775
1776Returns the current value of EmailAddress.
1777(In the database, EmailAddress is stored as varchar(120).)
1778
1779
1780
1781=head2 SetEmailAddress VALUE
1782
1783
1784Set EmailAddress to VALUE.
1785Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1786(In the database, EmailAddress will be stored as a varchar(120).)
1787
1788
1789=cut
1790
1791
1792=head2 FreeformContactInfo
1793
1794Returns the current value of FreeformContactInfo.
1795(In the database, FreeformContactInfo is stored as text.)
1796
1797
1798
1799=head2 SetFreeformContactInfo VALUE
1800
1801
1802Set FreeformContactInfo to VALUE.
1803Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1804(In the database, FreeformContactInfo will be stored as a text.)
1805
1806
1807=cut
1808
1809
1810=head2 Organization
1811
1812Returns the current value of Organization.
1813(In the database, Organization is stored as varchar(200).)
1814
1815
1816
1817=head2 SetOrganization VALUE
1818
1819
1820Set Organization to VALUE.
1821Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1822(In the database, Organization will be stored as a varchar(200).)
1823
1824
1825=cut
1826
1827
1828=head2 RealName
1829
1830Returns the current value of RealName.
1831(In the database, RealName is stored as varchar(120).)
1832
1833
1834
1835=head2 SetRealName VALUE
1836
1837
1838Set RealName to VALUE.
1839Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1840(In the database, RealName will be stored as a varchar(120).)
1841
1842
1843=cut
1844
1845
1846=head2 NickName
1847
1848Returns the current value of NickName.
1849(In the database, NickName is stored as varchar(16).)
1850
1851
1852
1853=head2 SetNickName VALUE
1854
1855
1856Set NickName to VALUE.
1857Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1858(In the database, NickName will be stored as a varchar(16).)
1859
1860
1861=cut
1862
1863
1864=head2 Lang
1865
1866Returns the current value of Lang.
1867(In the database, Lang is stored as varchar(16).)
1868
1869
1870
1871=head2 SetLang VALUE
1872
1873
1874Set Lang to VALUE.
1875Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1876(In the database, Lang will be stored as a varchar(16).)
1877
1878
1879=cut
1880
1881
1882=head2 EmailEncoding
1883
1884Returns the current value of EmailEncoding.
1885(In the database, EmailEncoding is stored as varchar(16).)
1886
1887
1888
1889=head2 SetEmailEncoding VALUE
1890
1891
1892Set EmailEncoding to VALUE.
1893Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1894(In the database, EmailEncoding will be stored as a varchar(16).)
1895
1896
1897=cut
1898
1899
1900=head2 WebEncoding
1901
1902Returns the current value of WebEncoding.
1903(In the database, WebEncoding is stored as varchar(16).)
1904
1905
1906
1907=head2 SetWebEncoding VALUE
1908
1909
1910Set WebEncoding to VALUE.
1911Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1912(In the database, WebEncoding will be stored as a varchar(16).)
1913
1914
1915=cut
1916
1917
1918=head2 ExternalContactInfoId
1919
1920Returns the current value of ExternalContactInfoId.
1921(In the database, ExternalContactInfoId is stored as varchar(100).)
1922
1923
1924
1925=head2 SetExternalContactInfoId VALUE
1926
1927
1928Set ExternalContactInfoId to VALUE.
1929Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1930(In the database, ExternalContactInfoId will be stored as a varchar(100).)
1931
1932
1933=cut
1934
1935
1936=head2 ContactInfoSystem
1937
1938Returns the current value of ContactInfoSystem.
1939(In the database, ContactInfoSystem is stored as varchar(30).)
1940
1941
1942
1943=head2 SetContactInfoSystem VALUE
1944
1945
1946Set ContactInfoSystem to VALUE.
1947Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1948(In the database, ContactInfoSystem will be stored as a varchar(30).)
1949
1950
1951=cut
1952
1953
1954=head2 ExternalAuthId
1955
1956Returns the current value of ExternalAuthId.
1957(In the database, ExternalAuthId is stored as varchar(100).)
1958
1959
1960
1961=head2 SetExternalAuthId VALUE
1962
1963
1964Set ExternalAuthId to VALUE.
1965Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1966(In the database, ExternalAuthId will be stored as a varchar(100).)
1967
1968
1969=cut
1970
1971
1972=head2 AuthSystem
1973
1974Returns the current value of AuthSystem.
1975(In the database, AuthSystem is stored as varchar(30).)
1976
1977
1978
1979=head2 SetAuthSystem VALUE
1980
1981
1982Set AuthSystem to VALUE.
1983Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1984(In the database, AuthSystem will be stored as a varchar(30).)
1985
1986
1987=cut
1988
1989
1990=head2 Gecos
1991
1992Returns the current value of Gecos.
1993(In the database, Gecos is stored as varchar(16).)
1994
1995
1996
1997=head2 SetGecos VALUE
1998
1999
2000Set Gecos to VALUE.
2001Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2002(In the database, Gecos will be stored as a varchar(16).)
2003
2004
2005=cut
2006
2007
2008=head2 HomePhone
2009
2010Returns the current value of HomePhone.
2011(In the database, HomePhone is stored as varchar(30).)
2012
2013
2014
2015=head2 SetHomePhone VALUE
2016
2017
2018Set HomePhone to VALUE.
2019Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2020(In the database, HomePhone will be stored as a varchar(30).)
2021
2022
2023=cut
2024
2025
2026=head2 WorkPhone
2027
2028Returns the current value of WorkPhone.
2029(In the database, WorkPhone is stored as varchar(30).)
2030
2031
2032
2033=head2 SetWorkPhone VALUE
2034
2035
2036Set WorkPhone to VALUE.
2037Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2038(In the database, WorkPhone will be stored as a varchar(30).)
2039
2040
2041=cut
2042
2043
2044=head2 MobilePhone
2045
2046Returns the current value of MobilePhone.
2047(In the database, MobilePhone is stored as varchar(30).)
2048
2049
2050
2051=head2 SetMobilePhone VALUE
2052
2053
2054Set MobilePhone to VALUE.
2055Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2056(In the database, MobilePhone will be stored as a varchar(30).)
2057
2058
2059=cut
2060
2061
2062=head2 PagerPhone
2063
2064Returns the current value of PagerPhone.
2065(In the database, PagerPhone is stored as varchar(30).)
2066
2067
2068
2069=head2 SetPagerPhone VALUE
2070
2071
2072Set PagerPhone to VALUE.
2073Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2074(In the database, PagerPhone will be stored as a varchar(30).)
2075
2076
2077=cut
2078
2079
2080=head2 Address1
2081
2082Returns the current value of Address1.
2083(In the database, Address1 is stored as varchar(200).)
2084
2085
2086
2087=head2 SetAddress1 VALUE
2088
2089
2090Set Address1 to VALUE.
2091Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2092(In the database, Address1 will be stored as a varchar(200).)
2093
2094
2095=cut
2096
2097
2098=head2 Address2
2099
2100Returns the current value of Address2.
2101(In the database, Address2 is stored as varchar(200).)
2102
2103
2104
2105=head2 SetAddress2 VALUE
2106
2107
2108Set Address2 to VALUE.
2109Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2110(In the database, Address2 will be stored as a varchar(200).)
2111
2112
2113=cut
2114
2115
2116=head2 City
2117
2118Returns the current value of City.
2119(In the database, City is stored as varchar(100).)
2120
2121
2122
2123=head2 SetCity VALUE
2124
2125
2126Set City to VALUE.
2127Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2128(In the database, City will be stored as a varchar(100).)
2129
2130
2131=cut
2132
2133
2134=head2 State
2135
2136Returns the current value of State.
2137(In the database, State is stored as varchar(100).)
2138
2139
2140
2141=head2 SetState VALUE
2142
2143
2144Set State to VALUE.
2145Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2146(In the database, State will be stored as a varchar(100).)
2147
2148
2149=cut
2150
2151
2152=head2 Zip
2153
2154Returns the current value of Zip.
2155(In the database, Zip is stored as varchar(16).)
2156
2157
2158
2159=head2 SetZip VALUE
2160
2161
2162Set Zip to VALUE.
2163Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2164(In the database, Zip will be stored as a varchar(16).)
2165
2166
2167=cut
2168
2169
2170=head2 Country
2171
2172Returns the current value of Country.
2173(In the database, Country is stored as varchar(50).)
2174
2175
2176
2177=head2 SetCountry VALUE
2178
2179
2180Set Country to VALUE.
2181Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2182(In the database, Country will be stored as a varchar(50).)
2183
2184
2185=cut
2186
2187
2188=head2 Timezone
2189
2190Returns the current value of Timezone.
2191(In the database, Timezone is stored as varchar(50).)
2192
2193
2194
2195=head2 SetTimezone VALUE
2196
2197
2198Set Timezone to VALUE.
2199Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2200(In the database, Timezone will be stored as a varchar(50).)
2201
2202
2203=cut
2204
2205
2206=head2 PGPKey
2207
2208Returns the current value of PGPKey.
2209(In the database, PGPKey is stored as text.)
2210
2211
2212
2213=head2 SetPGPKey VALUE
2214
2215
2216Set PGPKey to VALUE.
2217Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
2218(In the database, PGPKey will be stored as a text.)
2219
2220
2221=cut
2222
2223
2224=head2 Creator
2225
2226Returns the current value of Creator.
2227(In the database, Creator is stored as int(11).)
2228
2229
2230=cut
2231
2232
2233=head2 Created
2234
2235Returns the current value of Created.
2236(In the database, Created is stored as datetime.)
2237
2238
2239=cut
2240
2241
2242=head2 LastUpdatedBy
2243
2244Returns the current value of LastUpdatedBy.
2245(In the database, LastUpdatedBy is stored as int(11).)
2246
2247
2248=cut
2249
2250
2251=head2 LastUpdated
2252
2253Returns the current value of LastUpdated.
2254(In the database, LastUpdated is stored as datetime.)
2255
2256
2257=cut
2258
2259
2260
2261sub _CoreAccessible {
2262 {
2263
2264 id =>
2265 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
2266 Name =>
2267 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2268 Password =>
2269 {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''},
2270 AuthToken =>
2271 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2272 Comments =>
2273 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2274 Signature =>
2275 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2276 EmailAddress =>
2277 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2278 FreeformContactInfo =>
2279 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2280 Organization =>
2281 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2282 RealName =>
2283 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
2284 NickName =>
2285 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2286 Lang =>
2287 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2288 EmailEncoding =>
2289 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2290 WebEncoding =>
2291 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2292 ExternalContactInfoId =>
2293 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2294 ContactInfoSystem =>
2295 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2296 ExternalAuthId =>
2297 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2298 AuthSystem =>
2299 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2300 Gecos =>
2301 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2302 HomePhone =>
2303 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2304 WorkPhone =>
2305 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2306 MobilePhone =>
2307 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2308 PagerPhone =>
2309 {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''},
2310 Address1 =>
2311 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2312 Address2 =>
2313 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
2314 City =>
2315 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2316 State =>
2317 {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''},
2318 Zip =>
2319 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
2320 Country =>
2321 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2322 Timezone =>
2323 {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''},
2324 PGPKey =>
2325 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
2326 Creator =>
2327 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2328 Created =>
2329 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2330 LastUpdatedBy =>
2331 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
2332 LastUpdated =>
2333 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
2334
2335 }
2336};
2337
2338RT::Base->_ImportOverlays();
2339
2340
23411;