You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
444 lines
15 KiB
444 lines
15 KiB
package Anvil::Tools::Account; |
|
# |
|
# This module contains methods used to handle user accounts, logging in and out. |
|
# |
|
|
|
use strict; |
|
use warnings; |
|
use Digest::SHA qw(sha256_base64 sha384_base64 sha512_base64); |
|
use Scalar::Util qw(weaken isweak); |
|
|
|
our $VERSION = "3.0.0"; |
|
my $THIS_FILE = "Account.pm"; |
|
|
|
### Methods; |
|
# encrypt_password |
|
# read_details |
|
# validate_password |
|
|
|
|
|
=pod |
|
|
|
=encoding utf8 |
|
|
|
=head1 NAME |
|
|
|
Anvil::Tools::Account |
|
|
|
Provides all methods related to user management and log in/out features. |
|
|
|
=head1 SYNOPSIS |
|
|
|
use Anvil::Tools; |
|
|
|
# Get a common object handle on all Anvil::Tools modules. |
|
my $anvil = Anvil::Tools->new(); |
|
|
|
# Access to methods using '$anvil->Account->X'. |
|
|
|
=head1 METHODS |
|
|
|
Methods in the core module; |
|
|
|
=cut |
|
|
|
sub new |
|
{ |
|
my $class = shift; |
|
my $self = {}; |
|
|
|
bless $self, $class; |
|
|
|
return ($self); |
|
} |
|
|
|
# Get a handle on the Anvil::Tools object. I know that technically that is a sibling module, but it makes more |
|
# sense in this case to think of it as a parent. |
|
sub parent |
|
{ |
|
my $self = shift; |
|
my $parent = shift; |
|
|
|
$self->{HANDLE}{TOOLS} = $parent if $parent; |
|
|
|
# Defend against memory leads. See Scalar::Util'. |
|
if (not isweak($self->{HANDLE}{TOOLS})) |
|
{ |
|
weaken($self->{HANDLE}{TOOLS});; |
|
} |
|
|
|
return ($self->{HANDLE}{TOOLS}); |
|
} |
|
|
|
|
|
############################################################################################################# |
|
# Public methods # |
|
############################################################################################################# |
|
|
|
=head2 encrypt_password |
|
|
|
This takes a string (a new password from a user), generates a salt, appends the salt to the string and hashes that using C<< sys::password::algorithm >>, the re-hashes the string C<< sys::password::hash_count >> times. The default algorithm is 'sha512' and the default rehashing count is '500,000' times. |
|
|
|
This method returns a hash reference with the following keys; |
|
|
|
* user_password_hash: The final encrypted hash. |
|
* user_salt: The salt created (or used) to generate the hash. |
|
* user_algorithm: The algorithm used to compute the hash. |
|
* user_hash_count: The number of re-encryptions of the initial hash. |
|
|
|
If anything goes wrong, all four keys will have empty strings. |
|
|
|
Parameters |
|
|
|
=head3 password (required) |
|
|
|
This is the password (string) to encrypt. |
|
|
|
=cut |
|
sub encrypt_password |
|
{ |
|
my $self = shift; |
|
my $parameter = shift; |
|
my $anvil = $self->parent; |
|
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; |
|
|
|
my $user_password_hash = defined $parameter->{password} ? $parameter->{password} : ""; |
|
my $user_hash_count = $anvil->data->{sys}{password}{hash_count} =~ /^\d+$/ ? $anvil->data->{sys}{password}{hash_count} : 500000; |
|
my $user_algorithm = $anvil->data->{sys}{password}{algorithm} ? $anvil->data->{sys}{password}{algorithm} : "sha512"; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
password => $anvil->Log->secure ? $user_password_hash : "--", |
|
}}); |
|
|
|
# We'll fill these out below if we succeed. |
|
my $answer = { |
|
user_password_hash => "", |
|
user_salt => "", |
|
user_hash_count => "", |
|
user_algorithm => "", |
|
}; |
|
|
|
# Make sure we got a string |
|
if (not $user_password_hash) |
|
{ |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Account->encrypt_password()", parameter => "password" }}); |
|
return($answer); |
|
} |
|
|
|
# Generate a salt. |
|
my $user_salt = $anvil->Get->_salt; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_salt => $user_salt }}); |
|
|
|
### TODO: Look at using/support bcrypt as the default algorithm. Needed RPMs are already in the el7 AN!Repo. |
|
# We support sha256, sha384 and sha512, possible new ones later. |
|
if ($user_algorithm eq "sha256" ) |
|
{ |
|
$user_password_hash = sha256_base64($user_password_hash.$user_salt); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }}); |
|
|
|
if ($user_hash_count > 0) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_hash_count => $user_hash_count }}); |
|
for (1..$user_hash_count) |
|
{ |
|
$user_password_hash = sha256_base64($user_password_hash); |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }}); |
|
} |
|
} |
|
elsif ($user_algorithm eq "sha384" ) |
|
{ |
|
$user_password_hash = sha384_base64($user_password_hash.$user_salt); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }}); |
|
|
|
if ($user_hash_count > 0) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_hash_count => $user_hash_count }}); |
|
for (1..$user_hash_count) |
|
{ |
|
$user_password_hash = sha384_base64($user_password_hash); |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }}); |
|
} |
|
} |
|
elsif ($user_algorithm eq "sha512" ) |
|
{ |
|
$user_password_hash = sha512_base64($user_password_hash.$user_salt); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }}); |
|
|
|
if ($user_hash_count > 0) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_hash_count => $user_hash_count }}); |
|
for (1..$user_hash_count) |
|
{ |
|
$user_password_hash = sha512_base64($user_password_hash); |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }}); |
|
} |
|
} |
|
else |
|
{ |
|
# Bash algorith. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0171", variables => { user_algorithm => $user_algorithm }}); |
|
return($answer); |
|
} |
|
|
|
$answer = { |
|
user_password_hash => $user_password_hash, |
|
user_salt => $user_salt, |
|
user_hash_count => $user_hash_count, |
|
user_algorithm => $user_algorithm, |
|
}; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
'answer->user_password_hash' => $answer->{user_password_hash}, |
|
'answer->user_salt' => $answer->{user_salt}, |
|
'answer->user_hash_count' => $answer->{user_hash_count}, |
|
'answer->user_algorithm' => $answer->{user_algorithm}, |
|
|
|
}}); |
|
|
|
return($answer); |
|
} |
|
|
|
=head2 read_details |
|
|
|
This method takes a user name and, if the user is found, reads in the details and sets C<< sys::user::<column names> >>. If the user is found, C<< 1 >> is returned. If not, C<< 0 >> is returned. |
|
|
|
Parameters; |
|
|
|
=head3 user_name (required) |
|
|
|
This is the user name being searched for. It is case sensitive. |
|
|
|
=cut |
|
sub read_details |
|
{ |
|
my $self = shift; |
|
my $parameter = shift; |
|
my $anvil = $self->parent; |
|
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; |
|
|
|
my $user_name = defined $parameter->{user_name} ? $parameter->{user_name} : ""; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_name => $user_name }}); |
|
|
|
my $query = " |
|
SELECT |
|
user_uuid, |
|
user_password_hash, |
|
user_salt, |
|
user_algorithm, |
|
user_hash_count, |
|
user_language, |
|
user_is_admin, |
|
user_is_experienced, |
|
user_is_trusted |
|
FROM |
|
users |
|
WHERE |
|
user_name = ".$anvil->data->{sys}{use_db_fh}->quote($user_name)." |
|
;"; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); |
|
|
|
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); |
|
my $count = @{$results}; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
results => $results, |
|
count => $count, |
|
}}); |
|
if (not $count) |
|
{ |
|
# User doesn't exist. |
|
return(0); |
|
} |
|
my $user_uuid = $results->[0]->[0]; |
|
my $user_password_hash = $results->[0]->[1]; |
|
my $user_salt = $results->[0]->[2]; |
|
my $user_algorithm = $results->[0]->[3]; |
|
my $user_hash_count = $results->[0]->[4]; |
|
my $user_language = $results->[0]->[5]; |
|
my $user_is_admin = $results->[0]->[6]; |
|
my $user_is_experienced = $results->[0]->[7]; |
|
my $user_is_trusted = $results->[0]->[8]; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
user_uuid => $user_uuid, |
|
user_password_hash => $user_password_hash, |
|
user_salt => $user_salt, |
|
user_algorithm => $user_algorithm, |
|
user_hash_count => $user_hash_count, |
|
user_language => $user_language, |
|
user_is_admin => $user_is_admin, |
|
user_is_experienced => $user_is_experienced, |
|
user_is_trusted => $user_is_trusted, |
|
}}); |
|
|
|
$anvil->data->{sys}{user}{user_name} = $user_name; |
|
$anvil->data->{sys}{user}{user_uuid} = $user_uuid; |
|
$anvil->data->{sys}{user}{user_password_hash} = $user_password_hash, |
|
$anvil->data->{sys}{user}{user_salt} = $user_salt, |
|
$anvil->data->{sys}{user}{user_algorithm} = $user_algorithm, |
|
$anvil->data->{sys}{user}{user_hash_count} = $user_hash_count, |
|
$anvil->data->{sys}{user}{user_language} = $user_language, |
|
$anvil->data->{sys}{user}{user_is_admin} = $user_is_admin, |
|
$anvil->data->{sys}{user}{user_is_experienced} = $user_is_experienced, |
|
$anvil->data->{sys}{user}{user_is_trusted} = $user_is_trusted, |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
'sys::user::user_name' => $anvil->data->{sys}{user}{user_name}, |
|
'sys::user::user_uuid' => $anvil->data->{sys}{user}{user_uuid}, |
|
'sys::user::user_password_hash' => $anvil->data->{sys}{user}{user_password_hash}, |
|
'sys::user::user_salt' => $anvil->data->{sys}{user}{user_salt}, |
|
'sys::user::user_algorithm' => $anvil->data->{sys}{user}{user_algorithm}, |
|
'sys::user::user_hash_count' => $anvil->data->{sys}{user}{user_hash_count}, |
|
'sys::user::user_language' => $anvil->data->{sys}{user}{user_language}, |
|
'sys::user::user_is_admin' => $anvil->data->{sys}{user}{user_is_admin}, |
|
'sys::user::user_is_experienced' => $anvil->data->{sys}{user}{user_is_experienced}, |
|
'sys::user::user_is_trusted' => $anvil->data->{sys}{user}{user_is_trusted}, |
|
}}); |
|
|
|
return(1); |
|
} |
|
|
|
=head2 validate_password |
|
|
|
This method takes a user name and password and checks to see if the password matches. |
|
|
|
If the password is wrong, or if the user isn't found, C<< 0 >> is returned. If the password matches, C<< 1 >> is returned. |
|
|
|
Parameters; |
|
|
|
=head3 password (required) |
|
|
|
This is the password to test. |
|
|
|
=head3 user (required) |
|
|
|
This is the user whose password we're testing. |
|
|
|
=cut |
|
sub validate_password |
|
{ |
|
my $self = shift; |
|
my $parameter = shift; |
|
my $anvil = $self->parent; |
|
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; |
|
|
|
my $password = defined $parameter->{password} ? $parameter->{password} : ""; |
|
my $user = defined $parameter->{user} ? $parameter->{user} : ""; |
|
my $valid = 0; |
|
my $hash = ""; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
password => $anvil->Log->secure ? $password : "--", |
|
user => $user, |
|
}}); |
|
|
|
if (not $password) |
|
{ |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Account->validate_password()", parameter => "password" }}); |
|
return($valid); |
|
} |
|
if (not $user) |
|
{ |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Account->validate_password()", parameter => "user" }}); |
|
return($valid); |
|
} |
|
|
|
my $query = " |
|
SELECT |
|
user_password_hash, |
|
user_salt, |
|
user_algorithm, |
|
user_hash_count |
|
FROM |
|
users |
|
WHERE |
|
user_name = ".$anvil->data->{sys}{use_db_fh}->quote($user)." |
|
;"; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); |
|
|
|
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); |
|
my $count = @{$results}; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
results => $results, |
|
count => $count, |
|
}}); |
|
|
|
if (not $count) |
|
{ |
|
# User not found. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0172", variables => { user => $user }}); |
|
return($valid); |
|
} |
|
|
|
my $user_password_hash = $results->[0]->[0]; |
|
my $user_salt = $results->[0]->[1]; |
|
my $user_algorithm = $results->[0]->[2]; |
|
my $user_hash_count = $results->[0]->[3]; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { |
|
user_password_hash => $user_password_hash, |
|
user_salt => $user_salt, |
|
user_algorithm => $user_algorithm, |
|
user_hash_count => $user_hash_count, |
|
}}); |
|
|
|
if ($user_algorithm eq "sha256" ) |
|
{ |
|
$hash = sha256_base64($password.$user_salt); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }}); |
|
|
|
if ($user_hash_count > 0) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_hash_count => $user_hash_count }}); |
|
for (1..$user_hash_count) |
|
{ |
|
$hash = sha256_base64($hash); |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }}); |
|
} |
|
} |
|
elsif ($user_algorithm eq "sha384" ) |
|
{ |
|
$hash = sha384_base64($password.$user_salt); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }}); |
|
|
|
if ($user_hash_count > 0) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_hash_count => $user_hash_count }}); |
|
for (1..$user_hash_count) |
|
{ |
|
$hash = sha384_base64($hash); |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }}); |
|
} |
|
} |
|
elsif ($user_algorithm eq "sha512" ) |
|
{ |
|
$hash = sha512_base64($password.$user_salt); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }}); |
|
|
|
if ($user_hash_count > 0) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_hash_count => $user_hash_count }}); |
|
for (1..$user_hash_count) |
|
{ |
|
$hash = sha512_base64($hash); |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }}); |
|
} |
|
} |
|
else |
|
{ |
|
# Bad algorith. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0173", variables => { user_algorithm => $user_algorithm }}); |
|
return($valid); |
|
} |
|
|
|
# Test. |
|
if ($hash eq $user_password_hash) |
|
{ |
|
# Good password. |
|
$valid = 1; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { valid => $valid }}); |
|
} |
|
|
|
return($valid); |
|
} |
|
|
|
1;
|
|
|