NAME
App::ZofCMS::Plugin::UserLogin - restrict access to pages based on user accounts
SYNOPSIS
In $your_database_of_choice that is supported by DBI create a table. You can have extra columns in it, but the first five must be named as appears below. login_time
is the return of Perl's time()
. Password will be md5_hex()
ed (with Digest::MD5, session_id
is rand() . rand() . rand()
and role depends on what you set the roles to be:
create TABLE users (
login TEXT,
password VARCHAR(32),
login_time VARCHAR(10),
session_id VARCHAR(55),
role VARCHAR(20)
);
Main config file:
template_defaults => {
plugins => [ { UserLogin => 10000 } ],
},
plug_login => {
dsn => "DBI:mysql:database=test;host=localhost",
user => 'test', # user,
pass => 'test', # pass
opt => { RaiseError => 1, AutoCommit => 0 },
table => 'users',
login_page => '/login',
redirect_on_restricted => '/login',
redirect_on_login => '/',
redirect_on_logout => '/',
not_restricted => [ qw(/ /index) ],
restricted => [ qr/^/ ],
smart_deny => 'login_redirect_page',
preserve_login => 'my_site_login',
login_button => '<input type="submit"
class="input_submit" value="Login">',
logout_button => '<input type="submit"
class="input_submit" value="Logout">',
},
In HTML::Template template for '/login'
page:
<tmpl_var name="plug_login_form">
<tmpl_var name="plug_login_logout">
DESCRIPTION
The module is a plugin for App::ZofCMS; it provides functionality to restrict access to some pages based on user accounts (which support "roles")
Plugin uses HTTP cookies to set user sessions.
This documentation assumes you've read App::ZofCMS, App::ZofCMS::Config and App::ZofCMS::Template
NOTE ON LOGINS
Plugin makes the logins lowercased when doing its processing; thus FooBar
login is the same as foobar
.
NOTE ON REDIRECTS
There are quite a few options that redirect the user upon a certain event. The exit()
will be called upon a redirect so keep that in mind when setting plugin's priority setting.
DATABASE
Plugin needs access to the database that is supported by DBI module. You'll need to create a table the format of which is described in the first paragraph of SYNOPSIS section above. Note: plugin does not support creation of user accounts. That was left for other plugins (e.g. App::ZofCMS::Plugin::FormToDatabase) considering that you are flexible in what the entry for each user in the database can contain.
ROLES
The "role" of a user can be used to limit access only to certain users. In the database the user can have several roles which are to be separated by commas (,
). For example:
foo,bar,baz
The user with that role is member of role "foo", "bar" and "baz".
TEMPLATE/CONFIG FILE SETTINGS
plug_login => {
dsn => "DBI:mysql:database=test;host=localhost",
user => 'test',
pass => 'test',
opt => { RaiseError => 1, AutoCommit => 0 },
table => 'users',
user_ref => sub {
my ( $user_ref, $template ) = @_;
$template->{d}{plug_login_user} = $user_ref;
},
login_page => '/login',
redirect_on_restricted => '/login',
redirect_on_login => '/',
redirect_on_logout => '/',
not_restricted => [ qw(/ /index) ],
restricted => [ qr/^/ ],
smart_deny => 'login_redirect_page',
preserve_login => 'my_site_login',
login_button => '<input type="submit"
class="input_submit" value="Login">',
logout_button => '<input type="submit"
class="input_submit" value="Logout">',
},
These settings can be set via plug_login
first-level key in ZofCMS template, but you probably would want to set all this in main config file via plug_login
first-level key.
dsn
dsn => "DBI:mysql:database=test;host=localhost",
Mandatory. The dsn
key will be passed to DBI's connect_cached()
method, see documentation for DBI and DBD::your_database
for the correct syntax of this one. The example above uses MySQL database called test
which is location on localhost
user
user => 'test',
Mandatory. Specifies the user name (login) for the database. This can be an empty string if, for example, you are connecting using SQLite driver.
pass
pass => 'test',
Mandatory. Same as user
except specifies the password for the database.
table
table => 'users',
Optional. Specifies which table in the database stores user accounts. For format of this table see SYNOPSIS section. Defaults to: users
opt
opt => { RaiseError => 1, AutoCommit => 0 },
Optional. Will be passed directly to DBI
's connect_cached()
method as "options". Defaults to: { RaiseError => 1, AutoCommit => 0 }
user_ref
user_ref => sub {
my ( $user_ref, $template ) = @_;
$template->{d}{plug_login_user} = $user_ref;
},
Optional. Takes a subref as an argument. When specified the subref will be called and its @_
will contain the following: $user_ref
, $template_ref
, $query_ref
, $config_obj
, where $user_ref
will be either undef
(e.g. when user is not logged on) or will contain an arrayref with user data pulled from the SQL table, i.e. an arrayref with all the columns in a table that correspond to the currently logged in user. The $template_ref
is the reference to your ZofCMS template, $query_ref
is the reference to a query hashref as is returned from CGI's Vars()
call. Finally, $config_obj
is the App::ZofCMS::Config object. Basically you'd use user_ref
to stick user's data into your ZofCMS template for later processing, e.g. displaying parts of it or making it accessible to other plugins. Defaults to: (will stick user data into {d}{plug_login_user}
in ZofCMS template)
user_ref => sub {
my ( $user_ref, $template ) = @_;
$template->{d}{plug_login_user} = $user_ref;
},
login_page
login_page => '/login',
login_page => qr|^/log(?:in)?|i;
Optional. Specifies what page is a page with a login form. The check will be done against a "page" that is constructed by $query{dir} . $query{page}
(the dir
and page
are discussed in ZofCMS's core documentation). The value for the login_page
key can be either a string or a regex. Note: the access is NOT restricted to pages matching login_page
. Defaults to: /login
redirect_on_restricted
redirect_on_restricted => '/uri',
Optional. Specifies the URI to which to redirect if access to the page is denied, e.g. if user does not have an appropriate role or is not logged in. Defaults to: /
redirect_on_login
redirect_on_login => '/uri',
Optional. Specifies the URI to which to redirect after user successfully logged in. By default is not specified.
smart_deny
smart_deny => 'login_redirect_page',
Optional. Takes a scalar as a value that represents a query parameter name into which to store the URI of the page that not-logged-in user attempted to access. This option works only when redirect_on_login
is specified. When specified, plugin enables the magic to "remember" the page that a not-logged-in user tried to access, and once the user enters correct login credentials, he is redirected to said page automatically; thereby making the login process transparent. By default is not specified.
preserve_login
preserve_login => 'my_site_login',
Optional. Takes a scalar that represents the name of a cookie as a value. When specified, the plugin will automatically (via the cookie, name of which you specify here) remember, and fill out, the username from last successfull login. This option only works when no_cookies
is set to a false value (that's the default). By default is not specified
login_button
login_button => '<input type="submit"
class="input_submit" value="Login">',
Optional. Takes HTML code for the login button, though, feel free to use it as an insertion point for any extra code you might want in your login form. Defaults to: <input type="submit" class="input_submit" value="Login">
logout_button
logout_button => '<input type="submit"
class="input_submit" value="Logout">'
Optional. Takes HTML code for the logout button, though, feel free to use it as an insertion point for any extra code you might want in your logout form. Defaults to: <input type="submit" class="input_submit" value="Logout">
redirect_on_logout
redirect_on_logout => '/uri',
Optional. Specifies the URI to which to redirect the user after he or she logged out.
restricted
restricted => [
qw(/foo /bar /baz),
qr|^/foo/|i,
{ page => '/admin', role => 'admin' },
{ page => qr|^/customers/|, role => 'customer' },
],
Optional but doesn't make sense to not specify this one. By default is not specified. Takes an arrayref as a value. Elements of this arrayref can be as follows:
a string
restricted => [ qw(/foo /bar) ],
Elements that are plain strings represent direct pages ( page is made out of $query{dir} . $query{page} ). The example above will restrict access only to pages http://foo.com/index.pl?page=foo
and http://foo.com/index.pl?page=bar
for users that are not logged in.
a regex
restricted => [ qr|^/foo/| ],
Elements that are regexes (qr//
) will be matched against the page. If the page matches the given regex access will be restricted to any user who is not logged in.
a hashref
restricted => [
{ page => '/secret', role => \1 },
{ page => '/admin', role => 'customer' },
{ page => '/admin', role => 'not_customer' },
{ page => qr|^/customers/|, role => 'not_customer' },
],
Using hashrefs you can set specific roles that are restricted from a given page. The hashref must contain two keys: the page
key and role
key. The value of the page
key can be either a string or a regex which will be matched against the current page the same way as described above. The role
key must contain a role of users that are restricted from accessing the page specified by page
key or a scalarref (meaning "any role"). Note you can specify only one role per hashref. If you want to have several roles you need to specify several hashrefs or use not_restricted
option described below.
In the example above only logged in users who are NOT members of role customer
or not_customer
can access /admin
page and only logged in users who are NOT members of role not_customer
can access pages that begin with /customers/
. The page /secret
is restricted for everyone (see note on scalarref below).
IMPORTANT NOTE: the restrictions will be checked until the first one matching the page criteria found. Therefore, make sure to place the most restrictive restrictions first. In other words:
restricted => [
qr/^/,
{ page => '/foo', role => \1 },
],
Will NOT block logged in users from page /foo
because qr/^/
matches first. Proper way to write this restriction would be:
restricted => [
{ page => '/foo', role => \1 },
qr/^/,
],
Note: the role can also be a scalarref; if it is, it means "any role". In other words:
restricted => [ qr/^/ ],
Means "all the pages are restricted for users who are not logged in". While:
restricted => [ { page => qr/^/, role \1 } ],
Means that "all pages are restricted for everyone" (in this case you'd use not_restricted
option described below to ease the restrictions).
not_restricted
not_restricted => [
qw(/foo /bar /baz),
qr|^/foo/|i,
{ page => '/garbage', role => \1 },
{ page => '/admin', role => 'admin' },
{ page => qr|^/customers/|, role => 'customer' },
],
Optional. The value is the exact same format as for restricted
option described above. By default is not specified. The purpose of not_restricted
is the reverse of restricted
option. Note that pages that match anything in not_restricted
option will not be checked against restricted
. In other words you can construct rules such as this:
restricted => [
qr/^/,
{ page => qr|^/admin|, role => \1 },
],
not_restricted => [
qw(/ /index),
{ page => qr|^/admin|, role => 'admin' },
],
The example above will restrict access to every page on the site that is not /
or /index
to any user who is not logged in. In addition, pages that begin with /admin
will be accessible only to users who are members of role admin
.
limited_time
limited_time => 600,
Optional. Takes integer values greater than 0. Specifies the amount of seconds after which user's session expires. In other words, if you set limited_time
to 600 and user went to the crapper for 10 minutes, then came back, he's session would expire and he would have to log in again. By default not specified and sessions expire when the cookies do so (which is "by the end of browser's session", let me know if you wish to control that).
no_cookies
no_cookies => 1,
Optional. When set to a false value plugin will set two cookies: md5_hex()
ed user login and session ID. When set to a true value plugin will not set any cookies and instead will put session ID into plug_login_session_id
key under ZofCMS template's {t}
special key. By default is not specified (false).
HTML::Template TEMPLATE
There are two (or three, depending if you set no_cookies
to a true value) keys created in ZofCMS template {t}
special key, thus are available in your HTML::Template templates:
plug_login_form
<tmpl_var name="plug_login_form">
The plug_login_form
key will contain the HTML code for the "login form". You'd use <tmpl_var name="plug_login_form">
on your "login page". Note that login errors, i.e. "wrong login or password" will be automagically display inside that form in a <p class="error">
.
plug_login_logout
<tmpl_var name="plug_login_logout">
This one is again an HTML form except for the "logout" button. Drop it anywhere you want.
plug_login_user
<tmpl_if name="plug_login_user">
Logged in as <tmpl_var name="plug_login_user">.
</tmpl_if>
The plug_login_user
will contain the login name of the currently logged in user.
plug_login_session_id
If you set no_cookies
argument to a true value, this key will contain session ID.
GENERATED HTML CODE
Below are the snippets of HTML code generated by the plugin; here for the reference when styling your login/logout forms.
login form
<form action="" method="POST" id="zofcms_plugin_login">
<div>
<input type="hidden" name="page" value="/login">
<input type="hidden" name="zofcms_plugin_login" value="login_user">
<ul>
<li>
<label for="zofcms_plugin_login_login">Login: </label
><input type="text" name="login" id="zofcms_plugin_login_login">
</li>
<li>
<label for="zofcms_plugin_login_pass">Password: </label
><input type="password" name="pass" id="zofcms_plugin_login_pass">
</li>
</ul>
<input type="submit" value="Login">
</div>
</form>
login form with a login error
<form action="" method="POST" id="zofcms_plugin_login">
<div><p class="error">Invalid login or password</p>
<input type="hidden" name="page" value="/login">
<input type="hidden" name="zofcms_plugin_login" value="login_user">
<ul>
<li>
<label for="zofcms_plugin_login_login">Login: </label
><input type="text" class="input_text" name="login" id="zofcms_plugin_login_login">
</li>
<li>
<label for="zofcms_plugin_login_pass">Password: </label
><input type="password" class="input_password" name="pass" id="zofcms_plugin_login_pass">
</li>
</ul>
<input type="submit" class="input_submit" value="Login">
</div>
</form>
logout form
<form action="" method="POST" id="zofcms_plugin_login_logout">
<div>
<input type="hidden" name="page" value="/login">
<input type="hidden" name="zofcms_plugin_login" value="logout_user">
<input type="submit" class="input_submit" value="Logout">
</div>
</form>
REPOSITORY
Fork this module on GitHub: https://github.com/zoffixznet/App-ZofCMS
BUGS
To report bugs or request features, please use https://github.com/zoffixznet/App-ZofCMS/issues
If you can't access GitHub, you can email your request to bug-App-ZofCMS at rt.cpan.org
AUTHOR
Zoffix Znet <zoffix at cpan.org> (http://zoffix.com/, http://haslayout.net/)
LICENSE
You can use and distribute this module under the same terms as Perl itself. See the LICENSE
file included in this distribution for complete details.