3 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
5 # (Except where explictly superceded by other copyright notices)
7 # This work is made available to you under the terms of Version 2 of
8 # the GNU General Public License. A copy of that license should have
9 # been provided with this software, but in any event can be snarfed
12 # This work is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
17 # Unless otherwise specified, all modifications, corrections or
18 # extensions to this work which alter its source code become the
19 # property of Best Practical Solutions, LLC when submitted for
20 # inclusion in the work.
26 RT::User - RT User object
47 no warnings qw(redefine);
49 use vars qw(%_USERS_KEY_CACHE);
51 %_USERS_KEY_CACHE = ();
61 sub _ClassAccessible {
65 {read => 1, type => 'int(11)', default => ''},
67 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(120)', default => ''},
69 { write => 1, type => 'varchar(40)', default => ''},
71 {read => 1, write => 1, admin => 1, type => 'blob', default => ''},
73 {read => 1, write => 1, type => 'blob', default => ''},
75 {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''},
76 FreeformContactInfo =>
77 {read => 1, write => 1, type => 'blob', default => ''},
79 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(200)', default => ''},
81 {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''},
83 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
85 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
87 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
89 {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
90 ExternalContactInfoId =>
91 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
93 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(30)', default => ''},
95 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
97 {read => 1, write => 1, public => 1, admin => 1,type => 'varchar(30)', default => ''},
99 {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(16)', default => ''},
102 {read => 1, write => 1, public => 1, admin => 1, type => 'text', default => ''},
105 {read => 1, write => 1, type => 'varchar(30)', default => ''},
107 {read => 1, write => 1, type => 'varchar(30)', default => ''},
109 {read => 1, write => 1, type => 'varchar(30)', default => ''},
111 {read => 1, write => 1, type => 'varchar(30)', default => ''},
113 {read => 1, write => 1, type => 'varchar(200)', default => ''},
115 {read => 1, write => 1, type => 'varchar(200)', default => ''},
117 {read => 1, write => 1, type => 'varchar(100)', default => ''},
119 {read => 1, write => 1, type => 'varchar(100)', default => ''},
121 {read => 1, write => 1, type => 'varchar(16)', default => ''},
123 {read => 1, write => 1, type => 'varchar(50)', default => ''},
125 {read => 1, auto => 1, type => 'int(11)', default => ''},
127 {read => 1, auto => 1, type => 'datetime', default => ''},
129 {read => 1, auto => 1, type => 'int(11)', default => ''},
131 {read => 1, auto => 1, type => 'datetime', default => ''},
141 =head2 Create { PARAMHASH }
146 # Make sure we can create a user
148 my $u1 = RT::User->new($RT::SystemUser);
149 is(ref($u1), 'RT::User');
150 my ($id, $msg) = $u1->Create(Name => 'CreateTest1', EmailAddress => 'create-test-1@example.com');
151 ok ($id, "Creating user CreateTest1 - " . $msg );
153 # Make sure we can't create a second user with the same name
154 my $u2 = RT::User->new($RT::SystemUser);
155 ($id, $msg) = $u2->Create(Name => 'CreateTest1', EmailAddress => 'create-test-2@example.com');
159 # Make sure we can't create a second user with the same EmailAddress address
160 my $u3 = RT::User->new($RT::SystemUser);
161 ($id, $msg) = $u3->Create(Name => 'CreateTest2', EmailAddress => 'create-test-1@example.com');
164 # Make sure we can create a user with no EmailAddress address
165 my $u4 = RT::User->new($RT::SystemUser);
166 ($id, $msg) = $u4->Create(Name => 'CreateTest3');
169 # make sure we can create a second user with no EmailAddress address
170 my $u5 = RT::User->new($RT::SystemUser);
171 ($id, $msg) = $u5->Create(Name => 'CreateTest4');
174 # make sure we can create a user with a blank EmailAddress address
175 my $u6 = RT::User->new($RT::SystemUser);
176 ($id, $msg) = $u6->Create(Name => 'CreateTest6', EmailAddress => '');
178 # make sure we can create a second user with a blankEmailAddress address
179 my $u7 = RT::User->new($RT::SystemUser);
180 ($id, $msg) = $u7->Create(Name => 'CreateTest7', EmailAddress => '');
183 # Can we change the email address away from from "";
184 ($id,$msg) = $u7->SetEmailAddress('foo@bar');
186 # can we change the address back to "";
187 ($id,$msg) = $u7->SetEmailAddress('');
189 is ($u7->EmailAddress, '');
203 @_ # get the real argumentlist
207 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
208 return ( 0, $self->loc('No permission to create users') );
211 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
212 # if the user doesn't have a name defined, set it to the email address
213 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
217 # Privileged is no longer a column in users
218 my $privileged = $args{'Privileged'};
219 delete $args{'Privileged'};
222 if ($args{'CryptedPassword'} ) {
223 $args{'Password'} = $args{'CryptedPassword'};
224 delete $args{'CryptedPassword'};
226 elsif ( !$args{'Password'} ) {
227 $args{'Password'} = '*NO-PASSWORD*';
229 elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) {
230 return ( 0, $self->loc("Password too short") );
234 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
237 #TODO Specify some sensible defaults.
239 unless ( $args{'Name'} ) {
241 $RT::Logger->crit(Dumper \%args);
242 return ( 0, $self->loc("Must specify 'Name' attribute") );
245 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
246 if ($RT::SystemUser) { #This only works if RT::SystemUser has been defined
247 my $TempUser = RT::User->new($RT::SystemUser);
248 $TempUser->Load( $args{'Name'} );
249 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
251 return ( 0, $self->loc('Email address in use') )
252 unless ( $self->ValidateEmailAddress( $args{'EmailAddress'} ) );
255 $RT::Logger->warning( "$self couldn't check for pre-existing users");
259 $RT::Handle->BeginTransaction();
260 # Groups deal with principal ids, rather than user ids.
261 # When creating this user, set up a principal Id for it.
262 my $principal = RT::Principal->new($self->CurrentUser);
263 my $principal_id = $principal->Create(PrincipalType => 'User',
264 Disabled => $args{'Disabled'},
266 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
267 # If we couldn't create a principal Id, get the fuck out.
268 unless ($principal_id) {
269 $RT::Handle->Rollback();
270 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
271 return ( 0, $self->loc('Could not create user') );
274 delete $args{'Disabled'};
276 $self->SUPER::Create(id => $principal_id , %args);
279 #If the create failed.
281 $RT::Handle->Rollback();
282 $RT::Logger->error("Could not create a new user - " .join('-'. %args));
284 return ( 0, $self->loc('Could not create user') );
289 #if ($args{'SendWelcomeMessage'}) {
290 # #TODO: Check if the email exists and looks valid
291 # #TODO: Send the user a "welcome message"
296 my $aclstash = RT::Group->new($self->CurrentUser);
297 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
300 $RT::Handle->Rollback();
301 $RT::Logger->crit("Couldn't stash the user in groumembers");
302 return ( 0, $self->loc('Could not create user') );
307 #$RT::Logger->debug("Adding the user as a member of everyone");
308 my $everyone = RT::Group->new($self->CurrentUser);
309 $everyone->LoadSystemInternalGroup('Everyone');
310 $everyone->AddMember($self->PrincipalId);
313 my $priv = RT::Group->new($self->CurrentUser);
314 #$RT::Logger->debug("Making ".$self->Id." a privileged user");
315 $priv->LoadSystemInternalGroup('Privileged');
316 $priv->AddMember($self->PrincipalId);
318 my $unpriv = RT::Group->new($self->CurrentUser);
319 #$RT::Logger->debug("Making ".$self->Id." an unprivileged user");
320 $unpriv->LoadSystemInternalGroup('Unprivileged');
321 $unpriv->AddMember($self->PrincipalId);
325 # $RT::Logger->debug("Finished creating the user");
326 return ( $id, $self->loc('User created') );
335 =head2 SetPrivileged BOOL
337 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
338 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
340 Returns a standard RT tuple of (val, msg);
345 ok(my $user = RT::User->new($RT::SystemUser));
346 ok($user->Load('root'), "Loaded user 'root'");
347 ok($user->Privileged, "User 'root' is privileged");
348 ok(my ($v,$m) = $user->SetPrivileged(0));
349 ok ($v ==1, "Set unprivileged suceeded ($m)");
350 ok(!$user->Privileged, "User 'root' is no longer privileged");
351 ok(my ($v2,$m2) = $user->SetPrivileged(1));
352 ok ($v2 ==1, "Set privileged suceeded ($m2");
353 ok($user->Privileged, "User 'root' is privileged again");
363 my $priv = RT::Group->new($self->CurrentUser);
364 $priv->LoadSystemInternalGroup('Privileged');
367 $RT::Logger->crit("Could not find Privileged pseudogroup");
368 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
371 my $unpriv = RT::Group->new($self->CurrentUser);
372 $unpriv->LoadSystemInternalGroup('Unprivileged');
373 unless ($unpriv->Id) {
374 $RT::Logger->crit("Could not find unprivileged pseudogroup");
375 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
379 if ($priv->HasMember($self->PrincipalObj)) {
380 #$RT::Logger->debug("That user is already privileged");
381 return (0,$self->loc("That user is already privileged"));
383 if ($unpriv->HasMember($self->PrincipalObj)) {
384 $unpriv->DeleteMember($self->PrincipalId);
386 # if we had layered transactions, life would be good
387 # sadly, we have to just go ahead, even if something
389 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
390 "unprivileged. something is drastically wrong.");
392 my ($status, $msg) = $priv->AddMember($self->PrincipalId);
394 return (1, $self->loc("That user is now privileged"));
400 if ($unpriv->HasMember($self->PrincipalObj)) {
401 #$RT::Logger->debug("That user is already unprivileged");
402 return (0,$self->loc("That user is already unprivileged"));
404 if ($priv->HasMember($self->PrincipalObj)) {
405 $priv->DeleteMember($self->PrincipalId);
407 # if we had layered transactions, life would be good
408 # sadly, we have to just go ahead, even if something
410 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
411 "unprivileged. something is drastically wrong.");
413 my ($status, $msg) = $unpriv->AddMember($self->PrincipalId);
415 return (1, $self->loc("That user is now unprivileged"));
428 Returns true if this user is privileged. Returns undef otherwise.
434 my $priv = RT::Group->new($self->CurrentUser);
435 $priv->LoadSystemInternalGroup('Privileged');
436 if ($priv->HasMember($self->PrincipalObj)) {
446 # {{{ sub _BootstrapCreate
448 #create a user without validating _any_ data.
450 #To be used only on database init.
451 # We can't localize here because it's before we _have_ a loc framework
453 sub _BootstrapCreate {
457 $args{'Password'} = '*NO-PASSWORD*';
460 $RT::Handle->BeginTransaction();
462 # Groups deal with principal ids, rather than user ids.
463 # When creating this user, set up a principal Id for it.
464 my $principal = RT::Principal->new($self->CurrentUser);
465 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
466 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
468 # If we couldn't create a principal Id, get the fuck out.
469 unless ($principal_id) {
470 $RT::Handle->Rollback();
471 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
472 return ( 0, 'Could not create user' );
474 $self->SUPER::Create(id => $principal_id, %args);
476 #If the create failed.
478 $RT::Handle->Rollback();
479 return ( 0, 'Could not create user' ) ; #never loc this
482 my $aclstash = RT::Group->new($self->CurrentUser);
483 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
486 $RT::Handle->Rollback();
487 $RT::Logger->crit("Couldn't stash the user in groupmembers");
488 return ( 0, $self->loc('Could not create user') );
492 $RT::Handle->Commit();
494 return ( $id, 'User created' );
504 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
514 Load a user object from the database. Takes a single argument.
515 If the argument is numerical, load by the column 'id'. Otherwise, load by
516 the "Name" column which is the user's textual username.
522 my $identifier = shift || return undef;
524 #if it's an int, load by id. otherwise, load by name.
525 if ( $identifier !~ /\D/ ) {
526 $self->SUPER::LoadById($identifier);
529 $self->LoadByCol( "Name", $identifier );
535 # {{{ sub LoadByEmail
539 Tries to load this user object from the database by the user's email address.
548 # Never load an empty address as an email address.
553 $address = $self->CanonicalizeEmailAddress($address);
555 #$RT::Logger->debug("Trying to load an email address: $address\n");
556 return $self->LoadByCol( "EmailAddress", $address );
561 # {{{ LoadOrCreateByEmail
563 =head2 LoadOrCreateByEmail ADDRESS
565 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
566 the provided email address. and loads them.
568 Returns a tuple of the user's id and a status message.
569 0 will be returned in place of the user's id in case of failure.
573 sub LoadOrCreateByEmail {
579 $self->LoadByEmail($email);
580 $message = $self->loc('User loaded');
582 ( $val, $message ) = $self->Create(
584 EmailAddress => $email,
587 Comments => 'Autocreated when added as a watcher');
589 # Deal with the race condition of two account creations at once
590 $self->LoadByEmail($email);
593 $self->LoadByEmail($email);
596 $RT::Logger->error("Recovered from creation failure due to race condition");
597 $message = $self->loc("User loaded");
600 $RT::Logger->crit("Failed to create user ".$email .": " .$message);
606 return($self->Id, $message);
617 # {{{ sub ValidateEmailAddress
619 =head2 ValidateEmailAddress ADDRESS
621 Returns true if the email address entered is not in use by another user or is
622 undef or ''. Returns false if it's in use.
626 sub ValidateEmailAddress {
630 # if the email address is null, it's always valid
631 return (1) if ( !$Value || $Value eq "" );
633 my $TempUser = RT::User->new($RT::SystemUser);
634 $TempUser->LoadByEmail($Value);
636 if ( $TempUser->id && ( $TempUser->id != $self->id ) )
637 { # if we found a user with that address
638 # it's invalid to set this user's address to it
641 else { #it's a valid email address
648 # {{{ sub CanonicalizeEmailAddress
652 =item CanonicalizeEmailAddress ADDRESS
654 # CanonicalizeEmailAddress converts email addresses into canonical form.
655 # it takes one email address in and returns the proper canonical
656 # form. You can dump whatever your proper local config is in here
660 sub CanonicalizeEmailAddress {
663 # Example: the following rule would treat all email
664 # coming from a subdomain as coming from second level domain
666 if ($RT::CanonicalizeEmailAddressMatch && $RT::CanonicalizeEmailAddressReplace ) {
667 $email =~ s/$RT::CanonicalizeEmailAddressMatch/$RT::CanonicalizeEmailAddressReplace/gi;
676 # {{{ Password related functions
678 # {{{ sub SetRandomPassword
680 =head2 SetRandomPassword
682 Takes no arguments. Returns a status code and a new password or an error message.
683 If the status is 1, the second value returned is the new password.
684 If the status is anything else, the new value returned is the error code.
688 sub SetRandomPassword {
691 unless ( $self->CurrentUserCanModify('Password') ) {
692 return ( 0, $self->loc("Permission Denied") );
695 my $pass = $self->GenerateRandomPassword( 6, 8 );
697 # If we have "notify user on
699 my ( $val, $msg ) = $self->SetPassword($pass);
701 #If we got an error return the error.
702 return ( 0, $msg ) unless ($val);
704 #Otherwise, we changed the password, lets return it.
711 # {{{ sub ResetPassword
715 Returns status, [ERROR or new password]. Resets this user\'s password to
716 a randomly generated pronouncable password and emails them, using a
717 global template called "RT_PasswordChange", which can be overridden
718 with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged"
719 for privileged and Non-privileged users respectively.
726 unless ( $self->CurrentUserCanModify('Password') ) {
727 return ( 0, $self->loc("Permission Denied") );
729 my ( $status, $pass ) = $self->SetRandomPassword();
732 return ( 0, "$pass" );
735 my $template = RT::Template->new( $self->CurrentUser );
737 if ( $self->Privileged ) {
738 $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
741 $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
744 unless ( $template->Id ) {
745 $template->LoadGlobalTemplate('RT_PasswordChange');
748 unless ( $template->Id ) {
749 $RT::Logger->crit( "$self tried to send "
751 . " a password reminder "
752 . "but couldn't find a password change template" );
755 my $notification = RT::Action::SendPasswordEmail->new(
756 TemplateObj => $template,
760 $notification->SetTo( $self->EmailAddress );
763 $ret = $notification->Prepare();
765 $ret = $notification->Commit();
769 return ( 1, $self->loc('New password notification sent') );
772 return ( 0, $self->loc('Notification could not be sent') );
779 # {{{ sub GenerateRandomPassword
781 =head2 GenerateRandomPassword MIN_LEN and MAX_LEN
783 Returns a random password between MIN_LEN and MAX_LEN characters long.
787 sub GenerateRandomPassword {
789 my $min_length = shift;
790 my $max_length = shift;
792 #This code derived from mpw.pl, a bit of code with a sordid history
795 # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
796 # Converted to perl from C by Marc Horowitz, 1/20/2000.
797 # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
798 # Original PL/I version provided by Jerry Saltzer.
800 my ( $frequency, $start_freq, $total_sum, $row_sums );
802 #When munging characters, we need to know where to start counting letters from
805 # frequency of English digraphs (from D Edwards 1/27/66)
808 4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23, 167,
809 2, 14, 0, 83, 76, 127, 7, 25, 8, 1, 9, 1
812 13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0,
813 11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0
816 32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1, 0,
817 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0
820 40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15, 6,
821 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0
824 84, 20, 55, 125, 51, 40, 19, 16, 50, 1,
825 4, 55, 54, 146, 35, 37, 6, 191, 149, 65,
829 19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1, 0,
830 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0
833 20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1, 4,
834 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0
837 101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3, 2,
838 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0
841 40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25, 202,
842 56, 12, 1, 46, 79, 117, 1, 22, 0, 4, 0, 3
845 3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0,
846 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0
849 1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
850 0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0
853 44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2, 2,
854 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0
857 52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1,
858 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0
861 42, 10, 47, 122, 63, 19, 106, 12, 30, 1,
862 6, 6, 9, 7, 54, 7, 1, 7, 44, 124,
866 7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41, 134,
867 13, 23, 0, 91, 23, 42, 55, 16, 28, 0, 4, 1
870 19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
871 27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0
874 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
875 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0
878 83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26, 16,
879 60, 4, 0, 24, 37, 55, 6, 11, 4, 0, 28, 0
882 65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11, 12,
883 56, 17, 6, 9, 48, 116, 35, 1, 28, 0, 4, 0
886 57, 22, 3, 1, 76, 5, 2, 330, 126, 1,
887 0, 14, 10, 6, 79, 7, 0, 49, 50, 56,
891 11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31,
892 1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0
895 7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
896 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0
899 36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8,
900 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0
903 1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0,
904 1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0
907 14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7, 5,
908 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0
911 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
912 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
916 #We need to know the totals for each row
920 map { $sum += $_ } @$_;
925 #Frequency with which a given letter starts a word.
927 1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24,
928 20, 355, 379, 319, 823, 618, 21, 317, 962, 1991,
929 271, 104, 516, 6, 16, 14
933 map { $total_sum += $_ } @$start_freq;
935 my $length = $min_length + int( rand( $max_length - $min_length ) );
937 my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
938 my @word = ( $char + $a );
939 for ( 2 .. $length ) {
941 $self->_GenerateRandomNextChar( $row_sums->[$char],
942 $frequency->[$char] );
943 push ( @word, $char + $a );
947 return pack( "C*", @word );
951 #A private helper function for RandomPassword
952 # Takes a row summary and a frequency chart for the next character to be searched
953 sub _GenerateRandomNextChar {
955 my ( $all, $freq ) = @_;
958 for ( $pos = int( rand($all) ), $i = 0 ;
959 $pos >= $freq->[$i] ;
960 $pos -= $freq->[$i], $i++ )
969 # {{{ sub SetPassword
973 Takes a string. Checks the string's length and sets this user's password
980 my $password = shift;
982 unless ( $self->CurrentUserCanModify('Password') ) {
983 return ( 0, $self->loc('Permission Denied') );
987 return ( 0, $self->loc("No password set") );
989 elsif ( length($password) < $RT::MinimumPasswordLength ) {
990 return ( 0, $self->loc("Password too short") );
993 $password = $self->_GeneratePassword($password);
994 return ( $self->SUPER::SetPassword( $password));
999 =head2 _GeneratePassword PASSWORD
1001 returns an MD5 hash of the password passed in, in base64 encoding.
1005 sub _GeneratePassword {
1007 my $password = shift;
1009 my $md5 = Digest::MD5->new();
1010 $md5->add($password);
1011 return ($md5->b64digest);
1017 # {{{ sub IsPassword
1021 Returns true if the passed in value is this user's password.
1022 Returns undef otherwise.
1030 #TODO there isn't any apparent way to legitimately ACL this
1032 # RT does not allow null passwords
1033 if ( ( !defined($value) ) or ( $value eq '' ) ) {
1037 if ( $self->PrincipalObj->Disabled ) {
1039 "Disabled user " . $self->Name . " tried to log in" );
1043 if ( ($self->__Value('Password') eq '') ||
1044 ($self->__Value('Password') eq undef) ) {
1048 # generate an md5 password
1049 if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
1053 # if it's a historical password we say ok.
1055 if ( $self->__Value('Password') eq crypt( $value, $self->__Value('Password') ) ) {
1059 # no password check has succeeded. get out
1068 # {{{ sub SetDisabled
1070 =head2 Sub SetDisabled
1072 Toggles the user's disabled flag.
1074 set, all password checks for this user will fail. All ACL checks for this
1075 user will fail. The user will appear in no user listings.
1083 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1084 return (0, $self->loc('Permission Denied'));
1086 return $self->PrincipalObj->SetDisabled(@_);
1091 return $self->PrincipalObj->Disabled(@_);
1095 # {{{ Principal related routines
1099 Returns the principal object for this user. returns an empty RT::Principal
1100 if there's no principal object matching this user.
1101 The response is cached. PrincipalObj should never ever change.
1105 ok(my $u = RT::User->new($RT::SystemUser));
1106 ok($u->Load(1), "Loaded the first user");
1107 ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal");
1108 ok($u->PrincipalObj->PrincipalType eq 'User' , "Principal 1 is a user, not a group");
1117 unless ($self->{'PrincipalObj'} &&
1118 ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
1119 ($self->{'PrincipalObj'}->PrincipalType eq 'User')) {
1121 $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
1122 $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
1123 'PrincipalType' => 'User') ;
1125 return($self->{'PrincipalObj'});
1131 Returns this user's PrincipalId
1144 # {{{ sub HasGroupRight
1146 =head2 HasGroupRight
1148 Takes a paramhash which can contain
1150 GroupObj => RT::Group or Group => integer
1154 Returns 1 if this user has the right specified in the paramhash for the Group
1157 Returns undef if they don't.
1171 if ( defined $args{'Group'} ) {
1172 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1173 $args{'GroupObj'}->Load( $args{'Group'} );
1176 # {{{ Validate and load up the GroupId
1177 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1184 # Figure out whether a user has the right we're asking about.
1185 my $retval = $self->HasRight(
1186 Object => $args{'GroupObj'},
1187 Right => $args{'Right'},
1197 # {{{ sub Rights testing
1199 =head2 Rights testing
1204 my $root = RT::User->new($RT::SystemUser);
1205 $root->Load('root');
1206 ok($root->Id, "Found the root user");
1207 my $rootq = RT::Queue->new($root);
1209 ok($rootq->Id, "Loaded the first queue");
1211 ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets");
1213 my $new_user = RT::User->new($RT::SystemUser);
1214 my ($id, $msg) = $new_user->Create(Name => 'ACLTest');
1216 ok ($id, "Created a new user for acl test $msg");
1218 my $q = RT::Queue->new($new_user);
1220 ok($q->Id, "Loaded the first queue");
1223 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
1224 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
1225 ok ($gval, "Grant succeeded - $gmsg");
1228 ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
1229 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
1230 ok ($gval, "Revocation succeeded - $gmsg");
1231 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
1237 # Create a ticket in the queue
1238 my $new_tick = RT::Ticket->new($RT::SystemUser);
1239 my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
1240 ok($tickid, "Created ticket: $tickid");
1241 # Make sure the user doesn't have the right to modify tickets in the queue
1242 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1243 # Create a new group
1244 my $group = RT::Group->new($RT::SystemUser);
1245 $group->CreateUserDefinedGroup(Name => 'ACLTest');
1246 ok($group->Id, "Created a new group Ok");
1247 # Grant a group the right to modify tickets in a queue
1248 ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1249 ok($gv,"Grant succeeed - $gm");
1250 # Add the user to the group
1251 ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
1252 ok ($aid, "Member added to group: $amsg");
1253 # Make sure the user does have the right to modify tickets in the queue
1254 ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
1257 # Remove the user from the group
1258 ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
1259 ok ($did,"Deleted the group member: $dmsg");
1260 # Make sure the user doesn't have the right to modify tickets in the queue
1261 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1264 my $q_as_system = RT::Queue->new($RT::SystemUser);
1265 $q_as_system->Load(1);
1266 ok($q_as_system->Id, "Loaded the first queue");
1268 # Create a ticket in the queue
1269 my $new_tick2 = RT::Ticket->new($RT::SystemUser);
1270 my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
1271 ok($tick2id, "Created ticket: $tick2id");
1272 ok($new_tick2->QueueObj->id eq $q_as_system->Id, "Created a new ticket in queue 1");
1275 # make sure that the user can't do this without subgroup membership
1276 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1279 my $subgroup = RT::Group->new($RT::SystemUser);
1280 $subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest');
1281 ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok");
1282 #Add the subgroup as a subgroup of the group
1283 my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId);
1284 ok ($said, "Added the subgroup as a member of the group");
1285 # Add the user to a subgroup of the group
1287 my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId);
1288 ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
1289 # Make sure the user does have the right to modify tickets in the queue
1290 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
1292 # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights
1295 ($id, $msg) = $group->SetDisabled(1);
1297 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled");
1298 ($id, $msg) = $group->SetDisabled(0);
1300 # Test what happens when we disable the group the user is a member of directly
1302 ($id, $msg) = $subgroup->SetDisabled(1);
1304 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
1305 ($id, $msg) = $subgroup->SetDisabled(0);
1307 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
1312 my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId);
1313 ok($usrid,"removed the user from the group - $usrmsg");
1314 # Make sure the user doesn't have the right to modify tickets in the queue
1315 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1317 #revoke the right to modify tickets in a queue
1318 ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1319 ok($gv,"revoke succeeed - $gm");
1321 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
1323 # Grant queue admin cc the right to modify ticket in the queue
1324 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $q_as_system, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1325 ok($qv, "Granted the right successfully - $qm");
1327 # Add the user as a queue admincc
1328 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1329 ok ($add_id, "the user is now a queue admincc - $add_msg");
1331 # Make sure the user does have the right to modify tickets in the queue
1332 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1333 # Remove the user from the role group
1334 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1336 # Make sure the user doesn't have the right to modify tickets in the queue
1337 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1341 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1343 # Add the user as a ticket admincc
1344 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1345 ok ($add_id, "the user is now a queue admincc - $add_msg");
1347 # Make sure the user does have the right to modify tickets in the queue
1348 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1350 # Remove the user from the role group
1351 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1353 # Make sure the user doesn't have the right to modify tickets in the queue
1354 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1357 # Revoke the right to modify ticket in the queue
1358 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $q_as_system, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1359 ok($rqv, "Revoked the right successfully - $rqm");
1365 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
1367 # Before we start Make sure the user does not have the right to modify tickets in the queue
1368 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
1369 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
1371 # Grant queue admin cc the right to modify ticket in the queue
1372 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1373 ok($qv, "Granted the right successfully - $qm");
1375 # Make sure the user can't modify the ticket before they're added as a watcher
1376 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1377 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
1379 # Add the user as a queue admincc
1380 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1381 ok ($add_id, "the user is now a queue admincc - $add_msg");
1383 # Make sure the user does have the right to modify tickets in the queue
1384 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1385 ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
1386 # Remove the user from the role group
1387 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1389 # Make sure the user doesn't have the right to modify tickets in the queue
1390 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1391 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
1395 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1397 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1398 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1401 # Add the user as a ticket admincc
1402 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1403 ok ($add_id, "the user is now a queue admincc - $add_msg");
1405 # Make sure the user does have the right to modify tickets in the queue
1406 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1407 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
1409 # Remove the user from the role group
1410 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1412 # Make sure the user doesn't have the right to modify tickets in the queue
1413 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
1414 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1417 # Revoke the right to modify ticket in the queue
1418 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1419 ok($rqv, "Revoked the right successfully - $rqm");
1426 # Grant "privileged users" the system right to create users
1427 # Create a privileged user.
1428 # have that user create another user
1429 # Revoke the right for privileged users to create users
1430 # have the privileged user try to create another user and fail the ACL check
1443 Shim around PrincipalObj->HasRight. See RT::Principal
1450 return $self->PrincipalObj->HasRight(@_);
1455 # {{{ sub CurrentUserCanModify
1457 =head2 CurrentUserCanModify RIGHT
1459 If the user has rights for this object, either because
1460 he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an
1461 admin right) 'ModifySelf', return 1. otherwise, return undef.
1465 sub CurrentUserCanModify {
1469 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1473 #If the field is marked as an "administrators only" field,
1474 # don\'t let the user touch it.
1475 elsif ( $self->_Accessible( $right, 'admin' ) ) {
1479 #If the current user is trying to modify themselves
1480 elsif ( ( $self->id == $self->CurrentUser->id )
1481 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1486 #If we don\'t have a good reason to grant them rights to modify
1496 # {{{ sub CurrentUserHasRight
1498 =head2 CurrentUserHasRight
1500 Takes a single argument. returns 1 if $Self->CurrentUser
1501 has the requested right. returns undef otherwise
1505 sub CurrentUserHasRight {
1509 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1525 # Nobody is allowed to futz with RT_System or Nobody
1527 if ( ($self->Id == $RT::SystemUser->Id ) ||
1528 ($self->Id == $RT::Nobody->Id)) {
1529 return ( 0, $self->loc("Can not modify system users") );
1531 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
1532 return ( 0, $self->loc("Permission Denied") );
1536 my ( $ret, $msg ) = $self->SUPER::_Set(
1537 Field => $args{'Field'},
1538 Value => $args{'Value'}
1541 return ( $ret, $msg );
1550 Takes the name of a table column.
1551 Returns its value as a string, if the user passes an ACL check
1560 #If the current user doesn't have ACLs, don't let em at it.
1562 my @PublicFields = qw( Name EmailAddress Organization Disabled
1563 RealName NickName Gecos ExternalAuthId
1564 AuthSystem ExternalContactInfoId
1565 ContactInfoSystem );
1567 #if the field is public, return it.
1568 if ( $self->_Accessible( $field, 'public' ) ) {
1569 return ( $self->SUPER::_Value($field) );
1573 #If the user wants to see their own values, let them
1574 # TODO figure ouyt a better way to deal with this
1575 elsif ( $self->CurrentUser->Id == $self->Id ) {
1576 return ( $self->SUPER::_Value($field) );
1579 #If the user has the admin users right, return the field
1580 elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
1581 return ( $self->SUPER::_Value($field) );