Refactored authentication/activation/password reset code.
This commit is contained in:
parent
dac55ff239
commit
55b442b761
@ -197,6 +197,27 @@ sub get_user {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ get_user_byactcode($actcode, $onlyreal)
|
||||||
|
# Obtain the user record for the user with the specified activation code, if they
|
||||||
|
# exist. This returns a reference to a hash of user data for the user with the
|
||||||
|
# code, or undef if the code does not correspond to a valid user. If the onlyreal
|
||||||
|
# argument is set, the userid must correspond to 'real' user - bots or inactive
|
||||||
|
# users are not be returned.
|
||||||
|
#
|
||||||
|
# @param actcode The actcode to look for in the user table.
|
||||||
|
# @param onlyreal If true, only users of type 0 or 3 are returned.
|
||||||
|
# @return A reference to a hash containing the user's data, or undef if the user
|
||||||
|
# can not be located (or is not real)
|
||||||
|
sub get_user_byactcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $actcode = shift;
|
||||||
|
my $onlyreal = shift;
|
||||||
|
|
||||||
|
# Return the user record
|
||||||
|
return $self -> _get_user("act_code", $actcode, $onlyreal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ get_user_authmethod($username)
|
## @method $ get_user_authmethod($username)
|
||||||
# Attempt to obtain the auth method id set for the user with the specified
|
# Attempt to obtain the auth method id set for the user with the specified
|
||||||
# username. If the user does not exist, or does not have an authmethod set,
|
# username. If the user does not exist, or does not have an authmethod set,
|
||||||
@ -244,78 +265,6 @@ sub set_user_authmethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ activated($userid)
|
|
||||||
# Determine whether the user account specified has been activated.
|
|
||||||
#
|
|
||||||
# @param userid The ID of the user account to check the activation status of.
|
|
||||||
# @return true if the user has been activated (actually, the unix timestamp of
|
|
||||||
# their activation), 0 if the user has not been activated/does not exist,
|
|
||||||
# or undef on error.
|
|
||||||
sub activated {
|
|
||||||
my $self = shift;
|
|
||||||
my $userid = shift;
|
|
||||||
|
|
||||||
$self -> clear_error();
|
|
||||||
|
|
||||||
my $acth = $self -> {"dbh"} -> prepare("SELECT activated FROM ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
|
||||||
WHERE user_id = ?");
|
|
||||||
$acth -> execute($userid)
|
|
||||||
or return $self -> self_error("Unable to perform user activation check: ". $self -> {"dbh"} -> errstr);
|
|
||||||
|
|
||||||
my $act = $acth -> fetchrow_arrayref();
|
|
||||||
return $act ? $act -> [0] : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
## @method $ activate_user_byid($userid)
|
|
||||||
# Activate the user account with the specified id. This clears the user's
|
|
||||||
# activation code, and sets the activation timestamp.
|
|
||||||
#
|
|
||||||
# @param userid The ID of the user account to activate.
|
|
||||||
# @return true on success, undef on error.
|
|
||||||
sub activate_user_byid {
|
|
||||||
my $self = shift;
|
|
||||||
my $userid = shift;
|
|
||||||
|
|
||||||
$self -> clear_error();
|
|
||||||
|
|
||||||
my $activate = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
|
||||||
SET activated = UNIX_TIMESTAMP(), act_code = NULL
|
|
||||||
WHERE user_id = ?");
|
|
||||||
my $rows = $activate -> execute($userid);
|
|
||||||
return $self -> self_error("Unable to perform user update: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
|
||||||
return $self -> self_error("User update failed, no rows modified - bad userid?") if($rows eq "0E0");
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
## @method $ activate_user($actcode)
|
|
||||||
# Activate the user account with the specified code. This clears the user's
|
|
||||||
# activation code, and sets the activation timestamp.
|
|
||||||
#
|
|
||||||
# @param actcode The activation code to look for and clear.
|
|
||||||
# @return A reference to the user's data on success, undef on error.
|
|
||||||
sub activate_user {
|
|
||||||
my $self = shift;
|
|
||||||
my $actcode = shift;
|
|
||||||
|
|
||||||
$self -> clear_error();
|
|
||||||
|
|
||||||
# Look up a user with the specified code
|
|
||||||
my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
|
||||||
WHERE act_code = ?");
|
|
||||||
$userh -> execute($actcode)
|
|
||||||
or return $self -> self_error("Unable to perform user lookup: ". $self -> {"dbh"} -> errstr);
|
|
||||||
|
|
||||||
my $user = $userh -> fetchrow_hashref()
|
|
||||||
or return $self -> self_error("The specified activation code is not set for any users.");
|
|
||||||
|
|
||||||
# Activate the user, and return their data if successful.
|
|
||||||
return $self -> activate_user_byid($user -> {"user_id"}) ? $user : undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Pre- and Post-auth functions.
|
# Pre- and Post-auth functions.
|
||||||
|
|
||||||
@ -360,33 +309,30 @@ sub pre_authenticate {
|
|||||||
# values for all the fields. If this behaviour is not required or
|
# values for all the fields. If this behaviour is not required or
|
||||||
# desirable, subclasses may wish to override this function completely.
|
# desirable, subclasses may wish to override this function completely.
|
||||||
#
|
#
|
||||||
# @param username The username of the user to perform post-auth tasks on.
|
# @param username The username of the user to perform post-auth tasks on.
|
||||||
# @param password The password the user authenticated with.
|
# @param password The password the user authenticated with.
|
||||||
# @param auth A reference to the auth object calling this.
|
# @param auth A reference to the auth object calling this.
|
||||||
|
# @param authmethod The id of the authmethod to set for the user.
|
||||||
# @return A reference to a hash containing the user's data on success,
|
# @return A reference to a hash containing the user's data on success,
|
||||||
# undef otherwise. If this returns undef, an error message will be
|
# undef otherwise. If this returns undef, an error message will be
|
||||||
# set in the specified auth's errstr field.
|
# set in the specified auth's errstr field.
|
||||||
sub post_authenticate {
|
sub post_authenticate {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $username = shift;
|
my $username = shift;
|
||||||
my $auth = shift;
|
my $auth = shift;
|
||||||
|
my $authmethod = shift;
|
||||||
|
|
||||||
$self -> clear_error();
|
$self -> clear_error();
|
||||||
|
|
||||||
|
# Load the authmethod so that it can be called on if needed
|
||||||
|
my $methodimpl = $auth -> get_authmethod_module($authmethod)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
# Determine whether the user exists. If not, create the user.
|
# Determine whether the user exists. If not, create the user.
|
||||||
my $user = $self -> get_user($username);
|
my $user = $self -> get_user($username);
|
||||||
if(!$user) {
|
|
||||||
# No record for this user, need to make one...
|
|
||||||
my $newuser = $self -> {"dbh"} -> prepare("INSERT INTO ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
|
||||||
(username, created, last_login)
|
|
||||||
VALUES(?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())");
|
|
||||||
$newuser -> execute($username)
|
|
||||||
or $self -> {"logger"} -> die_log($self -> {"cgi"} -> remote_host(), "FATAL: Unable to create new user record: ".$self -> {"dbh"} -> errstr);
|
|
||||||
|
|
||||||
$user = $self -> get_user($username);
|
# No record for this user, need to make one...
|
||||||
}
|
$user = $methodimpl -> create_user($username, $authmethod) or return $auth -> self_error("User addition failed: ".$methodimpl -> errstr())
|
||||||
|
|
||||||
return $auth -> self_error("User addition failed.")
|
|
||||||
if(!$user);
|
if(!$user);
|
||||||
|
|
||||||
# Touch the user's record...
|
# Touch the user's record...
|
||||||
|
252
Webperl/Auth.pm
252
Webperl/Auth.pm
@ -231,7 +231,8 @@ sub valid_user {
|
|||||||
|
|
||||||
# Try the user's set authmethod if possible
|
# Try the user's set authmethod if possible
|
||||||
if($authmethod) {
|
if($authmethod) {
|
||||||
$methodimpl = $self -> {"methods"} -> load_method($authmethod);
|
$methodimpl = $self -> get_authmethod_module($authmethod)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
# Check whether the user can authenticate if the implementation was found
|
# Check whether the user can authenticate if the implementation was found
|
||||||
$valid = $methodimpl -> authenticate($username, $password, $self)
|
$valid = $methodimpl -> authenticate($username, $password, $self)
|
||||||
@ -244,8 +245,8 @@ sub valid_user {
|
|||||||
# methods instead.
|
# methods instead.
|
||||||
if(!$valid && (!$authmethod || !$methodimpl || $self -> {"settings"} -> {"Auth:enable_fallback"})) {
|
if(!$valid && (!$authmethod || !$methodimpl || $self -> {"settings"} -> {"Auth:enable_fallback"})) {
|
||||||
foreach my $trymethod (@{$methods}) {
|
foreach my $trymethod (@{$methods}) {
|
||||||
my $methodimpl = $self -> {"methods"} -> load_method($trymethod)
|
my $methodimpl = $self -> get_authmethod_module($trymethod)
|
||||||
or $self -> {"logger"} -> die_log($self -> {"cgi"} -> remote_host(), "Auth implementation load failed: ".$self -> {"methods"} -> {"errstr"});
|
or return undef;
|
||||||
|
|
||||||
$valid = $methodimpl -> authenticate($username, $password, $self);
|
$valid = $methodimpl -> authenticate($username, $password, $self);
|
||||||
|
|
||||||
@ -260,25 +261,8 @@ sub valid_user {
|
|||||||
# If one of the auth methods succeeded in validating the user, record it
|
# If one of the auth methods succeeded in validating the user, record it
|
||||||
# invoke the app standard post-auth for the user, and return the user's
|
# invoke the app standard post-auth for the user, and return the user's
|
||||||
# database record.
|
# database record.
|
||||||
if($valid) {
|
return $self -> {"app"} -> post_authenticate($username, $password, $self, $authmethod)
|
||||||
# If postauth fails, treat the user as invalid
|
if($valid);
|
||||||
if($self -> {"app"} -> post_authenticate($username, $password, $self)) {
|
|
||||||
$self -> {"app"} -> set_user_authmethod($username, $authmethod);
|
|
||||||
|
|
||||||
my $user = $self -> {"app"} -> get_user($username);
|
|
||||||
|
|
||||||
# Determine whether the user's authentication method requires account activation
|
|
||||||
my $methodimpl = $self -> {"methods"} -> load_method($authmethod);
|
|
||||||
return $self -> self_error("AuthMethod implementation failure during post-auth.") if(!$methodimpl);
|
|
||||||
|
|
||||||
# If it doesn't require activation, and the user isn't active yet, activate them
|
|
||||||
$self -> {"app"} -> activate_user($user -> {"user_id"})
|
|
||||||
unless($methodimpl -> require_activate() || $self -> {"app"} -> activated($user -> {"user_id"}));
|
|
||||||
|
|
||||||
return $self -> {"app"} -> get_user($username);
|
|
||||||
}
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Authentication failed.
|
# Authentication failed.
|
||||||
return $self -> self_error("Invalid username or password specified.");
|
return $self -> self_error("Invalid username or password specified.");
|
||||||
@ -299,11 +283,233 @@ sub get_user_authmethod_module {
|
|||||||
# Does the user have an authmethod set?
|
# Does the user have an authmethod set?
|
||||||
my $authmethod = $self -> {"app"} -> get_user_authmethod($username);
|
my $authmethod = $self -> {"app"} -> get_user_authmethod($username);
|
||||||
|
|
||||||
return $self -> {"methods"} -> load_method($authmethod)
|
return $self -> get_authmethod_module($authmethod)
|
||||||
if($authmethod);
|
if($authmethod);
|
||||||
|
|
||||||
# If the user doesn't have an AuthMethod set, fall back on the base class.
|
# If the user doesn't have an AuthMethod set, fall back on the base class.
|
||||||
return Webperl::AuthMethod -> new();
|
return Webperl::AuthMethod -> new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ get_authmethod_module($moduleid)
|
||||||
|
# A convenience wrapper around calls to AuthMethods::load_method to help reduce
|
||||||
|
# exposure throughout the rest of the system somewhat.
|
||||||
|
#
|
||||||
|
# @param moduleid The ID of the AuthMethod module to load.
|
||||||
|
# @return A reference to an AuthMethod on success, undef on error.
|
||||||
|
sub get_authmethod_module {
|
||||||
|
my $self = shift;
|
||||||
|
my $moduleid = shift;
|
||||||
|
|
||||||
|
$self -> clear_error();
|
||||||
|
|
||||||
|
return $self -> {"methods"} -> load_method($moduleid)
|
||||||
|
or return $self -> self_error("Auth implementation load failed: ".$self -> {"methods"} -> errstr());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# AuthMethod abstraction
|
||||||
|
|
||||||
|
# These functions exist to insulate the rest of the system from the actual
|
||||||
|
# authemthod set for a user, and the implementation of the various user ops.
|
||||||
|
# Direct user access is still supported through AppUser and other modules as
|
||||||
|
# needed, but credential management and checking should be done through
|
||||||
|
# these functions to ensure that auth-specific code doesn't leak.
|
||||||
|
|
||||||
|
# Note that this doesn't cover user creation, as these can not establish
|
||||||
|
# which authmodule to use until the user has been created...
|
||||||
|
|
||||||
|
## @method $ require_activate($username)
|
||||||
|
# Determine whether the user's AuthMethod module requires that user accounts
|
||||||
|
# be activated before they can be used.
|
||||||
|
#
|
||||||
|
# @param username The name of the user to check for authentication requirement.
|
||||||
|
# @return true if the AuthMethod requires activation, false if it does not.
|
||||||
|
sub require_activate {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> require_activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ noactivate_message($username)
|
||||||
|
# Generate a message (or, better yet, a language variable marker) to show to users
|
||||||
|
# who attempt to activate an account that uses an AuthMethod that does not require it.
|
||||||
|
#
|
||||||
|
# @param username The name of the user trying to activate
|
||||||
|
# @return A message to show to the user when redundantly attempting to activate.
|
||||||
|
sub noactivate_message {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> noactivate_message();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ activated($username)
|
||||||
|
# Determine whether the user account specified has been activated.
|
||||||
|
#
|
||||||
|
# @param username The name of the user to check
|
||||||
|
# @return true if the user has been activated (actually, the unix timestamp of
|
||||||
|
# their activation), 0 if the user has not been activated/does not exist,
|
||||||
|
# or undef on error.
|
||||||
|
sub activated {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
my $user = $self -> get_user($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> activated($user -> {"user_id"});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ activate_user($actcode)
|
||||||
|
# Activate the user account with the specified code. This clears the user's
|
||||||
|
# activation code, and sets the activation timestamp.
|
||||||
|
#
|
||||||
|
# @param actcode The activation code to look for and clear.
|
||||||
|
# @return A reference to the user's data on success, undef on error.
|
||||||
|
sub activate_user {
|
||||||
|
my $self = shift;
|
||||||
|
my $actcode = shift;
|
||||||
|
|
||||||
|
$self -> clear_error();
|
||||||
|
|
||||||
|
# Look up a user with the specified code
|
||||||
|
my $user = $self -> {"app"} -> get_user_byactcode($actcode)
|
||||||
|
or return $self -> self_error("The specified activation code is not set for any users.");
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($user -> {"username"})
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
# Activate the user, and return their data if successful.
|
||||||
|
return $methodimpl -> activate_user($user -> {"user_id"});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ supports_recovery($username)
|
||||||
|
# Determine whether the user's AuthMethod allows users to recover their account details
|
||||||
|
# within the system.
|
||||||
|
#
|
||||||
|
# @param username The name of the user to check for recovery support for.
|
||||||
|
# @return True if the AuthMethod supports in-system account recovery, false if it does not.
|
||||||
|
sub supports_recovery {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> supports_recovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ norecover_message($username)
|
||||||
|
# Generate a message to show users who attempt to recover their account using an AuthMethod
|
||||||
|
# that does not support in-system recovery.
|
||||||
|
#
|
||||||
|
# @param username The name of the user to obtain the 'recovery unsupported' message for
|
||||||
|
# @return A message to show to the user attempting an unsupported recovery operation.
|
||||||
|
sub norecover_message {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> norecover_message();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method @ reset_password_actcode($username)
|
||||||
|
# Forcibly reset the user's password and activation code to new random values.
|
||||||
|
#
|
||||||
|
# @param username The username of the user to reset the password and act code for
|
||||||
|
# @return The new password and activation code set for the user, undef on error.
|
||||||
|
sub reset_password_actcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
my $user = $self -> get_user($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> reset_password_actcode($user -> {"user_id"});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ reset_password($username)
|
||||||
|
# Forcibly reset the user's password to a new random value.
|
||||||
|
#
|
||||||
|
# @param username The username of the user to reset the password for
|
||||||
|
# @return The (unencrypted) new password set for the user, undef on error.
|
||||||
|
sub reset_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
my $user = $self -> get_user($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> reset_password($user -> {"user_id"});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ set_password($username, $password)
|
||||||
|
# Set the user's password to the specified value.
|
||||||
|
#
|
||||||
|
# @param username The ID of the user to set the password for
|
||||||
|
# @param password The password to set for the user.
|
||||||
|
# @return True on success, undef on error.
|
||||||
|
sub set_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
my $password = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
my $user = $self -> get_user($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> set_password($user -> {"user_id"}, $password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ generate_actcode($username)
|
||||||
|
# Generate a new activation code for the specified user.
|
||||||
|
#
|
||||||
|
# @param username The username of the user to generate a new act code for.
|
||||||
|
# @return The new activation code for the user
|
||||||
|
sub generate_actcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
|
||||||
|
my $methodimpl = $self -> get_user_authmethod_module($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
my $user = $self -> get_user($username)
|
||||||
|
or return undef;
|
||||||
|
|
||||||
|
return $methodimpl -> generate_actcode($user -> {"user_id"});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -24,33 +24,43 @@
|
|||||||
package Webperl::AuthMethod;
|
package Webperl::AuthMethod;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
|
use base qw(Webperl::SystemModule);
|
||||||
# ============================================================================
|
|
||||||
# Constructor
|
|
||||||
|
|
||||||
## @cmethod $ new(%args)
|
|
||||||
# Construct a new AuthMethod object. This will create a new AuthMethod object
|
|
||||||
# initialised with the provided arguments. All the arguments are copied into
|
|
||||||
# the new object 'as is', with no processing - the caller must make sure they
|
|
||||||
# are sane before calling this.
|
|
||||||
#
|
|
||||||
# @param args A hash of arguments to initialise the AuthMethod object with.
|
|
||||||
# @return A new AuthMethod object.
|
|
||||||
sub new {
|
|
||||||
my $invocant = shift;
|
|
||||||
my $class = ref($invocant) || $invocant;
|
|
||||||
|
|
||||||
my $self = {
|
|
||||||
@_,
|
|
||||||
};
|
|
||||||
|
|
||||||
return bless $self, $class;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Interface code
|
# Interface code
|
||||||
|
|
||||||
|
## @method $ create_user($username, $authmethod)
|
||||||
|
# Create a user account in the database. Note that, unless overridden in a subclass,
|
||||||
|
# this creates a 'stub' user in the database, with minimal information required to
|
||||||
|
# simply get a user ID needed for other areas of the system. If more complete data
|
||||||
|
# should be stored with the user, subclasses need to deal with that. For AuthMethods
|
||||||
|
# that do their authentication against other systems, this user creation function
|
||||||
|
# is sufficient to pass post_auth requirements - however, they may need to perform
|
||||||
|
# additional checks in their AppUser implementation to ensure that required fields
|
||||||
|
# (like email) are populated by the user before they continue.
|
||||||
|
#
|
||||||
|
# @param username The name of the user to create.
|
||||||
|
# @param authmethod The ID of the authmethod to set as the user's default authmethod.
|
||||||
|
# @return A reference to the new user's database entry on success, undef on error.
|
||||||
|
sub create_user {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
my $authmethod = shift;
|
||||||
|
|
||||||
|
$self -> clear_error();
|
||||||
|
|
||||||
|
my $active = !$self -> require_activate();
|
||||||
|
|
||||||
|
my $newuser = $self -> {"dbh"} -> prepare("INSERT INTO ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
(user_auth, activated, username, created, last_login)
|
||||||
|
VALUES(?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())");
|
||||||
|
$newuser -> execute($authmethod, $active, $username)
|
||||||
|
or $self -> self_error("Unable to create new user record: ".$self -> {"dbh"} -> errstr);
|
||||||
|
|
||||||
|
return $self -> get_user($username);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ authenticate($username, $password, $auth)
|
## @method $ authenticate($username, $password, $auth)
|
||||||
# Authenticate a user based on the credentials supplied. This will attempt
|
# Authenticate a user based on the credentials supplied. This will attempt
|
||||||
# to determine whether the user's credentials are valid, and will return
|
# to determine whether the user's credentials are valid, and will return
|
||||||
@ -99,6 +109,36 @@ sub noactivate_message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ activated($userid)
|
||||||
|
# Determine whether the user account specified has been activated.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user account to check the activation status of.
|
||||||
|
# @return true if the user has been activated (actually, the unix timestamp of
|
||||||
|
# their activation), 0 if the user has not been activated/does not exist,
|
||||||
|
# or undef on error.
|
||||||
|
sub activated {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
# By default, users are always active, as activation is not required.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ activate_user($userid)
|
||||||
|
# Activate the user account with the specified id. This clears the user's
|
||||||
|
# activation code, and sets the activation timestamp.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user account to activate.
|
||||||
|
# @return true on success, undef on error.
|
||||||
|
sub activate_user {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
# Activation will always fail if not needed
|
||||||
|
return $self -> self_error("Unsupported activation requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ supports_recovery()
|
## @method $ supports_recovery()
|
||||||
# Determine whether the AuthMethod allows users to recover their account details
|
# Determine whether the AuthMethod allows users to recover their account details
|
||||||
# within the system.
|
# within the system.
|
||||||
@ -123,4 +163,61 @@ sub norecover_message {
|
|||||||
return $self -> {"norecover_message"} || $self -> {"settings"} -> {"config"} -> {"AuthMethod::norecover_message"};
|
return $self -> {"norecover_message"} || $self -> {"settings"} -> {"config"} -> {"AuthMethod::norecover_message"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method @ reset_password_actcode($userid)
|
||||||
|
# Forcibly reset the user's password and activation code to new random values.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to reset the password and act code for
|
||||||
|
# @return The new password and activation code set for the user, undef on error.
|
||||||
|
sub reset_password_actcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
# Do nothing, as by default activation and password change are not supported
|
||||||
|
return $self -> self_error("Unsupported password and activation code change requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ reset_password($userid)
|
||||||
|
# Forcibly reset the user's password to a new random value.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to reset the password for
|
||||||
|
# @return The (unencrypted) new password set for the user, undef on error.
|
||||||
|
sub reset_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
# Do nothing as password changes are not supported
|
||||||
|
return $self -> self_error("Unsupported password change requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ set_password($userid, $password)
|
||||||
|
# Set the user's password to the specified value.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to set the password for
|
||||||
|
# @param password The password to set for the user.
|
||||||
|
# @return True on success, undef on error.
|
||||||
|
sub set_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
# Do nothing as password changes are not supported
|
||||||
|
return $self -> self_error("Unsupported password change requested");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ generate_actcode($userid)
|
||||||
|
# Generate a new activation code for the specified user.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to reset the actcode for
|
||||||
|
# @return The new activation code for the user
|
||||||
|
sub generate_actcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
# do nothing as activation is not required
|
||||||
|
return $self -> self_error("Unsupported activation code change requested");
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -78,6 +78,75 @@ sub new {
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Interface code
|
# Interface code
|
||||||
|
|
||||||
|
## @method $ hash_password($password, $settings)
|
||||||
|
# Generate a salted hash of the supplied password. This will create a 59 character
|
||||||
|
# long string containing the hashed password and its salt suitable for storing in
|
||||||
|
# the database. If the $settings string is not provided, one will be generated.
|
||||||
|
# When creating accounts, $settings will be omitted unless the caller wants to
|
||||||
|
# provide its own salting system. When checking passwords, password should be the
|
||||||
|
# password being checked, and settings should be a hash string previously
|
||||||
|
# generated by this function. The result of this function can then be compared to
|
||||||
|
# the stored hash to determine whether the password is correct.
|
||||||
|
#
|
||||||
|
# @param password The plain-text password to check.
|
||||||
|
# @param settings An optional settings string, leave undefined for new accounts,
|
||||||
|
# set to a previously generated hash string when doing password
|
||||||
|
# validity checking.
|
||||||
|
# @return A bcrypt() generated, 59 character hash containing the settings string
|
||||||
|
# and the hashed, salted password.
|
||||||
|
sub hash_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $password = shift;
|
||||||
|
my $settings = shift || generate_settings($self -> {"bcrypt_cost"});
|
||||||
|
|
||||||
|
return bcrypt($password, $settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method @ create_user($username, $authmethod, $email)
|
||||||
|
# Create a new user, with a randomly generated password and activation code.
|
||||||
|
# This will set the user's name, email, password, activation code, and creation
|
||||||
|
# date, all other fields will be defaults.
|
||||||
|
#
|
||||||
|
# @param username The username of the user to create a record for.
|
||||||
|
# @param authmethod The ID of the authmethod to set as the user's default authmethod.
|
||||||
|
# @param email The email address of the new user.
|
||||||
|
# @return An array of two values: the first is either a reference to the user's
|
||||||
|
# new record data on success, or undef on failure; the second is the
|
||||||
|
# 10 character alphanumeric unencrypted password to send to the user.
|
||||||
|
sub create_user {
|
||||||
|
my $self = shift;
|
||||||
|
my $username = shift;
|
||||||
|
my $authmethod = shift;
|
||||||
|
my $email = shift;
|
||||||
|
|
||||||
|
# Generate some randomness for the authcode and password. These don't need
|
||||||
|
# to be Insanely Secure, so this should be sufficient...
|
||||||
|
my $actcode = join("", map { ("a".."z", "A".."Z", 0..9)[rand 62] } 1..64);
|
||||||
|
my $password = join("", map { ("a".."z", "A".."Z", 0..9)[rand 62] } 1..10);
|
||||||
|
|
||||||
|
# Hash the password using the method AuthMethod::Database uses internally
|
||||||
|
my $cryptpass = Webperl::AuthMethod::Database::hash_password({"bcrypt_cost" => 14}, $password);
|
||||||
|
|
||||||
|
# Do the insert
|
||||||
|
my $userh = $self -> {"dbh"} -> prepare("INSERT INTO ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
(user_auth, username, password, email, created, act_code)
|
||||||
|
VALUES(?, ?, ?, ?, UNIX_TIMESTAMP(), ?)");
|
||||||
|
my $rows = $userh -> execute($authmethod, $username, $cryptpass, $email, $actcode);
|
||||||
|
return $self -> self_error("Unable to perform user insert: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
||||||
|
return $self -> self_error("User insert failed, no rows added.") if($rows eq "0E0");
|
||||||
|
|
||||||
|
# FIXME: This ties to MySQL, but is more reliable that last_insert_id in general.
|
||||||
|
# Try to find a decent solution for this mess...
|
||||||
|
my $userid = $self -> {"dbh"} -> {"mysql_insertid"};
|
||||||
|
return $self -> self_error("Unable to obtain id for user '$username'") if(!$userid);
|
||||||
|
|
||||||
|
my $user = $self -> get_user_byid($userid);
|
||||||
|
|
||||||
|
return ($user, $password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ authenticate($username, $password, $auth)
|
## @method $ authenticate($username, $password, $auth)
|
||||||
# Attempt to authenticate the user against the database. This will check the user's
|
# Attempt to authenticate the user against the database. This will check the user's
|
||||||
# login against the configured database tabke, and return true if the login is valid.
|
# login against the configured database tabke, and return true if the login is valid.
|
||||||
@ -112,31 +181,6 @@ sub authenticate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ hash_password($password, $settings)
|
|
||||||
# Generate a salted hash of the supplied password. This will create a 59 character
|
|
||||||
# long string containing the hashed password and its salt suitable for storing in
|
|
||||||
# the database. If the $settings string is not provided, one will be generated.
|
|
||||||
# When creating accounts, $settings will be omitted unless the caller wants to
|
|
||||||
# provide its own salting system. When checking passwords, password should be the
|
|
||||||
# password being checked, and settings should be a hash string previously
|
|
||||||
# generated by this function. The result of this function can then be compared to
|
|
||||||
# the stored hash to determine whether the password is correct.
|
|
||||||
#
|
|
||||||
# @param password The plain-text password to check.
|
|
||||||
# @param settings An optional settings string, leave undefined for new accounts,
|
|
||||||
# set to a previously generated hash string when doing password
|
|
||||||
# validity checking.
|
|
||||||
# @return A bcrypt() generated, 59 character hash containing the settings string
|
|
||||||
# and the hashed, salted password.
|
|
||||||
sub hash_password {
|
|
||||||
my $self = shift;
|
|
||||||
my $password = shift;
|
|
||||||
my $settings = shift || generate_settings($self -> {"bcrypt_cost"});
|
|
||||||
|
|
||||||
return bcrypt($password, $settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
## @method $ require_activate()
|
## @method $ require_activate()
|
||||||
# Determine whether the AuthMethod module requires that user accounts
|
# Determine whether the AuthMethod module requires that user accounts
|
||||||
# be activated before they can be used.
|
# be activated before they can be used.
|
||||||
@ -149,6 +193,52 @@ sub require_activate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ activated($userid)
|
||||||
|
# Determine whether the user account specified has been activated.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user account to check the activation status of.
|
||||||
|
# @return true if the user has been activated (actually, the unix timestamp of
|
||||||
|
# their activation), 0 if the user has not been activated/does not exist,
|
||||||
|
# or undef on error.
|
||||||
|
sub activated {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
$self -> clear_error();
|
||||||
|
|
||||||
|
my $acth = $self -> {"dbh"} -> prepare("SELECT activated FROM ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
WHERE user_id = ?");
|
||||||
|
$acth -> execute($userid)
|
||||||
|
or return $self -> self_error("Unable to perform user activation check: ". $self -> {"dbh"} -> errstr);
|
||||||
|
|
||||||
|
my $act = $acth -> fetchrow_arrayref();
|
||||||
|
return $act ? $act -> [0] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ activate_user($userid)
|
||||||
|
# Activate the user account with the specified id. This clears the user's
|
||||||
|
# activation code, and sets the activation timestamp.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user account to activate.
|
||||||
|
# @return true on success, undef on error.
|
||||||
|
sub activate_user {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
$self -> clear_error();
|
||||||
|
|
||||||
|
my $activate = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
SET activated = UNIX_TIMESTAMP(), act_code = NULL
|
||||||
|
WHERE user_id = ?");
|
||||||
|
my $rows = $activate -> execute($userid);
|
||||||
|
return $self -> self_error("Unable to perform user update: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
||||||
|
return $self -> self_error("User update failed, no rows modified - bad userid?") if($rows eq "0E0");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
## @method $ supports_recovery()
|
## @method $ supports_recovery()
|
||||||
# Determine whether the AuthMethod allows users to recover their account details
|
# Determine whether the AuthMethod allows users to recover their account details
|
||||||
# within the system.
|
# within the system.
|
||||||
@ -161,6 +251,104 @@ sub supports_recovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method @ reset_password_actcode($userid)
|
||||||
|
# Forcibly reset the user's password and activation code to new random values.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to reset the password and act code for
|
||||||
|
# @return The new password and activation code set for the user
|
||||||
|
sub reset_password_actcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
my $actcode = join("", map { ("a".."z", "A".."Z", 0..9)[rand 62] } 1..64);
|
||||||
|
my $password = join("", map { ("a".."z", "A".."Z", 0..9)[rand 62] } 1..10);
|
||||||
|
|
||||||
|
# Hash the password using the method AuthMethod::Database uses internally
|
||||||
|
my $cryptpass = $self -> hash_password({"bcrypt_cost" => 14}, $password);
|
||||||
|
|
||||||
|
my $reseth = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
SET password = ?, act_code = ?
|
||||||
|
WHERE user_id = ?");
|
||||||
|
my $rows = $reseth -> execute($cryptpass, $actcode, $userid);
|
||||||
|
return $self -> self_error("Unable to perform user update: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
||||||
|
return $self -> self_error("User update failed, no rows changed.") if($rows eq "0E0");
|
||||||
|
|
||||||
|
return ($password, $actcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ reset_password($userid)
|
||||||
|
# Forcibly reset the user's password to a new random value.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to reset the password for
|
||||||
|
# @return The (unencrypted) new password set for the user
|
||||||
|
sub reset_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
my $password = join("", map { ("a".."z", "A".."Z", 0..9)[rand 62] } 1..10);
|
||||||
|
|
||||||
|
# Hash the password using the method AuthMethod::Database uses internally
|
||||||
|
my $cryptpass = $self -> hash_password({"bcrypt_cost" => 14}, $password);
|
||||||
|
|
||||||
|
my $reseth = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
SET password = ?
|
||||||
|
WHERE user_id = ?");
|
||||||
|
my $rows = $reseth -> execute($cryptpass, $userid);
|
||||||
|
return $self -> self_error("Unable to perform user update: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
||||||
|
return $self -> self_error("User update failed, no rows changed.") if($rows eq "0E0");
|
||||||
|
|
||||||
|
return $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ set_password($userid, $password)
|
||||||
|
# Set the user's password to the specified value.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to set the password for
|
||||||
|
# @param password The password to set for the user.
|
||||||
|
# @return True on success, undef on error.
|
||||||
|
sub set_password {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
my $password = shift;
|
||||||
|
|
||||||
|
# Hash the password using the method AuthMethod::Database uses internally
|
||||||
|
my $cryptpass = $self -> hash_password({"bcrypt_cost" => 14}, $password);
|
||||||
|
|
||||||
|
my $reseth = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
SET password = ?
|
||||||
|
WHERE user_id = ?");
|
||||||
|
my $rows = $reseth -> execute($cryptpass, $userid);
|
||||||
|
return $self -> self_error("Unable to perform user update: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
||||||
|
return $self -> self_error("User update failed, no rows changed.") if($rows eq "0E0");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## @method $ generate_actcode($userid)
|
||||||
|
# Generate a new activation code for the specified user.
|
||||||
|
#
|
||||||
|
# @param userid The ID of the user to reset the act code for
|
||||||
|
# @return The new activation code for the user
|
||||||
|
sub generate_actcode {
|
||||||
|
my $self = shift;
|
||||||
|
my $userid = shift;
|
||||||
|
|
||||||
|
my $actcode = join("", map { ("a".."z", "A".."Z", 0..9)[rand 62] } 1..64);
|
||||||
|
|
||||||
|
my $reseth = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
|
||||||
|
SET act_code = ?
|
||||||
|
WHERE user_id = ?");
|
||||||
|
my $rows = $reseth -> execute($actcode, $userid);
|
||||||
|
return $self -> self_error("Unable to perform user update: ". $self -> {"dbh"} -> errstr) if(!$rows);
|
||||||
|
return $self -> self_error("User update failed, no rows changed.") if($rows eq "0E0");
|
||||||
|
|
||||||
|
return $actcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Ghastly internals
|
# Ghastly internals
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ sub load_method {
|
|||||||
my $moduleh = $self -> {"dbh"} -> prepare("SELECT perl_module, enabled FROM ".$self -> {"settings"} -> {"database"} -> {"auth_methods"}."
|
my $moduleh = $self -> {"dbh"} -> prepare("SELECT perl_module, enabled FROM ".$self -> {"settings"} -> {"database"} -> {"auth_methods"}."
|
||||||
WHERE id = ?");
|
WHERE id = ?");
|
||||||
$moduleh -> execute($method_id)
|
$moduleh -> execute($method_id)
|
||||||
or $self -> {"logger"} -> die_log($self -> {"cgi"} -> remote_host(), "Unable to execute auth method lookup query: ".$self -> {"dbh"} -> errstr);
|
or $self -> self_error("Unable to execute auth method lookup query: ".$self -> {"dbh"} -> errstr);
|
||||||
|
|
||||||
my $module = $moduleh -> fetchrow_hashref();
|
my $module = $moduleh -> fetchrow_hashref();
|
||||||
return $self -> self_error("Unknown auth method requested in load_method($method_id)") if(!$module);
|
return $self -> self_error("Unknown auth method requested in load_method($method_id)") if(!$module);
|
||||||
@ -116,7 +116,7 @@ sub load_method {
|
|||||||
my $paramh = $self -> {"dbh"} -> prepare("SELECT name, value FROM ".$self -> {"settings"} -> {"database"} -> {"auth_params"}."
|
my $paramh = $self -> {"dbh"} -> prepare("SELECT name, value FROM ".$self -> {"settings"} -> {"database"} -> {"auth_params"}."
|
||||||
WHERE method_id = ?");
|
WHERE method_id = ?");
|
||||||
$paramh -> execute($method_id)
|
$paramh -> execute($method_id)
|
||||||
or $self -> {"logger"} -> die_log($self -> {"cgi"} -> remote_host(), "Unable to execute auth method parameter query: ".$self -> {"dbh"} -> errstr);
|
or $self -> self_error("Unable to execute auth method parameter query: ".$self -> {"dbh"} -> errstr);
|
||||||
|
|
||||||
# Build up a settings hash using the standard objects, and settings for the
|
# Build up a settings hash using the standard objects, and settings for the
|
||||||
# module loaded from the database.
|
# module loaded from the database.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user