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