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