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

NAME

XAO::DO::Web::IdentifyUser - class for user identification and verification

SYNOPSYS

Currently is only useful in XAO::Web site context.

DESCRIPTION

!!XXX!!TODO!! - document key_list_uri and multi-key logons in general!

IdentifyUser class is used for user identification and verification purposes. In 'login' mode it logs a user in while in 'logout' mode, it logs a user out. In 'check' mode it determines the identification status of the user using cookies.

Possible user identification status are:

  • anonymous - user cannot be identified

  • identified - user has been identified

  • verified - user has recently been positively identified

The 'IdentifyUser' class takes the following parameters:

  • mode

    Indicates how 'IdentifyUser' will be used. Possible values are

    - check: (default) check the identification status
    - login: log user in
    - logout: Log current user out
  • anonymous.path

    Template to display if user has not been identified.

  • identified.path

    Template to display if user has been identified, but not verified.

  • verified.path

    Template to display if user has been identified.

  • hard_logout

    If 'true' in logout mode, this parameter not only unverifies the user, but erases identification cookies too. The default is to retain identified status.

  • stop

    Directive indicating that if a specified template is displayed, the remainder of the current template must not be displayed.

The 'IdentifyUser' class relies on some site configuration values. These values are available in the form of a reference to a hash obtained as follows:

 my $config=$page->siteconfig->get('identify_user');

where $page is a 'Page' object. The keys of such a hash correspond to the 'type' parameter of the 'IdentifyUser' class. An example of a $config hash with all required parameters is presented below:

 customer => {
    list_uri            => '/Customers',
    id_cookie           => 'id_customer',
    id_cookie_expire    => 126230400,       # (seconds) optional,
                                            # default is 10 years
    id_cookie_type      => 'name',          # optional, see below
    user_prop           => 'email',         # optional, see below
    alt_user_prop       => 'logname',       # deprecated, see below
    pass_prop           => 'password',
    pass_encrypt        =>  'md5',          # optional, see below
    vf_key_prop         => 'verify_key',    # optional, see below
    vf_key_cookie       => 'key_customer',  # optional, see below
    vf_time_prop        => 'verify_time',   # time of last verification
    vf_expire_time      => '600',           # seconds
    cb_uri              => 'IdentifyUser/customer' # optional
 }
list_uri

URI of users list (see XAO::FS and XAO::DO::FS::List).

Name of cookie that IdentifyUser sets to identificate the user in the future

Expiration time for the identification cookie (default is 4 years).

Can be either 'name' (default), 'key', or 'id'. Determines what is stored in the cookie on the client browser's side -- in 'name' mode it stores user name (possibly different in caseness from what was entered on login form), in 'key' mode it stores the key within 'key_list_uri', and in 'id' mode the internal id (container_key) of the user object is stored.

Downside to storing names is that some browsers fail to return exactly the stored value if it had international characters in the name. Downside to storing IDs is that you expose a bit of internal structure to the outside world. Usually its harmless though.

If 'user_prop' is not used then it does not matter, as the name and id are the same thing.

user_prop

Name attribute of a user object. If there is no 'user_prop' parameter in the configuration it is assumed that user ID is the key for the given list.

An interesting capability is to specify name as a URI style path, for instance if a member has many alternative names that all can be used to log-in and these names are stored in a list called Nicknames on each member object, then the following might be used:

 user_prop => 'Nicknames/nickname'

See below for how to access deeper objects and ids (the object in 'Nicknames' list in that case).

It is possible to set user_prop to an array reference. In that case each element of the array is assumed to be a potential key. They are checked in order they are listed and if exactly one match is found (with user_condition in effect) then this is the user whose password is checked.

This is useful to let users log in with either an email or a log name for example.

alt_user_prop

If this is given then on login the username is checked against this database property. If there is exactly one match it is used, otherwise (no matches or multiple matches) the logic goes back to user_prop, etc.

Using this is deprecated -- pass an array reference to user_prop instead.

user_condition

This is an optional condition that if present must be satisfied for user name to match user prop. The condition is added with an 'and' to the user_prop search similarly to this:

    $list->search(
        [ $user_prop,'eq',$user_name ],
        'and',
        $user_condition
    );

This can be used to narrow down the entities in the list that are supposed to be able to log in. For instance if the same list contains customers of different types with different login schemas.

The 'user_condition' argument can be an array (directly passed into search), or a hash. If it is a hash then the keys are user_prop values, and the values are user conditions. This can be used to set different conditions for different user props.

To avoid checking user_condition a non-zero 'skip_user_condition' argument can be passed to login().

pass_prop

Password attribute of user object.

pass_encrypt

Encryption method for the password. The value can be one or more comma separated algorithm tags. The password in login() is checked against each in order (unless the stored password has a specific algorithm code embedded in it, as do all digest algorithm password built with data_password_encrypt() call).

