1 # $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/User.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
2 # (c) 1996-2000 Jesse Vincent <jesse@fsck.com>
3 # This software is redistributable under the terms of the GNU GPL
7 RT::User - RT User object
20 ok(require RT::TestHarness);
36 $self->{'table'} = "Users";
37 return($self->SUPER::_Init(@_));
47 Name => 'public/read/write/admin',
49 Comments => 'read/write/admin',
50 Signature => 'read/write',
51 EmailAddress => 'public/read/write',
52 PagerEmailAddress => 'read/write',
53 FreeformContactInfo => 'read/write',
54 Organization => 'public/read/write/admin',
55 Disabled => 'public/read/write/admin', #To modify this attribute, we have helper
57 Privileged => 'read/write/admin', # 0=no 1=user 2=system
63 RealName => 'public/read/write',
64 NickName => 'public/read/write',
67 # {{{ Localization and Internationalization
68 Lang => 'public/read/write',
69 EmailEncoding => 'public/read/write',
70 WebEncoding => 'public/read/write',
73 # {{{ External ContactInfo Linkage
74 ExternalContactInfoId => 'public/read/write/admin',
75 ContactInfoSystem => 'public/read/write/admin',
78 # {{{ User Authentication identifier
79 ExternalAuthId => 'public/read/write/admin',
80 #Authentication system used for user
81 AuthSystem => 'public/read/write/admin',
82 Gecos => 'public/read/write/admin', #Gecos is the name of the fields in a
83 # unix passwd file. In this case, it refers to "Unix Username"
86 # {{{ Telephone numbers
87 HomePhone => 'read/write',
88 WorkPhone => 'read/write',
89 MobilePhone => 'read/write',
90 PagerPhone => 'read/write',
95 Address1 => 'read/write',
96 Address2 => 'read/write',
98 State => 'read/write',
100 Country => 'read/write',
103 # {{{ Core DBIx::Record Attributes
104 Creator => 'read/auto',
105 Created => 'read/auto',
106 LastUpdatedBy => 'read/auto',
107 LastUpdated => 'read/auto'
111 return($self->SUPER::_Accessible(@_, %Cols));
120 my %args = (Privileged => 0,
121 @_ # get the real argumentlist
125 unless ($self->CurrentUserHasRight('AdminUsers')) {
126 return (0, 'No permission to create users');
129 if (! $args{'Password'}) {
130 $args{'Password'} = '*NO-PASSWORD*';
132 elsif (length($args{'Password'}) < $RT::MinimumPasswordLength) {
133 return(0,"Password too short");
136 my $salt = join '', ('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64];
137 $args{'Password'} = crypt($args{'Password'}, $salt);
141 #TODO Specify some sensible defaults.
143 unless (defined ($args{'Name'})) {
144 return(0, "Must specify 'Name' attribute");
148 #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
149 if ($RT::SystemUser) { #This only works if RT::SystemUser has been defined
150 my $TempUser = RT::User->new($RT::SystemUser);
151 $TempUser->Load($args{'Name'});
152 return (0, 'Name in use') if ($TempUser->Id);
154 return(0, 'Email address in use')
155 unless ($self->ValidateEmailAddress($args{'EmailAddress'}));
158 $RT::Logger->warning("$self couldn't check for pre-existing ".
159 " users on create. This will happen".
160 " on installation\n");
163 my $id = $self->SUPER::Create(%args);
165 #If the create failed.
167 return (0, 'Could not create user');
172 #if ($args{'SendWelcomeMessage'}) {
173 # #TODO: Check if the email exists and looks valid
174 # #TODO: Send the user a "welcome message"
177 return ($id, 'User created');
182 # {{{ sub _BootstrapCreate
184 #create a user without validating _any_ data.
186 #To be used only on database init.
188 sub _BootstrapCreate {
192 $args{'Password'} = "*NO-PASSWORD*";
193 my $id = $self->SUPER::Create(%args);
195 #If the create failed.
196 return (0, 'Could not create user')
199 return ($id, 'User created');
209 return(0, 'Deleting this object would violate referential integrity');
219 Load a user object from the database. Takes a single argument.
220 If the argument is numerical, load by the column 'id'. Otherwise, load by
221 the "Name" column which is the user's textual username.
227 my $identifier = shift || return undef;
229 #if it's an int, load by id. otherwise, load by name.
230 if ($identifier !~ /\D/) {
231 $self->SUPER::LoadById($identifier);
234 $self->LoadByCol("Name",$identifier);
241 # {{{ sub LoadByEmail
245 Tries to load this user object from the database by the user's email address.
254 # Never load an empty address as an email address.
259 $address = RT::CanonicalizeAddress($address);
260 #$RT::Logger->debug("Trying to load an email address: $address\n");
261 return $self->LoadByCol("EmailAddress", $address);
266 # {{{ sub ValidateEmailAddress
268 =head2 ValidateEmailAddress ADDRESS
270 Returns true if the email address entered is not in use by another user or is
271 undef or ''. Returns false if it's in use.
275 sub ValidateEmailAddress {
279 # if the email address is null, it's always valid
280 return (1) if(!$Value || $Value eq "");
282 my $TempUser = RT::User->new($RT::SystemUser);
283 $TempUser->LoadByEmail($Value);
286 ($TempUser->id != $self->id)) { # if we found a user with that address
287 # it's invalid to set this user's address to it
290 else { #it's a valid email address
300 # {{{ sub SetRandomPassword
302 =head2 SetRandomPassword
304 Takes no arguments. Returns a status code and a new password or an error message.
305 If the status is 1, the second value returned is the new password.
306 If the status is anything else, the new value returned is the error code.
310 sub SetRandomPassword {
314 unless ($self->CurrentUserCanModify('Password')) {
315 return (0, "Permission Denied");
318 my $pass = $self->GenerateRandomPassword(6,8);
320 # If we have "notify user on
322 my ($val, $msg) = $self->SetPassword($pass);
324 #If we got an error return the error.
325 return (0, $msg) unless ($val);
327 #Otherwise, we changed the password, lets return it.
335 # {{{ sub ResetPassword
339 Returns status, [ERROR or new password]. Resets this user\'s password to
340 a randomly generated pronouncable password and emails them, using a
341 global template called "RT_PasswordChange", which can be overridden
342 with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged"
343 for privileged and Non-privileged users respectively.
350 unless ($self->CurrentUserCanModify('Password')) {
351 return (0, "Permission Denied");
353 my ($status, $pass) = $self->SetRandomPassword();
359 my $template = RT::Template->new($self->CurrentUser);
362 if ($self->IsPrivileged) {
363 $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
366 $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
369 unless ($template->Id) {
370 $template->LoadGlobalTemplate('RT_PasswordChange');
373 unless ($template->Id) {
374 $RT::Logger->crit("$self tried to send ".$self->Name." a password reminder ".
375 "but couldn't find a password change template");
378 my $notification = RT::Action::SendPasswordEmail->new(TemplateObj => $template,
381 $notification->SetTo($self->EmailAddress);
384 $ret = $notification->Prepare();
386 $ret = $notification->Commit();
390 return(1, 'New password notification sent');
392 return (0, 'Notification could not be sent');
400 # {{{ sub GenerateRandomPassword
402 =head2 GenerateRandomPassword MIN_LEN and MAX_LEN
404 Returns a random password between MIN_LEN and MAX_LEN characters long.
408 sub GenerateRandomPassword {
410 my $min_length = shift;
411 my $max_length = shift;
413 #This code derived from mpw.pl, a bit of code with a sordid history
416 # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
417 # Converted to perl from C by Marc Horowitz, 1/20/2000.
418 # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
419 # Original PL/I version provided by Jerry Saltzer.
422 my ($frequency, $start_freq, $total_sum, $row_sums);
424 #When munging characters, we need to know where to start counting letters from
427 # frequency of English digraphs (from D Edwards 1/27/66)
429 [ [ 4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23,
430 167, 2, 14, 0, 83, 76, 127, 7, 25, 8, 1, 9, 1 ], # aa - az
431 [ 13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0,
432 0, 11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0 ], # ba - bz
433 [ 32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1,
434 0, 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0 ], # ca - cz
435 [ 40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15,
436 6, 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0 ], # da - dz
437 [ 84, 20, 55, 125, 51, 40, 19, 16, 50, 1, 4, 55, 54,
438 146, 35, 37, 6, 191, 149, 65, 9, 26, 21, 12, 5, 0 ], # ea - ez
439 [ 19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1,
440 0, 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0 ], # fa - fz
441 [ 20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1,
442 4, 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0 ], # ga - gz
443 [ 101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3,
444 2, 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0 ], # ha - hz
445 [ 40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25,
446 202, 56, 12, 1, 46, 79, 117, 1, 22, 0, 4, 0, 3 ], # ia - iz
447 [ 3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0,
448 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0 ], # ja - jz
449 [ 1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0,
450 2, 0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0 ], # ka - kz
451 [ 44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2,
452 2, 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0 ], # la - lz
453 [ 52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7,
454 1, 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0 ], # ma - mz
455 [ 42, 10, 47, 122, 63, 19, 106, 12, 30, 1, 6, 6, 9,
456 7, 54, 7, 1, 7, 44, 124, 6, 1, 15, 0, 12, 0 ], # na - nz
457 [ 7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41,
458 134, 13, 23, 0, 91, 23, 42, 55, 16, 28, 0, 4, 1 ], # oa - oz
459 [ 19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1,
460 0, 27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0 ], # pa - pz
461 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
462 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0 ], # qa - qz
463 [ 83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26,
464 16, 60, 4, 0, 24, 37, 55, 6, 11, 4, 0, 28, 0 ], # ra - rz
465 [ 65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11,
466 12, 56, 17, 6, 9, 48, 116, 35, 1, 28, 0, 4, 0 ], # sa - sz
467 [ 57, 22, 3, 1, 76, 5, 2, 330, 126, 1, 0, 14, 10,
468 6, 79, 7, 0, 49, 50, 56, 21, 2, 27, 0, 24, 0 ], # ta - tz
469 [ 11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5,
470 31, 1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0 ], # ua - uz
471 [ 7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0,
472 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0 ], # va - vz
473 [ 36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1,
474 8, 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0 ], # wa - wz
475 [ 1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0,
476 0, 1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0 ], # xa - xz
477 [ 14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7,
478 5, 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0 ], # ya - yz
479 [ 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0,
480 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ]; # za - zz
482 #We need to know the totals for each row
484 [ map { my $sum = 0; map { $sum += $_ } @$_; $sum } @$frequency ];
487 #Frequency with which a given letter starts a word.
489 [ 1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24, 20, 355, 379,
490 319, 823, 618, 21, 317, 962, 1991, 271, 104, 516, 6, 16, 14 ];
492 $total_sum = 0; map { $total_sum += $_ } @$start_freq;
495 my $length = $min_length + int(rand($max_length-$min_length));
497 my $char = $self->GenerateRandomNextChar($total_sum, $start_freq);
498 my @word = ($char+$a);
500 $char = $self->_GenerateRandomNextChar($row_sums->[$char], $frequency->[$char]);
501 push(@word, $char+$a);
505 return pack("C*",@word);
510 #A private helper function for RandomPassword
511 # Takes a row summary and a frequency chart for the next character to be searched
512 sub _GenerateRandomNextChar {
514 my($all, $freq) = @_;
517 for ($pos = int(rand($all)), $i=0;
519 $pos -= $freq->[$i], $i++) {};
526 # {{{ sub SetPassword
530 Takes a string. Checks the string's length and sets this user's password
537 my $password = shift;
539 unless ($self->CurrentUserCanModify('Password')) {
540 return(0, 'Permission Denied');
544 return(0, "No password set");
546 elsif (length($password) < $RT::MinimumPasswordLength) {
547 return(0,"Password too short");
550 my $salt = join '', ('.','/',0..9,'A'..'Z','a'..'z')[rand 64, rand 64];
551 return ( $self->SUPER::SetPassword(crypt($password, $salt)) );
562 Returns true if the passed in value is this user's password.
563 Returns undef otherwise.
571 #TODO there isn't any apparent way to legitimately ACL this
573 # RT does not allow null passwords
574 if ((!defined ($value)) or ($value eq '')) {
577 if ($self->Disabled) {
578 $RT::Logger->info("Disabled user ".$self->Name." tried to log in");
582 if ( ($self->__Value('Password') eq '') ||
583 ($self->__Value('Password') eq undef) ) {
586 if ($self->__Value('Password') eq crypt($value, $self->__Value('Password'))) {
596 # {{{ sub SetDisabled
598 =head2 Sub SetDisabled
600 Toggles the user's disabled flag.
602 set, all password checks for this user will fail. All ACL checks for this
603 user will fail. The user will appear in no user listings.
609 # {{{ ACL Related routines
611 # {{{ GrantQueueRight
613 =head2 GrantQueueRight
615 Grant a queue right to this user. Takes a paramhash of which the elements
616 RightAppliesTo and RightName are important.
620 sub GrantQueueRight {
623 my %args = ( RightScope => 'Queue',
625 RightAppliesTo => undef,
626 PrincipalType => 'User',
627 PrincipalId => $self->Id,
630 #ACL check handled in ACE.pm
634 # $RT::Logger->debug("$self ->GrantQueueRight right:". $args{'RightName'} .
635 # " applies to queue ".$args{'RightAppliesTo'}."\n");
637 my $ace = new RT::ACE($self->CurrentUser);
639 return ($ace->Create(%args));
644 # {{{ GrantSystemRight
646 =head2 GrantSystemRight
648 Grant a system right to this user.
649 The only element that's important to set is RightName.
652 sub GrantSystemRight {
655 my %args = ( RightScope => 'System',
658 PrincipalType => 'User',
659 PrincipalId => $self->Id,
663 #ACL check handled in ACE.pm
666 my $ace = new RT::ACE($self->CurrentUser);
668 return ($ace->Create(%args));
674 # {{{ sub HasQueueRight
678 Takes a paramhash which can contain
680 TicketObj => RT::Ticket or QueueObj => RT::Queue or Queue => integer
681 IsRequestor => undef, (for bootstrapping create)
685 Returns 1 if this user has the right specified in the paramhash. for the queue
688 Returns undef if they don't
694 my %args = ( TicketObj => undef,
697 IsRequestor => undef,
701 my ($IsRequestor, $IsCc, $IsAdminCc, $IsOwner);
703 if (defined $args{'Queue'}) {
704 $args{'QueueObj'} = new RT::Queue($self->CurrentUser);
705 $args{'QueueObj'}->Load($args{'Queue'});
708 if (defined $args{'TicketObj'}) {
709 $args{'QueueObj'} = $args{'TicketObj'}->QueueObj();
712 # {{{ Validate and load up the QueueId
713 unless ((defined $args{'QueueObj'}) and ($args{'QueueObj'}->Id)) {
715 $RT::Logger->debug(Carp::cluck ("$self->HasQueueRight Couldn't find a queue id"));
722 # Figure out whether a user has the right we're asking about.
723 # first see if they have the right personally for the queue in question.
724 my $retval = $self->_HasRight(Scope => 'Queue',
725 AppliesTo => $args{'QueueObj'}->Id,
726 Right => $args{'Right'},
727 IsOwner => $IsOwner);
729 return ($retval) if (defined $retval);
731 # then we see whether they have the right personally globally.
732 $retval = $self->HasSystemRight( $args{'Right'});
734 return ($retval) if (defined $retval);
736 # now that we know they don't have the right personally,
738 # {{{ Find out about whether the current user is a Requestor, Cc, AdminCc or Owner
740 if (defined $args{'TicketObj'}) {
741 if ($args{'TicketObj'}->IsRequestor($self)) {#user is requestor
745 if ($args{'TicketObj'}->IsCc($self)) { #If user is a cc
749 if ($args{'TicketObj'}->IsAdminCc($self)) { #If user is an admin cc
753 if ($args{'TicketObj'}->IsOwner($self)) { #If user is an owner
758 if (defined $args{'QueueObj'}) {
759 if ($args{'QueueObj'}->IsCc($self)) { #If user is a cc
762 if ($args{'QueueObj'}->IsAdminCc($self)) { #If user is an admin cc
769 # then see whether they have the right for the queue as a member of a metagroup
771 $retval = $self->_HasRight(Scope => 'Queue',
772 AppliesTo => $args{'QueueObj'}->Id,
773 Right => $args{'Right'},
776 IsAdminCc => $IsAdminCc,
777 IsRequestor => $IsRequestor
780 return ($retval) if (defined $retval);
782 # then we see whether they have the right globally as a member of a metagroup
783 $retval = $self->HasSystemRight( $args{'Right'},
784 (IsOwner => $IsOwner,
786 IsAdminCc => $IsAdminCc,
787 IsRequestor => $IsRequestor
790 #If they haven't gotten it by now, they just lose.
797 # {{{ sub HasSystemRight
799 =head2 HasSystemRight
801 takes an array of a single value and a paramhash.
802 The single argument is the right being passed in.
803 the param hash is some additional data. (IsCc, IsOwner, IsAdminCc and IsRequestor)
805 Returns 1 if this user has the listed 'right'. Returns undef if this user doesn't.
813 my %args = ( IsOwner => undef,
816 IsRequestor => undef,
819 unless (defined $right) {
821 $RT::Logger->debug("$self RT::User::HasSystemRight was passed in no right.");
824 return ( $self->_HasRight ( Scope => 'System',
827 IsOwner => $args{'IsOwner'},
828 IsCc => $args{'IsCc'},
829 IsAdminCc => $args{'IsAdminCc'},
830 IsRequestor => $args{'IsRequestor'},
841 =head2 sub _HasRight (Right => 'right', Scope => 'scope', AppliesTo => int, ExtendedPrincipals => SQL)
843 _HasRight is a private helper method for checking a user's rights. It takes
846 =item Right is a textual right name
848 =item Scope is a textual scope name. (As of July these were Queue, Ticket and System
850 =item AppliesTo is the numerical Id of the object identified in the scope. For tickets, this is the queue #. for queues, this is the queue #
852 =item ExtendedPrincipals is an SQL select clause which assumes that the only
853 table in play is ACL. It's used by HasQueueRight to pass in which
854 metaprincipals apply. Actually, it's probably obsolete. TODO: remove it.
856 Returns 1 if a matching ACE was found.
858 Returns undef if no ACE was found.
866 my %args = ( Right => undef,
869 IsRequestor => undef,
873 ExtendedPrincipals => undef,
876 if ($self->Disabled) {
877 $RT::Logger->debug ("Disabled User: ".$self->Name.
878 " failed access check for ".$args{'Right'}.
879 " to object ".$args{'Scope'}."/".
880 $args{'AppliesTo'}."\n");
884 if (!defined $args{'Right'}) {
885 $RT::Logger->debug("_HasRight called without a right\n");
888 elsif (!defined $args{'Scope'}) {
889 $RT::Logger->debug("_HasRight called without a scope\n");
892 elsif (!defined $args{'AppliesTo'}) {
893 $RT::Logger->debug("_HasRight called without an AppliesTo object\n");
897 #If we've cached a win or loss for this lookup say so
899 #TODO Security +++ check to make sure this is complete and right
901 #Construct a hashkey to cache decisions in
903 { #it's ugly, but we need to turn off warning, cuz we're joining nulls.
905 $hashkey =$self->Id .":". join(':',%args);
908 # $RT::Logger->debug($hashkey."\n");
910 #Anything older than 10 seconds needs to be rechecked
911 my $cache_timeout = (time - 10);
914 if ((defined $self->{'rights'}{"$hashkey"}) &&
915 ($self->{'rights'}{"$hashkey"} == 1 ) &&
916 (defined $self->{'rights'}{"$hashkey"}{'set'} ) &&
917 ($self->{'rights'}{"$hashkey"}{'set'} > $cache_timeout)) {
918 # $RT::Logger->debug("Cached ACL win for ".
919 # $args{'Right'}.$args{'Scope'}.
920 # $args{'AppliesTo'}."\n");
921 return ($self->{'rights'}{"$hashkey"});
923 elsif ((defined $self->{'rights'}{"$hashkey"}) &&
924 ($self->{'rights'}{"$hashkey"} == -1) &&
925 (defined $self->{'rights'}{"$hashkey"}{'set'}) &&
926 ($self->{'rights'}{"$hashkey"}{'set'} > $cache_timeout)) {
928 # $RT::Logger->debug("Cached ACL loss decision for ".
929 # $args{'Right'}.$args{'Scope'}.
930 # $args{'AppliesTo'}."\n");
936 my $RightClause = "(RightName = '$args{'Right'}')";
937 my $ScopeClause = "(RightScope = '$args{'Scope'}')";
939 #If an AppliesTo was passed in, we should pay attention to it.
940 #otherwise, none is needed
942 $ScopeClause = "($ScopeClause AND (RightAppliesTo = $args{'AppliesTo'}))"
943 if ($args{'AppliesTo'});
946 # The generic principals clause looks for users with my id
947 # and Rights that apply to _everyone_
948 my $PrincipalsClause = "((PrincipalType = 'User') AND (PrincipalId = ".$self->Id."))";
951 # If the user is the superuser, grant them the damn right ;)
952 my $SuperUserClause =
953 "(RightName = 'SuperUser') AND (RightScope = 'System') AND (RightAppliesTo = 0)";
955 # If we've been passed in an extended principals clause, we should lump it
956 # on to the existing principals clause. it'll make life easier
957 if ($args{'ExtendedPrincipals'}) {
958 $PrincipalsClause = "(($PrincipalsClause) OR ".
959 "($args{'ExtendedPrincipalsClause'}))";
962 my $GroupPrincipalsClause = "((ACL.PrincipalType = 'Group') ".
963 "AND (ACL.PrincipalId = Groups.Id) AND (GroupMembers.GroupId = Groups.Id) ".
964 " AND (GroupMembers.UserId = ".$self->Id."))";
969 # {{{ A bunch of magic statements that make the metagroups listed
970 # work. basically, we if the user falls into the right group,
971 # we add the type of ACL check needed
972 my (@MetaPrincipalsSubClauses, $MetaPrincipalsClause);
974 #The user is always part of the 'Everyone' Group
975 push (@MetaPrincipalsSubClauses, "((Groups.Name = 'Everyone') AND
976 (PrincipalType = 'Group') AND
977 (Groups.Id = PrincipalId))");
979 if ($args{'IsAdminCc'}) {
980 push (@MetaPrincipalsSubClauses, "((Groups.Name = 'AdminCc') AND
981 (PrincipalType = 'Group') AND
982 (Groups.Id = PrincipalId))");
985 push (@MetaPrincipalsSubClauses, " ((Groups.Name = 'Cc') AND
986 (PrincipalType = 'Group') AND
987 (Groups.Id = PrincipalId))");
989 if ($args{'IsRequestor'}) {
990 push (@MetaPrincipalsSubClauses, " ((Groups.Name = 'Requestor') AND
991 (PrincipalType = 'Group') AND
992 (Groups.Id = PrincipalId))");
994 if ($args{'IsOwner'}) {
996 push (@MetaPrincipalsSubClauses, " ((Groups.Name = 'Owner') AND
997 (PrincipalType = 'Group') AND
998 (Groups.Id = PrincipalId))");
1003 my ($GroupRightsQuery, $MetaGroupRightsQuery, $IndividualRightsQuery, $hitcount);
1005 # {{{ If there are any metaprincipals to be checked
1006 if (@MetaPrincipalsSubClauses) {
1007 #chop off the leading or
1008 #TODO redo this with an array and a join
1009 $MetaPrincipalsClause = join (" OR ", @MetaPrincipalsSubClauses);
1011 $MetaGroupRightsQuery = "SELECT COUNT(ACL.id) FROM ACL, Groups".
1013 " ($ScopeClause) AND ($RightClause) AND ($MetaPrincipalsClause)";
1015 # {{{ deal with checking if the user has a right as a member of a metagroup
1017 # $RT::Logger->debug("Now Trying $MetaGroupRightsQuery\n");
1018 $hitcount = $self->_Handle->FetchResult($MetaGroupRightsQuery);
1020 #if there's a match, the right is granted
1022 $self->{'rights'}{"$hashkey"}{'set'} = time;
1023 $self->{'rights'}{"$hashkey"} = 1;
1027 # $RT::Logger->debug("No ACL matched MetaGroups query: $MetaGroupRightsQuery\n");
1034 # {{{ deal with checking if the user has a right as a member of a group
1035 # This query checks to se whether the user has the right as a member of a
1037 $GroupRightsQuery = "SELECT COUNT(ACL.id) FROM ACL, GroupMembers, Groups".
1039 " (((($ScopeClause) AND ($RightClause)) OR ($SuperUserClause)) ".
1040 " AND ($GroupPrincipalsClause))";
1042 # $RT::Logger->debug("Now Trying $GroupRightsQuery\n");
1043 $hitcount = $self->_Handle->FetchResult($GroupRightsQuery);
1045 #if there's a match, the right is granted
1047 $self->{'rights'}{"$hashkey"}{'set'} = time;
1048 $self->{'rights'}{"$hashkey"} = 1;
1052 # $RT::Logger->debug("No ACL matched $GroupRightsQuery\n");
1056 # {{{ Check to see whether the user has a right as an individual
1058 # This query checks to see whether the current user has the right directly
1059 $IndividualRightsQuery = "SELECT COUNT(ACL.id) FROM ACL WHERE ".
1060 " ((($ScopeClause) AND ($RightClause)) OR ($SuperUserClause)) " .
1061 " AND ($PrincipalsClause)";
1064 $hitcount = $self->_Handle->FetchResult($IndividualRightsQuery);
1067 $self->{'rights'}{"$hashkey"}{'set'} = time;
1068 $self->{'rights'}{"$hashkey"} = 1;
1073 else { #If the user just doesn't have the right
1075 # $RT::Logger->debug("No ACL matched $IndividualRightsQuery\n");
1077 #If nothing matched, return 0.
1078 $self->{'rights'}{"$hashkey"}{'set'} = time;
1079 $self->{'rights'}{"$hashkey"} = -1;
1088 # {{{ sub CurrentUserCanModify
1090 =head2 CurrentUserCanModify RIGHT
1092 If the user has rights for this object, either because
1093 he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an
1094 admin right) 'ModifySelf', return 1. otherwise, return undef.
1098 sub CurrentUserCanModify {
1102 if ($self->CurrentUserHasRight('AdminUsers')) {
1105 #If the field is marked as an "administrators only" field,
1106 # don\'t let the user touch it.
1107 elsif ($self->_Accessible($right, 'admin')) {
1111 #If the current user is trying to modify themselves
1112 elsif ( ($self->id == $self->CurrentUser->id) and
1113 ($self->CurrentUserHasRight('ModifySelf'))) {
1117 #If we don\'t have a good reason to grant them rights to modify
1127 # {{{ sub CurrentUserHasRight
1129 =head2 CurrentUserHasRight
1131 Takes a single argument. returns 1 if $Self->CurrentUser
1132 has the requested right. returns undef otherwise
1136 sub CurrentUserHasRight {
1140 return ($self->CurrentUser->HasSystemRight($right));
1151 my %args = (Field => undef,
1156 # Nobody is allowed to futz with RT_System or Nobody unless they
1157 # want to change an email address. For 2.2, neither should have an email address
1159 if ($self->Privileged == 2) {
1160 return (0, "Can not modify system users");
1162 unless ($self->CurrentUserCanModify($args{'Field'})) {
1163 return (0, "Permission Denied");
1169 my ($ret, $msg)=$self->SUPER::_Set(Field => $args{'Field'},
1170 Value=> $args{'Value'});
1172 return ($ret, $msg);
1181 Takes the name of a table column.
1182 Returns its value as a string, if the user passes an ACL check
1191 #If the current user doesn't have ACLs, don't let em at it.
1193 my @PublicFields = qw( Name EmailAddress Organization Disabled
1194 RealName NickName Gecos ExternalAuthId
1195 AuthSystem ExternalContactInfoId
1196 ContactInfoSystem );
1198 #if the field is public, return it.
1199 if ($self->_Accessible($field, 'public')) {
1200 return($self->SUPER::_Value($field));
1203 #If the user wants to see their own values, let them
1204 elsif ($self->CurrentUser->Id == $self->Id) {
1205 return($self->SUPER::_Value($field));
1207 #If the user has the admin users right, return the field
1208 elsif ($self->CurrentUserHasRight('AdminUsers')) {
1209 return($self->SUPER::_Value($field));