# BEGIN BPS TAGGED BLOCK {{{
-#
+#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
+#
+# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# <sales@bestpractical.com>
+#
# (Except where explicitly superseded by other copyright notices)
-#
-#
+#
+#
# LICENSE:
-#
+#
# This work is made available to you under the terms of Version 2 of
# the GNU General Public License. A copy of that license should have
# been provided with this software, but in any event can be snarfed
# from www.gnu.org.
-#
+#
# This work is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
+#
+#
# CONTRIBUTION SUBMISSION POLICY:
-#
+#
# (The following paragraph is not intended to limit the rights granted
# to you to modify and distribute this software under the terms of
# the GNU General Public License and is only of importance to you if
# you choose to contribute your changes and enhancements to the
# community by submitting them to Best Practical Solutions, LLC.)
-#
+#
# By intentionally submitting any modifications, corrections or
# derivatives to this work, or any other work intended for use with
# Request Tracker, to Best Practical Solutions, LLC, you confirm that
# royalty-free, perpetual, license to use, copy, create derivative
# works based on those contributions, and sublicense and distribute
# those contributions and any derivatives thereof.
-#
+#
# END BPS TAGGED BLOCK }}}
=head1 NAME
use strict;
no warnings qw(redefine);
+use Digest::SHA;
use Digest::MD5;
use RT::Principals;
use RT::ACE;
return ($i);
}
+sub SafeSetPassword {
+ my $self = shift;
+ my %args = (
+ Current => undef,
+ New => undef,
+ Confirmation => undef,
+ @_,
+ );
+ return (1) unless defined $args{'New'} && length $args{'New'};
+
+ my %cond = $self->CurrentUserRequireToSetPassword;
+
+ unless ( $cond{'CanSet'} ) {
+ return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} );
+ }
+
+ my $error = '';
+ if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) {
+ if ( defined $args{'Current'} && length $args{'Current'} ) {
+ $error = $self->loc("Please enter your current password correctly.");
+ }
+ else {
+ $error = $self->loc("Please enter your current password.");
+ }
+ } elsif ( $args{'New'} ne $args{'Confirmation'} ) {
+ $error = $self->loc("Passwords do not match.");
+ }
+
+ if ( $error ) {
+ $error .= ' '. $self->loc('Password has not been set.');
+ return (0, $error);
+ }
+
+ return $self->SetPassword( $args{'New'} );
+}
+
=head3 SetPassword
Takes a string. Checks the string's length and sets this user's password
}
-=head3 _GeneratePassword PASSWORD
+=head3 _GeneratePassword PASSWORD [, SALT]
-returns an MD5 hash of the password passed in, in hexadecimal encoding.
+Returns a salted SHA-256 hash of the password passed in, in base64
+encoding.
=cut
sub _GeneratePassword {
my $self = shift;
- my $password = shift;
-
- my $md5 = Digest::MD5->new();
- $md5->add(encode_utf8($password));
- return ($md5->hexdigest);
-
+ my ($password, $salt) = @_;
+
+ # Generate a random 4-byte salt
+ $salt ||= pack("C4",map{int rand(256)} 1..4);
+
+ # Encode the salt, and a truncated SHA256 of the MD5 of the
+ # password. The additional, un-necessary level of MD5 allows for
+ # transparent upgrading to this scheme, from the previous unsalted
+ # MD5 one.
+ return MIME::Base64::encode_base64(
+ $salt . substr(Digest::SHA::sha256($salt . Digest::MD5::md5($password)),0,26),
+ "" # No newline
+ );
}
=head3 _GeneratePasswordBase64 PASSWORD
return(undef);
}
- # generate an md5 password
- if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
- return(1);
+ my $stored = $self->__Value('Password');
+ if (length $stored == 40) {
+ # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long
+ my $hash = MIME::Base64::decode_base64($stored);
+ # The first 4 bytes are the salt, the rest is substr(SHA256,0,26)
+ my $salt = substr($hash, 0, 4, "");
+ return substr(Digest::SHA::sha256($salt . Digest::MD5::md5($value)), 0, 26) eq $hash;
+ } elsif (length $stored == 32) {
+ # Hex nonsalted-md5
+ return 0 unless Digest::MD5::md5_hex(encode_utf8($value)) eq $stored;
+ } elsif (length $stored == 22) {
+ # Base64 nonsalted-md5
+ return 0 unless Digest::MD5::md5_base64(encode_utf8($value)) eq $stored;
+ } elsif (length $stored == 13) {
+ # crypt() output
+ return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
+ } else {
+ $RT::Logger->warn("Unknown password form");
+ return 0;
}
- # if it's a historical password we say ok.
- 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);
- }
+ # We got here by validating successfully, but with a legacy
+ # password form. Update to the most recent form.
+ my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self;
+ $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) );
+ return 1;
+}
+
+sub CurrentUserRequireToSetPassword {
+ my $self = shift;
- # no password check has succeeded. get out
+ my %res = (
+ CanSet => 1,
+ Reason => '',
+ RequireCurrent => 1,
+ );
- return (undef);
+ if ( RT->Config->Get('WebExternalAuth')
+ && !RT->Config->Get('WebFallbackToInternalAuth')
+ ) {
+ $res{'CanSet'} = 0;
+ $res{'Reason'} = $self->loc("External authentication enabled.");
+ }
+ elsif ( !$self->CurrentUser->HasPassword ) {
+ if ( $self->CurrentUser->id == ($self->id||0) ) {
+ # don't require current password if user has no
+ $res{'RequireCurrent'} = 0;
+ }
+ else {
+ $res{'CanSet'} = 0;
+ $res{'Reason'} = $self->loc("Your password is not set.");
+ }
+ }
+
+ return %res;
}
=head3 AuthToken
# {{{ Links
-#much false laziness w/Ticket_Overlay.pm
+#much false laziness w/Ticket_Overlay.pm. now with RT 3.8!
# A helper table for links mapping to make it easier
# to build and parse links between tickets
#check acls
my $right = 0;
- $right++ if $self->CurrentUserHasRight('ModifyUser');
+ $right++ if $self->CurrentUserHasRight('AdminUsers');
if ( !$right && $RT::StrictLinkACL ) {
return ( 0, $self->loc("Permission Denied") );
}
}
my $right = 0;
- $right++ if $self->CurrentUserHasRight('ModifyUser');
+ $right++ if $self->CurrentUserHasRight('AdminUsers');
if ( !$right && $RT::StrictLinkACL ) {
return ( 0, $self->loc("Permission Denied") );
}
# }}}
-
-# {{{ sub Rights testing
-
-=head1 Rights testing
-
-
-=begin testing
-
-my $root = RT::User->new($RT::SystemUser);
-$root->Load('root');
-ok($root->Id, "Found the root user");
-my $rootq = RT::Queue->new($root);
-$rootq->Load(1);
-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'.$$);
-
-ok ($id, "Created a new user for acl test $msg");
-
-my $q = RT::Queue->new($new_user);
-$q->Load(1);
-ok($q->Id, "Loaded the first queue");
-
-
-ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
-ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
-ok ($gval, "Grant succeeded - $gmsg");
-
-
-ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
-ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
-ok ($gval, "Revocation succeeded - $gmsg");
-ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
-
-
-
-
-
-# Create a ticket in the queue
-my $new_tick = RT::Ticket->new($RT::SystemUser);
-my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
-ok($tickid, "Created ticket: $tickid");
-# Make sure the user doesn't have the right to modify tickets in the queue
-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'.$$);
-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");
-ok($gv,"Grant succeeed - $gm");
-# Add the user to the group
-ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
-ok ($aid, "Member added to group: $amsg");
-# Make sure the user does have the right to modify tickets in the queue
-ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
-
-
-# Remove the user from the group
-ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
-ok ($did,"Deleted the group member: $dmsg");
-# Make sure the user doesn't have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
-
-
-my $q_as_system = RT::Queue->new($RT::SystemUser);
-$q_as_system->Load(1);
-ok($q_as_system->Id, "Loaded the first queue");
-
-# Create a ticket in the 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");
-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
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
-
-# Create a subgroup
-my $subgroup = RT::Group->new($RT::SystemUser);
-$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);
-ok ($said, "Added the subgroup as a member of the group");
-# Add the user to a subgroup of the group
-
-my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId);
-ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
-# Make sure the user does have the right to modify tickets in the queue
-ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
-
-# {{{ 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);
-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);
-# Test what happens when we disable the group the user is a member of directly
-
-($id, $msg) = $subgroup->SetDisabled(1);
- ok ($id,$msg);
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
- ($id, $msg) = $subgroup->SetDisabled(0);
- ok ($id,$msg);
-ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
-
-# }}}
-
-
-my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId);
-ok($usrid,"removed the user from the group - $usrmsg");
-# Make sure the user doesn't have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
-
-#revoke the right to modify tickets in a queue
-ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
-ok($gv,"revoke succeeed - $gm");
-
-# {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
-
-# Grant queue admin cc the right to modify ticket in the queue
-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");
-ok($qv, "Granted the right successfully - $qm");
-
-# Add the user as a queue admincc
-ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
-ok ($add_id, "the user is now a queue admincc - $add_msg");
-
-# Make sure the user does have the right to modify tickets in the queue
-ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
-# Remove the user from the role group
-ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
-
-# Make sure the user doesn't have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
-
-# }}}
-
-# {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
-
-# Add the user as a ticket admincc
-ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
-ok ($add_id, "the user is now a queue admincc - $add_msg");
-
-# Make sure the user does have the right to modify tickets in the queue
-ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
-
-# Remove the user from the role group
-ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
-
-# Make sure the user doesn't have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
-
-
-# Revoke the right to modify ticket in the queue
-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");
-ok($rqv, "Revoked the right successfully - $rqm");
-
-# }}}
-
-
-
-# {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
-
-# Before we start Make sure the user does not have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
-ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
-
-# Grant queue admin cc the right to modify ticket in the queue
-ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
-ok($qv, "Granted the right successfully - $qm");
-
-# Make sure the user can't modify the ticket before they're added as a watcher
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
-ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
-
-# Add the user as a queue admincc
-ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
-ok ($add_id, "the user is now a queue admincc - $add_msg");
-
-# Make sure the user does have the right to modify tickets in the queue
-ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
-ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
-# Remove the user from the role group
-ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
-
-# Make sure the user doesn't have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
-ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
-
-# }}}
-
-# {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
-
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
-ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
-
-
-# Add the user as a ticket admincc
-ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
-ok ($add_id, "the user is now a queue admincc - $add_msg");
-
-# Make sure the user does have the right to modify tickets in the queue
-ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
-ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
-
-# Remove the user from the role group
-ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
-
-# Make sure the user doesn't have the right to modify tickets in the queue
-ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
-ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
-
-
-# Revoke the right to modify ticket in the queue
-ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
-ok($rqv, "Revoked the right successfully - $rqm");
-
-# }}}
-
-
-
-
-# Grant "privileged users" the system right to create users
-# Create a privileged user.
-# have that user create another user
-# Revoke the right for privileged users to create users
-# have the privileged user try to create another user and fail the ACL check
-
-=end testing
-
-=cut
-
-# }}}
-
-
-# {{{ sub HasRight
-
=head2 HasRight
Shim around PrincipalObj->HasRight. See L<RT::Principal>.
sub CurrentUserCanModify {
my $self = shift;
- my $right = shift;
+ my $field = shift;
if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
return (1);
#If the field is marked as an "administrators only" field,
# don\'t let the user touch it.
- elsif ( $self->_Accessible( $right, 'admin' ) ) {
+ elsif ( $self->_Accessible( $field, 'admin' ) ) {
return (undef);
}
{
my $self = shift;
return undef unless RT->Config->Get('GnuPG')->{'Enable'};
+
+ if ( ($self->CurrentUser->Id != $self->Id ) &&
+ !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
+ return undef;
+ }
+
+
+
my $prefkey = $self->FirstAttribute('PreferredKey');
return $prefkey->Content if $prefkey;
sub PrivateKey {
my $self = shift;
+
+ #If the user wants to see their own values, let them.
+ #If the user is an admin, let them.
+ #Otherwwise, don't let them.
+ #
+ if ( ($self->CurrentUser->Id != $self->Id ) &&
+ !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
+ return undef;
+ }
+
my $key = $self->FirstAttribute('PrivateKey') or return undef;
return $key->Content;
}
sub SetPrivateKey {
my $self = shift;
my $key = shift;
- # XXX: ACL
+
+ unless ($self->CurrentUserCanModify('PrivateKey')) {
+ return (0, $self->loc("Permission Denied"));
+ }
+
unless ( $key ) {
my ($status, $msg) = $self->DeleteAttribute('PrivateKey');
unless ( $status ) {
);
return ($status, $self->loc("Couldn't set private key"))
unless $status;
- return ($status, $self->loc("Unset private key"));
+ return ($status, $self->loc("Set private key"));
}
sub BasicColumns {