X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FUser_Overlay.pm;fp=rt%2Flib%2FRT%2FUser_Overlay.pm;h=27ddd4cd00ed0fa94d16d4e2023e54c593f66760;hb=d4d0590bef31071e8809ec046717444b95b3f30a;hp=c4ef340c946384795d15ee6b659514a992da52d5;hpb=d39d52aac8f38ea9115628039f0df5aa3ac826de;p=freeside.git diff --git a/rt/lib/RT/User_Overlay.pm b/rt/lib/RT/User_Overlay.pm index c4ef340c9..27ddd4cd0 100644 --- a/rt/lib/RT/User_Overlay.pm +++ b/rt/lib/RT/User_Overlay.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,8 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} + =head1 NAME RT::User - RT User object @@ -65,6 +66,9 @@ ok(require RT::User); =cut + +package RT::User; + use strict; no warnings qw(redefine); @@ -75,7 +79,7 @@ use vars qw(%_USERS_KEY_CACHE); use Digest::MD5; use RT::Principals; use RT::ACE; -use RT::EmailParser; +use RT::Interface::Email; # {{{ sub _Accessible @@ -118,41 +122,41 @@ sub _OverlayAccessible { my $u1 = RT::User->new($RT::SystemUser); is(ref($u1), 'RT::User'); -my ($id, $msg) = $u1->Create(Name => 'CreateTest1', EmailAddress => 'create-test-1@example.com'); +my ($id, $msg) = $u1->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-1@example.com'); ok ($id, "Creating user CreateTest1 - " . $msg ); # Make sure we can't create a second user with the same name my $u2 = RT::User->new($RT::SystemUser); -($id, $msg) = $u2->Create(Name => 'CreateTest1', EmailAddress => 'create-test-2@example.com'); +($id, $msg) = $u2->Create(Name => 'CreateTest1'.$$, EmailAddress => $$.'create-test-2@example.com'); ok (!$id, $msg); # Make sure we can't create a second user with the same EmailAddress address my $u3 = RT::User->new($RT::SystemUser); -($id, $msg) = $u3->Create(Name => 'CreateTest2', EmailAddress => 'create-test-1@example.com'); +($id, $msg) = $u3->Create(Name => 'CreateTest2'.$$, EmailAddress => $$.'create-test-1@example.com'); ok (!$id, $msg); # Make sure we can create a user with no EmailAddress address my $u4 = RT::User->new($RT::SystemUser); -($id, $msg) = $u4->Create(Name => 'CreateTest3'); +($id, $msg) = $u4->Create(Name => 'CreateTest3'.$$); ok ($id, $msg); # make sure we can create a second user with no EmailAddress address my $u5 = RT::User->new($RT::SystemUser); -($id, $msg) = $u5->Create(Name => 'CreateTest4'); +($id, $msg) = $u5->Create(Name => 'CreateTest4'.$$); ok ($id, $msg); # make sure we can create a user with a blank EmailAddress address my $u6 = RT::User->new($RT::SystemUser); -($id, $msg) = $u6->Create(Name => 'CreateTest6', EmailAddress => ''); +($id, $msg) = $u6->Create(Name => 'CreateTest6'.$$, EmailAddress => ''); ok ($id, $msg); # make sure we can create a second user with a blankEmailAddress address my $u7 = RT::User->new($RT::SystemUser); -($id, $msg) = $u7->Create(Name => 'CreateTest7', EmailAddress => ''); +($id, $msg) = $u7->Create(Name => 'CreateTest7'.$$, EmailAddress => ''); ok ($id, $msg); # Can we change the email address away from from ""; -($id,$msg) = $u7->SetEmailAddress('foo@bar'); +($id,$msg) = $u7->SetEmailAddress('foo@bar'.$$); ok ($id, $msg); # can we change the address back to ""; ($id,$msg) = $u7->SetEmailAddress(''); @@ -171,9 +175,13 @@ sub Create { Privileged => 0, Disabled => 0, EmailAddress => '', + _RecordTransaction => 1, @_ # get the real argumentlist ); + # remove the value so it does not cripple SUPER::Create + my $record_transaction = delete $args{'_RecordTransaction'}; + #Check the ACL unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { return ( 0, $self->loc('No permission to create users') ); @@ -204,7 +212,7 @@ sub Create { $args{'Password'} = '*NO-PASSWORD*'; } elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) { - return ( 0, $self->loc("Password too short") ); + return ( 0, $self->loc("Password needs to be at least [_1] characters long",$RT::MinimumPasswordLength) ); } else { @@ -314,7 +322,12 @@ sub Create { } + if ( $record_transaction ) { + $self->_NewTransaction( Type => "Create" ); + } + $RT::Handle->Commit; + return ( $id, $self->loc('User created') ); } @@ -573,12 +586,15 @@ sub LoadOrCreateByEmail { my ($val, $message); my ( $Address, $Name ) = - RT::EmailParser::ParseAddressFromHeader('', $email); + RT::Interface::Email::ParseAddressFromHeader($email); $email = $Address; $self->LoadByEmail($email); $message = $self->loc('User loaded'); unless ($self->Id) { + $self->Load($email); + } + unless($self->Id) { ( $val, $message ) = $self->Create( Name => $email, EmailAddress => $email, @@ -649,11 +665,13 @@ sub ValidateEmailAddress { -=item CanonicalizeEmailAddress ADDRESS +=head2 CanonicalizeEmailAddress ADDRESS -# CanonicalizeEmailAddress converts email addresses into canonical form. -# it takes one email address in and returns the proper canonical -# form. You can dump whatever your proper local config is in here +CanonicalizeEmailAddress converts email addresses into canonical form. +it takes one email address in and returns the proper canonical +form. You can dump whatever your proper local config is in here. Note +that it may be called as a static method; in this case, $self may be +undef. =cut @@ -676,14 +694,14 @@ sub CanonicalizeEmailAddress { -=item CanonicalizeUserInfo HASH of ARGS +=head2 CanonicalizeUserInfo HASH of ARGS -# CanonicalizeUserInfo can convert all User->Create options. -# it takes a hashref of all the params sent to User->Create and -# returns that same hash, by default nothing is done. +CanonicalizeUserInfo can convert all User->Create options. +it takes a hashref of all the params sent to User->Create and +returns that same hash, by default nothing is done. -# This function is intended to allow users to have their info looked up via -# an outside source and modified upon creation. +This function is intended to allow users to have their info looked up via +an outside source and modified upon creation. =cut @@ -718,7 +736,11 @@ sub SetRandomPassword { return ( 0, $self->loc("Permission Denied") ); } - my $pass = $self->GenerateRandomPassword( 6, 8 ); + + my $min = ( $RT::MinimumPasswordLength > 6 ? $RT::MinimumPasswordLength : 6); + my $max = ( $RT::MinimumPasswordLength > 8 ? $RT::MinimumPasswordLength : 8); + + my $pass = $self->GenerateRandomPassword( $min, $max) ; # If we have "notify user on @@ -764,7 +786,7 @@ sub ResetPassword { $template->LoadGlobalTemplate('RT_PasswordChange_Privileged'); } else { - $template->LoadGlobalTemplate('RT_PasswordChange_Privileged'); + $template->LoadGlobalTemplate('RT_PasswordChange_NonPrivileged'); } unless ( $template->Id ) { @@ -1006,25 +1028,33 @@ sub SetPassword { my $password = shift; unless ( $self->CurrentUserCanModify('Password') ) { - return ( 0, $self->loc('Permission Denied') ); + return ( 0, $self->loc('Password: Permission Denied') ); } if ( !$password ) { return ( 0, $self->loc("No password set") ); } elsif ( length($password) < $RT::MinimumPasswordLength ) { - return ( 0, $self->loc("Password too short") ); + return ( 0, $self->loc("Password needs to be at least [_1] characters long", $RT::MinimumPasswordLength) ); } else { + my $new = !$self->HasPassword; $password = $self->_GeneratePassword($password); - return ( $self->SUPER::SetPassword( $password)); + my ( $val, $msg ) = $self->SUPER::SetPassword($password); + if ($val) { + return ( 1, $self->loc("Password set") ) if $new; + return ( 1, $self->loc("Password changed") ); + } + else { + return ( $val, $msg ); + } } } =head2 _GeneratePassword PASSWORD -returns an MD5 hash of the password passed in, in base64 encoding. +returns an MD5 hash of the password passed in, in hexadecimal encoding. =cut @@ -1034,12 +1064,54 @@ sub _GeneratePassword { my $md5 = Digest::MD5->new(); $md5->add($password); + return ($md5->hexdigest); + +} + +=head2 _GeneratePasswordBase64 PASSWORD + +returns an MD5 hash of the password passed in, in base64 encoding +(obsoleted now). + +=cut + +sub _GeneratePasswordBase64 { + my $self = shift; + my $password = shift; + + my $md5 = Digest::MD5->new(); + $md5->add($password); return ($md5->b64digest); } # }}} + +=head2 HasPassword + +Returns true if the user has a valid password, otherwise returns false. + +=cut + + +sub HasPassword { + my $self = shift; + if ( ( $self->__Value('Password') eq '' ) + || ( $self->__Value('Password') eq undef ) ) + { + + return (undef); + } + if ( $self->__Value('Password') eq '*NO-PASSWORD*' ) { + return undef; + } + + return 1; + +} + + # {{{ sub IsPassword =head2 IsPassword @@ -1066,8 +1138,7 @@ sub IsPassword { return (undef); } - if ( ($self->__Value('Password') eq '') || - ($self->__Value('Password') eq undef) ) { + unless ($self->HasPassword) { return(undef); } @@ -1077,9 +1148,12 @@ sub IsPassword { } # if it's a historical password we say ok. - - if ( $self->__Value('Password') eq crypt( $value, $self->__Value('Password') ) ) { - return (1); + if ($self->__Value('Password') eq crypt($value, $self->__Value('Password')) + or $self->_GeneratePasswordBase64($value) eq $self->__Value('Password')) + { + # ...but upgrade the legacy password inplace. + $self->SUPER::SetPassword( $self->_GeneratePassword($value) ); + return(1); } # no password check has succeeded. get out @@ -1131,7 +1205,7 @@ The response is cached. PrincipalObj should never ever change. ok(my $u = RT::User->new($RT::SystemUser)); ok($u->Load(1), "Loaded the first user"); ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal"); -ok($u->PrincipalObj->PrincipalType eq 'User' , "Principal 1 is a user, not a group"); +is($u->PrincipalObj->PrincipalType, 'User' , "Principal 1 is a user, not a group"); =end testing @@ -1220,9 +1294,29 @@ sub HasGroupRight { # }}} +# {{{ sub OwnGroups + +=head2 OwnGroups + +Returns a group collection object containing the groups of which this +user is a member. + +=cut + +sub OwnGroups { + my $self = shift; + my $groups = RT::Groups->new($self->CurrentUser); + $groups->LimitToUserDefinedGroups; + $groups->WithMember(PrincipalId => $self->Id, + Recursively => 1); + return $groups; +} + +# }}} + # {{{ sub Rights testing -=head2 Rights testing +=head1 Rights testing =begin testing @@ -1237,7 +1331,7 @@ ok($rootq->Id, "Loaded the first queue"); ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets"); my $new_user = RT::User->new($RT::SystemUser); -my ($id, $msg) = $new_user->Create(Name => 'ACLTest'); +my ($id, $msg) = $new_user->Create(Name => 'ACLTest'.$$); ok ($id, "Created a new user for acl test $msg"); @@ -1268,7 +1362,7 @@ ok($tickid, "Created ticket: $tickid"); ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership"); # Create a new group my $group = RT::Group->new($RT::SystemUser); -$group->CreateUserDefinedGroup(Name => 'ACLTest'); +$group->CreateUserDefinedGroup(Name => 'ACLTest'.$$); ok($group->Id, "Created a new group Ok"); # Grant a group the right to modify tickets in a queue ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets"); @@ -1295,7 +1389,7 @@ ok($q_as_system->Id, "Loaded the first queue"); my $new_tick2 = RT::Ticket->new($RT::SystemUser); my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id); ok($tick2id, "Created ticket: $tick2id"); -ok($new_tick2->QueueObj->id eq $q_as_system->Id, "Created a new ticket in queue 1"); +is($new_tick2->QueueObj->id, $q_as_system->Id, "Created a new ticket in queue 1"); # make sure that the user can't do this without subgroup membership @@ -1303,7 +1397,7 @@ ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User # Create a subgroup my $subgroup = RT::Group->new($RT::SystemUser); -$subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest'); +$subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest',$$); ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok"); #Add the subgroup as a subgroup of the group my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId); @@ -1318,8 +1412,8 @@ ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User c # {{{ Deal with making sure that members of subgroups of a disabled group don't have rights my ($id, $msg); - ($id, $msg) = $group->SetDisabled(1); - ok ($id,$msg); +($id, $msg) = $group->SetDisabled(1); +ok ($id,$msg); ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled"); ($id, $msg) = $group->SetDisabled(0); ok($id,$msg); @@ -1464,7 +1558,7 @@ ok($rqv, "Revoked the right successfully - $rqm"); # {{{ sub HasRight -=head2 sub HasRight +=head2 HasRight Shim around PrincipalObj->HasRight. See RT::Principal @@ -1523,8 +1617,8 @@ sub CurrentUserCanModify { =head2 CurrentUserHasRight - Takes a single argument. returns 1 if $Self->CurrentUser - has the requested right. returns undef otherwise +Takes a single argument. returns 1 if $Self->CurrentUser +has the requested right. returns undef otherwise =cut @@ -1537,6 +1631,77 @@ sub CurrentUserHasRight { # }}} +# {{{ sub _CleanupInvalidDelegations + +=head2 _CleanupInvalidDelegations { InsideTransaction => undef } + +Revokes all ACE entries delegated by this user which are inconsistent +with their current delegation rights. Does not perform permission +checks. Should only ever be called from inside the RT library. + +If called from inside a transaction, specify a true value for the +InsideTransaction parameter. + +Returns a true value if the deletion succeeded; returns a false value +and logs an internal error if the deletion fails (should not happen). + +=cut + +# XXX Currently there is a _CleanupInvalidDelegations method in both +# RT::User and RT::Group. If the recursive cleanup call for groups is +# ever unrolled and merged, this code will probably want to be +# factored out into RT::Principal. + +sub _CleanupInvalidDelegations { + my $self = shift; + my %args = ( InsideTransaction => undef, + @_ ); + + unless ( $self->Id ) { + $RT::Logger->warning("User not loaded."); + return (undef); + } + + my $in_trans = $args{InsideTransaction}; + + return(1) if ($self->HasRight(Right => 'DelegateRights', + Object => $RT::System)); + + # Look up all delegation rights currently posessed by this user. + my $deleg_acl = RT::ACL->new($RT::SystemUser); + $deleg_acl->LimitToPrincipal(Type => 'User', + Id => $self->PrincipalId, + IncludeGroupMembership => 1); + $deleg_acl->Limit( FIELD => 'RightName', + OPERATOR => '=', + VALUE => 'DelegateRights' ); + my @allowed_deleg_objects = map {$_->Object()} + @{$deleg_acl->ItemsArrayRef()}; + + # Look up all rights delegated by this principal which are + # inconsistent with the allowed delegation objects. + my $acl_to_del = RT::ACL->new($RT::SystemUser); + $acl_to_del->DelegatedBy(Id => $self->Id); + foreach (@allowed_deleg_objects) { + $acl_to_del->LimitNotObject($_); + } + + # Delete all disallowed delegations + while ( my $ace = $acl_to_del->Next() ) { + my $ret = $ace->_Delete(InsideTransaction => 1); + unless ($ret) { + $RT::Handle->Rollback() unless $in_trans; + $RT::Logger->warning("Couldn't delete delegated ACL entry ".$ace->Id); + return (undef); + } + } + + $RT::Handle->Commit() unless $in_trans; + return (1); +} + +# }}} + # {{{ sub _Set sub _Set { @@ -1545,6 +1710,8 @@ sub _Set { my %args = ( Field => undef, Value => undef, + TransactionType => 'Set', + RecordTransaction => 1, @_ ); @@ -1558,13 +1725,29 @@ sub _Set { return ( 0, $self->loc("Permission Denied") ); } - #Set the new value - my ( $ret, $msg ) = $self->SUPER::_Set( - Field => $args{'Field'}, - Value => $args{'Value'} - ); + my $Old = $self->SUPER::_Value("$args{'Field'}"); + + my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'}, + Value => $args{'Value'} ); + + #If we can't actually set the field to the value, don't record + # a transaction. instead, get out of here. + if ( $ret == 0 ) { return ( 0, $msg ); } - return ( $ret, $msg ); + if ( $args{'RecordTransaction'} == 1 ) { + + my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( + Type => $args{'TransactionType'}, + Field => $args{'Field'}, + NewValue => $args{'Value'}, + OldValue => $Old, + TimeTaken => $args{'TimeTaken'}, + ); + return ( $Trans, scalar $TransObj->BriefDescription ); + } + else { + return ( $ret, $msg ); + } } # }}} @@ -1614,6 +1797,14 @@ sub _Value { # }}} +sub BasicColumns { + ( + [ Name => 'User Id' ], + [ EmailAddress => 'Email' ], + [ RealName => 'Name' ], + [ Organization => 'Organization' ], + ); +} 1;