1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2009 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/licenses/old-licenses/gpl-2.0.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 }}}
51 RT::User - RT User object
70 no warnings qw(redefine);
75 use RT::Interface::Email;
78 sub _OverlayAccessible {
81 Name => { public => 1, admin => 1 },
82 Password => { read => 0 },
83 EmailAddress => { public => 1 },
84 Organization => { public => 1, admin => 1 },
85 RealName => { public => 1 },
86 NickName => { public => 1 },
87 Lang => { public => 1 },
88 EmailEncoding => { public => 1 },
89 WebEncoding => { public => 1 },
90 ExternalContactInfoId => { public => 1, admin => 1 },
91 ContactInfoSystem => { public => 1, admin => 1 },
92 ExternalAuthId => { public => 1, admin => 1 },
93 AuthSystem => { public => 1, admin => 1 },
94 Gecos => { public => 1, admin => 1 },
95 PGPKey => { public => 1, admin => 1 },
102 =head2 Create { PARAMHASH }
115 _RecordTransaction => 1,
116 @_ # get the real argumentlist
119 # remove the value so it does not cripple SUPER::Create
120 my $record_transaction = delete $args{'_RecordTransaction'};
123 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
124 return ( 0, $self->loc('Permission Denied') );
128 unless ($self->CanonicalizeUserInfo(\%args)) {
129 return ( 0, $self->loc("Could not set user info") );
132 $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
134 # if the user doesn't have a name defined, set it to the email address
135 $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
139 my $privileged = delete $args{'Privileged'};
142 if ($args{'CryptedPassword'} ) {
143 $args{'Password'} = $args{'CryptedPassword'};
144 delete $args{'CryptedPassword'};
146 elsif ( !$args{'Password'} ) {
147 $args{'Password'} = '*NO-PASSWORD*';
149 elsif ( length( $args{'Password'} ) < RT->Config->Get('MinimumPasswordLength') ) {
150 return ( 0, $self->loc("Password needs to be at least [_1] characters long",RT->Config->Get('MinimumPasswordLength')) );
154 $args{'Password'} = $self->_GeneratePassword($args{'Password'});
157 #TODO Specify some sensible defaults.
159 unless ( $args{'Name'} ) {
160 return ( 0, $self->loc("Must specify 'Name' attribute") );
163 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
164 if ($RT::SystemUser) { #This only works if RT::SystemUser has been defined
165 my $TempUser = RT::User->new($RT::SystemUser);
166 $TempUser->Load( $args{'Name'} );
167 return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
169 my ($val, $message) = $self->ValidateEmailAddress( $args{'EmailAddress'} );
170 return (0, $message) unless ( $val );
173 $RT::Logger->warning( "$self couldn't check for pre-existing users");
177 $RT::Handle->BeginTransaction();
178 # Groups deal with principal ids, rather than user ids.
179 # When creating this user, set up a principal Id for it.
180 my $principal = RT::Principal->new($self->CurrentUser);
181 my $principal_id = $principal->Create(PrincipalType => 'User',
182 Disabled => $args{'Disabled'},
184 # If we couldn't create a principal Id, get the fuck out.
185 unless ($principal_id) {
186 $RT::Handle->Rollback();
187 $RT::Logger->crit("Couldn't create a Principal on new user create.");
188 $RT::Logger->crit("Strange things are afoot at the circle K");
189 return ( 0, $self->loc('Could not create user') );
192 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
193 delete $args{'Disabled'};
195 $self->SUPER::Create(id => $principal_id , %args);
198 #If the create failed.
200 $RT::Handle->Rollback();
201 $RT::Logger->error("Could not create a new user - " .join('-', %args));
203 return ( 0, $self->loc('Could not create user') );
206 my $aclstash = RT::Group->new($self->CurrentUser);
207 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
210 $RT::Handle->Rollback();
211 $RT::Logger->crit("Couldn't stash the user in groupmembers");
212 return ( 0, $self->loc('Could not create user') );
216 my $everyone = RT::Group->new($self->CurrentUser);
217 $everyone->LoadSystemInternalGroup('Everyone');
218 unless ($everyone->id) {
219 $RT::Logger->crit("Could not load Everyone group on user creation.");
220 $RT::Handle->Rollback();
221 return ( 0, $self->loc('Could not create user') );
225 my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
226 unless ($everyone_id) {
227 $RT::Logger->crit("Could not add user to Everyone group on user creation.");
228 $RT::Logger->crit($everyone_msg);
229 $RT::Handle->Rollback();
230 return ( 0, $self->loc('Could not create user') );
234 my $access_class = RT::Group->new($self->CurrentUser);
236 $access_class->LoadSystemInternalGroup('Privileged');
238 $access_class->LoadSystemInternalGroup('Unprivileged');
241 unless ($access_class->id) {
242 $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation");
243 $RT::Handle->Rollback();
244 return ( 0, $self->loc('Could not create user') );
248 my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId);
251 $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted");
252 $RT::Logger->crit($ac_msg);
253 $RT::Handle->Rollback();
254 return ( 0, $self->loc('Could not create user') );
258 if ( $record_transaction ) {
259 $self->_NewTransaction( Type => "Create" );
264 return ( $id, $self->loc('User created') );
267 =head2 SetPrivileged BOOL
269 If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
270 Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
272 Returns a standard RT tuple of (val, msg);
282 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
283 return ( 0, $self->loc('Permission Denied') );
286 my $priv = RT::Group->new($self->CurrentUser);
287 $priv->LoadSystemInternalGroup('Privileged');
289 $RT::Logger->crit("Could not find Privileged pseudogroup");
290 return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
293 my $unpriv = RT::Group->new($self->CurrentUser);
294 $unpriv->LoadSystemInternalGroup('Unprivileged');
295 unless ($unpriv->Id) {
296 $RT::Logger->crit("Could not find unprivileged pseudogroup");
297 return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
300 my $principal = $self->PrincipalId;
302 if ($priv->HasMember($principal)) {
303 #$RT::Logger->debug("That user is already privileged");
304 return (0,$self->loc("That user is already privileged"));
306 if ($unpriv->HasMember($principal)) {
307 $unpriv->_DeleteMember($principal);
309 # if we had layered transactions, life would be good
310 # sadly, we have to just go ahead, even if something
312 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
313 "unprivileged. something is drastically wrong.");
315 my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
317 return (1, $self->loc("That user is now privileged"));
323 if ($unpriv->HasMember($principal)) {
324 #$RT::Logger->debug("That user is already unprivileged");
325 return (0,$self->loc("That user is already unprivileged"));
327 if ($priv->HasMember($principal)) {
328 $priv->_DeleteMember( $principal );
330 # if we had layered transactions, life would be good
331 # sadly, we have to just go ahead, even if something
333 $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
334 "unprivileged. something is drastically wrong.");
336 my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal);
338 return (1, $self->loc("That user is now unprivileged"));
347 Returns true if this user is privileged. Returns undef otherwise.
353 my $priv = RT::Group->new($self->CurrentUser);
354 $priv->LoadSystemInternalGroup('Privileged');
355 if ( $priv->HasMember( $self->PrincipalId ) ) {
363 #create a user without validating _any_ data.
365 #To be used only on database init.
366 # We can't localize here because it's before we _have_ a loc framework
368 sub _BootstrapCreate {
372 $args{'Password'} = '*NO-PASSWORD*';
375 $RT::Handle->BeginTransaction();
377 # Groups deal with principal ids, rather than user ids.
378 # When creating this user, set up a principal Id for it.
379 my $principal = RT::Principal->new($self->CurrentUser);
380 my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
381 $principal->__Set(Field => 'ObjectId', Value => $principal_id);
383 # If we couldn't create a principal Id, get the fuck out.
384 unless ($principal_id) {
385 $RT::Handle->Rollback();
386 $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
387 return ( 0, 'Could not create user' );
389 $self->SUPER::Create(id => $principal_id, %args);
391 #If the create failed.
393 $RT::Handle->Rollback();
394 return ( 0, 'Could not create user' ) ; #never loc this
397 my $aclstash = RT::Group->new($self->CurrentUser);
398 my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
401 $RT::Handle->Rollback();
402 $RT::Logger->crit("Couldn't stash the user in groupmembers");
403 return ( 0, $self->loc('Could not create user') );
407 $RT::Handle->Commit();
409 return ( $id, 'User created' );
415 return ( 0, $self->loc('Deleting this object would violate referential integrity') );
421 Load a user object from the database. Takes a single argument.
422 If the argument is numerical, load by the column 'id'. If a user
423 object or its subclass passed then loads the same user by id.
424 Otherwise, load by the "Name" column which is the user's textual
431 my $identifier = shift || return undef;
433 if ( $identifier !~ /\D/ ) {
434 return $self->SUPER::LoadById( $identifier );
436 elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) {
437 return $self->SUPER::LoadById( $identifier->Id );
440 return $self->LoadByCol( "Name", $identifier );
446 Tries to load this user object from the database by the user's email address.
454 # Never load an empty address as an email address.
459 $address = $self->CanonicalizeEmailAddress($address);
461 #$RT::Logger->debug("Trying to load an email address: $address");
462 return $self->LoadByCol( "EmailAddress", $address );
465 =head2 LoadOrCreateByEmail ADDRESS
467 Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
468 the provided email address and loads them. Address can be provided either as L<Email::Address> object
469 or string which is parsed using the module.
471 Returns a tuple of the user's id and a status message.
472 0 will be returned in place of the user's id in case of failure.
476 sub LoadOrCreateByEmail {
480 my ($message, $name);
481 if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) {
482 ($email, $name) = ($email->address, $email->phrase);
484 ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email );
487 $self->LoadByEmail( $email );
488 $self->Load( $email ) unless $self->Id;
489 $message = $self->loc('User loaded');
491 unless( $self->Id ) {
493 ($val, $message) = $self->Create(
495 EmailAddress => $email,
498 Comments => 'Autocreated when added as a watcher',
501 # Deal with the race condition of two account creations at once
502 $self->LoadByEmail( $email );
503 unless ( $self->Id ) {
505 $self->LoadByEmail( $email );
508 $RT::Logger->error("Recovered from creation failure due to race condition");
509 $message = $self->loc("User loaded");
512 $RT::Logger->crit("Failed to create user ". $email .": " .$message);
516 return (0, $message) unless $self->id;
517 return ($self->Id, $message);
520 =head2 ValidateEmailAddress ADDRESS
522 Returns true if the email address entered is not in use by another user or is
523 undef or ''. Returns false if it's in use.
527 sub ValidateEmailAddress {
531 # if the email address is null, it's always valid
532 return (1) if ( !$Value || $Value eq "" );
534 if ( RT->Config->Get('ValidateUserEmailAddresses') ) {
535 # We only allow one valid email address
536 my @addresses = Email::Address->parse($Value);
537 return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) );
541 my $TempUser = RT::User->new($RT::SystemUser);
542 $TempUser->LoadByEmail($Value);
544 if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) )
545 { # if we found a user with that address
546 # it's invalid to set this user's address to it
547 return ( 0, $self->loc('Email address in use') );
549 else { #it's a valid email address
554 =head2 SetEmailAddress
556 Check to make sure someone else isn't using this email address already
557 so that a better email address can be returned
561 sub SetEmailAddress {
565 my ($val, $message) = $self->ValidateEmailAddress( $Value );
567 return $self->_Set( Field => 'EmailAddress', Value => $Value );
569 return ( 0, $message )
574 =head2 EmailFrequency
576 Takes optional Ticket argument in paramhash. Returns 'no email',
577 'squelched', 'daily', 'weekly' or empty string depending on
582 =item 'no email' - user has no email, so can not recieve notifications.
584 =item 'squelched' - returned only when Ticket argument is provided and
585 notifications to the user has been supressed for this ticket.
587 =item 'daily' - retruned when user recieve daily messages digest instead
588 of immediate delivery.
590 =item 'weekly' - previous, but weekly.
592 =item empty string returned otherwise.
604 return '' unless $self->id && $self->id != $RT::Nobody->id
605 && $self->id != $RT::SystemUser->id;
606 return 'no email' unless my $email = $self->EmailAddress;
607 return 'squelched' if $args{'Ticket'} &&
608 grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo;
609 my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || '';
610 return 'daily' if $frequency =~ /daily/i;
611 return 'weekly' if $frequency =~ /weekly/i;
615 =head2 CanonicalizeEmailAddress ADDRESS
617 CanonicalizeEmailAddress converts email addresses into canonical form.
618 it takes one email address in and returns the proper canonical
619 form. You can dump whatever your proper local config is in here. Note
620 that it may be called as a static method; in this case the first argument
621 is class name not an object.
625 sub CanonicalizeEmailAddress {
628 # Example: the following rule would treat all email
629 # coming from a subdomain as coming from second level domain
631 if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and
632 my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') )
634 $email =~ s/$match/$replace/gi;
639 =head2 CanonicalizeUserInfo HASH of ARGS
641 CanonicalizeUserInfo can convert all User->Create options.
642 it takes a hashref of all the params sent to User->Create and
643 returns that same hash, by default nothing is done.
645 This function is intended to allow users to have their info looked up via
646 an outside source and modified upon creation.
650 sub CanonicalizeUserInfo {
659 =head2 Password and authentication related functions
661 =head3 SetRandomPassword
663 Takes no arguments. Returns a status code and a new password or an error message.
664 If the status is 1, the second value returned is the new password.
665 If the status is anything else, the new value returned is the error code.
669 sub SetRandomPassword {
672 unless ( $self->CurrentUserCanModify('Password') ) {
673 return ( 0, $self->loc("Permission Denied") );
677 my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6);
678 my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8);
680 my $pass = $self->GenerateRandomPassword( $min, $max) ;
682 # If we have "notify user on
684 my ( $val, $msg ) = $self->SetPassword($pass);
686 #If we got an error return the error.
687 return ( 0, $msg ) unless ($val);
689 #Otherwise, we changed the password, lets return it.
696 Returns status, [ERROR or new password]. Resets this user\'s password to
697 a randomly generated pronouncable password and emails them, using a
698 global template called "RT_PasswordChange", which can be overridden
699 with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged"
700 for privileged and Non-privileged users respectively.
707 unless ( $self->CurrentUserCanModify('Password') ) {
708 return ( 0, $self->loc("Permission Denied") );
710 my ( $status, $pass ) = $self->SetRandomPassword();
713 return ( 0, "$pass" );
716 my $ret = RT::Interface::Email::SendEmailUsingTemplate(
717 To => $self->EmailAddress,
718 Template => 'PasswordChange',
720 NewPassword => $pass,
725 return ( 1, $self->loc('New password notification sent') );
728 return ( 0, $self->loc('Notification could not be sent') );
733 =head3 GenerateRandomPassword MIN_LEN and MAX_LEN
735 Returns a random password between MIN_LEN and MAX_LEN characters long.
739 sub GenerateRandomPassword {
741 my $min_length = shift;
742 my $max_length = shift;
744 #This code derived from mpw.pl, a bit of code with a sordid history
747 # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
748 # Converted to perl from C by Marc Horowitz, 1/20/2000.
749 # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
750 # Original PL/I version provided by Jerry Saltzer.
752 my ( $frequency, $start_freq, $total_sum, $row_sums );
754 #When munging characters, we need to know where to start counting letters from
757 # frequency of English digraphs (from D Edwards 1/27/66)
760 4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23, 167,
761 2, 14, 0, 83, 76, 127, 7, 25, 8, 1, 9, 1
764 13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0,
765 11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0
768 32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1, 0,
769 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0
772 40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15, 6,
773 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0
776 84, 20, 55, 125, 51, 40, 19, 16, 50, 1,
777 4, 55, 54, 146, 35, 37, 6, 191, 149, 65,
781 19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1, 0,
782 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0
785 20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1, 4,
786 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0
789 101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3, 2,
790 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0
793 40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25, 202,
794 56, 12, 1, 46, 79, 117, 1, 22, 0, 4, 0, 3
797 3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0,
798 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0
801 1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
802 0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0
805 44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2, 2,
806 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0
809 52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1,
810 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0
813 42, 10, 47, 122, 63, 19, 106, 12, 30, 1,
814 6, 6, 9, 7, 54, 7, 1, 7, 44, 124,
818 7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41, 134,
819 13, 23, 0, 91, 23, 42, 55, 16, 28, 0, 4, 1
822 19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
823 27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0
826 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
827 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0
830 83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26, 16,
831 60, 4, 0, 24, 37, 55, 6, 11, 4, 0, 28, 0
834 65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11, 12,
835 56, 17, 6, 9, 48, 116, 35, 1, 28, 0, 4, 0
838 57, 22, 3, 1, 76, 5, 2, 330, 126, 1,
839 0, 14, 10, 6, 79, 7, 0, 49, 50, 56,
843 11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31,
844 1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0
847 7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
848 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0
851 36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8,
852 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0
855 1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0,
856 1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0
859 14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7, 5,
860 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0
863 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
864 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
868 #We need to know the totals for each row
872 map { $sum += $_ } @$_;
877 #Frequency with which a given letter starts a word.
879 1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24,
880 20, 355, 379, 319, 823, 618, 21, 317, 962, 1991,
881 271, 104, 516, 6, 16, 14
885 map { $total_sum += $_ } @$start_freq;
887 my $length = $min_length + int( rand( $max_length - $min_length ) );
889 my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
890 my @word = ( $char + $a );
891 for ( 2 .. $length ) {
893 $self->_GenerateRandomNextChar( $row_sums->[$char],
894 $frequency->[$char] );
895 push ( @word, $char + $a );
899 return pack( "C*", @word );
903 #A private helper function for RandomPassword
904 # Takes a row summary and a frequency chart for the next character to be searched
905 sub _GenerateRandomNextChar {
907 my ( $all, $freq ) = @_;
910 for ( $pos = int( rand($all) ), $i = 0 ;
911 $pos >= $freq->[$i] ;
912 $pos -= $freq->[$i], $i++ )
921 Takes a string. Checks the string's length and sets this user's password
928 my $password = shift;
930 unless ( $self->CurrentUserCanModify('Password') ) {
931 return ( 0, $self->loc('Password: Permission Denied') );
935 return ( 0, $self->loc("No password set") );
937 elsif ( length($password) < RT->Config->Get('MinimumPasswordLength') ) {
938 return ( 0, $self->loc("Password needs to be at least [_1] characters long", RT->Config->Get('MinimumPasswordLength')) );
941 my $new = !$self->HasPassword;
942 $password = $self->_GeneratePassword($password);
943 my ( $val, $msg ) = $self->SUPER::SetPassword($password);
945 return ( 1, $self->loc("Password set") ) if $new;
946 return ( 1, $self->loc("Password changed") );
949 return ( $val, $msg );
955 =head3 _GeneratePassword PASSWORD
957 returns an MD5 hash of the password passed in, in hexadecimal encoding.
961 sub _GeneratePassword {
963 my $password = shift;
965 my $md5 = Digest::MD5->new();
966 $md5->add(encode_utf8($password));
967 return ($md5->hexdigest);
971 =head3 _GeneratePasswordBase64 PASSWORD
973 returns an MD5 hash of the password passed in, in base64 encoding
978 sub _GeneratePasswordBase64 {
980 my $password = shift;
982 my $md5 = Digest::MD5->new();
983 $md5->add(encode_utf8($password));
984 return ($md5->b64digest);
990 Returns true if the user has a valid password, otherwise returns false.
996 my $pwd = $self->__Value('Password');
997 return undef if !defined $pwd
999 || $pwd eq '*NO-PASSWORD*';
1005 Returns true if the passed in value is this user's password.
1006 Returns undef otherwise.
1014 #TODO there isn't any apparent way to legitimately ACL this
1016 # RT does not allow null passwords
1017 if ( ( !defined($value) ) or ( $value eq '' ) ) {
1021 if ( $self->PrincipalObj->Disabled ) {
1023 "Disabled user " . $self->Name . " tried to log in" );
1027 unless ($self->HasPassword) {
1031 # generate an md5 password
1032 if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
1036 # if it's a historical password we say ok.
1037 if ($self->__Value('Password') eq crypt($value, $self->__Value('Password'))
1038 or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password'))
1040 # ...but upgrade the legacy password inplace.
1041 $self->SUPER::SetPassword( $self->_GeneratePassword($value) );
1045 # no password check has succeeded. get out
1052 Returns an authentication string associated with the user. This
1053 string can be used to generate passwordless URLs to integrate
1054 RT with services and programms like callendar managers, rss
1061 my $secret = $self->FirstAttribute("AuthToken");
1062 return $secret->Content if $secret;
1065 $self = RT::User->new( $RT::SystemUser );
1067 $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1068 my ($status, $msg) = $self->SetAttribute( Name => "AuthToken", Content => $secret );
1069 unless ( $status ) {
1070 $RT::Logger->error( "Couldn't set auth token: $msg" );
1076 =head3 GenerateAuthToken
1078 Generate a random authentication string for the user.
1082 sub GenerateAuthToken {
1084 my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16);
1085 return $self->SetAttribute( Name => "AuthToken", Content => $token );
1088 =head3 GenerateAuthString
1090 Takes a string and returns back a hex hash string. Later you can use
1091 this pair to make sure it's generated by this user using L</ValidateAuthString>
1095 sub GenerateAuthString {
1097 my $protect = shift;
1099 my $str = $self->AuthToken . $protect;
1102 return substr(Digest::MD5::md5_hex($str),0,16);
1105 =head3 ValidateAuthString
1107 Takes auth string and protected string. Returns true is protected string
1108 has been protected by user's L</AuthToken>. See also L</GenerateAuthString>.
1112 sub ValidateAuthString {
1114 my $auth_string = shift;
1115 my $protected = shift;
1117 my $str = $self->AuthToken . $protected;
1118 utf8::encode( $str );
1120 return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16);
1125 Toggles the user's disabled flag.
1127 set, all password checks for this user will fail. All ACL checks for this
1128 user will fail. The user will appear in no user listings.
1135 unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1136 return (0, $self->loc('Permission Denied'));
1139 $RT::Handle->BeginTransaction();
1140 my $set_err = $self->PrincipalObj->SetDisabled($val);
1142 $RT::Handle->Rollback();
1143 $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id));
1146 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
1148 $RT::Handle->Commit();
1151 return (1, $self->loc("User disabled"));
1153 return (1, $self->loc("User enabled"));
1160 Returns true if user is disabled or false otherwise
1166 return $self->PrincipalObj->Disabled(@_);
1171 Returns the principal object for this user. returns an empty RT::Principal
1172 if there's no principal object matching this user.
1173 The response is cached. PrincipalObj should never ever change.
1180 unless ( $self->id ) {
1181 $RT::Logger->error("Couldn't get principal for not loaded object");
1185 my $obj = RT::Principal->new( $self->CurrentUser );
1186 $obj->LoadById( $self->id );
1187 unless ( $obj->id ) {
1188 $RT::Logger->crit( 'No principal for user #'. $self->id );
1190 } elsif ( $obj->PrincipalType ne 'User' ) {
1191 $RT::Logger->crit( 'User #'. $self->id .' has principal of '. $obj->PrincipalType .' type' );
1200 Returns this user's PrincipalId
1209 =head2 HasGroupRight
1211 Takes a paramhash which can contain
1213 GroupObj => RT::Group or Group => integer
1217 Returns 1 if this user has the right specified in the paramhash for the Group
1220 Returns undef if they don't.
1234 if ( defined $args{'Group'} ) {
1235 $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
1236 $args{'GroupObj'}->Load( $args{'Group'} );
1239 # Validate and load up the GroupId
1240 unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
1244 # Figure out whether a user has the right we're asking about.
1245 my $retval = $self->HasRight(
1246 Object => $args{'GroupObj'},
1247 Right => $args{'Right'},
1255 Returns a group collection object containing the groups of which this
1262 my $groups = RT::Groups->new($self->CurrentUser);
1263 $groups->LimitToUserDefinedGroups;
1264 $groups->WithMember(PrincipalId => $self->Id,
1273 #much false laziness w/Ticket_Overlay.pm
1275 # A helper table for links mapping to make it easier
1276 # to build and parse links between tickets
1278 use vars '%LINKDIRMAP';
1281 MemberOf => { Base => 'MemberOf',
1282 Target => 'HasMember', },
1283 RefersTo => { Base => 'RefersTo',
1284 Target => 'ReferredToBy', },
1285 DependsOn => { Base => 'DependsOn',
1286 Target => 'DependedOnBy', },
1287 MergedInto => { Base => 'MergedInto',
1288 Target => 'MergedInto', },
1292 sub LINKDIRMAP { return \%LINKDIRMAP }
1297 # #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic ---
1298 # #tobias meant by $f
1299 # my $field = shift;
1300 # my $type = shift || "";
1302 # unless ( $self->{"$field$type"} ) {
1303 # $self->{"$field$type"} = new RT::Links( $self->CurrentUser );
1304 # if ( $self->CurrentUserHasRight('ShowTicket') ) {
1305 # # Maybe this ticket is a merged ticket
1306 # my $Tickets = new RT::Tickets( $self->CurrentUser );
1307 # # at least to myself
1308 # $self->{"$field$type"}->Limit( FIELD => $field,
1309 # VALUE => $self->URI,
1310 # ENTRYAGGREGATOR => 'OR' );
1311 # $Tickets->Limit( FIELD => 'EffectiveId',
1312 # VALUE => $self->EffectiveId );
1313 # while (my $Ticket = $Tickets->Next) {
1314 # $self->{"$field$type"}->Limit( FIELD => $field,
1315 # VALUE => $Ticket->URI,
1316 # ENTRYAGGREGATOR => 'OR' );
1318 # $self->{"$field$type"}->Limit( FIELD => 'Type',
1323 # return ( $self->{"$field$type"} );
1328 Delete a link. takes a paramhash of Base, Target and Type.
1329 Either Base or Target must be null. The null value will
1330 be replaced with this ticket\'s id
1343 unless ( $args{'Target'} || $args{'Base'} ) {
1344 $RT::Logger->error("Base or Target must be specified\n");
1345 return ( 0, $self->loc('Either base or target must be specified') );
1350 $right++ if $self->CurrentUserHasRight('ModifyUser');
1351 if ( !$right && $RT::StrictLinkACL ) {
1352 return ( 0, $self->loc("Permission Denied") );
1355 # # If the other URI is an RT::Ticket, we want to make sure the user
1356 # # can modify it too...
1357 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
1358 # return (0, $msg) unless $status;
1359 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
1362 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
1363 # ( $RT::StrictLinkACL && $right < 2 ) )
1365 # return ( 0, $self->loc("Permission Denied") );
1368 my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);
1371 $RT::Logger->debug("Couldn't find that link\n");
1375 my ($direction, $remote_link);
1377 if ( $args{'Base'} ) {
1378 $remote_link = $args{'Base'};
1379 $direction = 'Target';
1381 elsif ( $args{'Target'} ) {
1382 $remote_link = $args{'Target'};
1386 if ( $args{'Silent'} ) {
1387 return ( $val, $Msg );
1390 my $remote_uri = RT::URI->new( $self->CurrentUser );
1391 $remote_uri->FromURI( $remote_link );
1393 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
1394 Type => 'DeleteLink',
1395 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
1396 OldValue => $remote_uri->URI || $remote_link,
1400 if ( $remote_uri->IsLocal ) {
1402 my $OtherObj = $remote_uri->Object;
1403 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'DeleteLink',
1404 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
1405 : $LINKDIRMAP{$args{'Type'}}->{Target},
1406 OldValue => $self->URI,
1407 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
1411 return ( $Trans, $Msg );
1417 my %args = ( Target => '',
1423 unless ( $args{'Target'} || $args{'Base'} ) {
1424 $RT::Logger->error("Base or Target must be specified\n");
1425 return ( 0, $self->loc('Either base or target must be specified') );
1429 $right++ if $self->CurrentUserHasRight('ModifyUser');
1430 if ( !$right && $RT::StrictLinkACL ) {
1431 return ( 0, $self->loc("Permission Denied") );
1434 # # If the other URI is an RT::Ticket, we want to make sure the user
1435 # # can modify it too...
1436 # my ($status, $msg, $other_ticket) = $self->__GetTicketFromURI( URI => $args{'Target'} || $args{'Base'} );
1437 # return (0, $msg) unless $status;
1438 # if ( !$other_ticket || $other_ticket->CurrentUserHasRight('ModifyTicket') ) {
1441 # if ( ( !$RT::StrictLinkACL && $right == 0 ) ||
1442 # ( $RT::StrictLinkACL && $right < 2 ) )
1444 # return ( 0, $self->loc("Permission Denied") );
1447 return $self->_AddLink(%args);
1450 #sub __GetTicketFromURI {
1452 # my %args = ( URI => '', @_ );
1454 # # If the other URI is an RT::Ticket, we want to make sure the user
1455 # # can modify it too...
1456 # my $uri_obj = RT::URI->new( $self->CurrentUser );
1457 # $uri_obj->FromURI( $args{'URI'} );
1459 # unless ( $uri_obj->Resolver && $uri_obj->Scheme ) {
1460 # my $msg = $self->loc( "Couldn't resolve '[_1]' into a URI.", $args{'URI'} );
1461 # $RT::Logger->warning( "$msg\n" );
1462 # return( 0, $msg );
1464 # my $obj = $uri_obj->Resolver->Object;
1465 # unless ( UNIVERSAL::isa($obj, 'RT::Ticket') && $obj->id ) {
1466 # return (1, 'Found not a ticket', undef);
1468 # return (1, 'Found ticket', $obj);
1473 Private non-acled variant of AddLink so that links can be added during create.
1479 my %args = ( Target => '',
1485 my ($val, $msg, $exist) = $self->SUPER::_AddLink(%args);
1486 return ($val, $msg) if !$val || $exist;
1488 my ($direction, $remote_link);
1489 if ( $args{'Target'} ) {
1490 $remote_link = $args{'Target'};
1491 $direction = 'Base';
1492 } elsif ( $args{'Base'} ) {
1493 $remote_link = $args{'Base'};
1494 $direction = 'Target';
1497 # Don't write the transaction if we're doing this on create
1498 if ( $args{'Silent'} ) {
1499 return ( $val, $msg );
1502 my $remote_uri = RT::URI->new( $self->CurrentUser );
1503 $remote_uri->FromURI( $remote_link );
1505 #Write the transaction
1506 my ( $Trans, $Msg, $TransObj ) =
1507 $self->_NewTransaction(Type => 'AddLink',
1508 Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
1509 NewValue => $remote_uri->URI || $remote_link,
1512 if ( $remote_uri->IsLocal ) {
1514 my $OtherObj = $remote_uri->Object;
1515 my ( $val, $Msg ) = $OtherObj->_NewTransaction(Type => 'AddLink',
1516 Field => $direction eq 'Target' ? $LINKDIRMAP{$args{'Type'}}->{Base}
1517 : $LINKDIRMAP{$args{'Type'}}->{Target},
1518 NewValue => $self->URI,
1519 ActivateScrips => ! $RT::LinkTransactionsRun1Scrip,
1522 return ( $val, $Msg );
1532 # {{{ sub Rights testing
1534 =head1 Rights testing
1539 my $root = RT::User->new($RT::SystemUser);
1540 $root->Load('root');
1541 ok($root->Id, "Found the root user");
1542 my $rootq = RT::Queue->new($root);
1544 ok($rootq->Id, "Loaded the first queue");
1546 ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets");
1548 my $new_user = RT::User->new($RT::SystemUser);
1549 my ($id, $msg) = $new_user->Create(Name => 'ACLTest'.$$);
1551 ok ($id, "Created a new user for acl test $msg");
1553 my $q = RT::Queue->new($new_user);
1555 ok($q->Id, "Loaded the first queue");
1558 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
1559 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
1560 ok ($gval, "Grant succeeded - $gmsg");
1563 ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
1564 ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
1565 ok ($gval, "Revocation succeeded - $gmsg");
1566 ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
1572 # Create a ticket in the queue
1573 my $new_tick = RT::Ticket->new($RT::SystemUser);
1574 my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
1575 ok($tickid, "Created ticket: $tickid");
1576 # Make sure the user doesn't have the right to modify tickets in the queue
1577 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1578 # Create a new group
1579 my $group = RT::Group->new($RT::SystemUser);
1580 $group->CreateUserDefinedGroup(Name => 'ACLTest'.$$);
1581 ok($group->Id, "Created a new group Ok");
1582 # Grant a group the right to modify tickets in a queue
1583 ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1584 ok($gv,"Grant succeeed - $gm");
1585 # Add the user to the group
1586 ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
1587 ok ($aid, "Member added to group: $amsg");
1588 # Make sure the user does have the right to modify tickets in the queue
1589 ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
1592 # Remove the user from the group
1593 ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
1594 ok ($did,"Deleted the group member: $dmsg");
1595 # Make sure the user doesn't have the right to modify tickets in the queue
1596 ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1599 my $q_as_system = RT::Queue->new($RT::SystemUser);
1600 $q_as_system->Load(1);
1601 ok($q_as_system->Id, "Loaded the first queue");
1603 # Create a ticket in the queue
1604 my $new_tick2 = RT::Ticket->new($RT::SystemUser);
1605 my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
1606 ok($tick2id, "Created ticket: $tick2id");
1607 is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1");
1610 # make sure that the user can't do this without subgroup membership
1611 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1614 my $subgroup = RT::Group->new($RT::SystemUser);
1615 $subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest',$$);
1616 ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok");
1617 #Add the subgroup as a subgroup of the group
1618 my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId);
1619 ok ($said, "Added the subgroup as a member of the group");
1620 # Add the user to a subgroup of the group
1622 my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId);
1623 ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
1624 # Make sure the user does have the right to modify tickets in the queue
1625 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
1627 # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights
1630 ($id, $msg) = $group->SetDisabled(1);
1632 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled");
1633 ($id, $msg) = $group->SetDisabled(0);
1635 # Test what happens when we disable the group the user is a member of directly
1637 ($id, $msg) = $subgroup->SetDisabled(1);
1639 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
1640 ($id, $msg) = $subgroup->SetDisabled(0);
1642 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
1647 my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId);
1648 ok($usrid,"removed the user from the group - $usrmsg");
1649 # Make sure the user doesn't have the right to modify tickets in the queue
1650 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1652 #revoke the right to modify tickets in a queue
1653 ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
1654 ok($gv,"revoke succeeed - $gm");
1656 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
1658 # Grant queue admin cc the right to modify ticket in the queue
1659 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");
1660 ok($qv, "Granted the right successfully - $qm");
1662 # Add the user as a queue admincc
1663 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1664 ok ($add_id, "the user is now a queue admincc - $add_msg");
1666 # Make sure the user does have the right to modify tickets in the queue
1667 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1668 # Remove the user from the role group
1669 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1671 # Make sure the user doesn't have the right to modify tickets in the queue
1672 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1676 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1678 # Add the user as a ticket admincc
1679 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1680 ok ($add_id, "the user is now a queue admincc - $add_msg");
1682 # Make sure the user does have the right to modify tickets in the queue
1683 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1685 # Remove the user from the role group
1686 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1688 # Make sure the user doesn't have the right to modify tickets in the queue
1689 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1692 # Revoke the right to modify ticket in the queue
1693 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");
1694 ok($rqv, "Revoked the right successfully - $rqm");
1700 # {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
1702 # Before we start Make sure the user does not have the right to modify tickets in the queue
1703 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
1704 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
1706 # Grant queue admin cc the right to modify ticket in the queue
1707 ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
1708 ok($qv, "Granted the right successfully - $qm");
1710 # Make sure the user can't modify the ticket before they're added as a watcher
1711 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1712 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
1714 # Add the user as a queue admincc
1715 ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1716 ok ($add_id, "the user is now a queue admincc - $add_msg");
1718 # Make sure the user does have the right to modify tickets in the queue
1719 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1720 ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
1721 # Remove the user from the role group
1722 ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1724 # Make sure the user doesn't have the right to modify tickets in the queue
1725 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
1726 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
1730 # {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
1732 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
1733 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1736 # Add the user as a ticket admincc
1737 ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
1738 ok ($add_id, "the user is now a queue admincc - $add_msg");
1740 # Make sure the user does have the right to modify tickets in the queue
1741 ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
1742 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
1744 # Remove the user from the role group
1745 ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
1747 # Make sure the user doesn't have the right to modify tickets in the queue
1748 ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
1749 ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
1752 # Revoke the right to modify ticket in the queue
1753 ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
1754 ok($rqv, "Revoked the right successfully - $rqm");
1761 # Grant "privileged users" the system right to create users
1762 # Create a privileged user.
1763 # have that user create another user
1764 # Revoke the right for privileged users to create users
1765 # have the privileged user try to create another user and fail the ACL check
1778 Shim around PrincipalObj->HasRight. See L<RT::Principal>.
1784 return $self->PrincipalObj->HasRight(@_);
1787 =head2 CurrentUserCanModify RIGHT
1789 If the user has rights for this object, either because
1790 he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an
1791 admin right) 'ModifySelf', return 1. otherwise, return undef.
1795 sub CurrentUserCanModify {
1799 if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
1803 #If the field is marked as an "administrators only" field,
1804 # don\'t let the user touch it.
1805 elsif ( $self->_Accessible( $right, 'admin' ) ) {
1809 #If the current user is trying to modify themselves
1810 elsif ( ( $self->id == $self->CurrentUser->id )
1811 and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
1816 #If we don\'t have a good reason to grant them rights to modify
1824 =head2 CurrentUserHasRight
1826 Takes a single argument. returns 1 if $Self->CurrentUser
1827 has the requested right. returns undef otherwise
1831 sub CurrentUserHasRight {
1835 return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
1841 $name = ref($name).'-'.$name->Id;
1844 return 'Pref-'.$name;
1847 =head2 Preferences NAME/OBJ DEFAULT
1849 Obtain user preferences associated with given object or name.
1850 Returns DEFAULT if no preferences found. If DEFAULT is a hashref,
1851 override the entries with user preferences.
1857 my $name = _PrefName (shift);
1858 my $default = shift;
1860 my $attr = RT::Attribute->new( $self->CurrentUser );
1861 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1863 my $content = $attr->Id ? $attr->Content : undef;
1864 unless ( ref $content eq 'HASH' ) {
1865 return defined $content ? $content : $default;
1868 if (ref $default eq 'HASH') {
1869 for (keys %$default) {
1870 exists $content->{$_} or $content->{$_} = $default->{$_};
1873 elsif (defined $default) {
1874 $RT::Logger->error("Preferences $name for user".$self->Id." is hash but default is not");
1879 =head2 SetPreferences NAME/OBJ VALUE
1881 Set user preferences associated with given object or name.
1885 sub SetPreferences {
1887 my $name = _PrefName( shift );
1890 return (0, $self->loc("No permission to set preferences"))
1891 unless $self->CurrentUserCanModify('Preferences');
1893 my $attr = RT::Attribute->new( $self->CurrentUser );
1894 $attr->LoadByNameAndObject( Object => $self, Name => $name );
1896 return $attr->SetContent( $value );
1899 return $self->AddAttribute( Name => $name, Content => $value );
1903 =head2 WatchedQueues ROLE_LIST
1905 Returns a RT::Queues object containing every queue watched by the user.
1907 Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to:
1909 $user->WatchedQueues('Cc', 'AdminCc');
1916 my @roles = @_ || ('Cc', 'AdminCc');
1918 $RT::Logger->debug('WatcheQueues got user ' . $self->Name);
1920 my $watched_queues = RT::Queues->new($self->CurrentUser);
1922 my $group_alias = $watched_queues->Join(
1926 FIELD2 => 'Instance',
1929 $watched_queues->Limit(
1930 ALIAS => $group_alias,
1932 VALUE => 'RT::Queue-Role',
1933 ENTRYAGGREGATOR => 'AND',
1935 if (grep { $_ eq 'Cc' } @roles) {
1936 $watched_queues->Limit(
1937 SUBCLAUSE => 'LimitToWatchers',
1938 ALIAS => $group_alias,
1941 ENTRYAGGREGATOR => 'OR',
1944 if (grep { $_ eq 'AdminCc' } @roles) {
1945 $watched_queues->Limit(
1946 SUBCLAUSE => 'LimitToWatchers',
1947 ALIAS => $group_alias,
1950 ENTRYAGGREGATOR => 'OR',
1954 my $queues_alias = $watched_queues->Join(
1955 ALIAS1 => $group_alias,
1957 TABLE2 => 'CachedGroupMembers',
1958 FIELD2 => 'GroupId',
1960 $watched_queues->Limit(
1961 ALIAS => $queues_alias,
1962 FIELD => 'MemberId',
1963 VALUE => $self->PrincipalId,
1966 $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
1968 return $watched_queues;
1972 =head2 _CleanupInvalidDelegations { InsideTransaction => undef }
1974 Revokes all ACE entries delegated by this user which are inconsistent
1975 with their current delegation rights. Does not perform permission
1976 checks. Should only ever be called from inside the RT library.
1978 If called from inside a transaction, specify a true value for the
1979 InsideTransaction parameter.
1981 Returns a true value if the deletion succeeded; returns a false value
1982 and logs an internal error if the deletion fails (should not happen).
1986 # XXX Currently there is a _CleanupInvalidDelegations method in both
1987 # RT::User and RT::Group. If the recursive cleanup call for groups is
1988 # ever unrolled and merged, this code will probably want to be
1989 # factored out into RT::Principal.
1991 sub _CleanupInvalidDelegations {
1993 my %args = ( InsideTransaction => undef,
1996 unless ( $self->Id ) {
1997 $RT::Logger->warning("User not loaded.");
2001 my $in_trans = $args{InsideTransaction};
2003 return(1) if ($self->HasRight(Right => 'DelegateRights',
2004 Object => $RT::System));
2006 # Look up all delegation rights currently posessed by this user.
2007 my $deleg_acl = RT::ACL->new($RT::SystemUser);
2008 $deleg_acl->LimitToPrincipal(Type => 'User',
2009 Id => $self->PrincipalId,
2010 IncludeGroupMembership => 1);
2011 $deleg_acl->Limit( FIELD => 'RightName',
2013 VALUE => 'DelegateRights' );
2014 my @allowed_deleg_objects = map {$_->Object()}
2015 @{$deleg_acl->ItemsArrayRef()};
2017 # Look up all rights delegated by this principal which are
2018 # inconsistent with the allowed delegation objects.
2019 my $acl_to_del = RT::ACL->new($RT::SystemUser);
2020 $acl_to_del->DelegatedBy(Id => $self->Id);
2021 foreach (@allowed_deleg_objects) {
2022 $acl_to_del->LimitNotObject($_);
2025 # Delete all disallowed delegations
2026 while ( my $ace = $acl_to_del->Next() ) {
2027 my $ret = $ace->_Delete(InsideTransaction => 1);
2029 $RT::Handle->Rollback() unless $in_trans;
2030 $RT::Logger->warning("Couldn't delete delegated ACL entry ".$ace->Id);
2035 $RT::Handle->Commit() unless $in_trans;
2045 TransactionType => 'Set',
2046 RecordTransaction => 1,
2050 # Nobody is allowed to futz with RT_System or Nobody
2052 if ( ($self->Id == $RT::SystemUser->Id ) ||
2053 ($self->Id == $RT::Nobody->Id)) {
2054 return ( 0, $self->loc("Can not modify system users") );
2056 unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
2057 return ( 0, $self->loc("Permission Denied") );
2060 my $Old = $self->SUPER::_Value("$args{'Field'}");
2062 my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
2063 Value => $args{'Value'} );
2065 #If we can't actually set the field to the value, don't record
2066 # a transaction. instead, get out of here.
2067 if ( $ret == 0 ) { return ( 0, $msg ); }
2069 if ( $args{'RecordTransaction'} == 1 ) {
2071 my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
2072 Type => $args{'TransactionType'},
2073 Field => $args{'Field'},
2074 NewValue => $args{'Value'},
2076 TimeTaken => $args{'TimeTaken'},
2078 return ( $Trans, scalar $TransObj->BriefDescription );
2081 return ( $ret, $msg );
2087 Takes the name of a table column.
2088 Returns its value as a string, if the user passes an ACL check
2097 #If the current user doesn't have ACLs, don't let em at it.
2099 my @PublicFields = qw( Name EmailAddress Organization Disabled
2100 RealName NickName Gecos ExternalAuthId
2101 AuthSystem ExternalContactInfoId
2102 ContactInfoSystem );
2104 #if the field is public, return it.
2105 if ( $self->_Accessible( $field, 'public' ) ) {
2106 return ( $self->SUPER::_Value($field) );
2110 #If the user wants to see their own values, let them
2111 # TODO figure ouyt a better way to deal with this
2112 elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
2113 return ( $self->SUPER::_Value($field) );
2116 #If the user has the admin users right, return the field
2117 elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
2118 return ( $self->SUPER::_Value($field) );
2128 Return the friendly name
2134 return $self->RealName if defined($self->RealName);
2135 return $self->Name if defined($self->Name);
2141 Returns the preferred key of the user. If none is set, then this will query
2142 GPG and set the preferred key to the maximally trusted key found (and then
2143 return it). Returns C<undef> if no preferred key can be found.
2150 return undef unless RT->Config->Get('GnuPG')->{'Enable'};
2151 my $prefkey = $self->FirstAttribute('PreferredKey');
2152 return $prefkey->Content if $prefkey;
2154 # we don't have a preferred key for this user, so now we must query GPG
2155 require RT::Crypt::GnuPG;
2156 my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
2157 return undef unless defined $res{'info'};
2158 my @keys = @{ $res{'info'} };
2159 return undef if @keys == 0;
2162 $prefkey = $keys[0]->{'Fingerprint'};
2165 # prefer the maximally trusted key
2166 @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
2167 $prefkey = $keys[0]->{'Fingerprint'};
2170 $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
2177 my $key = $self->FirstAttribute('PrivateKey') or return undef;
2178 return $key->Content;
2186 my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
2187 unless ( $status ) {
2188 $RT::Logger->error( "Couldn't delete attribute: $msg" );
2189 return ($status, $self->loc("Couldn't unset private key"));
2191 return ($status, $self->loc("Unset private key"));
2194 # check that it's really private key
2196 my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
2197 return (0, $self->loc("No such key or it's not suitable for signing"))
2198 if $tmp{'exit_code'} || !$tmp{'info'};
2201 my ($status, $msg) = $self->SetAttribute(
2202 Name => 'PrivateKey',
2205 return ($status, $self->loc("Couldn't set private key"))
2207 return ($status, $self->loc("Unset private key"));
2212 [ Name => 'User Id' ],
2213 [ EmailAddress => 'Email' ],
2214 [ RealName => 'Name' ],
2215 [ Organization => 'Organization' ],