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