* Added Account->read_details to collect data on the given user name.

* Updated Account->encrypt_password to return a hash reference with keys matching to database column names for consistency sake,
* Renamed 'users -> user_password' to 'user_password_hash' for clarity.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 7 years ago
parent 59245a4f0b
commit 6f3537807a
  1. 228
      Anvil/Tools/Account.pm
  2. 54
      Anvil/Tools/Database.pm
  3. 2
      cgi-bin/home
  4. 3
      tools/anvil-change-password
  5. 8
      tools/anvil.sql

@ -13,6 +13,7 @@ my $THIS_FILE = "Account.pm";
### Methods;
# encrypt_password
# read_details
# validate_password
@ -80,10 +81,10 @@ This takes a string (a new password from a user), generates a salt, appends the
This method returns a hash reference with the following keys;
* hash: The final encrypted hash.
* salt: The salt created (or used) to generate the hash.
* algorithm: The algorithm used to compute the hash.
* loops: The number of re-encryptions of the initial hash.
* 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.
@ -93,10 +94,6 @@ Parameters
This is the password (string) to encrypt.
=head3 salt (optional)
If passed, this string will be appended to the password to salt the string. If this is not passed, a random, new hash
=cut
sub encrypt_password
{
@ -105,109 +102,200 @@ sub encrypt_password
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $password = defined $parameter->{password} ? $parameter->{password} : "";
my $salt = defined $parameter->{salt} ? $parameter->{target} : "";
my $hash = "";
my $loops = $anvil->data->{sys}{password}{hash_count} =~ /^\d+$/ ? $anvil->data->{sys}{password}{hash_count} : 500000;
my $algorithm = $anvil->data->{sys}{password}{algorithm} ? $anvil->data->{sys}{password}{algorithm} : "sha512";
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 ? $password : "--",
salt => $salt,
password => $anvil->Log->secure ? $user_password_hash : "--",
}});
# We'll fill these out below if we succeed.
my $answer = {
hash => "",
salt => "",
loops => "",
algorithm => "",
user_password_hash => "",
user_salt => "",
user_hash_count => "",
user_algorithm => "",
};
# Make sure we got a string
if (not $password)
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);
}
# If we weren't passed a salt, generate one node.
if (not $salt)
{
$salt = $anvil->Get->_salt;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { salt => $salt }});
}
# 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 ($algorithm eq "sha256" )
if ($user_algorithm eq "sha256" )
{
$hash = sha256_base64($password.$salt);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
$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 ($loops > 0)
if ($user_hash_count > 0)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { loops => $loops }});
for (1..$loops)
$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);
$user_password_hash = sha256_base64($user_password_hash);
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }});
}
}
elsif ($algorithm eq "sha384" )
elsif ($user_algorithm eq "sha384" )
{
$hash = sha384_base64($password.$salt);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
$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 ($loops > 0)
if ($user_hash_count > 0)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { loops => $loops }});
for (1..$loops)
$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);
$user_password_hash = sha384_base64($user_password_hash);
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user_password_hash => $user_password_hash }});
}
}
elsif ($algorithm eq "sha512" )
elsif ($user_algorithm eq "sha512" )
{
$hash = sha512_base64($password.$salt);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
$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 ($loops > 0)
if ($user_hash_count > 0)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { loops => $loops }});
for (1..$loops)
$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);
$user_password_hash = sha512_base64($user_password_hash);
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $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 => { algorithm => $algorithm }});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0171", variables => { user_algorithm => $user_algorithm }});
return($answer);
}
$answer = {
hash => $hash,
salt => $salt,
loops => $loops,
algorithm => $algorithm,
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->hash' => $answer->{hash},
'answer->salt' => $answer->{salt},
'answer->loops' => $answer->{loops},
'answer->algorithm' => $answer->{algorithm},
'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.
@ -254,7 +342,7 @@ sub validate_password
my $query = "
SELECT
user_password,
user_password_hash,
user_salt,
user_algorithm,
user_hash_count
@ -278,16 +366,16 @@ WHERE
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0172", variables => { user => $user }});
return($valid);
}
my $user_password = $results->[0]->[0];
my $user_salt = $results->[0]->[1];
my $user_algorithm = $results->[0]->[2];
my $user_hash_count = $results->[0]->[3];
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 => $user_password,
user_salt => $user_salt,
user_algorithm => $user_algorithm,
user_hash_count => $user_hash_count,
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" )
@ -343,7 +431,7 @@ WHERE
}
# Test.
if ($hash eq $user_password)
if ($hash eq $user_password_hash)
{
# Good password.
$valid = 1;

@ -2528,19 +2528,19 @@ Is passed, the associated record will be updated.
This is the user's name they type when logging into Striker.
=head3 user_password (required)
=head3 user_password_hash (required)
This is either the B<< hash >> of the user's password, or the raw password. Which it is will be determined by whether C<< user_salt >> is passed in. If it is, C<< user_algorithm >> and C<< user_hash_count >> will also be required. If not, the password will be hashed (and a salt generated) using the default algorithm and hash count.
=head3 user_salt (optional, see 'user_password')
=head3 user_salt (optional, see 'user_password_hash')
This is the random salt used to generate the password hash.
=head3 user_algorithm (optional, see 'user_password')
=head3 user_algorithm (optional, see 'user_password_hash')
This is the algorithm used to create the password hash (with the salt appended to the password).
=head3 user_hash_count (optional, see 'user_password')
=head3 user_hash_count (optional, see 'user_password_hash')
This is how many times the initial hash is re-encrypted.
@ -2572,7 +2572,7 @@ sub insert_or_update_users
my $line = defined $parameter->{line} ? $parameter->{line} : "";
my $user_uuid = defined $parameter->{user_uuid} ? $parameter->{user_uuid} : "";
my $user_name = defined $parameter->{user_name} ? $parameter->{user_name} : "";
my $user_password = defined $parameter->{user_password} ? $parameter->{user_password} : "";
my $user_password_hash = defined $parameter->{user_password_hash} ? $parameter->{user_password_hash} : "";
my $user_salt = defined $parameter->{user_salt} ? $parameter->{user_salt} : "";
my $user_algorithm = defined $parameter->{user_algorithm} ? $parameter->{user_algorithm} : "";
my $user_hash_count = defined $parameter->{user_hash_count} ? $parameter->{user_hash_count} : "";
@ -2586,7 +2586,7 @@ sub insert_or_update_users
line => $line,
user_uuid => $user_uuid,
user_name => $user_name,
user_password => (($anvil->Log->secure) or ($user_salt)) ? $user_password : "--" ,
user_password_hash => (($anvil->Log->secure) or ($user_salt)) ? $user_password_hash : "--" ,
user_salt => $user_salt,
user_algorithm => $user_algorithm,
user_hash_count => $user_hash_count,
@ -2602,10 +2602,10 @@ sub insert_or_update_users
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Database->insert_or_update_users()", parameter => "user_name" }});
return("");
}
if (not $user_password)
if (not $user_password_hash)
{
# Throw an error and exit.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Database->insert_or_update_users()", parameter => "user_password" }});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Database->insert_or_update_users()", parameter => "user_password_hash" }});
return("");
}
@ -2630,17 +2630,23 @@ sub insert_or_update_users
else
{
# No salt given, we'll generate a hash now.
my $answer = $anvil->Account->encrypt_password({password => $user_password});
$user_password = $answer->{hash};
$user_salt = $answer->{salt};
$user_algorithm = $answer->{algorithm};
$user_hash_count = $answer->{loops};
my $answer = $anvil->Account->encrypt_password({password => $user_password_hash});
$user_password_hash = $answer->{user_password_hash};
$user_salt = $answer->{user_salt};
$user_algorithm = $answer->{user_algorithm};
$user_hash_count = $answer->{user_hash_count};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
user_password => (($anvil->Log->secure) or ($user_salt)) ? $user_password : "--" ,
user_salt => $user_salt,
user_algorithm => $user_algorithm,
user_hash_count => $user_hash_count,
user_password_hash => (($anvil->Log->secure) or ($user_salt)) ? $user_password_hash : "--" ,
user_salt => $user_salt,
user_algorithm => $user_algorithm,
user_hash_count => $user_hash_count,
}});
if (not $user_salt)
{
# Something went wrong.
return("");
}
}
# If we don't have a UUID, see if we can find one for the given user server name.
@ -2713,7 +2719,7 @@ INSERT INTO
(
user_uuid,
user_name,
user_password,
user_password_hash,
user_salt,
user_algorithm,
user_hash_count,
@ -2725,7 +2731,7 @@ INSERT INTO
) VALUES (
".$anvil->data->{sys}{use_db_fh}->quote($user_uuid).",
".$anvil->data->{sys}{use_db_fh}->quote($user_name).",
".$anvil->data->{sys}{use_db_fh}->quote($user_password).",
".$anvil->data->{sys}{use_db_fh}->quote($user_password_hash).",
".$anvil->data->{sys}{use_db_fh}->quote($user_salt).",
".$anvil->data->{sys}{use_db_fh}->quote($user_algorithm).",
".$anvil->data->{sys}{use_db_fh}->quote($user_hash_count).",
@ -2746,7 +2752,7 @@ INSERT INTO
my $query = "
SELECT
user_name,
user_password,
user_password_hash,
user_salt,
user_algorithm,
user_hash_count,
@ -2770,7 +2776,7 @@ WHERE
foreach my $row (@{$results})
{
my $old_user_name = $row->[0];
my $old_user_password = $row->[1];
my $old_user_password_hash = $row->[1];
my $old_user_salt = $row->[2];
my $old_user_algorithm = $row->[3];
my $old_user_hash_count = $row->[4];
@ -2780,7 +2786,7 @@ WHERE
my $old_user_is_trusted = $row->[8];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
old_user_name => $old_user_name,
old_user_password => $old_user_password,
old_user_password_hash => $old_user_password_hash,
old_user_salt => $old_user_salt,
old_user_algorithm => $old_user_algorithm,
old_user_hash_count => $old_user_hash_count,
@ -2803,7 +2809,7 @@ WHERE
# Anything change?
if (($old_user_name ne $user_name) or
($old_user_name ne $user_name) or
($old_user_password ne $user_password) or
($old_user_password_hash ne $user_password_hash) or
($old_user_salt ne $user_salt) or
($old_user_algorithm ne $user_algorithm) or
($old_user_hash_count ne $user_hash_count) or
@ -2818,7 +2824,7 @@ UPDATE
users
SET
user_name = ".$anvil->data->{sys}{use_db_fh}->quote($user_name).",
user_password = ".$anvil->data->{sys}{use_db_fh}->quote($user_password).",
user_password_hash = ".$anvil->data->{sys}{use_db_fh}->quote($user_password_hash).",
user_salt = ".$anvil->data->{sys}{use_db_fh}->quote($user_salt).",
user_algorithm = ".$anvil->data->{sys}{use_db_fh}->quote($user_algorithm).",
user_hash_count = ".$anvil->data->{sys}{use_db_fh}->quote($user_hash_count).",

@ -177,7 +177,7 @@ sub check_availability
my $count = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__})->[0]->[0];
$count = 0 if not defined $count;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'say::maintenance' => $anvil->data->{say}{maintenance} }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { count => $count }});
if ($count)
{
# We're waiting for the network configuration

@ -63,7 +63,7 @@ my $password = "Initial2";
# my $user_uuid = $anvil->Database->insert_or_update_users({
# debug => 2,
# user_name => $user,
# user_password => $password,
# user_password_hash => $password,
# user_salt => "",
# user_algorithm => "",
# user_hash_count => "",
@ -72,6 +72,7 @@ my $password = "Initial2";
# user_is_trusted => 1,
# });
# print "User name: [".$user."], UUID: [".$user_uuid."]\n";
# die;
my $valid = $anvil->Account->validate_password({
debug => 2,
user => $user,

@ -43,7 +43,7 @@ $$;
CREATE TABLE users (
user_uuid uuid not null primary key, -- This is the single most important record in Anvil!. Everything links back to here.
user_name text not null,
user_password text not null, -- A user without a password is disabled.
user_password_hash text not null, -- A user without a password is disabled.
user_salt text not null, -- This is used to enhance the security of the user's password.
user_algorithm text not null, -- This is the algorithm used to encrypt the password and salt.
user_hash_count text not null, -- This is the number of times that the password+salt was re-hashed through the algorithm.
@ -59,7 +59,7 @@ CREATE TABLE history.users (
history_id bigserial,
user_uuid uuid,
user_name text,
user_password text,
user_password_hash text,
user_salt text,
user_algorithm text,
user_hash_count text,
@ -80,7 +80,7 @@ BEGIN
INSERT INTO history.users
(user_uuid,
user_name,
user_password,
user_password_hash,
user_salt,
user_algorithm,
user_hash_count,
@ -92,7 +92,7 @@ BEGIN
VALUES
(history_users.user_uuid,
history_users.user_name,
history_users.user_password,
history_users.user_password_hash,
history_users.user_salt,
history_users.user_algorithm,
history_users.user_hash_count,

Loading…
Cancel
Save