import Net::Whois::Raw into install/5.005 directory *sigh*
[freeside.git] / rt / lib / RT / User_Overlay.pm
1 # BEGIN LICENSE BLOCK
2
3 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
4
5 # (Except where explictly superceded by other copyright notices)
6
7 # This work is made available to you under the terms of Version 2 of
8 # the GNU General Public License. A copy of that license should have
9 # been provided with this software, but in any event can be snarfed
10 # from www.gnu.org.
11
12 # This work is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 # General Public License for more details.
16
17 # Unless otherwise specified, all modifications, corrections or
18 # extensions to this work which alter its source code become the
19 # property of Best Practical Solutions, LLC when submitted for
20 # inclusion in the work.
21
22
23 # END LICENSE BLOCK
24 =head1 NAME
25
26   RT::User - RT User object
27
28 =head1 SYNOPSIS
29
30   use RT::User;
31
32 =head1 DESCRIPTION
33
34
35 =head1 METHODS
36
37 =begin testing
38
39 ok(require RT::User);
40
41 =end testing
42
43
44 =cut
45
46 use strict;
47 no warnings qw(redefine);
48
49 use vars qw(%_USERS_KEY_CACHE);
50
51 %_USERS_KEY_CACHE = ();
52
53 use Digest::MD5;
54 use RT::Principals;
55 use RT::ACE;
56
57
58 # {{{ sub _Accessible 
59
60
61 sub _ClassAccessible {
62     {
63      
64         id =>
65                 {read => 1, type => 'int(11)', default => ''},
66         Name => 
67                 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(120)', default => ''},
68         Password => 
69                 { write => 1, type => 'varchar(40)', default => ''},
70         Comments => 
71                 {read => 1, write => 1, admin => 1, type => 'blob', default => ''},
72         Signature => 
73                 {read => 1, write => 1, type => 'blob', default => ''},
74         EmailAddress => 
75                 {read => 1, write => 1, public => 1,  type => 'varchar(120)', default => ''},
76         FreeformContactInfo => 
77                 {read => 1, write => 1, type => 'blob', default => ''},
78         Organization => 
79                 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(200)', default => ''},
80         RealName => 
81                 {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''},
82         NickName => 
83                 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
84         Lang => 
85                 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
86         EmailEncoding => 
87                 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
88         WebEncoding => 
89                 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
90         ExternalContactInfoId => 
91                 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
92         ContactInfoSystem => 
93                 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(30)', default => ''},
94         ExternalAuthId => 
95                 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
96         AuthSystem => 
97                 {read => 1, write => 1, public => 1, admin => 1,type => 'varchar(30)', default => ''},
98         Gecos => 
99                 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(16)', default => ''},
100
101         PGPKey => {
102                 {read => 1, write => 1, public => 1, admin => 1, type => 'text', default => ''},
103         },
104         HomePhone => 
105                 {read => 1, write => 1, type => 'varchar(30)', default => ''},
106         WorkPhone => 
107                 {read => 1, write => 1, type => 'varchar(30)', default => ''},
108         MobilePhone => 
109                 {read => 1, write => 1, type => 'varchar(30)', default => ''},
110         PagerPhone => 
111                 {read => 1, write => 1, type => 'varchar(30)', default => ''},
112         Address1 => 
113                 {read => 1, write => 1, type => 'varchar(200)', default => ''},
114         Address2 => 
115                 {read => 1, write => 1, type => 'varchar(200)', default => ''},
116         City => 
117                 {read => 1, write => 1, type => 'varchar(100)', default => ''},
118         State => 
119                 {read => 1, write => 1, type => 'varchar(100)', default => ''},
120         Zip => 
121                 {read => 1, write => 1, type => 'varchar(16)', default => ''},
122         Country => 
123                 {read => 1, write => 1, type => 'varchar(50)', default => ''},
124         Creator => 
125                 {read => 1, auto => 1, type => 'int(11)', default => ''},
126         Created => 
127                 {read => 1, auto => 1, type => 'datetime', default => ''},
128         LastUpdatedBy => 
129                 {read => 1, auto => 1, type => 'int(11)', default => ''},
130         LastUpdated => 
131                 {read => 1, auto => 1, type => 'datetime', default => ''},
132
133  }
134 };
135
136
137 # }}}
138
139 # {{{ sub Create 
140
141 =head2 Create { PARAMHASH }
142
143
144 =begin testing
145
146 # Make sure we can create a user
147
148 my $u1 = RT::User->new($RT::SystemUser);
149 is(ref($u1), 'RT::User');
150 my ($id, $msg) = $u1->Create(Name => 'CreateTest1', EmailAddress => 'create-test-1@example.com');
151 ok ($id, "Creating user CreateTest1 - " . $msg );
152
153 # Make sure we can't create a second user with the same name
154 my $u2 = RT::User->new($RT::SystemUser);
155 ($id, $msg) = $u2->Create(Name => 'CreateTest1', EmailAddress => 'create-test-2@example.com');
156 ok (!$id, $msg);
157
158
159 # Make sure we can't create a second user with the same EmailAddress address
160 my $u3 = RT::User->new($RT::SystemUser);
161 ($id, $msg) = $u3->Create(Name => 'CreateTest2', EmailAddress => 'create-test-1@example.com');
162 ok (!$id, $msg);
163
164 # Make sure we can create a user with no EmailAddress address
165 my $u4 = RT::User->new($RT::SystemUser);
166 ($id, $msg) = $u4->Create(Name => 'CreateTest3');
167 ok ($id, $msg);
168
169 # make sure we can create a second user with no EmailAddress address
170 my $u5 = RT::User->new($RT::SystemUser);
171 ($id, $msg) = $u5->Create(Name => 'CreateTest4');
172 ok ($id, $msg);
173
174 # make sure we can create a user with a blank EmailAddress address
175 my $u6 = RT::User->new($RT::SystemUser);
176 ($id, $msg) = $u6->Create(Name => 'CreateTest6', EmailAddress => '');
177 ok ($id, $msg);
178 # make sure we can create a second user with a blankEmailAddress address
179 my $u7 = RT::User->new($RT::SystemUser);
180 ($id, $msg) = $u7->Create(Name => 'CreateTest7', EmailAddress => '');
181 ok ($id, $msg);
182
183 # Can we change the email address away from from "";
184 ($id,$msg) = $u7->SetEmailAddress('foo@bar');
185 ok ($id, $msg);
186 # can we change the address back to "";  
187 ($id,$msg) = $u7->SetEmailAddress('');
188 ok ($id, $msg);
189 is ($u7->EmailAddress, '');
190
191
192 =end testing
193
194 =cut
195
196
197 sub Create {
198     my $self = shift;
199     my %args = (
200         Privileged => 0,
201         Disabled => 0,
202         EmailAddress => '',
203         @_    # get the real argumentlist
204     );
205
206     #Check the ACL
207     unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
208         return ( 0, $self->loc('No permission to create users') );
209     }
210
211     $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
212     # if the user doesn't have a name defined, set it to the email address
213     $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
214
215
216
217     # Privileged is no longer a column in users
218     my $privileged = $args{'Privileged'};
219     delete $args{'Privileged'};
220
221
222     if ($args{'CryptedPassword'} ) {
223         $args{'Password'} = $args{'CryptedPassword'};
224         delete $args{'CryptedPassword'};
225     }
226     elsif ( !$args{'Password'} ) {
227         $args{'Password'} = '*NO-PASSWORD*';
228     }
229     elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) {
230         return ( 0, $self->loc("Password too short") );
231     }
232
233     else {
234         $args{'Password'} = $self->_GeneratePassword($args{'Password'});
235     }
236
237     #TODO Specify some sensible defaults.
238
239     unless ( $args{'Name'} ) {
240         use Data::Dumper;
241         $RT::Logger->crit(Dumper \%args);
242         return ( 0, $self->loc("Must specify 'Name' attribute") );
243     }
244
245     #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
246     if ($RT::SystemUser) {   #This only works if RT::SystemUser has been defined
247         my $TempUser = RT::User->new($RT::SystemUser);
248         $TempUser->Load( $args{'Name'} );
249         return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
250
251         return ( 0, $self->loc('Email address in use') )
252           unless ( $self->ValidateEmailAddress( $args{'EmailAddress'} ) );
253     }
254     else {
255         $RT::Logger->warning( "$self couldn't check for pre-existing users");
256     }
257
258
259     $RT::Handle->BeginTransaction();
260     # Groups deal with principal ids, rather than user ids.
261     # When creating this user, set up a principal Id for it.
262     my $principal = RT::Principal->new($self->CurrentUser);
263     my $principal_id = $principal->Create(PrincipalType => 'User',
264                                 Disabled => $args{'Disabled'},
265                                 ObjectId => '0');
266     $principal->__Set(Field => 'ObjectId', Value => $principal_id);
267     # If we couldn't create a principal Id, get the fuck out.
268     unless ($principal_id) {
269         $RT::Handle->Rollback();
270         $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
271         return ( 0, $self->loc('Could not create user') );
272     }
273
274     delete $args{'Disabled'};
275
276     $self->SUPER::Create(id => $principal_id , %args);
277     my $id = $self->Id;
278
279     #If the create failed.
280     unless ($id) {
281         $RT::Handle->Rollback();
282         $RT::Logger->error("Could not create a new user - " .join('-'. %args));
283
284         return ( 0, $self->loc('Could not create user') );
285     }
286
287
288     #TODO post 2.0
289     #if ($args{'SendWelcomeMessage'}) {
290     #   #TODO: Check if the email exists and looks valid
291     #   #TODO: Send the user a "welcome message" 
292     #}
293
294
295
296     my $aclstash = RT::Group->new($self->CurrentUser);
297     my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
298
299     unless ($stash_id) {
300         $RT::Handle->Rollback();
301         $RT::Logger->crit("Couldn't stash the user in groumembers");
302         return ( 0, $self->loc('Could not create user') );
303     }
304
305     $RT::Handle->Commit;
306
307     #$RT::Logger->debug("Adding the user as a member of everyone"); 
308     my $everyone = RT::Group->new($self->CurrentUser);
309     $everyone->LoadSystemInternalGroup('Everyone');
310     $everyone->AddMember($self->PrincipalId);
311
312     if ($privileged)  {
313         my $priv = RT::Group->new($self->CurrentUser);
314         #$RT::Logger->debug("Making ".$self->Id." a privileged user");
315         $priv->LoadSystemInternalGroup('Privileged');
316         $priv->AddMember($self->PrincipalId);  
317     } else {
318         my $unpriv = RT::Group->new($self->CurrentUser);
319         #$RT::Logger->debug("Making ".$self->Id." an unprivileged user");
320         $unpriv->LoadSystemInternalGroup('Unprivileged');
321         $unpriv->AddMember($self->PrincipalId);  
322     }
323
324
325    #  $RT::Logger->debug("Finished creating the user");
326     return ( $id, $self->loc('User created') );
327 }
328
329 # }}}
330
331
332
333 # {{{ SetPrivileged
334
335 =head2 SetPrivileged BOOL
336
337 If passed a true value, makes this user a member of the "Privileged"  PseudoGroup.
338 Otherwise, makes this user a member of the "Unprivileged" pseudogroup. 
339
340 Returns a standard RT tuple of (val, msg);
341
342 =begin testing
343
344
345 ok(my $user = RT::User->new($RT::SystemUser));
346 ok($user->Load('root'), "Loaded user 'root'");
347 ok($user->Privileged, "User 'root' is privileged");
348 ok(my ($v,$m) = $user->SetPrivileged(0));
349 ok ($v ==1, "Set unprivileged suceeded ($m)");
350 ok(!$user->Privileged, "User 'root' is no longer privileged");
351 ok(my ($v2,$m2) = $user->SetPrivileged(1));
352 ok ($v2 ==1, "Set privileged suceeded ($m2");
353 ok($user->Privileged, "User 'root' is privileged again");
354
355 =end testing
356
357 =cut
358
359 sub SetPrivileged {
360     my $self = shift;
361     my $val = shift;
362
363     my $priv = RT::Group->new($self->CurrentUser);
364     $priv->LoadSystemInternalGroup('Privileged');
365    
366     unless ($priv->Id) {
367         $RT::Logger->crit("Could not find Privileged pseudogroup");
368         return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
369     }
370
371     my $unpriv = RT::Group->new($self->CurrentUser);
372     $unpriv->LoadSystemInternalGroup('Unprivileged');
373     unless ($unpriv->Id) {
374         $RT::Logger->crit("Could not find unprivileged pseudogroup");
375         return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
376     }
377
378     if ($val) {
379         if ($priv->HasMember($self->PrincipalObj)) {
380             #$RT::Logger->debug("That user is already privileged");
381             return (0,$self->loc("That user is already privileged"));
382         }
383         if ($unpriv->HasMember($self->PrincipalObj)) {
384             $unpriv->DeleteMember($self->PrincipalId);
385         } else {
386         # if we had layered transactions, life would be good
387         # sadly, we have to just go ahead, even if something
388         # bogus happened
389             $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
390                 "unprivileged. something is drastically wrong.");
391         }
392         my ($status, $msg) = $priv->AddMember($self->PrincipalId);  
393         if ($status) {
394             return (1, $self->loc("That user is now privileged"));
395         } else {
396             return (0, $msg);
397         }
398     }
399     else {
400         if ($unpriv->HasMember($self->PrincipalObj)) {
401             #$RT::Logger->debug("That user is already unprivileged");
402             return (0,$self->loc("That user is already unprivileged"));
403         }
404         if ($priv->HasMember($self->PrincipalObj)) {
405             $priv->DeleteMember($self->PrincipalId);
406         } else {
407         # if we had layered transactions, life would be good
408         # sadly, we have to just go ahead, even if something
409         # bogus happened
410             $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
411                 "unprivileged. something is drastically wrong.");
412         }
413         my ($status, $msg) = $unpriv->AddMember($self->PrincipalId);  
414         if ($status) {
415             return (1, $self->loc("That user is now unprivileged"));
416         } else {
417             return (0, $msg);
418         }
419     }
420 }
421
422 # }}}
423
424 # {{{ Privileged
425
426 =head2 Privileged
427
428 Returns true if this user is privileged. Returns undef otherwise.
429
430 =cut
431
432 sub Privileged {
433     my $self = shift;
434     my $priv = RT::Group->new($self->CurrentUser);
435     $priv->LoadSystemInternalGroup('Privileged');
436     if ($priv->HasMember($self->PrincipalObj)) {
437         return(1);
438     }
439     else {
440         return(undef);
441     }
442 }
443
444 # }}}
445
446 # {{{ sub _BootstrapCreate 
447
448 #create a user without validating _any_ data.
449
450 #To be used only on database init.
451 # We can't localize here because it's before we _have_ a loc framework
452
453 sub _BootstrapCreate {
454     my $self = shift;
455     my %args = (@_);
456
457     $args{'Password'} = '*NO-PASSWORD*';
458
459
460     $RT::Handle->BeginTransaction(); 
461
462     # Groups deal with principal ids, rather than user ids.
463     # When creating this user, set up a principal Id for it.
464     my $principal = RT::Principal->new($self->CurrentUser);
465     my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
466     $principal->__Set(Field => 'ObjectId', Value => $principal_id);
467    
468     # If we couldn't create a principal Id, get the fuck out.
469     unless ($principal_id) {
470         $RT::Handle->Rollback();
471         $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
472         return ( 0, 'Could not create user' );
473     }
474     $self->SUPER::Create(id => $principal_id, %args);
475     my $id = $self->Id;
476     #If the create failed.
477       unless ($id) {
478       $RT::Handle->Rollback();
479       return ( 0, 'Could not create user' ) ; #never loc this
480     }
481
482     my $aclstash = RT::Group->new($self->CurrentUser);
483     my $stash_id  = $aclstash->_CreateACLEquivalenceGroup($principal);
484
485     unless ($stash_id) {
486         $RT::Handle->Rollback();
487         $RT::Logger->crit("Couldn't stash the user in groupmembers");
488         return ( 0, $self->loc('Could not create user') );
489     }
490
491                                     
492     $RT::Handle->Commit();
493
494     return ( $id, 'User created' );
495 }
496
497 # }}}
498
499 # {{{ sub Delete 
500
501 sub Delete {
502     my $self = shift;
503
504     return ( 0, $self->loc('Deleting this object would violate referential integrity') );
505
506 }
507
508 # }}}
509
510 # {{{ sub Load 
511
512 =head2 Load
513
514 Load a user object from the database. Takes a single argument.
515 If the argument is numerical, load by the column 'id'. Otherwise, load by
516 the "Name" column which is the user's textual username.
517
518 =cut
519
520 sub Load {
521     my $self       = shift;
522     my $identifier = shift || return undef;
523
524     #if it's an int, load by id. otherwise, load by name.
525     if ( $identifier !~ /\D/ ) {
526         $self->SUPER::LoadById($identifier);
527     }
528     else {
529         $self->LoadByCol( "Name", $identifier );
530     }
531 }
532
533 # }}}
534
535 # {{{ sub LoadByEmail
536
537 =head2 LoadByEmail
538
539 Tries to load this user object from the database by the user's email address.
540
541
542 =cut
543
544 sub LoadByEmail {
545     my $self    = shift;
546     my $address = shift;
547
548     # Never load an empty address as an email address.
549     unless ($address) {
550         return (undef);
551     }
552
553     $address = $self->CanonicalizeEmailAddress($address);
554
555     #$RT::Logger->debug("Trying to load an email address: $address\n");
556     return $self->LoadByCol( "EmailAddress", $address );
557 }
558
559 # }}}
560
561 # {{{ LoadOrCreateByEmail 
562
563 =head2 LoadOrCreateByEmail ADDRESS
564
565 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
566 the provided email address. and loads them.
567
568 Returns a tuple of the user's id and a status message.
569 0 will be returned in place of the user's id in case of failure.
570
571 =cut
572
573 sub LoadOrCreateByEmail {
574     my $self = shift;
575     my $email = shift;
576
577         my ($val, $message);
578
579         $self->LoadByEmail($email);
580         $message = $self->loc('User loaded');
581         unless ($self->Id) {
582             ( $val, $message ) = $self->Create(
583                 Name => $email,
584                 EmailAddress => $email,
585                 RealName     => $email,
586                 Privileged   => 0,
587                 Comments     => 'Autocreated when added as a watcher');
588             unless ($val) {
589                 # Deal with the race condition of two account creations at once
590                 $self->LoadByEmail($email);
591                 unless ($self->Id) {
592                     sleep 5;
593                     $self->LoadByEmail($email);
594                 }
595                 if ($self->Id) {
596                     $RT::Logger->error("Recovered from creation failure due to race condition");
597                     $message = $self->loc("User loaded");
598                 }
599                 else {
600                     $RT::Logger->crit("Failed to create user ".$email .": " .$message);
601                 }
602             }
603         }
604
605         if ($self->Id) {
606             return($self->Id, $message);
607         }
608         else {
609             return(0, $message);
610         }
611
612
613     }
614
615 # }}}
616
617 # {{{ sub ValidateEmailAddress
618
619 =head2 ValidateEmailAddress ADDRESS
620
621 Returns true if the email address entered is not in use by another user or is 
622 undef or ''. Returns false if it's in use. 
623
624 =cut
625
626 sub ValidateEmailAddress {
627     my $self  = shift;
628     my $Value = shift;
629
630     # if the email address is null, it's always valid
631     return (1) if ( !$Value || $Value eq "" );
632
633     my $TempUser = RT::User->new($RT::SystemUser);
634     $TempUser->LoadByEmail($Value);
635
636     if ( $TempUser->id && ( $TempUser->id != $self->id ) )
637     {    # if we found a user with that address
638             # it's invalid to set this user's address to it
639         return (undef);
640     }
641     else {    #it's a valid email address
642         return (1);
643     }
644 }
645
646 # }}}
647
648 # {{{ sub CanonicalizeEmailAddress
649
650
651
652 =item CanonicalizeEmailAddress ADDRESS
653
654 # CanonicalizeEmailAddress converts email addresses into canonical form.
655 # it takes one email address in and returns the proper canonical
656 # form. You can dump whatever your proper local config is in here
657
658 =cut
659
660 sub CanonicalizeEmailAddress {
661     my $self = shift;
662     my $email = shift;
663     # Example: the following rule would treat all email
664     # coming from a subdomain as coming from second level domain
665     # foo.com
666     if ($RT::CanonicalizeEmailAddressMatch && $RT::CanonicalizeEmailAddressReplace ) {
667         $email =~ s/$RT::CanonicalizeEmailAddressMatch/$RT::CanonicalizeEmailAddressReplace/gi;
668     }
669     return ($email);
670 }
671
672
673 # }}}
674
675
676 # {{{ Password related functions
677
678 # {{{ sub SetRandomPassword
679
680 =head2 SetRandomPassword
681
682 Takes no arguments. Returns a status code and a new password or an error message.
683 If the status is 1, the second value returned is the new password.
684 If the status is anything else, the new value returned is the error code.
685
686 =cut
687
688 sub SetRandomPassword {
689     my $self = shift;
690
691     unless ( $self->CurrentUserCanModify('Password') ) {
692         return ( 0, $self->loc("Permission Denied") );
693     }
694
695     my $pass = $self->GenerateRandomPassword( 6, 8 );
696
697     # If we have "notify user on 
698
699     my ( $val, $msg ) = $self->SetPassword($pass);
700
701     #If we got an error return the error.
702     return ( 0, $msg ) unless ($val);
703
704     #Otherwise, we changed the password, lets return it.
705     return ( 1, $pass );
706
707 }
708
709 # }}}
710
711 # {{{ sub ResetPassword
712
713 =head2 ResetPassword
714
715 Returns status, [ERROR or new password].  Resets this user\'s password to
716 a randomly generated pronouncable password and emails them, using a 
717 global template called "RT_PasswordChange", which can be overridden
718 with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged" 
719 for privileged and Non-privileged users respectively.
720
721 =cut
722
723 sub ResetPassword {
724     my $self = shift;
725
726     unless ( $self->CurrentUserCanModify('Password') ) {
727         return ( 0, $self->loc("Permission Denied") );
728     }
729     my ( $status, $pass ) = $self->SetRandomPassword();
730
731     unless ($status) {
732         return ( 0, "$pass" );
733     }
734
735     my $template = RT::Template->new( $self->CurrentUser );
736
737     if ( $self->Privileged ) {
738         $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
739     }
740     else {
741         $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
742     }
743
744     unless ( $template->Id ) {
745         $template->LoadGlobalTemplate('RT_PasswordChange');
746     }
747
748     unless ( $template->Id ) {
749         $RT::Logger->crit( "$self tried to send "
750               . $self->Name
751               . " a password reminder "
752               . "but couldn't find a password change template" );
753     }
754
755     my $notification = RT::Action::SendPasswordEmail->new(
756         TemplateObj => $template,
757         Argument    => $pass
758     );
759
760     $notification->SetTo( $self->EmailAddress );
761
762     my ($ret);
763     $ret = $notification->Prepare();
764     if ($ret) {
765         $ret = $notification->Commit();
766     }
767
768     if ($ret) {
769         return ( 1, $self->loc('New password notification sent') );
770     }
771     else {
772         return ( 0, $self->loc('Notification could not be sent') );
773     }
774
775 }
776
777 # }}}
778
779 # {{{ sub GenerateRandomPassword
780
781 =head2 GenerateRandomPassword MIN_LEN and MAX_LEN
782
783 Returns a random password between MIN_LEN and MAX_LEN characters long.
784
785 =cut
786
787 sub GenerateRandomPassword {
788     my $self       = shift;
789     my $min_length = shift;
790     my $max_length = shift;
791
792     #This code derived from mpw.pl, a bit of code with a sordid history
793     # Its notes: 
794
795     # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
796     # Converted to perl from C by Marc Horowitz, 1/20/2000.
797     # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
798     # Original PL/I version provided by Jerry Saltzer.
799
800     my ( $frequency, $start_freq, $total_sum, $row_sums );
801
802     #When munging characters, we need to know where to start counting letters from
803     my $a = ord('a');
804
805     # frequency of English digraphs (from D Edwards 1/27/66) 
806     $frequency = [
807         [
808             4, 20, 28, 52, 2,  11,  28, 4,  32, 4, 6, 62, 23, 167,
809             2, 14, 0,  83, 76, 127, 7,  25, 8,  1, 9, 1
810         ],    # aa - az
811         [
812             13, 0, 0, 0,  55, 0, 0,  0, 8, 2, 0,  22, 0, 0,
813             11, 0, 0, 15, 4,  2, 13, 0, 0, 0, 15, 0
814         ],    # ba - bz
815         [
816             32, 0, 7, 1,  69, 0,  0,  33, 17, 0, 10, 9, 1, 0,
817             50, 3, 0, 10, 0,  28, 11, 0,  0,  0, 3,  0
818         ],    # ca - cz
819         [
820             40, 16, 9, 5,  65, 18, 3,  9, 56, 0, 1, 4, 15, 6,
821             16, 4,  0, 21, 18, 53, 19, 5, 15, 0, 3, 0
822         ],    # da - dz
823         [
824             84, 20, 55, 125, 51, 40, 19, 16,  50,  1,
825             4,  55, 54, 146, 35, 37, 6,  191, 149, 65,
826             9,  26, 21, 12,  5,  0
827         ],    # ea - ez
828         [
829             19, 3, 5, 1,  19, 21, 1, 3, 30, 2, 0, 11, 1, 0,
830             51, 0, 0, 26, 8,  47, 6, 3, 3,  0, 2, 0
831         ],    # fa - fz
832         [
833             20, 4, 3, 2,  35, 1,  3, 15, 18, 0, 0, 5, 1, 4,
834             21, 1, 1, 20, 9,  21, 9, 0,  5,  0, 1, 0
835         ],    # ga - gz
836         [
837             101, 1, 3, 0, 270, 5,  1, 6, 57, 0, 0, 0, 3, 2,
838             44,  1, 0, 3, 10,  18, 6, 0, 5,  0, 3, 0
839         ],    # ha - hz
840         [
841             40, 7,  51, 23, 25, 9,   11, 3,  0, 0, 2, 38, 25, 202,
842             56, 12, 1,  46, 79, 117, 1,  22, 0, 4, 0, 3
843         ],    # ia - iz
844         [
845             3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0,
846             4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0
847         ],    # ja - jz
848         [
849             1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
850             0, 0, 0, 0, 6,  2, 1, 0, 2,  0, 1, 0
851         ],    # ka - kz
852         [
853             44, 2, 5, 12, 62, 7,  5, 2, 42, 1, 1,  53, 2, 2,
854             25, 1, 1, 2,  16, 23, 9, 0, 1,  0, 33, 0
855         ],    # la - lz
856         [
857             52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1,
858             17, 18, 1, 2, 12, 3, 8, 0, 1,  0, 2, 0
859         ],    # ma - mz
860         [
861             42, 10, 47, 122, 63, 19, 106, 12, 30, 1,
862             6,  6,  9,  7,   54, 7,  1,   7,  44, 124,
863             6,  1,  15, 0,   12, 0
864         ],    # na - nz
865         [
866             7,  12, 14, 17, 5,  95, 3,  5,  14, 0, 0, 19, 41, 134,
867             13, 23, 0,  91, 23, 42, 55, 16, 28, 0, 4, 1
868         ],    # oa - oz
869         [
870             19, 1, 0, 0,  37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
871             27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0
872         ],    # pa - pz
873         [
874             0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0,
875             0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0
876         ],    # qa - qz
877         [
878             83, 8, 16, 23, 169, 4,  8, 8,  77, 1, 10, 5, 26, 16,
879             60, 4, 0,  24, 37,  55, 6, 11, 4,  0, 28, 0
880         ],    # ra - rz
881         [
882             65, 9,  17, 9, 73, 13,  1,  47, 75, 3, 0, 7, 11, 12,
883             56, 17, 6,  9, 48, 116, 35, 1,  28, 0, 4, 0
884         ],    # sa - sz
885         [
886             57, 22, 3,  1, 76, 5, 2, 330, 126, 1,
887             0,  14, 10, 6, 79, 7, 0, 49,  50,  56,
888             21, 2,  27, 0, 24, 0
889         ],    # ta - tz
890         [
891             11, 5,  9, 6,  9,  1,  6, 0, 9, 0, 1, 19, 5, 31,
892             1,  15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0
893         ],    # ua - uz
894         [
895             7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
896             5, 0, 0, 0, 0,  0, 0, 0, 0,  0, 3, 0
897         ],    # va - vz
898         [
899             36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8,
900             15, 0, 0, 0, 4,  2, 0, 0,  1,  0, 0, 0
901         ],    # wa - wz
902         [
903             1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0,
904             1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0
905         ],    # xa - xz
906         [
907             14, 5, 4, 2, 7,  12, 12, 6, 10, 0, 0, 3, 7, 5,
908             17, 3, 0, 4, 16, 30, 0,  0, 5,  0, 0, 0
909         ],    # ya - yz
910         [
911             1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
912             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
913         ]
914     ];    # za - zz
915
916     #We need to know the totals for each row 
917     $row_sums = [
918         map {
919             my $sum = 0;
920             map { $sum += $_ } @$_;
921             $sum;
922           } @$frequency
923     ];
924
925     #Frequency with which a given letter starts a word.
926     $start_freq = [
927         1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24,
928         20,   355, 379, 319, 823, 618, 21, 317, 962,  1991,
929         271,  104, 516, 6,   16,  14
930     ];
931
932     $total_sum = 0;
933     map { $total_sum += $_ } @$start_freq;
934
935     my $length = $min_length + int( rand( $max_length - $min_length ) );
936
937     my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
938     my @word = ( $char + $a );
939     for ( 2 .. $length ) {
940         $char =
941           $self->_GenerateRandomNextChar( $row_sums->[$char],
942             $frequency->[$char] );
943         push ( @word, $char + $a );
944     }
945
946     #Return the password
947     return pack( "C*", @word );
948
949 }
950
951 #A private helper function for RandomPassword
952 # Takes a row summary and a frequency chart for the next character to be searched
953 sub _GenerateRandomNextChar {
954     my $self = shift;
955     my ( $all, $freq ) = @_;
956     my ( $pos, $i );
957
958     for ( $pos = int( rand($all) ), $i = 0 ;
959         $pos >= $freq->[$i] ;
960         $pos -= $freq->[$i], $i++ )
961     {
962     }
963
964     return ($i);
965 }
966
967 # }}}
968
969 # {{{ sub SetPassword
970
971 =head2 SetPassword
972
973 Takes a string. Checks the string's length and sets this user's password 
974 to that string.
975
976 =cut
977
978 sub SetPassword {
979     my $self     = shift;
980     my $password = shift;
981
982     unless ( $self->CurrentUserCanModify('Password') ) {
983         return ( 0, $self->loc('Permission Denied') );
984     }
985
986     if ( !$password ) {
987         return ( 0, $self->loc("No password set") );
988     }
989     elsif ( length($password) < $RT::MinimumPasswordLength ) {
990         return ( 0, $self->loc("Password too short") );
991     }
992     else {
993         $password = $self->_GeneratePassword($password);
994         return ( $self->SUPER::SetPassword( $password));
995     }
996
997 }
998
999 =head2 _GeneratePassword PASSWORD
1000
1001 returns an MD5 hash of the password passed in, in base64 encoding.
1002
1003 =cut
1004
1005 sub _GeneratePassword {
1006     my $self = shift;
1007     my $password = shift;
1008
1009     my $md5 = Digest::MD5->new();
1010     $md5->add($password);
1011     return ($md5->b64digest);
1012
1013 }
1014
1015 # }}}
1016
1017 # {{{ sub IsPassword 
1018
1019 =head2 IsPassword
1020
1021 Returns true if the passed in value is this user's password.
1022 Returns undef otherwise.
1023
1024 =cut
1025
1026 sub IsPassword {
1027     my $self  = shift;
1028     my $value = shift;
1029
1030     #TODO there isn't any apparent way to legitimately ACL this
1031
1032     # RT does not allow null passwords 
1033     if ( ( !defined($value) ) or ( $value eq '' ) ) {
1034         return (undef);
1035     }
1036
1037    if ( $self->PrincipalObj->Disabled ) {
1038         $RT::Logger->info(
1039             "Disabled user " . $self->Name . " tried to log in" );
1040         return (undef);
1041     }
1042
1043     if ( ($self->__Value('Password') eq '') || 
1044          ($self->__Value('Password') eq undef) )  {
1045         return(undef);
1046      }
1047
1048     # generate an md5 password 
1049     if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
1050         return(1);
1051     }
1052
1053     #  if it's a historical password we say ok.
1054
1055     if ( $self->__Value('Password') eq crypt( $value, $self->__Value('Password') ) ) {
1056         return (1);
1057     }
1058
1059     # no password check has succeeded. get out
1060
1061     return (undef);
1062 }
1063
1064 # }}}
1065
1066 # }}}
1067
1068 # {{{ sub SetDisabled
1069
1070 =head2 Sub SetDisabled
1071
1072 Toggles the user's disabled flag.
1073 If this flag is
1074 set, all password checks for this user will fail. All ACL checks for this
1075 user will fail. The user will appear in no user listings.
1076
1077 =cut 
1078
1079 # }}}
1080
1081 sub SetDisabled {
1082     my $self = shift;
1083     unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1084         return (0, $self->loc('Permission Denied'));
1085     }
1086     return $self->PrincipalObj->SetDisabled(@_);
1087 }
1088
1089 sub Disabled {
1090     my $self = shift;
1091     return $self->PrincipalObj->Disabled(@_);
1092 }
1093
1094
1095 # {{{ Principal related routines
1096
1097 =head2 PrincipalObj 
1098
1099 Returns the principal object for this user. returns an empty RT::Principal
1100 if there's no principal object matching this user. 
1101 The response is cached. PrincipalObj should never ever change.
1102
1103 =begin testing
1104
1105 ok(my $u = RT::User->new($RT::SystemUser));
1106 ok($u->Load(1), "Loaded the first user");
1107 ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal");
1108 ok($u->PrincipalObj->PrincipalType eq 'User' , "Principal 1 is a user, not a group");
1109
1110 =end testing
1111
1112 =cut
1113
1114
1115 sub PrincipalObj {
1116     my $self = shift;
1117     unless ($self->{'PrincipalObj'} && 
1118             ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1119             ($self->{'PrincipalObj'}->PrincipalType eq 'User')) {
1120
1121             $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1122             $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1123                                                 'PrincipalType' => 'User') ;
1124             }
1125     return($self->{'PrincipalObj'});
1126 }
1127
1128
1129 =head2 PrincipalId  
1130
1131 Returns this user's PrincipalId
1132
1133 =cut
1134
1135 sub PrincipalId {
1136     my $self = shift;
1137     return $self->Id;
1138 }
1139
1140 # }}}
1141
1142
1143
1144 # {{{ sub HasGroupRight
1145
1146 =head2 HasGroupRight
1147
1148 Takes a paramhash which can contain
1149 these items:
1150     GroupObj => RT::Group or Group => integer
1151     Right => 'Right' 
1152
1153
1154 Returns 1 if this user has the right specified in the paramhash for the Group
1155 passed in.
1156
1157 Returns undef if they don't.
1158
1159 =cut
1160
1161 sub HasGroupRight {
1162     my $self = shift;
1163     my %args = (
1164         GroupObj    => undef,
1165         Group       => undef,
1166         Right       => undef,
1167         @_
1168     );
1169
1170
1171     if ( defined $args{'Group'} ) {
1172         $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1173         $args{'GroupObj'}->Load( $args{'Group'} );
1174     }
1175
1176     # {{{ Validate and load up the GroupId
1177     unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1178         return undef;
1179     }
1180
1181     # }}}
1182
1183
1184     # Figure out whether a user has the right we're asking about.
1185     my $retval = $self->HasRight(
1186         Object => $args{'GroupObj'},
1187         Right     => $args{'Right'},
1188     );
1189
1190     return ($retval);
1191
1192
1193 }
1194
1195 # }}}
1196
1197 # {{{ sub Rights testing
1198
1199 =head2 Rights testing
1200
1201
1202 =begin testing
1203
1204 my $root = RT::User->new($RT::SystemUser);
1205 $root->Load('root');
1206 ok($root->Id, "Found the root user");
1207 my $rootq = RT::Queue->new($root);
1208 $rootq->Load(1);
1209 ok($rootq->Id, "Loaded the first queue");
1210
1211 ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets");
1212
1213 my $new_user = RT::User->new($RT::SystemUser);
1214 my ($id, $msg) = $new_user->Create(Name => 'ACLTest');
1215
1216 ok ($id, "Created a new user for acl test $msg");
1217
1218 my $q = RT::Queue->new($new_user);
1219 $q->Load(1);
1220 ok($q->Id, "Loaded the first queue");
1221
1222
1223 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
1224 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
1225 ok ($gval, "Grant succeeded - $gmsg");
1226
1227
1228 ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
1229 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
1230 ok ($gval, "Revocation succeeded - $gmsg");
1231 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
1232
1233
1234
1235
1236
1237 # Create a ticket in the queue
1238 my $new_tick = RT::Ticket->new($RT::SystemUser);
1239 my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
1240 ok($tickid, "Created ticket: $tickid");
1241 # Make sure the user doesn't have the right to modify tickets in the queue
1242 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1243 # Create a new group
1244 my $group = RT::Group->new($RT::SystemUser);
1245 $group->CreateUserDefinedGroup(Name => 'ACLTest');
1246 ok($group->Id, "Created a new group Ok");
1247 # Grant a group the right to modify tickets in a queue
1248 ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1249 ok($gv,"Grant succeeed - $gm");
1250 # Add the user to the group
1251 ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
1252 ok ($aid, "Member added to group: $amsg");
1253 # Make sure the user does have the right to modify tickets in the queue
1254 ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
1255
1256
1257 # Remove the user from the group
1258 ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
1259 ok ($did,"Deleted the group member: $dmsg");
1260 # Make sure the user doesn't have the right to modify tickets in the queue
1261 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1262
1263
1264 my $q_as_system = RT::Queue->new($RT::SystemUser);
1265 $q_as_system->Load(1);
1266 ok($q_as_system->Id, "Loaded the first queue");
1267
1268 # Create a ticket in the queue
1269 my $new_tick2 = RT::Ticket->new($RT::SystemUser);
1270 my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
1271 ok($tick2id, "Created ticket: $tick2id");
1272 ok($new_tick2->QueueObj->id eq $q_as_system->Id, "Created a new ticket in queue 1");
1273
1274
1275 # make sure that the user can't do this without subgroup membership
1276 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1277
1278 # Create a subgroup
1279 my $subgroup = RT::Group->new($RT::SystemUser);
1280 $subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest');
1281 ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok");
1282 #Add the subgroup as a subgroup of the group
1283 my ($said, $samsg) =  $group->AddMember($subgroup->PrincipalId);
1284 ok ($said, "Added the subgroup as a member of the group");
1285 # Add the user to a subgroup of the group
1286
1287 my ($usaid, $usamsg) =  $subgroup->AddMember($new_user->PrincipalId);
1288 ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
1289 # Make sure the user does have the right to modify tickets in the queue
1290 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
1291
1292 #  {{{ Deal with making sure that members of subgroups of a disabled group don't have rights
1293
1294 my ($id, $msg);
1295  ($id, $msg) =  $group->SetDisabled(1);
1296  ok ($id,$msg);
1297 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled");
1298  ($id, $msg) =  $group->SetDisabled(0);
1299 ok($id,$msg);
1300 # Test what happens when we disable the group the user is a member of directly
1301
1302 ($id, $msg) =  $subgroup->SetDisabled(1);
1303  ok ($id,$msg);
1304 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
1305  ($id, $msg) =  $subgroup->SetDisabled(0);
1306  ok ($id,$msg);
1307 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
1308
1309 # }}}
1310
1311
1312 my ($usrid, $usrmsg) =  $subgroup->DeleteMember($new_user->PrincipalId);
1313 ok($usrid,"removed the user from the group - $usrmsg");
1314 # Make sure the user doesn't have the right to modify tickets in the queue
1315 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1316
1317 #revoke the right to modify tickets in a queue
1318 ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1319 ok($gv,"revoke succeeed - $gm");
1320
1321 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
1322
1323 # Grant queue admin cc the right to modify ticket in the queue 
1324 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $q_as_system, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1325 ok($qv, "Granted the right successfully - $qm");
1326
1327 # Add the user as a queue admincc
1328 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Added the new user as a queue admincc");
1329 ok ($add_id, "the user is now a queue admincc - $add_msg");
1330
1331 # Make sure the user does have the right to modify tickets in the queue
1332 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1333 # Remove the user from the role  group
1334 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Deleted the new user as a queue admincc");
1335
1336 # Make sure the user doesn't have the right to modify tickets in the queue
1337 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1338
1339 # }}}
1340
1341 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1342
1343 # Add the user as a ticket admincc
1344 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Added the new user as a queue admincc");
1345 ok ($add_id, "the user is now a queue admincc - $add_msg");
1346
1347 # Make sure the user does have the right to modify tickets in the queue
1348 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1349
1350 # Remove the user from the role  group
1351 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Deleted the new user as a queue admincc");
1352
1353 # Make sure the user doesn't have the right to modify tickets in the queue
1354 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1355
1356
1357 # Revoke the right to modify ticket in the queue 
1358 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $q_as_system, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1359 ok($rqv, "Revoked the right successfully - $rqm");
1360
1361 # }}}
1362
1363
1364
1365 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
1366
1367 # Before we start Make sure the user does not have the right to modify tickets in the queue
1368 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
1369 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
1370
1371 # Grant queue admin cc the right to modify ticket in the queue 
1372 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1373 ok($qv, "Granted the right successfully - $qm");
1374
1375 # Make sure the user can't modify the ticket before they're added as a watcher
1376 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1377 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
1378
1379 # Add the user as a queue admincc
1380 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Added the new user as a queue admincc");
1381 ok ($add_id, "the user is now a queue admincc - $add_msg");
1382
1383 # Make sure the user does have the right to modify tickets in the queue
1384 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1385 ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
1386 # Remove the user from the role  group
1387 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Deleted the new user as a queue admincc");
1388
1389 # Make sure the user doesn't have the right to modify tickets in the queue
1390 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1391 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
1392
1393 # }}}
1394
1395 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1396
1397 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1398 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1399
1400
1401 # Add the user as a ticket admincc
1402 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Added the new user as a queue admincc");
1403 ok ($add_id, "the user is now a queue admincc - $add_msg");
1404
1405 # Make sure the user does have the right to modify tickets in the queue
1406 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1407 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
1408
1409 # Remove the user from the role  group
1410 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId)  , "Deleted the new user as a queue admincc");
1411
1412 # Make sure the user doesn't have the right to modify tickets in the queue
1413 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
1414 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1415
1416
1417 # Revoke the right to modify ticket in the queue 
1418 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1419 ok($rqv, "Revoked the right successfully - $rqm");
1420
1421 # }}}
1422
1423
1424
1425
1426 # Grant "privileged users" the system right to create users
1427 # Create a privileged user.
1428 # have that user create another user
1429 # Revoke the right for privileged users to create users
1430 # have the privileged user try to create another user and fail the ACL check
1431
1432 =end testing
1433
1434 =cut
1435
1436 # }}}
1437
1438
1439 # {{{ sub HasRight
1440
1441 =head2 sub HasRight
1442
1443 Shim around PrincipalObj->HasRight. See RT::Principal
1444
1445 =cut
1446
1447 sub HasRight {
1448
1449     my $self = shift;
1450     return $self->PrincipalObj->HasRight(@_);
1451 }
1452
1453 # }}}
1454
1455 # {{{ sub CurrentUserCanModify
1456
1457 =head2 CurrentUserCanModify RIGHT
1458
1459 If the user has rights for this object, either because
1460 he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an 
1461 admin right) 'ModifySelf', return 1. otherwise, return undef.
1462
1463 =cut
1464
1465 sub CurrentUserCanModify {
1466     my $self  = shift;
1467     my $right = shift;
1468
1469     if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1470         return (1);
1471     }
1472
1473     #If the field is marked as an "administrators only" field, 
1474     # don\'t let the user touch it.
1475     elsif ( $self->_Accessible( $right, 'admin' ) ) {
1476         return (undef);
1477     }
1478
1479     #If the current user is trying to modify themselves
1480     elsif ( ( $self->id == $self->CurrentUser->id )
1481         and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1482     {
1483         return (1);
1484     }
1485
1486     #If we don\'t have a good reason to grant them rights to modify
1487     # by now, they lose
1488     else {
1489         return (undef);
1490     }
1491
1492 }
1493
1494 # }}}
1495
1496 # {{{ sub CurrentUserHasRight
1497
1498 =head2 CurrentUserHasRight
1499   
1500   Takes a single argument. returns 1 if $Self->CurrentUser
1501   has the requested right. returns undef otherwise
1502
1503 =cut
1504
1505 sub CurrentUserHasRight {
1506     my $self  = shift;
1507     my $right = shift;
1508
1509     return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1510 }
1511
1512 # }}}
1513
1514 # {{{ sub _Set
1515
1516 sub _Set {
1517     my $self = shift;
1518
1519     my %args = (
1520         Field => undef,
1521         Value => undef,
1522         @_
1523     );
1524
1525     # Nobody is allowed to futz with RT_System or Nobody 
1526
1527     if ( ($self->Id == $RT::SystemUser->Id )  || 
1528          ($self->Id == $RT::Nobody->Id)) {
1529         return ( 0, $self->loc("Can not modify system users") );
1530     }
1531     unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1532         return ( 0, $self->loc("Permission Denied") );
1533     }
1534
1535     #Set the new value
1536     my ( $ret, $msg ) = $self->SUPER::_Set(
1537         Field => $args{'Field'},
1538         Value => $args{'Value'}
1539     );
1540
1541     return ( $ret, $msg );
1542 }
1543
1544 # }}}
1545
1546 # {{{ sub _Value 
1547
1548 =head2 _Value
1549
1550 Takes the name of a table column.
1551 Returns its value as a string, if the user passes an ACL check
1552
1553 =cut
1554
1555 sub _Value {
1556
1557     my $self  = shift;
1558     my $field = shift;
1559
1560     #If the current user doesn't have ACLs, don't let em at it.  
1561
1562     my @PublicFields = qw( Name EmailAddress Organization Disabled
1563       RealName NickName Gecos ExternalAuthId
1564       AuthSystem ExternalContactInfoId
1565       ContactInfoSystem );
1566
1567     #if the field is public, return it.
1568     if ( $self->_Accessible( $field, 'public' ) ) {
1569         return ( $self->SUPER::_Value($field) );
1570
1571     }
1572
1573     #If the user wants to see their own values, let them
1574     # TODO figure ouyt a better way to deal with this
1575    elsif ( $self->CurrentUser->Id == $self->Id ) {
1576         return ( $self->SUPER::_Value($field) );
1577     }
1578
1579     #If the user has the admin users right, return the field
1580     elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1581         return ( $self->SUPER::_Value($field) );
1582     }
1583     else {
1584         return (undef);
1585     }
1586
1587 }
1588
1589 # }}}
1590
1591
1592 1;
1593
1594