Available algorithms:

    'bcrypt'      - bcrypt digest with salt & cost support (recommended)
    'sha256'      - SHA-256 digest
    'sha1'        - SHA-1 digest
    'md5'         - MD5 digest (deprecated, do not use)
    'crypt'       - system crypt() call (do not use)
    'custom'      - use login_password_encrypt() call
                    that must be overridden in a derived object
    'plaintext'   - no encryption at all, plain text (default)

In most situations using 'bcrypt' is a good choice. The default cost parameter is 8, can be changed with pass_encrypt_cost.

Sha256, Sha1, and md5 do not support "cost", can be easily hardware-accelerated, and as such are not recommended.

When creating a database record use data_password_encrypt() to properly encrypt a password.

pass_encrypt_cost

This parameter is currently only used in 'bcrypt' mode. See the explanation in Digest::Bcrypt::cost() method. On an Intel i5-4670K CPU @ 3.40GHz the default cost 8 results in about 15ms per password encryption.

pass_normalize

Normalize password before encrypting it. The default is not to do any pre-processing, but for new development where Unicode passwords are a possibility it is a good idea to use a normalization.

There are two supported values currently: 'saslprep' (using Authen::SASL::SASLprep implementation of RFC-4013) and 'nfkc' (using Unicode::Normalize NFKC normalization). For most unicode strings these are interchangable and using saslprep is recommended.

Unicode characters that are left after normalization (or lack thereof) are encoded into UTF-8 bytes before being encrypted.

Note: Unicode normalization only works on passwords containing perl characters, not byte encoded strings.

pass_pepper

An optional string that is added to passwords when they are encrypted. The actual encrypted password would use a combination of a random "salt" (stored with the password), a static "pepper" (not stored with the password), and the password itself.

The point of adding a pepper value is to make the database content alone not be enough to crack passwords unless the site code/config is also known. This adds an extra protection layer in case the database content is stolen, but the site code is not.

vf_key_prop

The purpose of two optional parameters 'vf_key_cookie' and 'vf_key_prop' is to limit verification to just one computer at a time. When these parameters are present in the configuration on login success 'IdentifyUser' object generates random key, stores it into user's profile, and creates a cookie named according to 'vf_key_cookie' with the value of the generated key.

vf_key_length

By default keys are 8 characters long. Use this option to set a custom key length. This only works for vf_key_prop single login keys. For key_list_uri based keys they are auto-generated based on the storage settings.

Temporary verifiction key cookie.

vf_time_prop

Attribute of user object which stores the time of latest verified access.

vf_expire_time

Time period for which user remains verified.

Please note, that the cookie with the customer key will be set to expire in 10 years and actual expiration will only be checked using the content of 'vf_time_prop' field value. The reason for such behavior is that many (if not all) versions of Microsoft IE have what can be considered a serious bug -- they compare the cookie expiration time to the local time on the computer. And therefore if customer computer is accidentally set to some future date the cookie might expire immediately and prevent this customer from logging into the system at all. Most (if not all) versions of Netscape and Mozilla do not have this problem.

Therefore, when possible we do not trust customer's computer to measure time for us and do that ourselves.

cb_uri

URI of clipboard where IdentifyUser stores identification and verification information about user and makes it globally available.

RESULTS

In addition to displaying the correct template, results of user verification or identification are stored in the clipboard. Base clipboard location is determined by 'cb_uri' configuration parameter and defaults to '/IdentifyUser/TYPE', where TYPE is the type of user.

Parameters that are stored into the clipboard are:

id

The internal ID of the user object (same as returned by container_key() method on the object).

name

Name as used in the 'login' mode. If 'user_prop' configuration parameter is not used then it is always the same as 'id'.

object

Reference to the user object loaded from the database.

verified

This is only set when user has 'verified' status.

Additional information will also be stored if 'user_prop' refers to deeper objects. For example, if user_prop is equal to 'Nicknames/nickname' then it is assumed that there is a list inside of user objects called Nicknames and there is a property in that list called 'nickname'. It is also implied that the 'nickname' is unique throughout all objects of its class.

Information that gets stored in the clipboard in that case is:

list_prop

Name of the list property of the user object that is used in 'user_prop'. In our example it will be 'Nicknames'.

Nicknames (only for the example above)

Name of the list property is used to store a hash containing 'id', 'object' and probably 'list_prop' for the next object in the 'user_prop' path (although in practice it is hard to imagine a situation where more then one level is required).

EXAMPLES

Now, let us look at some examples that show how each mode works.

LOGIN MODE

 <%IdentifyUser mode="login"
   type="customer"
   username="<%CgiParam param="username" %>
   password="<%CgiParam param="password" %>
   anonymous.path="/bits/login.html"
   verified.path="/bits/thankyou.html"
 %>

LOGOUT MODE

 <%IdentifyUser mode="logout"
   type="customer"
   anonymous.path="/bits/thankyou.html"
   identified.path="/bits/thankyou.html"
   hard_logout="<%CgiParam param="hard_logout" %>"
 %>

