1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
28 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
45 # END BPS TAGGED BLOCK }}}
49 RT::User - RT User object
73 no warnings qw(redefine);
75 use vars qw(%_USERS_KEY_CACHE);
77 %_USERS_KEY_CACHE = ();
82 use RT::Interface::Email;
88 sub _OverlayAccessible {
91 Name => { public => 1, admin => 1 },
92 Password => { read => 0 },
93 EmailAddress => { public => 1 },
94 Organization => { public => 1, admin => 1 },
95 RealName => { public => 1 },
96 NickName => { public => 1 },
97 Lang => { public => 1 },
98 EmailEncoding => { public => 1 },
99 WebEncoding => { public => 1 },
100 ExternalContactInfoId => { public => 1, admin => 1 },
101 ContactInfoSystem => { public => 1, admin => 1 },
102 ExternalAuthId => { public => 1, admin => 1 },
103 AuthSystem => { public => 1, admin => 1 },
104 Gecos => { public => 1, admin => 1 },
105 PGPKey => { public => 1, admin => 1 },
116 =head2 Create { PARAMHASH }
121 # Make sure we can create a user
123 my $u1 = RT::User->new($RT::SystemUser);
124 is(ref($u1), 'RT::User');
125 my ($id, $msg) = $u1->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-1@example.com');
126 ok ($id, "Creating user CreateTest1 - " . $msg );
128 # Make sure we can't create a second user with the same name
129 my $u2 = RT::User->new($RT::SystemUser);
130 ($id, $msg) = $u2->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-2@example.com');
134 # Make sure we can't create a second user with the same EmailAddress address
135 my $u3 = RT::User->new($RT::SystemUser);
136 ($id, $msg) = $u3->Create(Name => 'CreateTest2'.$$, EmailAddress => $$.'create-test-1@example.com');
139 # Make sure we can create a user with no EmailAddress address
140 my $u4 = RT::User->new($RT::SystemUser);
141 ($id, $msg) = $u4->Create(Name => 'CreateTest3'.$$);
144 # make sure we can create a second user with no EmailAddress address
145 my $u5 = RT::User->new($RT::SystemUser);
146 ($id, $msg) = $u5->Create(Name => 'CreateTest4'.$$);
149 # make sure we can create a user with a blank EmailAddress address
150 my $u6 = RT::User->new($RT::SystemUser);
151 ($id, $msg) = $u6->Create(Name => 'CreateTest6'.$$, EmailAddress => '');
153 # make sure we can create a second user with a blankEmailAddress address
154 my $u7 = RT::User->new($RT::SystemUser);
155 ($id, $msg) = $u7->Create(Name => 'CreateTest7'.$$, EmailAddress => '');
158 # Can we change the email address away from from "";
159 ($id,$msg) = $u7->SetEmailAddress('foo@bar'.$$);
161 # can we change the address back to "";
162 ($id,$msg) = $u7->SetEmailAddress('');
164 is ($u7->EmailAddress, '');
178 _RecordTransaction => 1,
179 @_ # get the real argumentlist
182 # remove the value so it does not cripple SUPER::Create
183 my $record_transaction = delete $args{'_RecordTransaction'};
186 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
187 return ( 0, $self->loc('No permission to create users') );
191 unless ($self->CanonicalizeUserInfo(\%args)) {
192 return ( 0, $self->loc("Could not set user info") );
195 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
197 # if the user doesn't have a name defined, set it to the email address
198 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
202 # Privileged is no longer a column in users
203 my $privileged = $args{'Privileged'};
204 delete $args{'Privileged'};
207 if ($args{'CryptedPassword'} ) {
208 $args{'Password'} = $args{'CryptedPassword'};
209 delete $args{'CryptedPassword'};
211 elsif ( !$args{'Password'} ) {
212 $args{'Password'} = '*NO-PASSWORD*';
214 elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) {
215 return ( 0, $self->loc("Password needs to be at least [_1] characters long",$RT::MinimumPasswordLength) );
219 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
222 #TODO Specify some sensible defaults.
224 unless ( $args{'Name'} ) {
226 $RT::Logger->crit(Dumper \%args);
227 return ( 0, $self->loc("Must specify 'Name' attribute") );
230 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
231 if ($RT::SystemUser) { #This only works if RT::SystemUser has been defined
232 my $TempUser = RT::User->new($RT::SystemUser);
233 $TempUser->Load( $args{'Name'} );
234 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
236 return ( 0, $self->loc('Email address in use') )
237 unless ( $self->ValidateEmailAddress( $args{'EmailAddress'} ) );
240 $RT::Logger->warning( "$self couldn't check for pre-existing users");
244 $RT::Handle->BeginTransaction();
245 # Groups deal with principal ids, rather than user ids.
246 # When creating this user, set up a principal Id for it.
247 my $principal = RT::Principal->new($self->CurrentUser);
248 my $principal_id = $principal->Create(PrincipalType => 'User',
249 Disabled => $args{'Disabled'},
251 # If we couldn't create a principal Id, get the fuck out.
252 unless ($principal_id) {
253 $RT::Handle->Rollback();
254 $RT::Logger->crit("Couldn't create a Principal on new user create.");
255 $RT::Logger->crit("Strange things are afoot at the circle K");
256 return ( 0, $self->loc('Could not create user') );
259 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
260 delete $args{'Disabled'};
262 $self->SUPER::Create(id => $principal_id , %args);
265 #If the create failed.
267 $RT::Handle->Rollback();
268 $RT::Logger->error("Could not create a new user - " .join('-'. %args));
270 return ( 0, $self->loc('Could not create user') );
273 my $aclstash = RT::Group->new($self->CurrentUser);
274 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
277 $RT::Handle->Rollback();
278 $RT::Logger->crit("Couldn't stash the user in groupmembers");
279 return ( 0, $self->loc('Could not create user') );
283 my $everyone = RT::Group->new($self->CurrentUser);
284 $everyone->LoadSystemInternalGroup('Everyone');
285 unless ($everyone->id) {
286 $RT::Logger->crit("Could not load Everyone group on user creation.");
287 $RT::Handle->Rollback();
288 return ( 0, $self->loc('Could not create user') );
292 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
293 unless ($everyone_id) {
294 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
295 $RT::Logger->crit($everyone_msg);
296 $RT::Handle->Rollback();
297 return ( 0, $self->loc('Could not create user') );
301 my $access_class = RT::Group->new($self->CurrentUser);
303 $access_class->LoadSystemInternalGroup('Privileged');
305 $access_class->LoadSystemInternalGroup('Unprivileged');
308 unless ($access_class->id) {
309 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
310 $RT::Handle->Rollback();
311 return ( 0, $self->loc('Could not create user') );
315 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
318 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
319 $RT::Logger->crit($ac_msg);
320 $RT::Handle->Rollback();
321 return ( 0, $self->loc('Could not create user') );
325 if ( $record_transaction ) {
326 $self->_NewTransaction( Type => "Create" );
331 return ( $id, $self->loc('User created') );
340 =head2 SetPrivileged BOOL
342 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
343 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
345 Returns a standard RT tuple of (val, msg);
350 ok(my $user = RT::User->new($RT::SystemUser));
351 ok($user->Load('root'), "Loaded user 'root'");
352 ok($user->Privileged, "User 'root' is privileged");
353 ok(my ($v,$m) = $user->SetPrivileged(0));
354 ok ($v ==1, "Set unprivileged suceeded ($m)");
355 ok(!$user->Privileged, "User 'root' is no longer privileged");
356 ok(my ($v2,$m2) = $user->SetPrivileged(1));
357 ok ($v2 ==1, "Set privileged suceeded ($m2");
358 ok($user->Privileged, "User 'root' is privileged again");
369 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
370 return ( 0, $self->loc('Permission Denied') );
372 my $priv = RT::Group->new($self->CurrentUser);
373 $priv->LoadSystemInternalGroup('Privileged');
376 $RT::Logger->crit("Could not find Privileged pseudogroup");
377 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
380 my $unpriv = RT::Group->new($self->CurrentUser);
381 $unpriv->LoadSystemInternalGroup('Unprivileged');
382 unless ($unpriv->Id) {
383 $RT::Logger->crit("Could not find unprivileged pseudogroup");
384 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
388 if ($priv->HasMember($self->PrincipalObj)) {
389 #$RT::Logger->debug("That user is already privileged");
390 return (0,$self->loc("That user is already privileged"));
392 if ($unpriv->HasMember($self->PrincipalObj)) {
393 $unpriv->_DeleteMember($self->PrincipalId);
395 # if we had layered transactions, life would be good
396 # sadly, we have to just go ahead, even if something
398 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
399 "unprivileged. something is drastically wrong.");
401 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
403 return (1, $self->loc("That user is now privileged"));
409 if ($unpriv->HasMember($self->PrincipalObj)) {
410 #$RT::Logger->debug("That user is already unprivileged");
411 return (0,$self->loc("That user is already unprivileged"));
413 if ($priv->HasMember($self->PrincipalObj)) {
414 $priv->_DeleteMember( $self->PrincipalId);
416 # if we had layered transactions, life would be good
417 # sadly, we have to just go ahead, even if something
419 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
420 "unprivileged. something is drastically wrong.");
422 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
424 return (1, $self->loc("That user is now unprivileged"));
437 Returns true if this user is privileged. Returns undef otherwise.
443 my $priv = RT::Group->new($self->CurrentUser);
444 $priv->LoadSystemInternalGroup('Privileged');
445 if ($priv->HasMember($self->PrincipalObj)) {
455 # {{{ sub _BootstrapCreate
457 #create a user without validating _any_ data.
459 #To be used only on database init.
460 # We can't localize here because it's before we _have_ a loc framework
462 sub _BootstrapCreate {
466 $args{'Password'} = '*NO-PASSWORD*';
469 $RT::Handle->BeginTransaction();
471 # Groups deal with principal ids, rather than user ids.
472 # When creating this user, set up a principal Id for it.
473 my $principal = RT::Principal->new($self->CurrentUser);
474 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
475 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
477 # If we couldn't create a principal Id, get the fuck out.
478 unless ($principal_id) {
479 $RT::Handle->Rollback();
480 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
481 return ( 0, 'Could not create user' );
483 $self->SUPER::Create(id => $principal_id, %args);
485 #If the create failed.
487 $RT::Handle->Rollback();
488 return ( 0, 'Could not create user' ) ; #never loc this
491 my $aclstash = RT::Group->new($self->CurrentUser);
492 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
495 $RT::Handle->Rollback();
496 $RT::Logger->crit("Couldn't stash the user in groupmembers");
497 return ( 0, $self->loc('Could not create user') );
501 $RT::Handle->Commit();
503 return ( $id, 'User created' );
513 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
523 Load a user object from the database. Takes a single argument.
524 If the argument is numerical, load by the column 'id'. Otherwise, load by
525 the "Name" column which is the user's textual username.
531 my $identifier = shift || return undef;
533 #if it's an int, load by id. otherwise, load by name.
534 if ( $identifier !~ /\D/ ) {
535 $self->SUPER::LoadById($identifier);
538 $self->LoadByCol( "Name", $identifier );
544 # {{{ sub LoadByEmail
548 Tries to load this user object from the database by the user's email address.
557 # Never load an empty address as an email address.
562 $address = $self->CanonicalizeEmailAddress($address);
564 #$RT::Logger->debug("Trying to load an email address: $address\n");
565 return $self->LoadByCol( "EmailAddress", $address );
570 # {{{ LoadOrCreateByEmail
572 =head2 LoadOrCreateByEmail ADDRESS
574 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
575 the provided email address. and loads them.
577 Returns a tuple of the user's id and a status message.
578 0 will be returned in place of the user's id in case of failure.
582 sub LoadOrCreateByEmail {
588 my ( $Address, $Name ) =
589 RT::Interface::Email::ParseAddressFromHeader($email);
592 $self->LoadByEmail($email);
593 $message = $self->loc('User loaded');
598 ( $val, $message ) = $self->Create(
600 EmailAddress => $email,
603 Comments => 'Autocreated when added as a watcher');
605 # Deal with the race condition of two account creations at once
606 $self->LoadByEmail($email);
609 $self->LoadByEmail($email);
612 $RT::Logger->error("Recovered from creation failure due to race condition");
613 $message = $self->loc("User loaded");
616 $RT::Logger->crit("Failed to create user ".$email .": " .$message);
622 return($self->Id, $message);
633 # {{{ sub ValidateEmailAddress
635 =head2 ValidateEmailAddress ADDRESS
637 Returns true if the email address entered is not in use by another user or is
638 undef or ''. Returns false if it's in use.
642 sub ValidateEmailAddress {
646 # if the email address is null, it's always valid
647 return (1) if ( !$Value || $Value eq "" );
649 my $TempUser = RT::User->new($RT::SystemUser);
650 $TempUser->LoadByEmail($Value);
652 if ( $TempUser->id && ( $TempUser->id != $self->id ) )
653 { # if we found a user with that address
654 # it's invalid to set this user's address to it
657 else { #it's a valid email address
664 # {{{ sub CanonicalizeEmailAddress
668 =head2 CanonicalizeEmailAddress ADDRESS
670 CanonicalizeEmailAddress converts email addresses into canonical form.
671 it takes one email address in and returns the proper canonical
672 form. You can dump whatever your proper local config is in here. Note
673 that it may be called as a static method; in this case, $self may be
678 sub CanonicalizeEmailAddress {
681 # Example: the following rule would treat all email
682 # coming from a subdomain as coming from second level domain
684 if ($RT::CanonicalizeEmailAddressMatch && $RT::CanonicalizeEmailAddressReplace ) {
685 $email =~ s/$RT::CanonicalizeEmailAddressMatch/$RT::CanonicalizeEmailAddressReplace/gi;
693 # {{{ sub CanonicalizeUserInfo
697 =head2 CanonicalizeUserInfo HASH of ARGS
699 CanonicalizeUserInfo can convert all User->Create options.
700 it takes a hashref of all the params sent to User->Create and
701 returns that same hash, by default nothing is done.
703 This function is intended to allow users to have their info looked up via
704 an outside source and modified upon creation.
708 sub CanonicalizeUserInfo {
720 # {{{ Password related functions
722 # {{{ sub SetRandomPassword
724 =head2 SetRandomPassword
726 Takes no arguments. Returns a status code and a new password or an error message.
727 If the status is 1, the second value returned is the new password.
728 If the status is anything else, the new value returned is the error code.
732 sub SetRandomPassword {
735 unless ( $self->CurrentUserCanModify('Password') ) {
736 return ( 0, $self->loc("Permission Denied") );
740 my $min = ( $RT::MinimumPasswordLength > 6 ? $RT::MinimumPasswordLength : 6);
741 my $max = ( $RT::MinimumPasswordLength > 8 ? $RT::MinimumPasswordLength : 8);
743 my $pass = $self->GenerateRandomPassword( $min, $max) ;
745 # If we have "notify user on
747 my ( $val, $msg ) = $self->SetPassword($pass);
749 #If we got an error return the error.
750 return ( 0, $msg ) unless ($val);
752 #Otherwise, we changed the password, lets return it.
759 # {{{ sub ResetPassword
763 Returns status, [ERROR or new password]. Resets this user\'s password to
764 a randomly generated pronouncable password and emails them, using a
765 global template called "RT_PasswordChange", which can be overridden
766 with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged"
767 for privileged and Non-privileged users respectively.
774 unless ( $self->CurrentUserCanModify('Password') ) {
775 return ( 0, $self->loc("Permission Denied") );
777 my ( $status, $pass ) = $self->SetRandomPassword();
780 return ( 0, "$pass" );
783 my $template = RT::Template->new( $self->CurrentUser );
785 if ( $self->Privileged ) {
786 $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
789 $template->LoadGlobalTemplate('RT_PasswordChange_NonPrivileged');
792 unless ( $template->Id ) {
793 $template->LoadGlobalTemplate('RT_PasswordChange');
796 unless ( $template->Id ) {
797 $RT::Logger->crit( "$self tried to send "
799 . " a password reminder "
800 . "but couldn't find a password change template" );
803 my $notification = RT::Action::SendPasswordEmail->new(
804 TemplateObj => $template,
808 $notification->SetHeader( 'To', $self->EmailAddress );
811 $ret = $notification->Prepare();
813 $ret = $notification->Commit();
817 return ( 1, $self->loc('New password notification sent') );
820 return ( 0, $self->loc('Notification could not be sent') );
827 # {{{ sub GenerateRandomPassword
829 =head2 GenerateRandomPassword MIN_LEN and MAX_LEN
831 Returns a random password between MIN_LEN and MAX_LEN characters long.
835 sub GenerateRandomPassword {
837 my $min_length = shift;
838 my $max_length = shift;
840 #This code derived from mpw.pl, a bit of code with a sordid history
843 # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
844 # Converted to perl from C by Marc Horowitz, 1/20/2000.
845 # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
846 # Original PL/I version provided by Jerry Saltzer.
848 my ( $frequency, $start_freq, $total_sum, $row_sums );
850 #When munging characters, we need to know where to start counting letters from
853 # frequency of English digraphs (from D Edwards 1/27/66)
856 4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23, 167,
857 2, 14, 0, 83, 76, 127, 7, 25, 8, 1, 9, 1
860 13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0,
861 11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0
864 32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1, 0,
865 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0
868 40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15, 6,
869 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0
872 84, 20, 55, 125, 51, 40, 19, 16, 50, 1,
873 4, 55, 54, 146, 35, 37, 6, 191, 149, 65,
877 19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1, 0,
878 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0
881 20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1, 4,
882 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0
885 101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3, 2,
886 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0
889 40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25, 202,
890 56, 12, 1, 46, 79, 117, 1, 22, 0, 4, 0, 3
893 3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0,
894 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0
897 1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
898 0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0
901 44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2, 2,
902 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0
905 52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1,
906 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0
909 42, 10, 47, 122, 63, 19, 106, 12, 30, 1,
910 6, 6, 9, 7, 54, 7, 1, 7, 44, 124,
914 7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41, 134,
915 13, 23, 0, 91, 23, 42, 55, 16, 28, 0, 4, 1
918 19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
919 27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0
922 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
923 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0
926 83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26, 16,
927 60, 4, 0, 24, 37, 55, 6, 11, 4, 0, 28, 0
930 65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11, 12,
931 56, 17, 6, 9, 48, 116, 35, 1, 28, 0, 4, 0
934 57, 22, 3, 1, 76, 5, 2, 330, 126, 1,
935 0, 14, 10, 6, 79, 7, 0, 49, 50, 56,
939 11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31,
940 1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0
943 7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
944 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0
947 36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8,
948 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0
951 1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0,
952 1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0
955 14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7, 5,
956 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0
959 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
960 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
964 #We need to know the totals for each row
968 map { $sum += $_ } @$_;
973 #Frequency with which a given letter starts a word.
975 1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24,
976 20, 355, 379, 319, 823, 618, 21, 317, 962, 1991,
977 271, 104, 516, 6, 16, 14
981 map { $total_sum += $_ } @$start_freq;
983 my $length = $min_length + int( rand( $max_length - $min_length ) );
985 my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
986 my @word = ( $char + $a );
987 for ( 2 .. $length ) {
989 $self->_GenerateRandomNextChar( $row_sums->[$char],
990 $frequency->[$char] );
991 push ( @word, $char + $a );
995 return pack( "C*", @word );
999 #A private helper function for RandomPassword
1000 # Takes a row summary and a frequency chart for the next character to be searched
1001 sub _GenerateRandomNextChar {
1003 my ( $all, $freq ) = @_;
1006 for ( $pos = int( rand($all) ), $i = 0 ;
1007 $pos >= $freq->[$i] ;
1008 $pos -= $freq->[$i], $i++ )
1017 # {{{ sub SetPassword
1021 Takes a string. Checks the string's length and sets this user's password
1028 my $password = shift;
1030 unless ( $self->CurrentUserCanModify('Password') ) {
1031 return ( 0, $self->loc('Password: Permission Denied') );
1035 return ( 0, $self->loc("No password set") );
1037 elsif ( length($password) < $RT::MinimumPasswordLength ) {
1038 return ( 0, $self->loc("Password needs to be at least [_1] characters long", $RT::MinimumPasswordLength) );
1041 my $new = !$self->HasPassword;
1042 $password = $self->_GeneratePassword($password);
1043 my ( $val, $msg ) = $self->SUPER::SetPassword($password);
1045 return ( 1, $self->loc("Password set") ) if $new;
1046 return ( 1, $self->loc("Password changed") );
1049 return ( $val, $msg );
1055 =head2 _GeneratePassword PASSWORD
1057 returns an MD5 hash of the password passed in, in hexadecimal encoding.
1061 sub _GeneratePassword {
1063 my $password = shift;
1065 my $md5 = Digest::MD5->new();
1066 $md5->add($password);
1067 return ($md5->hexdigest);
1071 =head2 _GeneratePasswordBase64 PASSWORD
1073 returns an MD5 hash of the password passed in, in base64 encoding
1078 sub _GeneratePasswordBase64 {
1080 my $password = shift;
1082 my $md5 = Digest::MD5->new();
1083 $md5->add($password);
1084 return ($md5->b64digest);
1093 Returns true if the user has a valid password, otherwise returns false.
1100 if ( ( $self->__Value('Password') eq '' )
1101 || ( $self->__Value('Password') eq undef ) )
1106 if ( $self->__Value('Password') eq '*NO-PASSWORD*' ) {
1115 # {{{ sub IsPassword
1119 Returns true if the passed in value is this user's password.
1120 Returns undef otherwise.
1128 #TODO there isn't any apparent way to legitimately ACL this
1130 # RT does not allow null passwords
1131 if ( ( !defined($value) ) or ( $value eq '' ) ) {
1135 if ( $self->PrincipalObj->Disabled ) {
1137 "Disabled user " . $self->Name . " tried to log in" );
1141 unless ($self->HasPassword) {
1145 # generate an md5 password
1146 if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
1150 # if it's a historical password we say ok.
1151 if ($self->__Value('Password') eq crypt($value, $self->__Value('Password'))
1152 or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password'))
1154 # ...but upgrade the legacy password inplace.
1155 $self->SUPER::SetPassword( $self->_GeneratePassword($value) );
1159 # no password check has succeeded. get out
1168 # {{{ sub SetDisabled
1170 =head2 Sub SetDisabled
1172 Toggles the user's disabled flag.
1174 set, all password checks for this user will fail. All ACL checks for this
1175 user will fail. The user will appear in no user listings.
1183 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1184 return (0, $self->loc('Permission Denied'));
1186 return $self->PrincipalObj->SetDisabled(@_);
1191 return $self->PrincipalObj->Disabled(@_);
1195 # {{{ Principal related routines
1199 Returns the principal object for this user. returns an empty RT::Principal
1200 if there's no principal object matching this user.
1201 The response is cached. PrincipalObj should never ever change.
1205 ok(my $u = RT::User->new($RT::SystemUser));
1206 ok($u->Load(1), "Loaded the first user");
1207 ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal");
1208 is($u->PrincipalObj->PrincipalType, 'User' , "Principal 1 is a user, not a group");
1217 unless ($self->{'PrincipalObj'} &&
1218 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1219 ($self->{'PrincipalObj'}->PrincipalType eq 'User')) {
1221 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1222 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1223 'PrincipalType' => 'User') ;
1225 return($self->{'PrincipalObj'});
1231 Returns this user's PrincipalId
1244 # {{{ sub HasGroupRight
1246 =head2 HasGroupRight
1248 Takes a paramhash which can contain
1250 GroupObj => RT::Group or Group => integer
1254 Returns 1 if this user has the right specified in the paramhash for the Group
1257 Returns undef if they don't.
1271 if ( defined $args{'Group'} ) {
1272 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1273 $args{'GroupObj'}->Load( $args{'Group'} );
1276 # {{{ Validate and load up the GroupId
1277 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1284 # Figure out whether a user has the right we're asking about.
1285 my $retval = $self->HasRight(
1286 Object => $args{'GroupObj'},
1287 Right => $args{'Right'},
1301 Returns a group collection object containing the groups of which this
1308 my $groups = RT::Groups->new($self->CurrentUser);
1309 $groups->LimitToUserDefinedGroups;
1310 $groups->WithMember(PrincipalId => $self->Id,
1319 #much false laziness w/Ticket_Overlay.pm
1321 # A helper table for links mapping to make it easier
1322 # to build and parse links between tickets
1324 use vars '%LINKDIRMAP';
1327 MemberOf => { Base => 'MemberOf',
1328 Target => 'HasMember', },
1329 RefersTo => { Base => 'RefersTo',
1330 Target => 'ReferredToBy', },
1331 DependsOn => { Base => 'DependsOn',
1332 Target => 'DependedOnBy', },
1333 MergedInto => { Base => 'MergedInto',
1334 Target => 'MergedInto', },
1338 sub LINKDIRMAP { return \%LINKDIRMAP }
1343 # #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic ---
1344 # #tobias meant by $f
1345 # my $field = shift;
1346 # my $type = shift || "";
1348 # unless ( $self->{"$field$type"} ) {
1349 # $self->{"$field$type"} = new RT::Links( $self->CurrentUser );
1350 # if ( $self->CurrentUserHasRight('ShowTicket') ) {
1351 # # Maybe this ticket is a merged ticket
1352 # my $Tickets = new RT::Tickets( $self->CurrentUser );
1353 # # at least to myself
1354 # $self->{"$field$type"}->Limit( FIELD => $field,
1355 # VALUE => $self->URI,
1356 # ENTRYAGGREGATOR => 'OR' );
1357 # $Tickets->Limit( FIELD => 'EffectiveId',
1358 # VALUE => $self->EffectiveId );
1359 # while (my $Ticket = $Tickets->Next) {
1360 # $self->{"$field$type"}->Limit( FIELD => $field,
1361 # VALUE => $Ticket->URI,
1362 # ENTRYAGGREGATOR => 'OR' );
1364 # $self->{"$field$type"}->Limit( FIELD => 'Type',
1369 # return ( $self->{"$field$type"} );
1374 Delete a link. takes a paramhash of Base, Target and Type.
1375 Either Base or Target must be null. The null value will
1376 be replaced with this ticket\'s id
1389 unless ( $args{'Target'} || $args{'Base'} ) {
1390 $RT::Logger->error("Base or Target must be specified\n");
1391 return ( 0, $self->loc('Either base or target must be specified') );
1396 $right++ if $self->CurrentUserHasRight('ModifyUser');
1397 if ( !$right && $RT::StrictLinkACL ) {
1398 return ( 0, $self->loc("Permission Denied") );
1401 # # If the other URI is an RT::Ticket, we want to make sure the user
1402 # # can modify it too...
1403 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
1404 # return (0, $msg) unless $status;
1405 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
1408 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
1409 # ( $RT::StrictLinkACL && $right < 2 ) )
1411 # return ( 0, $self->loc("Permission Denied") );
1414 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
1417 $RT::Logger->debug("Couldn't find that link\n");
1421 my ($direction, $remote_link);
1423 if ( $args{'Base'} ) {
1424 $remote_link = $args{'Base'};
1425 $direction = 'Target';
1427 elsif ( $args{'Target'} ) {
1428 $remote_link = $args{'Target'};
1432 if ( $args{'Silent'} ) {
1433 return ( $val, $Msg );
1436 my $remote_uri = RT::URI->new( $self->CurrentUser );
1437 $remote_uri->FromURI( $remote_link );
1439 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1440 Type => 'DeleteLink',
1441 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
1442 OldValue => $remote_uri->URI || $remote_link,
1446 if ( $remote_uri->IsLocal ) {
1448 my $OtherObj = $remote_uri->Object;
1449 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
1450 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
1451 : $LINKDIRMAP{$args{'Type'}}->{Target},
1452 OldValue => $self->URI,
1453 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
1457 return ( $Trans, $Msg );
1463 my %args = ( Target => '',
1469 unless ( $args{'Target'} || $args{'Base'} ) {
1470 $RT::Logger->error("Base or Target must be specified\n");
1471 return ( 0, $self->loc('Either base or target must be specified') );
1475 $right++ if $self->CurrentUserHasRight('ModifyUser');
1476 if ( !$right && $RT::StrictLinkACL ) {
1477 return ( 0, $self->loc("Permission Denied") );
1480 # # If the other URI is an RT::Ticket, we want to make sure the user
1481 # # can modify it too...
1482 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
1483 # return (0, $msg) unless $status;
1484 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
1487 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
1488 # ( $RT::StrictLinkACL && $right < 2 ) )
1490 # return ( 0, $self->loc("Permission Denied") );
1493 return $self->_AddLink(%args);
1496 #sub __GetTicketFromURI {
1498 # my %args = ( URI => '', @_ );
1500 # # If the other URI is an RT::Ticket, we want to make sure the user
1501 # # can modify it too...
1502 # my $uri_obj = RT::URI->new( $self->CurrentUser );
1503 # $uri_obj->FromURI( $args{'URI'} );
1505 # unless ( $uri_obj->Resolver && $uri_obj->Scheme ) {
1506 # my $msg = $self->loc( "Couldn't resolve '[_1]' into a URI.", $args{'URI'} );
1507 # $RT::Logger->warning( "$msg\n" );
1508 # return( 0, $msg );
1510 # my $obj = $uri_obj->Resolver->Object;
1511 # unless ( UNIVERSAL::isa($obj, 'RT::Ticket') && $obj->id ) {
1512 # return (1, 'Found not a ticket', undef);
1514 # return (1, 'Found ticket', $obj);
1519 Private non-acled variant of AddLink so that links can be added during create.
1525 my %args = ( Target => '',
1531 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
1532 return ($val, $msg) if !$val || $exist;
1534 my ($direction, $remote_link);
1535 if ( $args{'Target'} ) {
1536 $remote_link = $args{'Target'};
1537 $direction = 'Base';
1538 } elsif ( $args{'Base'} ) {
1539 $remote_link = $args{'Base'};
1540 $direction = 'Target';
1543 # Don't write the transaction if we're doing this on create
1544 if ( $args{'Silent'} ) {
1545 return ( $val, $msg );
1548 my $remote_uri = RT::URI->new( $self->CurrentUser );
1549 $remote_uri->FromURI( $remote_link );
1551 #Write the transaction
1552 my ( $Trans, $Msg, $TransObj ) =
1553 $self->_NewTransaction(Type => 'AddLink',
1554 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
1555 NewValue => $remote_uri->URI || $remote_link,
1558 if ( $remote_uri->IsLocal ) {
1560 my $OtherObj = $remote_uri->Object;
1561 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
1562 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
1563 : $LINKDIRMAP{$args{'Type'}}->{Target},
1564 NewValue => $self->URI,
1565 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
1568 return ( $val, $Msg );
1578 # {{{ sub Rights testing
1580 =head1 Rights testing
1585 my $root = RT::User->new($RT::SystemUser);
1586 $root->Load('root');
1587 ok($root->Id, "Found the root user");
1588 my $rootq = RT::Queue->new($root);
1590 ok($rootq->Id, "Loaded the first queue");
1592 ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets");
1594 my $new_user = RT::User->new($RT::SystemUser);
1595 my ($id, $msg) = $new_user->Create(Name => 'ACLTest'.$$);
1597 ok ($id, "Created a new user for acl test $msg");
1599 my $q = RT::Queue->new($new_user);
1601 ok($q->Id, "Loaded the first queue");
1604 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
1605 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
1606 ok ($gval, "Grant succeeded - $gmsg");
1609 ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
1610 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
1611 ok ($gval, "Revocation succeeded - $gmsg");
1612 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
1618 # Create a ticket in the queue
1619 my $new_tick = RT::Ticket->new($RT::SystemUser);
1620 my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
1621 ok($tickid, "Created ticket: $tickid");
1622 # Make sure the user doesn't have the right to modify tickets in the queue
1623 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1624 # Create a new group
1625 my $group = RT::Group->new($RT::SystemUser);
1626 $group->CreateUserDefinedGroup(Name => 'ACLTest'.$$);
1627 ok($group->Id, "Created a new group Ok");
1628 # Grant a group the right to modify tickets in a queue
1629 ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1630 ok($gv,"Grant succeeed - $gm");
1631 # Add the user to the group
1632 ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
1633 ok ($aid, "Member added to group: $amsg");
1634 # Make sure the user does have the right to modify tickets in the queue
1635 ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
1638 # Remove the user from the group
1639 ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
1640 ok ($did,"Deleted the group member: $dmsg");
1641 # Make sure the user doesn't have the right to modify tickets in the queue
1642 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1645 my $q_as_system = RT::Queue->new($RT::SystemUser);
1646 $q_as_system->Load(1);
1647 ok($q_as_system->Id, "Loaded the first queue");
1649 # Create a ticket in the queue
1650 my $new_tick2 = RT::Ticket->new($RT::SystemUser);
1651 my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
1652 ok($tick2id, "Created ticket: $tick2id");
1653 is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1");
1656 # make sure that the user can't do this without subgroup membership
1657 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1660 my $subgroup = RT::Group->new($RT::SystemUser);
1661 $subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest',$$);
1662 ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok");
1663 #Add the subgroup as a subgroup of the group
1664 my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId);
1665 ok ($said, "Added the subgroup as a member of the group");
1666 # Add the user to a subgroup of the group
1668 my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId);
1669 ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
1670 # Make sure the user does have the right to modify tickets in the queue
1671 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
1673 # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights
1676 ($id, $msg) = $group->SetDisabled(1);
1678 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled");
1679 ($id, $msg) = $group->SetDisabled(0);
1681 # Test what happens when we disable the group the user is a member of directly
1683 ($id, $msg) = $subgroup->SetDisabled(1);
1685 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
1686 ($id, $msg) = $subgroup->SetDisabled(0);
1688 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
1693 my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId);
1694 ok($usrid,"removed the user from the group - $usrmsg");
1695 # Make sure the user doesn't have the right to modify tickets in the queue
1696 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1698 #revoke the right to modify tickets in a queue
1699 ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1700 ok($gv,"revoke succeeed - $gm");
1702 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
1704 # Grant queue admin cc the right to modify ticket in the queue
1705 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");
1706 ok($qv, "Granted the right successfully - $qm");
1708 # Add the user as a queue admincc
1709 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1710 ok ($add_id, "the user is now a queue admincc - $add_msg");
1712 # Make sure the user does have the right to modify tickets in the queue
1713 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1714 # Remove the user from the role group
1715 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1717 # Make sure the user doesn't have the right to modify tickets in the queue
1718 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1722 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1724 # Add the user as a ticket admincc
1725 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1726 ok ($add_id, "the user is now a queue admincc - $add_msg");
1728 # Make sure the user does have the right to modify tickets in the queue
1729 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1731 # Remove the user from the role group
1732 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1734 # Make sure the user doesn't have the right to modify tickets in the queue
1735 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1738 # Revoke the right to modify ticket in the queue
1739 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");
1740 ok($rqv, "Revoked the right successfully - $rqm");
1746 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
1748 # Before we start Make sure the user does not have the right to modify tickets in the queue
1749 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
1750 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
1752 # Grant queue admin cc the right to modify ticket in the queue
1753 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1754 ok($qv, "Granted the right successfully - $qm");
1756 # Make sure the user can't modify the ticket before they're added as a watcher
1757 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1758 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
1760 # Add the user as a queue admincc
1761 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1762 ok ($add_id, "the user is now a queue admincc - $add_msg");
1764 # Make sure the user does have the right to modify tickets in the queue
1765 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1766 ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
1767 # Remove the user from the role group
1768 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1770 # Make sure the user doesn't have the right to modify tickets in the queue
1771 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1772 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
1776 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1778 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1779 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1782 # Add the user as a ticket admincc
1783 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1784 ok ($add_id, "the user is now a queue admincc - $add_msg");
1786 # Make sure the user does have the right to modify tickets in the queue
1787 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1788 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
1790 # Remove the user from the role group
1791 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1793 # Make sure the user doesn't have the right to modify tickets in the queue
1794 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
1795 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1798 # Revoke the right to modify ticket in the queue
1799 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1800 ok($rqv, "Revoked the right successfully - $rqm");
1807 # Grant "privileged users" the system right to create users
1808 # Create a privileged user.
1809 # have that user create another user
1810 # Revoke the right for privileged users to create users
1811 # have the privileged user try to create another user and fail the ACL check
1824 Shim around PrincipalObj->HasRight. See RT::Principal
1831 return $self->PrincipalObj->HasRight(@_);
1836 # {{{ sub CurrentUserCanModify
1838 =head2 CurrentUserCanModify RIGHT
1840 If the user has rights for this object, either because
1841 he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an
1842 admin right) 'ModifySelf', return 1. otherwise, return undef.
1846 sub CurrentUserCanModify {
1850 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1854 #If the field is marked as an "administrators only" field,
1855 # don\'t let the user touch it.
1856 elsif ( $self->_Accessible( $right, 'admin' ) ) {
1860 #If the current user is trying to modify themselves
1861 elsif ( ( $self->id == $self->CurrentUser->id )
1862 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1867 #If we don\'t have a good reason to grant them rights to modify
1877 # {{{ sub CurrentUserHasRight
1879 =head2 CurrentUserHasRight
1881 Takes a single argument. returns 1 if $Self->CurrentUser
1882 has the requested right. returns undef otherwise
1886 sub CurrentUserHasRight {
1890 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1895 # {{{ sub _CleanupInvalidDelegations
1897 =head2 _CleanupInvalidDelegations { InsideTransaction => undef }
1899 Revokes all ACE entries delegated by this user which are inconsistent
1900 with their current delegation rights. Does not perform permission
1901 checks. Should only ever be called from inside the RT library.
1903 If called from inside a transaction, specify a true value for the
1904 InsideTransaction parameter.
1906 Returns a true value if the deletion succeeded; returns a false value
1907 and logs an internal error if the deletion fails (should not happen).
1911 # XXX Currently there is a _CleanupInvalidDelegations method in both
1912 # RT::User and RT::Group. If the recursive cleanup call for groups is
1913 # ever unrolled and merged, this code will probably want to be
1914 # factored out into RT::Principal.
1916 sub _CleanupInvalidDelegations {
1918 my %args = ( InsideTransaction => undef,
1921 unless ( $self->Id ) {
1922 $RT::Logger->warning("User not loaded.");
1926 my $in_trans = $args{InsideTransaction};
1928 return(1) if ($self->HasRight(Right => 'DelegateRights',
1929 Object => $RT::System));
1931 # Look up all delegation rights currently posessed by this user.
1932 my $deleg_acl = RT::ACL->new($RT::SystemUser);
1933 $deleg_acl->LimitToPrincipal(Type => 'User',
1934 Id => $self->PrincipalId,
1935 IncludeGroupMembership => 1);
1936 $deleg_acl->Limit( FIELD => 'RightName',
1938 VALUE => 'DelegateRights' );
1939 my @allowed_deleg_objects = map {$_->Object()}
1940 @{$deleg_acl->ItemsArrayRef()};
1942 # Look up all rights delegated by this principal which are
1943 # inconsistent with the allowed delegation objects.
1944 my $acl_to_del = RT::ACL->new($RT::SystemUser);
1945 $acl_to_del->DelegatedBy(Id => $self->Id);
1946 foreach (@allowed_deleg_objects) {
1947 $acl_to_del->LimitNotObject($_);
1950 # Delete all disallowed delegations
1951 while ( my $ace = $acl_to_del->Next() ) {
1952 my $ret = $ace->_Delete(InsideTransaction => 1);
1954 $RT::Handle->Rollback() unless $in_trans;
1955 $RT::Logger->warning("Couldn't delete delegated ACL entry ".$ace->Id);
1960 $RT::Handle->Commit() unless $in_trans;
1974 TransactionType => 'Set',
1975 RecordTransaction => 1,
1979 # Nobody is allowed to futz with RT_System or Nobody
1981 if ( ($self->Id == $RT::SystemUser->Id ) ||
1982 ($self->Id == $RT::Nobody->Id)) {
1983 return ( 0, $self->loc("Can not modify system users") );
1985 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1986 return ( 0, $self->loc("Permission Denied") );
1989 my $Old = $self->SUPER::_Value("$args{'Field'}");
1991 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
1992 Value => $args{'Value'} );
1994 #If we can't actually set the field to the value, don't record
1995 # a transaction. instead, get out of here.
1996 if ( $ret == 0 ) { return ( 0, $msg ); }
1998 if ( $args{'RecordTransaction'} == 1 ) {
2000 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2001 Type => $args{'TransactionType'},
2002 Field => $args{'Field'},
2003 NewValue => $args{'Value'},
2005 TimeTaken => $args{'TimeTaken'},
2007 return ( $Trans, scalar $TransObj->BriefDescription );
2010 return ( $ret, $msg );
2020 Takes the name of a table column.
2021 Returns its value as a string, if the user passes an ACL check
2030 #If the current user doesn't have ACLs, don't let em at it.
2032 my @PublicFields = qw( Name EmailAddress Organization Disabled
2033 RealName NickName Gecos ExternalAuthId
2034 AuthSystem ExternalContactInfoId
2035 ContactInfoSystem );
2037 #if the field is public, return it.
2038 if ( $self->_Accessible( $field, 'public' ) ) {
2039 return ( $self->SUPER::_Value($field) );
2043 #If the user wants to see their own values, let them
2044 # TODO figure ouyt a better way to deal with this
2045 elsif ( $self->CurrentUser->Id == $self->Id ) {
2046 return ( $self->SUPER::_Value($field) );
2049 #If the user has the admin users right, return the field
2050 elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
2051 return ( $self->SUPER::_Value($field) );
2063 [ Name => 'User Id' ],
2064 [ EmailAddress => 'Email' ],
2065 [ RealName => 'Name' ],
2066 [ Organization => 'Organization' ],