Dancer-Plugin-Auth-Extensible-0.30/ 0000755 0001750 0001750 00000000000 12217541515 016366 5 ustar davidp davidp Dancer-Plugin-Auth-Extensible-0.30/README 0000644 0001750 0001750 00000026004 12217535153 017251 0 ustar davidp davidp NAME
Dancer::Plugin::Auth::Extensible - extensible authentication framework
for Dancer apps
DESCRIPTION
A user authentication and authorisation framework plugin for Dancer
apps.
Makes it easy to require a user to be logged in to access certain
routes, provides role-based access control, and supports various
authentication methods/sources (config file, database, Unix system
users, etc).
Designed to support multiple authentication realms and to be as
extensible as possible, and to make secure password handling easy (the
base class for auth providers makes handling `RFC2307'-style hashed
passwords really simple, so you have no excuse for storing plain-text
passwords).
SYNOPSIS
Configure the plugin to use the authentication provider class you wish
to use:
plugins:
Auth::Extensible:
realms:
users:
provider: Example
....
The configuration you provide will depend on the authentication provider
module in use. For a simple example, see
Dancer::Plugin::Auth::Extensible::Provider::Config.
Define that a user must be logged in and have the proper permissions to
access a route:
get '/secret' => require_role Confidant => sub { tell_secrets(); };
Define that a user must be logged in to access a route - and find out
who is logged in with the `logged_in_user' keyword:
get '/users' => require_login sub {
my $user = logged_in_user;
return "Hi there, $user->{username}";
};
AUTHENTICATION PROVIDERS
For flexibility, this authentication framework uses simple
authentication provider classes, which implement a simple interface and
do whatever is required to authenticate a user against the chosen source
of authentication.
For an example of how simple provider classes are, so you can build your
own if required or just try out this authentication framework plugin
easily, see Dancer::Plugin::Auth::Extensible::Provider::Example.
This framework supplies the following providers out-of-the-box:
Dancer::Plugin::Auth::Extensible::Provider::Unix
Authenticates users using system accounts on Linux/Unix type boxes
Dancer::Plugin::Auth::Extensible::Provider::Database
Authenticates users stored in a database table
Dancer::Plugin::Auth::Extensible::Provider::Config
Authenticates users stored in the app's config
Need to write your own? Just subclass
Dancer::Plugin::Auth::Extensible::Provider::Base and implement the
required methods, and you're good to go!
CONTROLLING ACCESS TO ROUTES
Keywords are provided to check if a user is logged in / has appropriate
roles.
require_login - require the user to be logged in
get '/dashboard' => require_login sub { .... };
If the user is not logged in, they will be redirected to the login
page URL to log in. The default URL is `/login' - this may be
changed with the `login_url' option.
require_role - require the user to have a specified role
get '/beer' => require_role BeerDrinker => sub { ... };
Requires that the user be logged in as a user who has the specified
role. If the user is not logged in, they will be redirected to the
login page URL. If they are logged in, but do not have the required
role, they will be redirected to the access denied URL.
require_any_roles - require the user to have one of a list of roles
get '/drink' => require_any_role [qw(BeerDrinker VodaDrinker)] => sub {
...
};
Requires that the user be logged in as a user who has any one (or
more) of the roles listed. If the user is not logged in, they will
be redirected to the login page URL. If they are logged in, but do
not have any of the specified roles, they will be redirected to the
access denied URL.
require_all_roles - require the user to have all roles listed
get '/foo' => require_all_roles [qw(Foo Bar)] => sub { ... };
Requires that the user be logged in as a user who has all of the
roles listed. If the user is not logged in, they will be redirected
to the login page URL. If they are logged in but do not have all of
the specified roles, they will be redirected to the access denied
URL.
Replacing the Default ` /login ' and ` /login/denied ' Routes
By default, the plugin adds a route to present a simple login form at
that URL. If you would rather add your own, set the `no_default_pages'
setting to a true value, and define your own route which responds to
`/login' with a login page.
If the user is logged in, but tries to access a route which requires a
specific role they don't have, they will be redirected to the
"permission denied" page URL, which defaults to `/login/denied' but may
be changed using the `denied_page' option.
Again, by default a route is added to respond to that URL with a default
page; again, you can disable this by setting `no_default_pages' and
creating your own.
This would still leave the routes `post '/login'' and `any '/logout''
routes in place. To disable them too, set the option `no_login_handler'
to a true value. In this case, these routes should be defined by the
user, and should do at least the following:
post '/login' => sub {
my ($success, $realm) = authenticate_user(
params->{username}, params->{password}
);
if ($success) {
session logged_in_user => params->{username};
session logged_in_user_realm => $realm;
# other code here
} else {
# authentication failed
}
};
any '/logout' => sub {
session->destroy;
};
If you want to use the default `post '/login'' and `any '/logout''
routes you can configure them. See below.
Keywords
require_login
Used to wrap a route which requires a user to be logged in order to
access it.
get '/secret' => require_login sub { .... };
require_role
Used to wrap a route which requires a user to be logged in as a user
with the specified role in order to access it.
get '/beer' => require_role BeerDrinker => sub { ... };
You can also provide a regular expression, if you need to match the
role using a regex - for example:
get '/beer' => require_role qr/Drinker$/ => sub { ... };
require_any_role
Used to wrap a route which requires a user to be logged in as a user
with any one (or more) of the specified roles in order to access it.
get '/foo' => require_any_role [qw(Foo Bar)] => sub { ... };
require_all_roles
Used to wrap a route which requires a user to be logged in as a user
with all of the roles listed in order to access it.
get '/foo' => require_all_roles [qw(Foo Bar)] => sub { ... };
logged_in_user
Returns a hashref of details of the currently logged-in user, if
there is one.
The details you get back will depend upon the authentication
provider in use.
user_has_role
Check if a user has the role named.
By default, the currently-logged-in user will be checked, so you
need only name the role you're looking for:
if (user_has_role('BeerDrinker')) { pour_beer(); }
You can also provide the username to check;
if (user_has_role($user, $role)) { .... }
user_roles
Returns a list of the roles of a user.
By default, roles for the currently-logged-in user will be checked;
alternatively, you may supply a username to check.
Returns a list or arrayref depending on context.
authenticate_user
Usually you'll want to let the built-in login handling code deal
with authenticating users, but in case you need to do it yourself,
this keyword accepts a username and password, and optionally a
specific realm, and checks whether the username and password are
valid.
For example:
if (authenticate_user($username, $password)) {
...
}
If you are using multiple authentication realms, by default each
realm will be consulted in turn. If you only wish to check one of
them (for instance, you're authenticating an admin user, and there's
only one realm which applies to them), you can supply the realm as
an optional third parameter.
In boolean context, returns simply true or false; in list context,
returns `($success, $realm)'.
SAMPLE CONFIGURATION
In your application's configuation file:
session: simple
plugins:
Auth::Extensible:
# Set to 1 if you want to disable the use of roles (0 is default)
disable_roles: 0
# After /login: If no return_url is given: land here ('/' is default)
user_home_page: '/user'
# After /logout: If no return_url is given: land here (no default)
exit_page: '/'
# List each authentication realm, with the provider to use and the
# provider-specific settings (see the documentation for the provider
# you wish to use)
realms:
realm_one:
provider: Database
db_connection_name: 'foo'
Please note that you must have a session provider configured. The
authentication framework requires sessions in order to track information
about the currently logged in user. Please see Dancer::Session for
information on how to configure session management within your
application.
BUGS / FEATURE REQUESTS
This is an early version; there may still be bugs present or features
missing.
This is developed on GitHub - please feel free to raise issues or pull
requests against the repo at:
https://github.com/bigpresh/Dancer-Plugin-Auth-Extensible
ACKNOWLEDGEMENTS
Valuable feedback on the early design of this module came from many
people, including Matt S Trout (mst), David Golden (xdg), Damien
Krotkine (dams), Daniel Perrett, and others.
Configurable login/logout URLs added by Rene (hertell)
Regex support for require_role by chenryn
Support for user_roles looking in other realms by Colin Ewen (casao)
LDAP provider added by Mark Meyer (ofosos)
Config options for default login/logout handlers by Henk van Oers
(hvoers)
LICENSE AND COPYRIGHT
Copyright 2012-13 David Precious.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
Dancer-Plugin-Auth-Extensible-0.30/t/ 0000755 0001750 0001750 00000000000 12217541515 016631 5 ustar davidp davidp Dancer-Plugin-Auth-Extensible-0.30/t/00-load.t 0000644 0001750 0001750 00000000347 12021514014 020141 0 ustar davidp davidp #!perl -T
use Test::More tests => 1;
BEGIN {
use_ok( 'Dancer::Plugin::Auth::Extensible' ) || print "Bail out!
";
}
diag( "Testing Dancer::Plugin::Auth::Extensible $Dancer::Plugin::Auth::Extensible::VERSION, Perl $], $^X" );
Dancer-Plugin-Auth-Extensible-0.30/t/pod.t 0000644 0001750 0001750 00000000350 12021514014 017561 0 ustar davidp davidp #!perl -T
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod
my $min_tp = 1.22;
eval "use Test::Pod $min_tp";
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
all_pod_files_ok();
Dancer-Plugin-Auth-Extensible-0.30/t/lib/ 0000755 0001750 0001750 00000000000 12217541515 017377 5 ustar davidp davidp Dancer-Plugin-Auth-Extensible-0.30/t/lib/TestApp.pm 0000644 0001750 0001750 00000002634 12200232763 021314 0 ustar davidp davidp package t::lib::TestApp;
use Dancer;
set session => 'simple';
set plugins => { 'Auth::Extensible' => { provider => 'Example' } };
use Dancer::Plugin::Auth::Extensible;
no warnings 'uninitialized';
get '/' => sub {
"Index always accessible";
};
get '/loggedin' => require_login sub {
"You are logged in";
};
get '/name' => require_login sub {
return "Hello, " . logged_in_user->{name};
};
get '/roles' => require_login sub {
return join ',', sort @{ user_roles() };
};
get '/roles/:user' => require_login sub {
my $user = param 'user';
return join ',', sort @{ user_roles($user) };
};
get '/roles/:user/:realm' => require_login sub {
my $user = param 'user';
my $realm = param 'realm';
return join ',', sort @{ user_roles($user, $realm) };
};
get '/realm' => require_login sub {
return session->{logged_in_user_realm};
};
get '/beer' => require_role BeerDrinker => sub {
"You can have a beer";
};
get '/piss' => require_role BearGrylls => sub {
"You can drink piss";
};
get '/piss/regex' => require_role qr/beer/i => sub {
"You can drink piss now";
};
get '/anyrole' => require_any_role ['Foo','BeerDrinker'] => sub {
"Matching one of multiple roles works";
};
get '/allroles' => require_all_roles ['BeerDrinker', 'Motorcyclist'] => sub {
"Matching multiple required roles works";
};
get qr{/regex/(.+)} => require_login sub {
return "Matched";
};
1;
Dancer-Plugin-Auth-Extensible-0.30/t/lib/views/ 0000755 0001750 0001750 00000000000 12217541515 020534 5 ustar davidp davidp Dancer-Plugin-Auth-Extensible-0.30/t/lib/views/.placeholder 0000644 0001750 0001750 00000000522 12022467635 023022 0 ustar davidp davidp This directory is empty on purpose, and this file is here solely so that git
tracks it, as git won't track empty directories (mostly because it doesn't track
directories at all, only the files in them).
This otherwise-empty directory is needed to avoid a strange bug on Windows
systems where Cwd::realpath() croaks if they don't exist.
Dancer-Plugin-Auth-Extensible-0.30/t/lib/public/ 0000755 0001750 0001750 00000000000 12217541515 020655 5 ustar davidp davidp Dancer-Plugin-Auth-Extensible-0.30/t/lib/public/.placeholder 0000644 0001750 0001750 00000000522 12022467635 023143 0 ustar davidp davidp This directory is empty on purpose, and this file is here solely so that git
tracks it, as git won't track empty directories (mostly because it doesn't track
directories at all, only the files in them).
This otherwise-empty directory is needed to avoid a strange bug on Windows
systems where Cwd::realpath() croaks if they don't exist.
Dancer-Plugin-Auth-Extensible-0.30/t/lib/lib/ 0000755 0001750 0001750 00000000000 12217541515 020145 5 ustar davidp davidp Dancer-Plugin-Auth-Extensible-0.30/t/lib/lib/.placeholder 0000644 0001750 0001750 00000000522 12022467635 022433 0 ustar davidp davidp This directory is empty on purpose, and this file is here solely so that git
tracks it, as git won't track empty directories (mostly because it doesn't track
directories at all, only the files in them).
This otherwise-empty directory is needed to avoid a strange bug on Windows
systems where Cwd::realpath() croaks if they don't exist.
Dancer-Plugin-Auth-Extensible-0.30/t/lib/config.yml 0000644 0001750 0001750 00000001507 12054743542 021375 0 ustar davidp davidp session: simple
plugins:
Auth::Extensible:
realms:
config1:
provider: Config
users:
- user: dave
pass: beer
name: "David Precious"
roles:
- BeerDrinker
- Motorcyclist
- user: bob
pass: cider
name: "Bob Smith"
roles:
- CiderDrinker
config2:
provider: Config
users:
- user: burt
pass: bacharach
- user: hashedpassword
pass: "{SSHA}+2u1HpOU7ak6iBR6JlpICpAUvSpA/zBM"
logger: console
log: core
show_errors: 1
Dancer-Plugin-Auth-Extensible-0.30/t/01-basic.t 0000644 0001750 0001750 00000012676 12217541221 020323 0 ustar davidp davidp use strict;
use warnings;
use Test::More import => ['!pass'];
use t::lib::TestApp;
use Dancer ':syntax';
my $dancer_version;
BEGIN {
$dancer_version = (exists &dancer_version) ? int(dancer_version()) : 1;
require Dancer::Test;
if ($dancer_version == 1) {
Dancer::Test->import();
} else {
Dancer::Test->import('t::lib::TestApp');
}
}
diag sprintf "Testing DPAE version %s under Dancer %s",
$Dancer::Plugin::Auth::Extensible::VERSION,
$Dancer::VERSION;
# First, without being logged in, check we can access the index page, but not
# stuff we need to be logged in for:
response_content_is [ GET => '/' ], 'Index always accessible',
'Index accessible while not logged in';
response_redirect_location_is [ GET => '/loggedin' ],
'http://localhost/login?return_url=%2Floggedin',
'/loggedin redirected to login page when not logged in';
response_redirect_location_is [ GET => '/beer' ],
'http://localhost/login?return_url=%2Fbeer',
'/beer redirected to login page when not logged in';
response_redirect_location_is [ GET => '/regex/a' ],
'http://localhost/login?return_url=%2Fregex%2Fa',
'/regex/a redirected to login page when not logged in';
# OK, now check we can't log in with fake details
response_status_is [
POST => '/login', { body => { username => 'foo', password => 'bar' } }
], 401, 'Login with fake details fails';
# ... and that we can log in with real details
response_status_is [
POST => '/login', { body => { username => 'dave', password => 'beer' } }
], 302, 'Login with real details succeeds';
# Now we're logged in, check we can access stuff we should...
response_status_is [ GET => '/loggedin' ], 200,
'Can access /loggedin now we are logged in';
response_content_is [ GET => '/loggedin' ], 'You are logged in',
'Correct page content while logged in, too';;
response_content_is [ GET => '/name' ], 'Hello, David Precious',
'Logged in user details via logged_in_user work';
response_content_is [ GET => '/roles' ], 'BeerDrinker,Motorcyclist',
'Correct roles for logged in user';
response_content_is [ GET => '/roles/bob'], 'CiderDrinker', 'Correct roles for other user in current realm';
# Check we can request something which requires a role we have....
response_status_is [ GET => '/beer' ], 200,
'We can request a route (/beer) requiring a role we have...';
# Check we can request a route that requires any of a list of roles, one of
# which we have:
response_status_is [ GET => '/anyrole' ], 200,
"We can request a multi-role route requiring with any one role";
response_status_is [ GET => '/allroles' ], 200,
"We can request a multi-role route with all roles required";
# And also a route declared as a regex (this should be no different, but
# melmothX was seeing issues with routes not requiring login when they should...
response_status_is [ GET => '/regex/a' ], 200,
"We can request a regex route when logged in";
response_status_is [ GET => '/piss/regex' ], 200,
"We can request a route requiring a regex role we have";
# ... but can't request something requiring a role we don't have
response_redirect_location_is [ GET => '/piss' ],
'http://localhost/login/denied?return_url=%2Fpiss',
"We cannot request a route requiring a role we don't have";
# Check the realm we authenticated against is what we expect
response_content_is [ GET => '/realm' ], 'config1',
'Authenticated against expected realm';
# Now, log out
response_status_is [
POST => '/logout', {},
], 200, 'Logging out returns 200';
# Check we can't access protected pages now we logged out:
response_redirect_location_is [ GET => '/loggedin' ],
'http://localhost/login?return_url=%2Floggedin',
'/loggedin redirected to login page after logging out';
response_redirect_location_is [ GET => '/beer' ],
'http://localhost/login?return_url=%2Fbeer',
'/beer redirected to login page after logging out';
# OK, log back in, this time as a user from the second realm
response_status_is [
POST => '/login', { body => { username => 'burt', password => 'bacharach' } }
], 302, 'Login as user from second realm succeeds';
# And that now we're logged in again, we can access protected pages
response_status_is [ GET => '/loggedin' ], 200,
'Can access /loggedin now we are logged in again';
# And that the realm we authenticated against is what we expect
response_content_is [ GET => '/realm' ], 'config2',
'Authenticated against expected realm';
response_content_is [ GET => '/roles/bob/config1'], 'CiderDrinker', 'Correct roles for other user in current realm';
# Now, log out again
response_status_is [
POST => '/logout', {},
], 200, 'Logged out again';
# Now check we can log in as a user whose password is stored hashed:
response_status_is [
POST => '/login', {
body => { username => 'hashedpassword', password => 'password' }
}
], 302, 'Login as user with hashed password succeeds';
# And that now we're logged in again, we can access protected pages
response_status_is [ GET => '/loggedin' ], 200,
'Can access /loggedin now we are logged in again';
# Check that the redirect URL can be set when logging in
response_redirect_location_is(
[
POST => '/login', {
body => {
username => 'dave',
password => 'beer',
return_url => '/foobar',
},
},
],
'http://localhost/foobar',
'Redirect after login to given return_url works',
);
done_testing();
Dancer-Plugin-Auth-Extensible-0.30/t/manifest.t 0000644 0001750 0001750 00000000420 12021514014 020603 0 ustar davidp davidp #!perl -T
use strict;
use warnings;
use Test::More;
unless ( $ENV{RELEASE_TESTING} ) {
plan( skip_all => "Author tests not required for installation" );
}
eval "use Test::CheckManifest 0.9";
plan skip_all => "Test::CheckManifest 0.9 required" if $@;
ok_manifest();
Dancer-Plugin-Auth-Extensible-0.30/META.yml 0000644 0001750 0001750 00000001671 12217541515 017644 0 ustar davidp davidp --- #YAML:1.0
name: Dancer-Plugin-Auth-Extensible
version: 0.30
abstract: extensible authentication framework for Dancer apps
author:
- David Precious
Sorry, you're not allowed to access that page.
LOGIN FAILED
You need to log in to continue.
Hi there, $user->{name}! Why not log in?Non-secret home page!
";
if (my $user = logged_in_user()) {
$content .= "
You can drink beer
"; } if (user_has_role('WineDrinker')) { $content .= "You can drink wine
"; } return $content; }; get '/secret' => require_login sub { "Only logged-in users can see this" }; get '/beer' => require_any_role [qw(BeerDrinker HardDrinker)], sub { "Any drinker can get beer."; }; get '/vodka' => require_role HardDrinker => sub { "Only hard drinkers get vodka"; }; get '/realm' => require_login sub { "You are logged in using realm: " . session->{logged_in_user_realm}; }; dance(); Dancer-Plugin-Auth-Extensible-0.30/example/config.yml 0000644 0001750 0001750 00000001207 12064170234 022005 0 ustar davidp davidp appname: authextest session: simple logger: console log: core plugins: Auth::Extensible: realms: example: provider: Example config: provider: Config users: - user: 'beerdrinker' pass: 'password' name: 'Beer drinker' roles: - BeerDrinker - user: 'vodkadrinker' pass: 'password' name: 'Vodka drinker' roles: - VodkaDrinker show_errors: 1 Dancer-Plugin-Auth-Extensible-0.30/ignore.txt 0000644 0001750 0001750 00000000213 12021514014 020371 0 ustar davidp davidp blib* Makefile Makefile.old Build Build.bat _build* pm_to_blib* *.tar.gz .lwpcookies cover_db pod2htm*.tmp Dancer-Plugin-Auth-Extensible-* Dancer-Plugin-Auth-Extensible-0.30/Makefile.PL 0000644 0001750 0001750 00000002206 12217541422 020335 0 ustar davidp davidp use strict; use warnings; use ExtUtils::MakeMaker; WriteMakefile( NAME => 'Dancer::Plugin::Auth::Extensible', AUTHOR => q{David Precious