1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/copyleft/gpl.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
50 RT::User - RT User object
74 no warnings qw(redefine);
76 use vars qw(%_USERS_KEY_CACHE);
78 %_USERS_KEY_CACHE = ();
83 use RT::Interface::Email;
89 sub _OverlayAccessible {
92 Name => { public => 1, admin => 1 },
93 Password => { read => 0 },
94 EmailAddress => { public => 1 },
95 Organization => { public => 1, admin => 1 },
96 RealName => { public => 1 },
97 NickName => { public => 1 },
98 Lang => { public => 1 },
99 EmailEncoding => { public => 1 },
100 WebEncoding => { public => 1 },
101 ExternalContactInfoId => { public => 1, admin => 1 },
102 ContactInfoSystem => { public => 1, admin => 1 },
103 ExternalAuthId => { public => 1, admin => 1 },
104 AuthSystem => { public => 1, admin => 1 },
105 Gecos => { public => 1, admin => 1 },
106 PGPKey => { public => 1, admin => 1 },
117 =head2 Create { PARAMHASH }
122 # Make sure we can create a user
124 my $u1 = RT::User->new($RT::SystemUser);
125 is(ref($u1), 'RT::User');
126 my ($id, $msg) = $u1->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-1@example.com');
127 ok ($id, "Creating user CreateTest1 - " . $msg );
129 # Make sure we can't create a second user with the same name
130 my $u2 = RT::User->new($RT::SystemUser);
131 ($id, $msg) = $u2->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-2@example.com');
135 # Make sure we can't create a second user with the same EmailAddress address
136 my $u3 = RT::User->new($RT::SystemUser);
137 ($id, $msg) = $u3->Create(Name => 'CreateTest2'.$$, EmailAddress => $$.'create-test-1@example.com');
140 # Make sure we can create a user with no EmailAddress address
141 my $u4 = RT::User->new($RT::SystemUser);
142 ($id, $msg) = $u4->Create(Name => 'CreateTest3'.$$);
145 # make sure we can create a second user with no EmailAddress address
146 my $u5 = RT::User->new($RT::SystemUser);
147 ($id, $msg) = $u5->Create(Name => 'CreateTest4'.$$);
150 # make sure we can create a user with a blank EmailAddress address
151 my $u6 = RT::User->new($RT::SystemUser);
152 ($id, $msg) = $u6->Create(Name => 'CreateTest6'.$$, EmailAddress => '');
154 # make sure we can create a second user with a blankEmailAddress address
155 my $u7 = RT::User->new($RT::SystemUser);
156 ($id, $msg) = $u7->Create(Name => 'CreateTest7'.$$, EmailAddress => '');
159 # Can we change the email address away from from "";
160 ($id,$msg) = $u7->SetEmailAddress('foo@bar'.$$);
162 # can we change the address back to "";
163 ($id,$msg) = $u7->SetEmailAddress('');
165 is ($u7->EmailAddress, '');
179 _RecordTransaction => 1,
180 @_ # get the real argumentlist
183 # remove the value so it does not cripple SUPER::Create
184 my $record_transaction = delete $args{'_RecordTransaction'};
187 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
188 return ( 0, $self->loc('No permission to create users') );
192 unless ($self->CanonicalizeUserInfo(\%args)) {
193 return ( 0, $self->loc("Could not set user info") );
196 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
198 # if the user doesn't have a name defined, set it to the email address
199 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
203 # Privileged is no longer a column in users
204 my $privileged = $args{'Privileged'};
205 delete $args{'Privileged'};
208 if ($args{'CryptedPassword'} ) {
209 $args{'Password'} = $args{'CryptedPassword'};
210 delete $args{'CryptedPassword'};
212 elsif ( !$args{'Password'} ) {
213 $args{'Password'} = '*NO-PASSWORD*';
215 elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) {
216 return ( 0, $self->loc("Password needs to be at least [_1] characters long",$RT::MinimumPasswordLength) );
220 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
223 #TODO Specify some sensible defaults.
225 unless ( $args{'Name'} ) {
227 $RT::Logger->crit(Dumper \%args);
228 return ( 0, $self->loc("Must specify 'Name' attribute") );
231 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
232 if ($RT::SystemUser) { #This only works if RT::SystemUser has been defined
233 my $TempUser = RT::User->new($RT::SystemUser);
234 $TempUser->Load( $args{'Name'} );
235 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
237 return ( 0, $self->loc('Email address in use') )
238 unless ( $self->ValidateEmailAddress( $args{'EmailAddress'} ) );
241 $RT::Logger->warning( "$self couldn't check for pre-existing users");
245 $RT::Handle->BeginTransaction();
246 # Groups deal with principal ids, rather than user ids.
247 # When creating this user, set up a principal Id for it.
248 my $principal = RT::Principal->new($self->CurrentUser);
249 my $principal_id = $principal->Create(PrincipalType => 'User',
250 Disabled => $args{'Disabled'},
252 # If we couldn't create a principal Id, get the fuck out.
253 unless ($principal_id) {
254 $RT::Handle->Rollback();
255 $RT::Logger->crit("Couldn't create a Principal on new user create.");
256 $RT::Logger->crit("Strange things are afoot at the circle K");
257 return ( 0, $self->loc('Could not create user') );
260 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
261 delete $args{'Disabled'};
263 $self->SUPER::Create(id => $principal_id , %args);
266 #If the create failed.
268 $RT::Handle->Rollback();
269 $RT::Logger->error("Could not create a new user - " .join('-', %args));
271 return ( 0, $self->loc('Could not create user') );
274 my $aclstash = RT::Group->new($self->CurrentUser);
275 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
278 $RT::Handle->Rollback();
279 $RT::Logger->crit("Couldn't stash the user in groupmembers");
280 return ( 0, $self->loc('Could not create user') );
284 my $everyone = RT::Group->new($self->CurrentUser);
285 $everyone->LoadSystemInternalGroup('Everyone');
286 unless ($everyone->id) {
287 $RT::Logger->crit("Could not load Everyone group on user creation.");
288 $RT::Handle->Rollback();
289 return ( 0, $self->loc('Could not create user') );
293 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
294 unless ($everyone_id) {
295 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
296 $RT::Logger->crit($everyone_msg);
297 $RT::Handle->Rollback();
298 return ( 0, $self->loc('Could not create user') );
302 my $access_class = RT::Group->new($self->CurrentUser);
304 $access_class->LoadSystemInternalGroup('Privileged');
306 $access_class->LoadSystemInternalGroup('Unprivileged');
309 unless ($access_class->id) {
310 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
311 $RT::Handle->Rollback();
312 return ( 0, $self->loc('Could not create user') );
316 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
319 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
320 $RT::Logger->crit($ac_msg);
321 $RT::Handle->Rollback();
322 return ( 0, $self->loc('Could not create user') );
326 if ( $record_transaction ) {
327 $self->_NewTransaction( Type => "Create" );
332 return ( $id, $self->loc('User created') );
341 =head2 SetPrivileged BOOL
343 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
344 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
346 Returns a standard RT tuple of (val, msg);
351 ok(my $user = RT::User->new($RT::SystemUser));
352 ok($user->Load('root'), "Loaded user 'root'");
353 ok($user->Privileged, "User 'root' is privileged");
354 ok(my ($v,$m) = $user->SetPrivileged(0));
355 ok ($v ==1, "Set unprivileged suceeded ($m)");
356 ok(!$user->Privileged, "User 'root' is no longer privileged");
357 ok(my ($v2,$m2) = $user->SetPrivileged(1));
358 ok ($v2 ==1, "Set privileged suceeded ($m2");
359 ok($user->Privileged, "User 'root' is privileged again");
370 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
371 return ( 0, $self->loc('Permission Denied') );
373 my $priv = RT::Group->new($self->CurrentUser);
374 $priv->LoadSystemInternalGroup('Privileged');
377 $RT::Logger->crit("Could not find Privileged pseudogroup");
378 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
381 my $unpriv = RT::Group->new($self->CurrentUser);
382 $unpriv->LoadSystemInternalGroup('Unprivileged');
383 unless ($unpriv->Id) {
384 $RT::Logger->crit("Could not find unprivileged pseudogroup");
385 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
389 if ($priv->HasMember($self->PrincipalObj)) {
390 #$RT::Logger->debug("That user is already privileged");
391 return (0,$self->loc("That user is already privileged"));
393 if ($unpriv->HasMember($self->PrincipalObj)) {
394 $unpriv->_DeleteMember($self->PrincipalId);
396 # if we had layered transactions, life would be good
397 # sadly, we have to just go ahead, even if something
399 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
400 "unprivileged. something is drastically wrong.");
402 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
404 return (1, $self->loc("That user is now privileged"));
410 if ($unpriv->HasMember($self->PrincipalObj)) {
411 #$RT::Logger->debug("That user is already unprivileged");
412 return (0,$self->loc("That user is already unprivileged"));
414 if ($priv->HasMember($self->PrincipalObj)) {
415 $priv->_DeleteMember( $self->PrincipalId);
417 # if we had layered transactions, life would be good
418 # sadly, we have to just go ahead, even if something
420 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
421 "unprivileged. something is drastically wrong.");
423 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
425 return (1, $self->loc("That user is now unprivileged"));
438 Returns true if this user is privileged. Returns undef otherwise.
444 my $priv = RT::Group->new($self->CurrentUser);
445 $priv->LoadSystemInternalGroup('Privileged');
446 if ($priv->HasMember($self->PrincipalObj)) {
456 # {{{ sub _BootstrapCreate
458 #create a user without validating _any_ data.
460 #To be used only on database init.
461 # We can't localize here because it's before we _have_ a loc framework
463 sub _BootstrapCreate {
467 $args{'Password'} = '*NO-PASSWORD*';
470 $RT::Handle->BeginTransaction();
472 # Groups deal with principal ids, rather than user ids.
473 # When creating this user, set up a principal Id for it.
474 my $principal = RT::Principal->new($self->CurrentUser);
475 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
476 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
478 # If we couldn't create a principal Id, get the fuck out.
479 unless ($principal_id) {
480 $RT::Handle->Rollback();
481 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
482 return ( 0, 'Could not create user' );
484 $self->SUPER::Create(id => $principal_id, %args);
486 #If the create failed.
488 $RT::Handle->Rollback();
489 return ( 0, 'Could not create user' ) ; #never loc this
492 my $aclstash = RT::Group->new($self->CurrentUser);
493 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
496 $RT::Handle->Rollback();
497 $RT::Logger->crit("Couldn't stash the user in groupmembers");
498 return ( 0, $self->loc('Could not create user') );
502 $RT::Handle->Commit();
504 return ( $id, 'User created' );
514 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
524 Load a user object from the database. Takes a single argument.
525 If the argument is numerical, load by the column 'id'. Otherwise, load by
526 the "Name" column which is the user's textual username.
532 my $identifier = shift || return undef;
534 #if it's an int, load by id. otherwise, load by name.
535 if ( $identifier !~ /\D/ ) {
536 $self->SUPER::LoadById($identifier);
539 $self->LoadByCol( "Name", $identifier );
545 # {{{ sub LoadByEmail
549 Tries to load this user object from the database by the user's email address.
558 # Never load an empty address as an email address.
563 $address = $self->CanonicalizeEmailAddress($address);
565 #$RT::Logger->debug("Trying to load an email address: $address\n");
566 return $self->LoadByCol( "EmailAddress", $address );
571 # {{{ LoadOrCreateByEmail
573 =head2 LoadOrCreateByEmail ADDRESS
575 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
576 the provided email address. and loads them.
578 Returns a tuple of the user's id and a status message.
579 0 will be returned in place of the user's id in case of failure.
583 sub LoadOrCreateByEmail {
589 my ( $Address, $Name ) =
590 RT::Interface::Email::ParseAddressFromHeader($email);
593 $self->LoadByEmail($email);
594 $message = $self->loc('User loaded');
599 ( $val, $message ) = $self->Create(
601 EmailAddress => $email,
604 Comments => 'Autocreated when added as a watcher');
606 # Deal with the race condition of two account creations at once
607 $self->LoadByEmail($email);
610 $self->LoadByEmail($email);
613 $RT::Logger->error("Recovered from creation failure due to race condition");
614 $message = $self->loc("User loaded");
617 $RT::Logger->crit("Failed to create user ".$email .": " .$message);
623 return($self->Id, $message);
634 # {{{ sub ValidateEmailAddress
636 =head2 ValidateEmailAddress ADDRESS
638 Returns true if the email address entered is not in use by another user or is
639 undef or ''. Returns false if it's in use.
643 sub ValidateEmailAddress {
647 # if the email address is null, it's always valid
648 return (1) if ( !$Value || $Value eq "" );
650 my $TempUser = RT::User->new($RT::SystemUser);
651 $TempUser->LoadByEmail($Value);
653 if ( $TempUser->id && ( $TempUser->id != $self->id ) )
654 { # if we found a user with that address
655 # it's invalid to set this user's address to it
658 else { #it's a valid email address
665 # {{{ sub CanonicalizeEmailAddress
669 =head2 CanonicalizeEmailAddress ADDRESS
671 CanonicalizeEmailAddress converts email addresses into canonical form.
672 it takes one email address in and returns the proper canonical
673 form. You can dump whatever your proper local config is in here. Note
674 that it may be called as a static method; in this case the first argument
675 is class name not an object.
679 sub CanonicalizeEmailAddress {
682 # Example: the following rule would treat all email
683 # coming from a subdomain as coming from second level domain
685 if ($RT::CanonicalizeEmailAddressMatch && $RT::CanonicalizeEmailAddressReplace ) {
686 $email =~ s/$RT::CanonicalizeEmailAddressMatch/$RT::CanonicalizeEmailAddressReplace/gi;
694 # {{{ sub CanonicalizeUserInfo
698 =head2 CanonicalizeUserInfo HASH of ARGS
700 CanonicalizeUserInfo can convert all User->Create options.
701 it takes a hashref of all the params sent to User->Create and
702 returns that same hash, by default nothing is done.
704 This function is intended to allow users to have their info looked up via
705 an outside source and modified upon creation.
709 sub CanonicalizeUserInfo {
721 # {{{ Password related functions
723 # {{{ sub SetRandomPassword
725 =head2 SetRandomPassword
727 Takes no arguments. Returns a status code and a new password or an error message.
728 If the status is 1, the second value returned is the new password.
729 If the status is anything else, the new value returned is the error code.
733 sub SetRandomPassword {
736 unless ( $self->CurrentUserCanModify('Password') ) {
737 return ( 0, $self->loc("Permission Denied") );
741 my $min = ( $RT::MinimumPasswordLength > 6 ? $RT::MinimumPasswordLength : 6);
742 my $max = ( $RT::MinimumPasswordLength > 8 ? $RT::MinimumPasswordLength : 8);
744 my $pass = $self->GenerateRandomPassword( $min, $max) ;
746 # If we have "notify user on
748 my ( $val, $msg ) = $self->SetPassword($pass);
750 #If we got an error return the error.
751 return ( 0, $msg ) unless ($val);
753 #Otherwise, we changed the password, lets return it.
760 # {{{ sub ResetPassword
764 Returns status, [ERROR or new password]. Resets this user\'s password to
765 a randomly generated pronouncable password and emails them, using a
766 global template called "RT_PasswordChange", which can be overridden
767 with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged"
768 for privileged and Non-privileged users respectively.
775 unless ( $self->CurrentUserCanModify('Password') ) {
776 return ( 0, $self->loc("Permission Denied") );
778 my ( $status, $pass ) = $self->SetRandomPassword();
781 return ( 0, "$pass" );
784 my $template = RT::Template->new( $self->CurrentUser );
786 if ( $self->Privileged ) {
787 $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
790 $template->LoadGlobalTemplate('RT_PasswordChange_NonPrivileged');
793 unless ( $template->Id ) {
794 $template->LoadGlobalTemplate('RT_PasswordChange');
797 unless ( $template->Id ) {
798 $RT::Logger->crit( "$self tried to send "
800 . " a password reminder "
801 . "but couldn't find a password change template" );
804 my $notification = RT::Action::SendPasswordEmail->new(
805 TemplateObj => $template,
809 $notification->SetHeader( 'To', $self->EmailAddress );
812 $ret = $notification->Prepare();
814 $ret = $notification->Commit();
818 return ( 1, $self->loc('New password notification sent') );
821 return ( 0, $self->loc('Notification could not be sent') );
828 # {{{ sub GenerateRandomPassword
830 =head2 GenerateRandomPassword MIN_LEN and MAX_LEN
832 Returns a random password between MIN_LEN and MAX_LEN characters long.
836 sub GenerateRandomPassword {
838 my $min_length = shift;
839 my $max_length = shift;
841 #This code derived from mpw.pl, a bit of code with a sordid history
844 # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
845 # Converted to perl from C by Marc Horowitz, 1/20/2000.
846 # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
847 # Original PL/I version provided by Jerry Saltzer.
849 my ( $frequency, $start_freq, $total_sum, $row_sums );
851 #When munging characters, we need to know where to start counting letters from
854 # frequency of English digraphs (from D Edwards 1/27/66)
857 4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23, 167,
858 2, 14, 0, 83, 76, 127, 7, 25, 8, 1, 9, 1
861 13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0,
862 11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0
865 32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1, 0,
866 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0
869 40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15, 6,
870 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0
873 84, 20, 55, 125, 51, 40, 19, 16, 50, 1,
874 4, 55, 54, 146, 35, 37, 6, 191, 149, 65,
878 19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1, 0,
879 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0
882 20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1, 4,
883 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0
886 101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3, 2,
887 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0
890 40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25, 202,
891 56, 12, 1, 46, 79, 117, 1, 22, 0, 4, 0, 3
894 3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0,
895 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0
898 1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
899 0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0
902 44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2, 2,
903 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0
906 52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1,
907 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0
910 42, 10, 47, 122, 63, 19, 106, 12, 30, 1,
911 6, 6, 9, 7, 54, 7, 1, 7, 44, 124,
915 7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41, 134,
916 13, 23, 0, 91, 23, 42, 55, 16, 28, 0, 4, 1
919 19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
920 27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0
923 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
924 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0
927 83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26, 16,
928 60, 4, 0, 24, 37, 55, 6, 11, 4, 0, 28, 0
931 65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11, 12,
932 56, 17, 6, 9, 48, 116, 35, 1, 28, 0, 4, 0
935 57, 22, 3, 1, 76, 5, 2, 330, 126, 1,
936 0, 14, 10, 6, 79, 7, 0, 49, 50, 56,
940 11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31,
941 1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0
944 7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
945 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0
948 36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8,
949 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0
952 1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0,
953 1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0
956 14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7, 5,
957 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0
960 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
961 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
965 #We need to know the totals for each row
969 map { $sum += $_ } @$_;
974 #Frequency with which a given letter starts a word.
976 1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24,
977 20, 355, 379, 319, 823, 618, 21, 317, 962, 1991,
978 271, 104, 516, 6, 16, 14
982 map { $total_sum += $_ } @$start_freq;
984 my $length = $min_length + int( rand( $max_length - $min_length ) );
986 my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
987 my @word = ( $char + $a );
988 for ( 2 .. $length ) {
990 $self->_GenerateRandomNextChar( $row_sums->[$char],
991 $frequency->[$char] );
992 push ( @word, $char + $a );
996 return pack( "C*", @word );
1000 #A private helper function for RandomPassword
1001 # Takes a row summary and a frequency chart for the next character to be searched
1002 sub _GenerateRandomNextChar {
1004 my ( $all, $freq ) = @_;
1007 for ( $pos = int( rand($all) ), $i = 0 ;
1008 $pos >= $freq->[$i] ;
1009 $pos -= $freq->[$i], $i++ )
1018 # {{{ sub SetPassword
1022 Takes a string. Checks the string's length and sets this user's password
1029 my $password = shift;
1031 unless ( $self->CurrentUserCanModify('Password') ) {
1032 return ( 0, $self->loc('Password: Permission Denied') );
1036 return ( 0, $self->loc("No password set") );
1038 elsif ( length($password) < $RT::MinimumPasswordLength ) {
1039 return ( 0, $self->loc("Password needs to be at least [_1] characters long", $RT::MinimumPasswordLength) );
1042 my $new = !$self->HasPassword;
1043 $password = $self->_GeneratePassword($password);
1044 my ( $val, $msg ) = $self->SUPER::SetPassword($password);
1046 return ( 1, $self->loc("Password set") ) if $new;
1047 return ( 1, $self->loc("Password changed") );
1050 return ( $val, $msg );
1056 =head2 _GeneratePassword PASSWORD
1058 returns an MD5 hash of the password passed in, in hexadecimal encoding.
1062 sub _GeneratePassword {
1064 my $password = shift;
1066 my $md5 = Digest::MD5->new();
1067 $md5->add(encode_utf8($password));
1068 return ($md5->hexdigest);
1072 =head2 _GeneratePasswordBase64 PASSWORD
1074 returns an MD5 hash of the password passed in, in base64 encoding
1079 sub _GeneratePasswordBase64 {
1081 my $password = shift;
1083 my $md5 = Digest::MD5->new();
1084 $md5->add(encode_utf8($password));
1085 return ($md5->b64digest);
1094 Returns true if the user has a valid password, otherwise returns false.
1101 my $pwd = $self->__Value('Password');
1102 return undef if !defined $pwd
1104 || $pwd eq '*NO-PASSWORD*';
1109 # {{{ sub IsPassword
1113 Returns true if the passed in value is this user's password.
1114 Returns undef otherwise.
1122 #TODO there isn't any apparent way to legitimately ACL this
1124 # RT does not allow null passwords
1125 if ( ( !defined($value) ) or ( $value eq '' ) ) {
1129 if ( $self->PrincipalObj->Disabled ) {
1131 "Disabled user " . $self->Name . " tried to log in" );
1135 unless ($self->HasPassword) {
1139 # generate an md5 password
1140 if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
1144 # if it's a historical password we say ok.
1145 if ($self->__Value('Password') eq crypt($value, $self->__Value('Password'))
1146 or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password'))
1148 # ...but upgrade the legacy password inplace.
1149 $self->SUPER::SetPassword( $self->_GeneratePassword($value) );
1153 # no password check has succeeded. get out
1162 # {{{ sub SetDisabled
1164 =head2 Sub SetDisabled
1166 Toggles the user's disabled flag.
1168 set, all password checks for this user will fail. All ACL checks for this
1169 user will fail. The user will appear in no user listings.
1177 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1178 return (0, $self->loc('Permission Denied'));
1180 return $self->PrincipalObj->SetDisabled(@_);
1185 return $self->PrincipalObj->Disabled(@_);
1189 # {{{ Principal related routines
1193 Returns the principal object for this user. returns an empty RT::Principal
1194 if there's no principal object matching this user.
1195 The response is cached. PrincipalObj should never ever change.
1199 ok(my $u = RT::User->new($RT::SystemUser));
1200 ok($u->Load(1), "Loaded the first user");
1201 ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal");
1202 is($u->PrincipalObj->PrincipalType, 'User' , "Principal 1 is a user, not a group");
1211 unless ($self->{'PrincipalObj'} &&
1212 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1213 ($self->{'PrincipalObj'}->PrincipalType eq 'User')) {
1215 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1216 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1217 'PrincipalType' => 'User') ;
1219 return($self->{'PrincipalObj'});
1225 Returns this user's PrincipalId
1238 # {{{ sub HasGroupRight
1240 =head2 HasGroupRight
1242 Takes a paramhash which can contain
1244 GroupObj => RT::Group or Group => integer
1248 Returns 1 if this user has the right specified in the paramhash for the Group
1251 Returns undef if they don't.
1265 if ( defined $args{'Group'} ) {
1266 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1267 $args{'GroupObj'}->Load( $args{'Group'} );
1270 # {{{ Validate and load up the GroupId
1271 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1278 # Figure out whether a user has the right we're asking about.
1279 my $retval = $self->HasRight(
1280 Object => $args{'GroupObj'},
1281 Right => $args{'Right'},
1295 Returns a group collection object containing the groups of which this
1302 my $groups = RT::Groups->new($self->CurrentUser);
1303 $groups->LimitToUserDefinedGroups;
1304 $groups->WithMember(PrincipalId => $self->Id,
1313 #much false laziness w/Ticket_Overlay.pm
1315 # A helper table for links mapping to make it easier
1316 # to build and parse links between tickets
1318 use vars '%LINKDIRMAP';
1321 MemberOf => { Base => 'MemberOf',
1322 Target => 'HasMember', },
1323 RefersTo => { Base => 'RefersTo',
1324 Target => 'ReferredToBy', },
1325 DependsOn => { Base => 'DependsOn',
1326 Target => 'DependedOnBy', },
1327 MergedInto => { Base => 'MergedInto',
1328 Target => 'MergedInto', },
1332 sub LINKDIRMAP { return \%LINKDIRMAP }
1337 # #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic ---
1338 # #tobias meant by $f
1339 # my $field = shift;
1340 # my $type = shift || "";
1342 # unless ( $self->{"$field$type"} ) {
1343 # $self->{"$field$type"} = new RT::Links( $self->CurrentUser );
1344 # if ( $self->CurrentUserHasRight('ShowTicket') ) {
1345 # # Maybe this ticket is a merged ticket
1346 # my $Tickets = new RT::Tickets( $self->CurrentUser );
1347 # # at least to myself
1348 # $self->{"$field$type"}->Limit( FIELD => $field,
1349 # VALUE => $self->URI,
1350 # ENTRYAGGREGATOR => 'OR' );
1351 # $Tickets->Limit( FIELD => 'EffectiveId',
1352 # VALUE => $self->EffectiveId );
1353 # while (my $Ticket = $Tickets->Next) {
1354 # $self->{"$field$type"}->Limit( FIELD => $field,
1355 # VALUE => $Ticket->URI,
1356 # ENTRYAGGREGATOR => 'OR' );
1358 # $self->{"$field$type"}->Limit( FIELD => 'Type',
1363 # return ( $self->{"$field$type"} );
1368 Delete a link. takes a paramhash of Base, Target and Type.
1369 Either Base or Target must be null. The null value will
1370 be replaced with this ticket\'s id
1383 unless ( $args{'Target'} || $args{'Base'} ) {
1384 $RT::Logger->error("Base or Target must be specified\n");
1385 return ( 0, $self->loc('Either base or target must be specified') );
1390 $right++ if $self->CurrentUserHasRight('ModifyUser');
1391 if ( !$right && $RT::StrictLinkACL ) {
1392 return ( 0, $self->loc("Permission Denied") );
1395 # # If the other URI is an RT::Ticket, we want to make sure the user
1396 # # can modify it too...
1397 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
1398 # return (0, $msg) unless $status;
1399 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
1402 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
1403 # ( $RT::StrictLinkACL && $right < 2 ) )
1405 # return ( 0, $self->loc("Permission Denied") );
1408 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
1411 $RT::Logger->debug("Couldn't find that link\n");
1415 my ($direction, $remote_link);
1417 if ( $args{'Base'} ) {
1418 $remote_link = $args{'Base'};
1419 $direction = 'Target';
1421 elsif ( $args{'Target'} ) {
1422 $remote_link = $args{'Target'};
1426 if ( $args{'Silent'} ) {
1427 return ( $val, $Msg );
1430 my $remote_uri = RT::URI->new( $self->CurrentUser );
1431 $remote_uri->FromURI( $remote_link );
1433 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1434 Type => 'DeleteLink',
1435 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
1436 OldValue => $remote_uri->URI || $remote_link,
1440 if ( $remote_uri->IsLocal ) {
1442 my $OtherObj = $remote_uri->Object;
1443 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
1444 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
1445 : $LINKDIRMAP{$args{'Type'}}->{Target},
1446 OldValue => $self->URI,
1447 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
1451 return ( $Trans, $Msg );
1457 my %args = ( Target => '',
1463 unless ( $args{'Target'} || $args{'Base'} ) {
1464 $RT::Logger->error("Base or Target must be specified\n");
1465 return ( 0, $self->loc('Either base or target must be specified') );
1469 $right++ if $self->CurrentUserHasRight('ModifyUser');
1470 if ( !$right && $RT::StrictLinkACL ) {
1471 return ( 0, $self->loc("Permission Denied") );
1474 # # If the other URI is an RT::Ticket, we want to make sure the user
1475 # # can modify it too...
1476 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
1477 # return (0, $msg) unless $status;
1478 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
1481 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
1482 # ( $RT::StrictLinkACL && $right < 2 ) )
1484 # return ( 0, $self->loc("Permission Denied") );
1487 return $self->_AddLink(%args);
1490 #sub __GetTicketFromURI {
1492 # my %args = ( URI => '', @_ );
1494 # # If the other URI is an RT::Ticket, we want to make sure the user
1495 # # can modify it too...
1496 # my $uri_obj = RT::URI->new( $self->CurrentUser );
1497 # $uri_obj->FromURI( $args{'URI'} );
1499 # unless ( $uri_obj->Resolver && $uri_obj->Scheme ) {
1500 # my $msg = $self->loc( "Couldn't resolve '[_1]' into a URI.", $args{'URI'} );
1501 # $RT::Logger->warning( "$msg\n" );
1502 # return( 0, $msg );
1504 # my $obj = $uri_obj->Resolver->Object;
1505 # unless ( UNIVERSAL::isa($obj, 'RT::Ticket') && $obj->id ) {
1506 # return (1, 'Found not a ticket', undef);
1508 # return (1, 'Found ticket', $obj);
1513 Private non-acled variant of AddLink so that links can be added during create.
1519 my %args = ( Target => '',
1525 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
1526 return ($val, $msg) if !$val || $exist;
1528 my ($direction, $remote_link);
1529 if ( $args{'Target'} ) {
1530 $remote_link = $args{'Target'};
1531 $direction = 'Base';
1532 } elsif ( $args{'Base'} ) {
1533 $remote_link = $args{'Base'};
1534 $direction = 'Target';
1537 # Don't write the transaction if we're doing this on create
1538 if ( $args{'Silent'} ) {
1539 return ( $val, $msg );
1542 my $remote_uri = RT::URI->new( $self->CurrentUser );
1543 $remote_uri->FromURI( $remote_link );
1545 #Write the transaction
1546 my ( $Trans, $Msg, $TransObj ) =
1547 $self->_NewTransaction(Type => 'AddLink',
1548 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
1549 NewValue => $remote_uri->URI || $remote_link,
1552 if ( $remote_uri->IsLocal ) {
1554 my $OtherObj = $remote_uri->Object;
1555 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
1556 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
1557 : $LINKDIRMAP{$args{'Type'}}->{Target},
1558 NewValue => $self->URI,
1559 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
1562 return ( $val, $Msg );
1572 # {{{ sub Rights testing
1574 =head1 Rights testing
1579 my $root = RT::User->new($RT::SystemUser);
1580 $root->Load('root');
1581 ok($root->Id, "Found the root user");
1582 my $rootq = RT::Queue->new($root);
1584 ok($rootq->Id, "Loaded the first queue");
1586 ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets");
1588 my $new_user = RT::User->new($RT::SystemUser);
1589 my ($id, $msg) = $new_user->Create(Name => 'ACLTest'.$$);
1591 ok ($id, "Created a new user for acl test $msg");
1593 my $q = RT::Queue->new($new_user);
1595 ok($q->Id, "Loaded the first queue");
1598 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
1599 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
1600 ok ($gval, "Grant succeeded - $gmsg");
1603 ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
1604 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
1605 ok ($gval, "Revocation succeeded - $gmsg");
1606 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
1612 # Create a ticket in the queue
1613 my $new_tick = RT::Ticket->new($RT::SystemUser);
1614 my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
1615 ok($tickid, "Created ticket: $tickid");
1616 # Make sure the user doesn't have the right to modify tickets in the queue
1617 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1618 # Create a new group
1619 my $group = RT::Group->new($RT::SystemUser);
1620 $group->CreateUserDefinedGroup(Name => 'ACLTest'.$$);
1621 ok($group->Id, "Created a new group Ok");
1622 # Grant a group the right to modify tickets in a queue
1623 ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1624 ok($gv,"Grant succeeed - $gm");
1625 # Add the user to the group
1626 ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
1627 ok ($aid, "Member added to group: $amsg");
1628 # Make sure the user does have the right to modify tickets in the queue
1629 ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
1632 # Remove the user from the group
1633 ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
1634 ok ($did,"Deleted the group member: $dmsg");
1635 # Make sure the user doesn't have the right to modify tickets in the queue
1636 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1639 my $q_as_system = RT::Queue->new($RT::SystemUser);
1640 $q_as_system->Load(1);
1641 ok($q_as_system->Id, "Loaded the first queue");
1643 # Create a ticket in the queue
1644 my $new_tick2 = RT::Ticket->new($RT::SystemUser);
1645 my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
1646 ok($tick2id, "Created ticket: $tick2id");
1647 is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1");
1650 # make sure that the user can't do this without subgroup membership
1651 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1654 my $subgroup = RT::Group->new($RT::SystemUser);
1655 $subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest',$$);
1656 ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok");
1657 #Add the subgroup as a subgroup of the group
1658 my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId);
1659 ok ($said, "Added the subgroup as a member of the group");
1660 # Add the user to a subgroup of the group
1662 my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId);
1663 ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
1664 # Make sure the user does have the right to modify tickets in the queue
1665 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
1667 # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights
1670 ($id, $msg) = $group->SetDisabled(1);
1672 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled");
1673 ($id, $msg) = $group->SetDisabled(0);
1675 # Test what happens when we disable the group the user is a member of directly
1677 ($id, $msg) = $subgroup->SetDisabled(1);
1679 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
1680 ($id, $msg) = $subgroup->SetDisabled(0);
1682 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
1687 my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId);
1688 ok($usrid,"removed the user from the group - $usrmsg");
1689 # Make sure the user doesn't have the right to modify tickets in the queue
1690 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1692 #revoke the right to modify tickets in a queue
1693 ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1694 ok($gv,"revoke succeeed - $gm");
1696 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
1698 # Grant queue admin cc the right to modify ticket in the queue
1699 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");
1700 ok($qv, "Granted the right successfully - $qm");
1702 # Add the user as a queue admincc
1703 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1704 ok ($add_id, "the user is now a queue admincc - $add_msg");
1706 # Make sure the user does have the right to modify tickets in the queue
1707 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1708 # Remove the user from the role group
1709 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1711 # Make sure the user doesn't have the right to modify tickets in the queue
1712 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1716 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1718 # Add the user as a ticket admincc
1719 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1720 ok ($add_id, "the user is now a queue admincc - $add_msg");
1722 # Make sure the user does have the right to modify tickets in the queue
1723 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1725 # Remove the user from the role group
1726 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1728 # Make sure the user doesn't have the right to modify tickets in the queue
1729 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1732 # Revoke the right to modify ticket in the queue
1733 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");
1734 ok($rqv, "Revoked the right successfully - $rqm");
1740 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
1742 # Before we start Make sure the user does not have the right to modify tickets in the queue
1743 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
1744 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
1746 # Grant queue admin cc the right to modify ticket in the queue
1747 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1748 ok($qv, "Granted the right successfully - $qm");
1750 # Make sure the user can't modify the ticket before they're added as a watcher
1751 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1752 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
1754 # Add the user as a queue admincc
1755 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1756 ok ($add_id, "the user is now a queue admincc - $add_msg");
1758 # Make sure the user does have the right to modify tickets in the queue
1759 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1760 ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
1761 # Remove the user from the role group
1762 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1764 # Make sure the user doesn't have the right to modify tickets in the queue
1765 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1766 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
1770 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1772 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1773 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1776 # Add the user as a ticket admincc
1777 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1778 ok ($add_id, "the user is now a queue admincc - $add_msg");
1780 # Make sure the user does have the right to modify tickets in the queue
1781 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1782 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
1784 # Remove the user from the role group
1785 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1787 # Make sure the user doesn't have the right to modify tickets in the queue
1788 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
1789 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1792 # Revoke the right to modify ticket in the queue
1793 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1794 ok($rqv, "Revoked the right successfully - $rqm");
1801 # Grant "privileged users" the system right to create users
1802 # Create a privileged user.
1803 # have that user create another user
1804 # Revoke the right for privileged users to create users
1805 # have the privileged user try to create another user and fail the ACL check
1818 Shim around PrincipalObj->HasRight. See RT::Principal
1825 return $self->PrincipalObj->HasRight(@_);
1830 # {{{ sub CurrentUserCanModify
1832 =head2 CurrentUserCanModify RIGHT
1834 If the user has rights for this object, either because
1835 he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an
1836 admin right) 'ModifySelf', return 1. otherwise, return undef.
1840 sub CurrentUserCanModify {
1844 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1848 #If the field is marked as an "administrators only" field,
1849 # don\'t let the user touch it.
1850 elsif ( $self->_Accessible( $right, 'admin' ) ) {
1854 #If the current user is trying to modify themselves
1855 elsif ( ( $self->id == $self->CurrentUser->id )
1856 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1861 #If we don\'t have a good reason to grant them rights to modify
1871 # {{{ sub CurrentUserHasRight
1873 =head2 CurrentUserHasRight
1875 Takes a single argument. returns 1 if $Self->CurrentUser
1876 has the requested right. returns undef otherwise
1880 sub CurrentUserHasRight {
1884 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1890 $name = ref ($name).'-'.$name->Id;
1893 return 'Pref-'.$name;
1896 # {{{ sub Preferences
1898 =head2 Preferences NAME/OBJ DEFAULT
1900 Obtain user preferences associated with given object or name.
1901 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1902 override the entries with user preferences.
1908 my $name = _PrefName (shift);
1909 my $default = shift;
1911 my $attr = RT::Attribute->new ($self->CurrentUser);
1912 $attr->LoadByNameAndObject (Object => $self, Name => $name);
1914 my $content = $attr->Id ? $attr->Content : undef;
1915 if (ref ($content) eq 'HASH') {
1916 if (ref ($default) eq 'HASH') {
1917 for (keys %$default) {
1918 exists $content->{$_} or $content->{$_} = $default->{$_};
1921 elsif (defined $default) {
1922 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1927 return defined $content ? $content : $default;
1933 # {{{ sub SetPreferences
1935 =head2 SetPreferences NAME/OBJ VALUE
1937 Set user preferences associated with given object or name.
1941 sub SetPreferences {
1943 my $name = _PrefName (shift);
1945 my $attr = RT::Attribute->new ($self->CurrentUser);
1946 $attr->LoadByNameAndObject (Object => $self, Name => $name);
1948 return $attr->SetContent ($value);
1951 return $self->AddAttribute ( Name => $name, Content => $value );
1958 =head2 WatchedQueues ROLE_LIST
1960 Returns a RT::Queues object containing every queue watched by the user.
1962 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1964 $user->WatchedQueues('Cc', 'AdminCc');
1971 my @roles = @_ || ('Cc', 'AdminCc');
1973 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1975 my $watched_queues = RT::Queues->new($self->CurrentUser);
1977 my $group_alias = $watched_queues->Join(
1981 FIELD2 => 'Instance',
1984 $watched_queues->Limit(
1985 ALIAS => $group_alias,
1987 VALUE => 'RT::Queue-Role',
1988 ENTRYAGGREGATOR => 'AND',
1990 if (grep { $_ eq 'Cc' } @roles) {
1991 $watched_queues->Limit(
1992 SUBCLAUSE => 'LimitToWatchers',
1993 ALIAS => $group_alias,
1996 ENTRYAGGREGATOR => 'OR',
1999 if (grep { $_ eq 'AdminCc' } @roles) {
2000 $watched_queues->Limit(
2001 SUBCLAUSE => 'LimitToWatchers',
2002 ALIAS => $group_alias,
2005 ENTRYAGGREGATOR => 'OR',
2009 my $queues_alias = $watched_queues->Join(
2010 ALIAS1 => $group_alias,
2012 TABLE2 => 'CachedGroupMembers',
2013 FIELD2 => 'GroupId',
2015 $watched_queues->Limit(
2016 ALIAS => $queues_alias,
2017 FIELD => 'MemberId',
2018 VALUE => $self->PrincipalId,
2021 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
2023 return $watched_queues;
2028 # {{{ sub _CleanupInvalidDelegations
2030 =head2 _CleanupInvalidDelegations { InsideTransaction => undef }
2032 Revokes all ACE entries delegated by this user which are inconsistent
2033 with their current delegation rights. Does not perform permission
2034 checks. Should only ever be called from inside the RT library.
2036 If called from inside a transaction, specify a true value for the
2037 InsideTransaction parameter.
2039 Returns a true value if the deletion succeeded; returns a false value
2040 and logs an internal error if the deletion fails (should not happen).
2044 # XXX Currently there is a _CleanupInvalidDelegations method in both
2045 # RT::User and RT::Group. If the recursive cleanup call for groups is
2046 # ever unrolled and merged, this code will probably want to be
2047 # factored out into RT::Principal.
2049 sub _CleanupInvalidDelegations {
2051 my %args = ( InsideTransaction => undef,
2054 unless ( $self->Id ) {
2055 $RT::Logger->warning("User not loaded.");
2059 my $in_trans = $args{InsideTransaction};
2061 return(1) if ($self->HasRight(Right => 'DelegateRights',
2062 Object => $RT::System));
2064 # Look up all delegation rights currently posessed by this user.
2065 my $deleg_acl = RT::ACL->new($RT::SystemUser);
2066 $deleg_acl->LimitToPrincipal(Type => 'User',
2067 Id => $self->PrincipalId,
2068 IncludeGroupMembership => 1);
2069 $deleg_acl->Limit( FIELD => 'RightName',
2071 VALUE => 'DelegateRights' );
2072 my @allowed_deleg_objects = map {$_->Object()}
2073 @{$deleg_acl->ItemsArrayRef()};
2075 # Look up all rights delegated by this principal which are
2076 # inconsistent with the allowed delegation objects.
2077 my $acl_to_del = RT::ACL->new($RT::SystemUser);
2078 $acl_to_del->DelegatedBy(Id => $self->Id);
2079 foreach (@allowed_deleg_objects) {
2080 $acl_to_del->LimitNotObject($_);
2083 # Delete all disallowed delegations
2084 while ( my $ace = $acl_to_del->Next() ) {
2085 my $ret = $ace->_Delete(InsideTransaction => 1);
2087 $RT::Handle->Rollback() unless $in_trans;
2088 $RT::Logger->warning("Couldn't delete delegated ACL entry ".$ace->Id);
2093 $RT::Handle->Commit() unless $in_trans;
2107 TransactionType => 'Set',
2108 RecordTransaction => 1,
2112 # Nobody is allowed to futz with RT_System or Nobody
2114 if ( ($self->Id == $RT::SystemUser->Id ) ||
2115 ($self->Id == $RT::Nobody->Id)) {
2116 return ( 0, $self->loc("Can not modify system users") );
2118 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
2119 return ( 0, $self->loc("Permission Denied") );
2122 my $Old = $self->SUPER::_Value("$args{'Field'}");
2124 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
2125 Value => $args{'Value'} );
2127 #If we can't actually set the field to the value, don't record
2128 # a transaction. instead, get out of here.
2129 if ( $ret == 0 ) { return ( 0, $msg ); }
2131 if ( $args{'RecordTransaction'} == 1 ) {
2133 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2134 Type => $args{'TransactionType'},
2135 Field => $args{'Field'},
2136 NewValue => $args{'Value'},
2138 TimeTaken => $args{'TimeTaken'},
2140 return ( $Trans, scalar $TransObj->BriefDescription );
2143 return ( $ret, $msg );
2153 Takes the name of a table column.
2154 Returns its value as a string, if the user passes an ACL check
2163 #If the current user doesn't have ACLs, don't let em at it.
2165 my @PublicFields = qw( Name EmailAddress Organization Disabled
2166 RealName NickName Gecos ExternalAuthId
2167 AuthSystem ExternalContactInfoId
2168 ContactInfoSystem );
2170 #if the field is public, return it.
2171 if ( $self->_Accessible( $field, 'public' ) ) {
2172 return ( $self->SUPER::_Value($field) );
2176 #If the user wants to see their own values, let them
2177 # TODO figure ouyt a better way to deal with this
2178 elsif ( $self->CurrentUser->Id == $self->Id ) {
2179 return ( $self->SUPER::_Value($field) );
2182 #If the user has the admin users right, return the field
2183 elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
2184 return ( $self->SUPER::_Value($field) );
2196 [ Name => 'User Id' ],
2197 [ EmailAddress => 'Email' ],
2198 [ RealName => 'Name' ],
2199 [ Organization => 'Organization' ],