Initial version of AppUser base class added.

This commit is contained in:
Chris 2012-03-16 13:49:52 +00:00
parent 7c0661ddae
commit 9a762ea119

306
AppUser.pm Normal file
View File

@ -0,0 +1,306 @@
## @file
# This file contains the implementation of the base class for application-specific
# user operations.
#
# @author Chris Page <chris@starforge.co.uk>
# @version 1.0
# @date 5 March 2012
# @copy 2012, Chris Page <chris@starforge.co.uk>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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, see <http://www.gnu.org/licenses/>.
## @class
# The base class for application-specific user actions that must be performed
# during authentication. This provides the functions, potentially minimally
# implemented, that the Auth class relies on to interact with user records
# in the wider system in which it is being used. Subclasses of AppUser may
# provide more complex facilities to support application-specific requirements.
#
# This class assumes that the user data is stored in a table whose name is
# set in the 'users' variable in the 'database' section of the settings, and
# that the following fields are present in the table:
#
# * user_id - unsigned int, unique to each user
# * user_type - unsigned tinyint, 0 = normal, 1 = disabled, 3 = admin usually
# * user_auth - unsigned tinyint, the id of the user's auth method, must allow null
# * username - varchar or text, contains the user's username, must be unique per user
# * created - unsigned int, stores the user's creation unix timestamp
# * last_login - unsigned int, stores the user's last login unix timestamp
#
# In general, most subclasses of this class will only really be concerned with
# overriding the post_authenticate() function - the other functions will usually
# be sufficient for most purposes, and system-specific work will usually happen
# in post_authenticate(). However, subclasses may wish to call the function in
# this class via $self -> SUPER::post_authenticate() to extend the default
# behaviour with system-specifics rather than entirely replacing it.
package AppUser;
use strict;
use constant ANONYMOUS_ID => 1; # Default anonymous user id.
use constant ADMIN_TYPE => 3; # User type for admin users.
our $errstr;
BEGIN {
$errstr = '';
}
# ============================================================================
# Constructor
## @cmethod AppUser new(%args)
# Create a new AppUser object. This will create an AppUser object that may be
# passed to the Auth class to provide application-specific user handling. The
# following arguments must be provided:
#
# - cgi, a reference to a CGI object.
# - dbh, a reference to the DBI object to issue database queries through.
# - settings, a reference to the global settings object.
# @param args A hash of arguments to initialise the UserApp object with.
# @return A new AuthMethod object.
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {
@_,
};
# Ensure that we have objects that we need
return set_error("cgi object not set") unless($self -> {"cgi"});
return set_error("dbh object not set") unless($self -> {"dbh"});
return set_error("settings object not set") unless($self -> {"settings"});
return bless $self, $class;
}
# ============================================================================
# Constants access
## @method $ anonymous_user()
# Obtain the ID of the anonymous user in the system.
#
# @return The ID of the anonymous user account.
sub anonymous_user {
return ANONYMOUS_USER;
}
## @method $ adminuser_type()
# Obtain the user type that corresponds to admin users.
#
# @return The admin user type number.
sub adminuser_type {
return ADMIN_TYPE;
}
# ============================================================================
# User access
## @method $ user_disabled($username)
# Determine whether the specified user's account is disabled. This will check
# that the user's type is 0 or 3, and return false if it is not.
#
# @param username The name of the user to check
# @return true if the user's account is disabled, false if the account is active
# or does not exist.
sub user_disabled {
my $self = shift;
my $username = shift;
my $user = $self -> get_user($username);
return ($user && !($user -> {"user_type"} == 0 || $user -> {"user_type"} == 3));
}
## @method $ get_user_byid($userid, $onlyreal)
# Obtain the user record for the specified user, if they exist. This returns a
# reference to a hash of user data corresponding to the specified userid,
# or undef if the userid 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 userid The id of the user to obtain the data for.
# @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_byid {
my $self = shift;
my $userid = shift;
my $onlyreal = shift;
# Return the user record
return $self -> _get_user("user_id", $userid, $onlyreal);
}
## @method $ get_user($username, $onlyreal)
# Obtain the user record for the specified user, if they exist. This returns a
# reference to a hash of user data corresponding to the specified userid,
# or undef if the userid 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 username The username of the user to obtain the data for.
# @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 {
my $self = shift;
my $username = shift;
my $onlyreal = shift;
return $self -> _get_user("username", $username, $onlyreal, 1);
}
## @method $ get_user_authmethod($username)
# 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,
# this returns undef, otherwise it returns the id of the auth method the
# user last logged in using.
#
# @param username The username of the user to fetch the auth method id for.
# @return The auth method id to try to authenticate the user with, or undef.
sub get_user_authmethod {
my $self = shift;
my $username = shift;
my $user = $self -> get_user($username);
return $user -> {"user_auth"} if($user);
return undef;
}
## @method $ set_user_authmethod($username, $methodid)
# Set the auth method id for the specified user. This attempts to update
# the user_auth field for the user with the specified username, it does
# not verify that the methodid corresponds to a valid method, so the
# caller needs to check it. Also note that methodid may be undef, in
# which case the user's auth_method is set to NULL.
#
# @param username The username of the user to update the user_auth field for.
# @param methodid The id of the auth method to set for this user, or undef.
# @return true if the user's user_auth field was updated, false on error.
sub set_user_authmethod {
my $self = shift;
my $username = shift;
my $methodid = shift;
my $seth = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
SET user_auth = ?
WHERE username LIKE ?");
my $result = $seth -> execute($methodid, $username)
or die_log($self -> {"cgi"} -> remote_host(), "Unable to execute user auth update query. Error was: ".$self -> {"dbh"} -> errstr);
$self -> {"lasterr"} = "Unable to update user auth method, unkown user selected"
if($result != 1);
return ($result == 1);
}
# ============================================================================
# Post-auth functions.
## @method $ post_authenticate($username)
# Perform any system-specific post-authentication tasks on the specified
# user's data. This function allows each system to tailor post-auth tasks
# to the requirements of the system.
#
# @note The implementation provided here will create an empty user record
# if one with the specified username does not already exist. The
# user is initialised as a type 0 ('normal') user, with default
# values for all the fields. If this behaviour is not required or
# desirable, subclasses may wish to override this function completely.
#
# @param username The username of the user to update the user_auth field for.
# @return An empty string on success, otherwise an error message.
sub post_authenticate {
my $self = shift;
my $username = shift;
# Determine whether the user exists. If not, create the user.
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 die_log($self -> {"cgi"} -> remote_host(), "FATAL: Unable to create new user record: ".$self -> {"dbh"} -> errstr);
$user = $self -> get_user($username);
}
return "User addition failed" if(!$user);
# Touch the user's record...
my $pokeh = $self -> {"dbh"} -> prepare("UPDATE ".$self -> {"settings"} -> {"database"} -> {"users"}."
SET last_login = UNIX_TIMESTAMP()
WHERE user_id = ?");
$pokeh -> execute($user -> {"user_id"})
or die_log($self -> {"cgi"} -> remote_host(), "FATAL: Unable to update user record: ".$self -> {"dbh"} -> errstr);
# All done...
return '';
}
# ============================================================================
# Internal functions
## @method private $ _get_user($field, $value, $onlyreal)
# Internal implementation of the get_user facility. This allows users to be
# searched for on any given user field, and if the user is found it returns
# the user's data, undef otherwise. If the onlyreal argument is set, the user
# must correspond to 'real' user - bots or inactive users are not be returned.
#
# @param field The name of the column to search for users on. If the column
# contains values that are not unique per user, only the first
# match is returned (ie: don't search on non-unique columns.)
# @param value The value to search for in the user table.
# @param onlyreal If true, only users of type 0 or 3 are returned.
# @param uselike If true the search uses 'LIKE' instead of exact comparison.
# @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 {
my $self = shift;
my $field = shift;
my $value = shift;
my $onlyreal = shift;
my $uselike = shift;
my $userh = $self -> {"dbh"} -> prepare("SELECT * FROM ".$self -> {"settings"} -> {"database"} -> {"users"}."
WHERE $field ".($uselike ? "LIKE" : "=")." ?".
($onlyreal ? " AND user_type IN (0,3)" : ""));
$userh -> execute($value)
or die_log($self -> {"cgi"} -> remote_host(), "Unable to execute user lookup query. Error was: ".$self -> {"dbh"} -> errstr);
return $userh -> fetchrow_hashref();
}
# ============================================================================
# Error functions
sub get_error { return $errstr; }
sub set_error { $errstr = shift; return undef; }
1;