CHECK MODE

 <%IdentifyUser mode="check"
   type="customer"
   anonymous.path="/bits/login.html"
   identified.path="/bits/order.html"
   verified.path="/bits/order.html"
 %>

METHODS

check_mode (%)

Checks operation mode and redirects to a method accordingly.

check ()

Checks identification/verification status of the user.

To determine identification status, first check clipboard to determine if there is such object present. If so, then that object identifies the user.

If not, then depending on 'id_cookie_type' parameter (that defaults to 'name') check whether there is an identification cookie or key cookie and if so, perform a search for object in database. If this search yields a positive result, the user's status is 'identified' and an attempt to verify user is made, otherwise the status is 'anonymous'.

Identification by key only works when keys are stored in a separate list.

Once identity is established, to determine verification status, first check the clipboard to determine if there is a 'verified' flag set. If so, then the user's status is 'verified'. If not, check whether the difference between the current time and the time of the latest visit is less than vf_expire_time property. If so, the user status considered 'verified', a new time is stored.

If optional 'vf_key_prop' and 'vf_key_cookie' parameters are present in the configuration then one additional check must be performed before changing status to 'verified' - the content of the key cookie and apropriate field in the user profile must match.

before_display (%)

Overridable method that gets called just before displaying results after all checks are done. Parameters it gets are:

 status     - one of 'anonymous', 'identified', or 'verified'
 type       - user type
 cbdata     - reference to clipboard data for the user
 config     - reference to the config for the user
 errstr     - error string, only available when called as part of login

Typically the method is used to add some other useful data to the clipboard on successful checks and logins. By default does nothing.

display_results ($$;$)

Displays template according to the given status. Third optinal parameter may include the content of 'ERRSTR'.

find_user ($$;$)

Searches for the user in the list according to the configuration:

    my $data=$self->find_user($config,$username);

Sets the same parameters in the returned hash as stored in the clipboard except 'verified'.

login_errstr ($)

Overridable method to translate login error codes to human readable strings. Can be used to for example translate messages into other languages.

Receives the following arguments:

    type    => user type
    object  => user object (or undef if not known)
    errcode => one of NO_INFO, NO_PASSWORD, BAD_PASSWORD, FAIL_LOCKED
login ()

Logs in user. Saves current time to vf_time_prop database field. Generates pseudo unique key and saves its value into either vf_key_prop or creates a record in key_list_uri. Sets identification cookies accordingly.

There is a parameter named 'force' that allows to log in a user without checking the password. One should be very careful not to abuse this possibility! For security reasons 'force' will only have effect when there is no 'password' parameter at all.

If an 'extended' parameter is present and is true, then the key is marked as extended with a potentially longer expiration time. This requires a configuration support as well (without configuration the presense of 'extended' is ignored):

    vf_expire_time_ext  => extended expiration period
    key_expire_ext_prop => db property where to store extended flag

'Extended' option is only supported with multiple keys per user ('key_list_uri' option).

login_check ()

A method that can be overriden in a derived object to check additional conditions for letting a user in. Gets the following arguments as its input:

 name       => name of user object
 password   => password
 object     => reference to a database object containing user info
 type       => user type
 cbdata     => reference to a hash that will be stored in clipboard on
               successful login

This method is called after all standard checks - it is guaranteed that user object exists and password matches its database record.

Must return empty string on success or suggested error message on failure. That error message will be passed in ERRSTR argument to the templates.

logout ()

Logs the user out.

Resets vf_time_prop if there is no vf_key_prop set as it is our only proof of authentication in this case. If vf_key_prop is in use then we clear the key, but leave the time alone -- helps to see when this user last logged in.

Clears identification cookie as well fo hard logout mode. Sets user status to 'anonymous' (hard logout mode) or 'identified'.

Will install data into clipboard in soft logout mode just the same way as mode='check' does.

data_password_encrypt (%)

Use this call to create a password for a user's database record. Call like so:

    my $pwdata=$identify_user->data_password_encrypt(
        type        => 'customer',
        password    => $plain_text_password,
    );

The resulting hash reference would have a member 'encrypted' that can be directly stored in the database.

verify_check (%)

Overridable method that is called from check() after user is identified and verified. May check for additional conditions, such as privilege level or something similar.

Gets the following arguments as its input:

 args       => arguments as passed to the check() method
 object     => reference to a database object containing user info
 type       => user type

Must return empty string on success.

EXPORTS

Nothing

AUTHOR

Copyright (c) 2005 Andrew Maltsev

<am@ejelta.com> -- http://ejelta.com/xao/

Copyright (c) 2001-2004 XAO Inc.

Andrew Maltsev <am@ejelta.com>, Marcos Alves <alves@xao.com>, Ilya Lityuga <ilya@boksoft.com>.

SEE ALSO

Recommended reading:

XAO::Web, XAO::DO::Web::Page, XAO::FS.