view release on metacpan or search on metacpan
{
"abstract" : "A User class: Login credentials, roles, privileges, domains.",
"author" : [
"David Oswald <davido [@at] cpan [d.o.t] org>"
],
"dynamic_config" : 1,
"generated_by" : "ExtUtils::MakeMaker version 6.6302, CPAN::Meta::Converter version 2.120921",
"license" : [
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
---
abstract: 'A User class: Login credentials, roles, privileges, domains.'
author:
- 'David Oswald <davido [@at] cpan [d.o.t] org>'
build_requires:
DBD::SQLite: 0
DBIx::Connector: 0
List::MoreUtils: 0
Test::Exception: 0
Test::More: 0.98
configure_requires:
ExtUtils::MakeMaker: 6.62
Class-User-DBI
A User class: Login credentials and roles.
Through a DBIx::Connector object, this module models a "User" class, with
login credentials, and access roles. Login credentials include a passphrase,
and optionally per user IP whitelisting.
The module is designed to simplify user logins, and basic administrative user
maintenance. Passphrases are salted with a 512 bit random salt (unique per
user) using a cryptographically strong random number generator, and converted
to a SHA2-512 digest before being stored in the database. All subsequent
passphrase validation checks test against the salt and passphrase SHA2 hash.
IP whitelists may be maintained per user. If a user is set to require an IP
check, then the user validates only if his passphrase authenticates AND his
examples/cudbi-example view on Meta::CPAN
print " He never sleeps!\n"
if ! $user->role_privileges->has_privilege( 'rest' );
my $profile = $user->load_profile;
print "He is $profile->{username}!\n";
print "And here is everything you need to know about him:\n";
print Dumper $profile;
my $credentials = $user->get_credentials;
print "And these are his secrets:\n";
print Dumper $credentials;
print "But eventually everyone kicks the bucket... " if $user->delete_user;
print "Loses validity,\n"
if ! $user->validated;
print "And ceases to exist.\n"
if ! $user->exists_user;
lib/Class/User/DBI.pm view on Meta::CPAN
# Quick check whether a userid exists inf the database.
# Return 0 if user doesn't exist. Caches result.
sub exists_user {
my $self = shift;
return 1 if $self->{exists_user};
my $sth = $self->_db_run( $USER_QUERY{SQL_exists_user}, $self->userid );
return defined $sth->fetchrow_array ? 1 : 0;
}
# Fetch user's salt_hex, pass_hex, ip_required, and valid ip's from database.
sub get_credentials {
my $self = shift;
my $sth = $self->_db_run( $USER_QUERY{SQL_get_credentials}, $self->userid );
my ( $salt_hex, $pass_hex, $ip_required ) = $sth->fetchrow_array;
return if not defined $salt_hex; # User wasn't found.
my @valid_ips = $self->get_valid_ips;
return {
userid => $self->userid,
salt_hex => $salt_hex,
pass_hex => $pass_hex,
ip_required => $ip_required,
valid_ips => [@valid_ips],
};
lib/Class/User/DBI.pm view on Meta::CPAN
sub validate {
my ( $self, $password, $ip, $force_revalidate ) = @_;
croak 'Cannot validate without a passphrase.'
if !defined $password || !length $password;
return 0 if !$self->exists_user;
# Save ourselves work if user is already authenticated.
if ( !$force_revalidate && $self->validated ) {
return 1;
}
my $credentials = $self->get_credentials;
my $auth = Authen::Passphrase::SaltedSHA512->new(
salt_hex => $credentials->{salt_hex},
hash_hex => $credentials->{pass_hex}
);
if ( !$auth->match($password) ) {
$self->validated(0);
return 0;
}
# Return 0 if an IP is required, and IP param is not in whitelist,
# or no IP parameter passed.
if ( $credentials->{ip_required} ) {
if ( !defined $ip
|| !any { $ip eq $_ } @{ $credentials->{valid_ips} } )
{
$self->validated(0);
return 0;
}
}
# We passed! Authenticate.
$self->{validated} = 1; # Set in object that we're authenticated.
return 1;
}
lib/Class/User/DBI.pm view on Meta::CPAN
return @rv;
}
sub update_password {
my ( $self, $newpass, $oldpass ) = @_;
return if !$self->exists_user;
# If an old passphrase is supplied, only update if it validates.
if ( defined $oldpass ) {
my $credentials = $self->get_credentials;
my $auth = Authen::Passphrase::SaltedSHA512->new(
salt_hex => $credentials->{salt_hex},
hash_hex => $credentials->{pass_hex}
);
# Return undef if password doesn't authenticate for the user.
return unless $auth->match($oldpass); ## no critic (postfix)
}
my $passgen =
Authen::Passphrase::SaltedSHA512->new( passphrase => $newpass );
my $salt_hex = $passgen->salt_hex;
my $hash_hex = $passgen->hash_hex;
lib/Class/User/DBI.pm view on Meta::CPAN
}
return 1;
}
1;
__END__
=head1 NAME
Class::User::DBI - A User class: Login credentials, roles, privileges, domains.
=head1 VERSION
Version 0.10
=head1 SYNOPSIS
This module models a "User" class, with login credentials, and Roles Based
Access Control. Additionally, IP whitelists may be used as an additional
validation measure. Domain (locality) based access control is also provided
independently of role based access control.
A brief description of authentication: Passphrases are stored as randomly
salted SHA2-512 hashes. Optional whitelisting of IP's is also available.
A brief description of this RBAC implementation: Users have roles and domains
(localities). Roles carry privileges. Roles with privileges, and domains
act independently, allowing for sophisticated access control.
lib/Class/User/DBI.pm view on Meta::CPAN
role => $role,
}
);
my $userid = $user->userid;
my $validated = $user->validated;
my $invalidated = $user->validated(0); # Cancel authentication.
my $is_valid = $user->validate( $pass, $ip ); # Validate including IP.
my $is_valid = $user->validate( $pass ); # Validate without IP.
my $info_href = $user->load_profile;
my $credentials = $user->get_credentials; # Returns a useful hashref.
my @valid_ips = $user->get_valid_ips;
my $ip_required = $user->get_ip_required;
my $success = $user->set_ip_required(1);
my $ exists = $user->exists_user;
my $success = $user->delete_user;
my $del_count = $user->delete_ips( @ips );
my $add_count = $user->add_ips( @ips );
my $success = $user->set_email( 'new@email.address' );
my $success = $user->set_username( 'Cool New User Name' );
my $success = $user->update_password( 'Old Pass', 'New Pass' );
lib/Class/User/DBI.pm view on Meta::CPAN
my $ud = $user->user_domains;
my $has_domain = $user->user_domains->has_domain( 'some_domain' );
=head1 DESCRIPTION
The module is designed to simplify user logins, authentication, role based
access control (authorization), as well as domain (locality) constraint access
control.
It stores user credentials, roles, and basic user information in a database via
a DBIx::Connector database connection.
User passphrases are salted with a 512 bit random salt (unique per user) using
a cryptographically strong random number generator, and converted to a SHA2-512
digest before being stored in the database. All subsequent passphrase
validation checks test against the salt and passphrase SHA2 hash.
IP whitelists may be maintained per user. If a user is set to require an IP
check, then the user validates only if his passphrase authenticates AND his
IP is found in the whitelist associated with his user id.
lib/Class/User/DBI.pm view on Meta::CPAN
Next, use L<Class::User::DBI::Privileges> to set up a list of privileges and
their corresponding descriptions.
Use L<Class::User::DBI::RolePrivileges> to associate one or more privileges with
each role.
Use L<Class::User::DBI::Domains> to create a list of domains (localities), along
with their descriptions.
Use L<Class::User::DBI> (This module) to create a set of users, establish
login credentials such as passphrases and optional IP whitelists, and assign
them roles.
Use L<Class::User::DBI::UserDomains> to associate one or more localities
(domains) with each user.
=head1 USING AN AUTHENTICATION AND ROLES BASED ACCESS CONTROL MODEL
Use L<Class::User::DBI> (This module) to instantiate a user, and validate him
by passphrase and optional whitelist.
lib/Class/User/DBI.pm view on Meta::CPAN
=head2 exists_user
Checks the database to verify that the user exists. As this method is used
internally frequently its B<positive> result is cached to minimize database
queries. Methods that would invalidate the existence of the user in the
database, such as C<< $user->delete_user >> will remove the cache entry, and
subsequent tests will access the database on each call to C<exists_user()>,
until such time that the result flips to positive again.
=head2 get_credentials
my $credentials_href = $user->get_credentials;
my @fields = qw( userid salt_hex pass_hex ip_required );
foreach my $field ( @fields ) {
print "$field => $credentials_href->{$field}\n";
}
my @valid_ips = @{$valid_ips};
foreach my $ip ( @valid_ips ) {
print "Whitelisted IP: $ip\n";
}
Accepts no parameters. Returns a hashref holding a small datastructure that
describes the user's credentials. The structure looks like this:
$href = {
userid => $userid, # The target user's userid.
salt_hex => $salt, # A 128 hex-character representation of
# the user's random salt.
pass_hex => $pass, # A 128 hex-character representation of
# the user's SHA2-512 digested passphrase.
lib/Class/User/DBI.pm view on Meta::CPAN
'127.0.0.1', # Some example whitelisted IP's.
'129.168.0.10',
],
};
A typical usage probably won't require calling this function directly very
often, if at all. In most cases where it would be useful to look at the salt,
the passphrase digest, and IP whitelists, the
C<< $user->validate( $passphrase, $ip ) >> method is easier to use and less
prone to error. But for those cases I haven't considered, the
C<get_credentials()> method exists.
=head2 get_role
my $user_role = $user->get_role;
Returns the user's assigned role. If no role is assigned, returns an empty
string.
=head2 role_privileges
lib/Class/User/DBI.pm view on Meta::CPAN
The C<salt> and C<password> fields are used to store a 128 hex-digit
representation of the 512 bit salt and 512 bit SHA2 hash of the user's
passphrase. More digits is not useful, and less won't store the full salt
and hash.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
Be advised that the the test suite drops its tables after completion, so be sure
to run the test suite only against a database set up explicitly for testing
purposes.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
lib/Class/User/DBI/DB.pm view on Meta::CPAN
use Carp;
our $VERSION = '0.10';
# $VERSION = eval $VERSION; ## no critic (eval)
# ---------------- SQL queries for Class::User::DBI --------------------------
our %USER_QUERY = (
SQL_get_valid_ips => 'SELECT ip FROM user_ips WHERE userid = ?',
SQL_get_credentials => 'SELECT salt, password, ip_required '
. 'FROM users WHERE userid = ?',
SQL_exists_user => 'SELECT userid FROM users WHERE userid = ?',
SQL_load_profile =>
'SELECT userid, username, email, role, ip_required FROM users WHERE userid = ?',
SQL_add_ips => 'INSERT INTO user_ips ( userid, ip ) VALUES( ?, ? )',
SQL_delete_ips => 'DELETE FROM user_ips WHERE userid = ? AND ip = ?',
SQL_add_user => 'INSERT INTO users ( userid, salt, password, ip_required, '
. 'username, email, role ) VALUES( ?, ?, ?, ?, ?, ?, ? )',
SQL_delete_user => 'DELETE FROM users WHERE userid = ?',
SQL_delete_user_ips => 'DELETE FROM user_ips WHERE userid = ?',
lib/Class/User/DBI/DB.pm view on Meta::CPAN
All of the SQL for this entire distribution is found in the
L<Class::User::DBI::DB> module. Any adjustments required to suit your database
engine may be made here. This module's SQL is known to run unaltered with
SQLite and MySQL.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
are successful in using it with other engines, please send me an email detailing
any additional configuration changes you had to make so that I can document
the compatibility, and improve the documentation for the configuration process.
lib/Class/User/DBI/Domains.pm view on Meta::CPAN
Please refer to the C<configure_db()> class method for this module for a
simple means of creating the table that supports this class.
All SQL for this distribution is contained in the L<Class::User::DBI::DB>
module.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
are successful in using it with other engines, please send me an email detailing
any additional configuration changes you had to make so that I can document
the compatibility, and improve the documentation for the configuration process.
lib/Class/User/DBI/Privileges.pm view on Meta::CPAN
simple means of creating the table that supports this class.
All SQL for this distribution is contained in the L<Class::User::DBI::DB>
module.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
are successful in using it with other engines, please send me an email detailing
any additional configuration changes you had to make so that I can document
the compatibility, and improve the documentation for the configuration process.
lib/Class/User/DBI/RolePrivileges.pm view on Meta::CPAN
simple means of creating the table that supports this class.
All SQL for this distribution is contained in the L<Class::User::DBI::DB>
module.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
are successful in using it with other engines, please send me an email detailing
any additional configuration changes you had to make so that I can document
the compatibility, and improve the documentation for the configuration process.
lib/Class/User/DBI/Roles.pm view on Meta::CPAN
simple means of creating the table that supports this class.
All SQL for this distribution is contained in the L<Class::User::DBI::DB>
module.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
are successful in using it with other engines, please send me an email detailing
any additional configuration changes you had to make so that I can document
the compatibility, and improve the documentation for the configuration process.
lib/Class/User/DBI/UserDomains.pm view on Meta::CPAN
simple means of creating the table that supports this class.
All SQL for this distribution is contained in the L<Class::User::DBI::DB>
module.
=head1 DIAGNOSTICS
If you find that your particular database engine is not playing nicely with the
test suite from this module, it may be necessary to provide the database login
credentials for a test database using the same engine that your application
will actually be using. You may do this by setting C<$ENV{CUDBI_TEST_DSN}>,
C<$ENV{CUDBI_TEST_DATABASE}>, C<$ENV{CUDBI_TEST_USER}>,
and C<$ENV{CUDBI_TEST_PASS}>.
Currently the test suite tests against a SQLite database since it's such a
lightweight dependency for the testing. The author also uses this module
with several MySQL databases. As you're configuring your database, providing
its credentials to the tests and running the test scripts will offer really
good diagnostics if some aspect of your database tables proves to be at odds
with what this module needs.
=head1 INCOMPATIBILITIES
This module has only been tested on MySQL and SQLite database engines. If you
are successful in using it with other engines, please send me an email detailing
any additional configuration changes you had to make so that I can document
the compatibility, and improve the documentation for the configuration process.
t/21-cu_dbi.t view on Meta::CPAN
my $test_ip = '192.168.0.198';
my $test_ip2 = '192.168.0.199';
my $appuser_pass = 'Morerugs';
subtest 'Class::User::DBI use and can tests.' => sub {
my $user = use_ok( 'Class::User::DBI', [ $conn, $appuser ] );
can_ok(
'Class::User::DBI', qw(
_db_conn _db_run add_ips add_user
configure_db delete_ips delete_user exists_user
get_credentials get_valid_ips get_role is_role
list_users load_profile new set_role
set_email update_password set_username
userid validate validated
)
);
done_testing();
};
# Prepare the database environment.
# Drop tables if they exist (in case we're testing against a non-memory
t/21-cu_dbi.t view on Meta::CPAN
isa_ok( $user, 'Class::User::DBI', 'new(): ' );
is( $user->userid, $appuser, 'userid(): Returns correct user ID.' );
is( $user->validated, 0,
'validated(): Returns false if user has not been validated yet.' );
isa_ok( $user->_db_conn, 'DBIx::Connector', '_db_conn(): ' );
my $query_handle = $user->_db_run( 'SELECT * FROM users', () );
isa_ok( $query_handle, 'DBI::st', '_db_run(): ' );
my $rv = $user->get_credentials();
is( ref($rv), 'HASH', 'get_credentials(): Returns a hashref.' );
ok( exists( $rv->{valid_ips} ),
'get_credentials(): valid_ips field found.' );
ok( exists( $rv->{ip_required} ),
'get_credentials(): ip_required field found.' );
ok( exists( $rv->{salt_hex} ),
'get_credentials(): salt_hex field found.' );
ok( exists( $rv->{pass_hex} ),
'get_credentials(): pass_hex field found.' );
ok( exists( $rv->{userid} ), 'get_credentials(): userid field found.' );
is( $rv->{userid}, $appuser, 'get_credentials(): Correct userid found.' );
is( ref( $rv->{valid_ips} ),
'ARRAY', 'get_credentials(): valid_ips contains aref.' );
is( $rv->{ip_required} == 0 || $rv->{ip_required} == 1,
1, 'get_credentials(): ip_required is a Boolean value.' );
like( $rv->{salt_hex}, qr/^[[:xdigit:]]{128}$/x,
'get_credentials(): salt_hex has 128 hex digits.' );
like( $rv->{pass_hex}, qr/^[[:xdigit:]]{128}$/x,
'get_credentials(): pass_hex has 128 hex digits.' );
is( scalar( $user->get_valid_ips ),
0, "get_valid_ips(): $appuser has no IP's." );
is( $user->exists_user, 1, "exists_user(): $appuser exists in DB." );
is( $user->validate('wrong pass'),
0, 'validate: Reject incorrect password with 0.' );
is( $user->validated, 0,
'validated(): Flag still false after rejected validation.' );
is( $user->validate($appuser_pass),
1, "validate(): $appuser validates by password." );
t/22-cudbi_working_example.t view on Meta::CPAN
my $profile;
is( ref( $profile = $user->load_profile ),
'HASH', 'load_profile returns a hashref.' );
foreach my $key (qw( username email domains role privileges )) {
ok( exists $profile->{$key}, "$key profile attribute exists." );
}
my $credentials;
is( ref( $credentials = $user->get_credentials ),
'HASH', 'get_credentials returns a hashref.' );
foreach my $key (qw( valid_ips ip_required salt_hex userid pass_hex )) {
ok( exists $credentials->{$key}, "$key credentials attribute exists." );
}
ok( $user->delete_user, 'User deleted.' );
ok( !$user->validated, 'User is no longer valid.' );
ok( !$user->exists_user, 'User no longer exists.' );
done_testing();