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