The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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.