/usr/share/perl5/Dancer/Plugin/Auth/Extensible/Provider/Database.pm is in libdancer-plugin-auth-extensible-perl 0.40-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | package Dancer::Plugin::Auth::Extensible::Provider::Database;
use strict;
use warnings;
use base 'Dancer::Plugin::Auth::Extensible::Provider::Base';
use Dancer::Plugin::Database;
use Dancer qw(:syntax);
=head1 NAME
Dancer::Plugin::Auth::Extensible::Database - authenticate via a database
=head1 DESCRIPTION
This class is an authentication provider designed to authenticate users against
a database, using L<Dancer::Plugin::Database> to access a database.
L<Crypt::SaltedHash> is used to handle hashed passwords securely; you wouldn't
want to store plain text passwords now, would you? (If your answer to that is
yes, please reconsider; you really don't want to do that, when it's so easy to
do things right!)
See L<Dancer::Plugin::Database> for how to configure a database connection
appropriately; see the L</CONFIGURATION> section below for how to configure this
authentication provider with database details.
See L<Dancer::Plugin::Auth::Extensible> for details on how to use the
authentication framework, including how to pick a more useful authentication
provider.
=head1 CONFIGURATION
This provider tries to use sensible defaults, so you may not need to provide
much configuration if your database tables look similar to those in the
L</SUGGESTED SCHEMA> section below.
The most basic configuration, assuming defaults for all options, and defining a
single authentication realm named 'users':
plugins:
Auth::Extensible:
realms:
users:
provider: 'Database'
You would still need to have provided suitable database connection details to
L<Dancer::Plugin::Database>, of course; see the docs for that plugin for full
details, but it could be as simple as, e.g.:
plugins:
Auth::Extensible:
realms:
users:
provider: 'Database'
Database:
driver: 'SQLite'
database: 'test.sqlite'
A full example showing all options:
plugins:
Auth::Extensible:
realms:
users:
provider: 'Database'
# optionally set DB connection name to use (see named
# connections in Dancer::Plugin::Database docs)
db_connection_name: 'foo'
# Optionally disable roles support, if you only want to check
# for successful logins but don't need to use role-based access:
disable_roles: 1
# optionally specify names of tables if they're not the defaults
# (defaults are 'users', 'roles' and 'user_roles')
users_table: 'users'
roles_table: 'roles'
user_roles_table: 'user_roles'
# optionally set the column names (see the SUGGESTED SCHEMA
# section below for the default names; if you use them, they'll
# Just Work)
users_id_column: 'id'
users_username_column: 'username'
users_password_column: 'password'
roles_id_column: 'id'
roles_role_column: 'role'
user_roles_user_id_column: 'user_id'
user_roles_role_id_column: 'roles_id'
See the main L<Dancer::Plugin::Auth::Extensible> documentation for how to
configure multiple authentication realms.
=head1 SUGGESTED SCHEMA
If you use a schema similar to the examples provided here, you should need
minimal configuration to get this authentication provider to work for you.
The examples given here should be MySQL-compatible; minimal changes should be
required to use them with other database engines.
=head2 users table
You'll need a table to store user accounts in, of course. A suggestion is
something like:
CREATE TABLE users (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(32) NOT NULL UNIQUE KEY,
password VARCHAR(40) NOT NULL
);
You will quite likely want other fields to store e.g. the user's name, email
address, etc; all columns from the users table will be returned by the
C<logged_in_user> keyword for your convenience.
=head2 roles table
You'll need a table to store a list of available roles in (unless you're not
using roles - in which case, disable role support (see the L</CONFIGURATION>
section).
CREATE TABLE roles (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(32) NOT NULL
);
=head2 user_roles table
Finally, (unless you've disabled role support) you'll need a table to store
user <-> role mappings (i.e. one row for every role a user has; so adding
extra roles to a user consists of adding a new role to this table). It's
entirely up to you whether you use an "id" column in this table; you probably
shouldn't need it.
CREATE TABLE user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
UNIQUE KEY user_role (user_id, role_id)
);
If you're using InnoDB tables rather than the default MyISAM, you could add a
foreign key constraint for better data integrity; see the MySQL documentation
for details, but a table definition using foreign keys could look like:
CREATE TABLE user_roles (
user_id INTEGER, FOREIGN KEY (user_id) REFERENCES users (id),
role_id INTEGER, FOREIGN_KEY (role_id) REFERENCES roles (id),
UNIQUE KEY user_role (user_id, role_id)
) ENGINE=InnoDB;
=cut
sub authenticate_user {
my ($self, $username, $password) = @_;
# Look up the user:
my $user = $self->get_user_details($username);
return unless $user;
# OK, we found a user, let match_password (from our base class) take care of
# working out if the password is correct
my $settings = $self->realm_settings;
my $password_column = $settings->{users_password_column} || 'password';
return $self->match_password($password, $user->{$password_column});
}
# Return details about the user. The user's row in the users table will be
# fetched and all columns returned as a hashref.
sub get_user_details {
my ($self, $username) = @_;
return unless defined $username;
my $settings = $self->realm_settings;
# Get our database handle and find out the table and column names:
my $database = database($settings->{db_connection_name})
or die "No database connection";
my $users_table = $settings->{users_table} || 'users';
my $username_column = $settings->{users_username_column} || 'username';
my $password_column = $settings->{users_password_column} || 'password';
# Look up the user,
my $user = $database->quick_select(
$users_table, { $username_column => $username }
);
if (!$user) {
debug("No such user $username");
return;
} else {
return $user;
}
}
sub get_user_roles {
my ($self, $username) = @_;
my $settings = $self->realm_settings;
# Get our database handle and find out the table and column names:
my $database = database($settings->{db_connection_name});
# Get details of the user first; both to check they exist, and so we have
# their ID to use.
my $user = $self->get_user_details($username)
or return;
# Right, fetch the roles they have. There's currently no support for
# JOINs in Dancer::Plugin::Database, so we'll need to do this query
# ourselves - so we'd better take care to quote the table & column names, as
# we're going to have to interpolate them. (They're coming from our config,
# so should be pretty trustable, but they might conflict with reserved
# identifiers or have unacceptable characters to not be quoted.)
# Because I've tried to be so flexible in allowing the user to configure
# table names, column names, etc, this is going to be fucking ugly.
# Seriously ugly. Clear bag of smashed arseholes territory.
my $roles_table = $database->quote_identifier(
$settings->{roles_table} || 'roles'
);
my $roles_role_id_column = $database->quote_identifier(
$settings->{roles_id_column} || 'id'
);
my $roles_role_column = $database->quote_identifier(
$settings->{roles_role_column} || 'role'
);
my $user_roles_table = $database->quote_identifier(
$settings->{user_roles_table} || 'user_roles'
);
my $user_roles_user_id_column = $database->quote_identifier(
$settings->{user_roles_user_id_column} || 'user_id'
);
my $user_roles_role_id_column = $database->quote_identifier(
$settings->{user_roles_role_id_column} || 'role_id'
);
# Yes, there's SQL interpolation here; yes, it makes me throw up a little.
# However, all the variables used have been quoted appropriately above, so
# although it might look like a camel's arsehole, at least it's safe.
my $sql = <<QUERY;
SELECT $roles_table.$roles_role_column
FROM $user_roles_table
JOIN $roles_table
ON $roles_table.$roles_role_id_column
= $user_roles_table.$user_roles_role_id_column
WHERE $user_roles_table.$user_roles_user_id_column = ?
QUERY
my $sth = $database->prepare($sql)
or die "Failed to prepare query - error: " . $database->err_str;
$sth->execute($user->{$settings->{users_id_column} || 'id'});
my @roles;
while (my($role) = $sth->fetchrow_array) {
push @roles, $role;
}
return \@roles;
# If you read through this, I'm truly, truly sorry. This mess was the price
# of making things so configurable. Send me your address, and I'll send you
# a complementary fork to remove your eyeballs with as way of apology.
# If I can bear to look at this code again, I think I might seriously
# refactor it and use Template::Tiny or something on it. Or Acme::Bleach.
}
1;
|