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