4. Customizing Metadot's User/Registration and Authentication Functionality

Metadot's user management classes make it easy to customize Authentication and Registration behavior by subclassing. This means that you, as a developer, can modify Metadot's behavior with respect to Authentication and Registration without needing to modify the existing base classes.

Customization requires subclassing the appropriate User, Register, Authenticator and Session-Handler classes, and overriding the methods necessary to achieve the behavior you want. Then, information must be placed in the site's metadot.conf file to tell the relevant Factory classes which subclasses to return.

Section 4.1 provides a tutorial on customizing User and Register classes. Section 4.2 describes how to use and subclass Authenticator and SessionHandler classes.

4.1 User and Registration Customization: Tutorial

This section provides a tutorial on the process of customizing user information and registration functionality. The user- and registration-related classes are as follows:

Metadot::User::Default, and its subclasses in Metadot/User/*:
Metadot::User::Default is Metadot's main user management class. Its main function is to provide a front-end to data storage of info about a user. Its various methods allow adding and deleting users, and editing of users data.

The Metadot::User::FlexUser subclass provides a more flexible definition of the fields in a user's profile. The FlexUser class provides a mechanism for mapping registration/profile fields to fields in an extended_user database table. By subclassing FlexUser to change the default mapping, arbitrary registration/profile fields can be defined.

FlexUser is the default class used when the regular login/password authentication method is used for a site-- this is the class that will be enabled with an out-of-the-box Metadot install.

Metadot::User::Default is used when LDAP authentication is enabled.

Metadot::Register, and its subclasses in Metadot/Register/* :
Metadot::Register handles the registration process. It provides methods to generate and process the HTML form for user registration data. It will invoke Methods of the User class to create users after validating registration form input. The Metadot::Register::FlexRegister subclass is used in the default Metadot install.

Metadot::UserFactory
Metadot::RegisterFactory
These Factory classes return the correct User or Register subclasses based on the configuration information in the site's metadot.conf file.

In this tutorial, we will go through the exercise of subclassing Metadot's Default User and Registration classes in order to provide new functionality. The end result of the tutorial exercise will be the `Metadot::User::FlexUser' and `Metadot::Register::FlexRegister' classes in the code distribution.

4.1.1 Customization Steps

To build FlexUser and FlexRegister as subclasses of the default User and Register classes, we will need to take the following steps:

STEP 1. Define the configuration data structure.

Subclass User::Default to make it store and retrieve fields according to configuration. We will need to extend the storage subsystem to be able to store and retrieve custom user fields.

STEP 2. Override methods for User Registration Form generation and processing.

Subclass Register::Default to make it render and process registration forms according to configuration.

STEP 3. Override methods for user data saving and retrieving.

Make sure we override all methods in User::Default and Register::Default that rely on knowledge of storage details, since we are going to extend the storage subsystem to allow for customizable parameters.

 

We will call the new User and Registration classes FlexUser and FlexRegister . These classes are already part of the Metadot main distribution and we are going to refer to their methods constantly during this explanation. Please be sure to have them by your side when studying this tutorial (either by printing them or viewing them in your editor).

Our new User and Registration classes are easily pluggable into the Metadot system, because the user and registration classes to be used are selected at runtime via the system's etc/metadot.conf configuration file. Thus, if we want FlexUser and FlexRegister to be used instead of the Default classes, we do not need to make any configuration changes to the Perl code, but only need to add the following lines to our metadot.conf file:

user_type = FlexUser registration_type = FlexRegister

Let's move forward with the tutorial.

STEP 1. Defining the configuration data structure.

In order for the registration form to be custom generated, we need to provide a specification of what fields are need in the generated form, and what their names, lengths, and other parameters are. We use a plain perl data structure for this. We have named this structure $FIELDS_CONFIG and it currently lives in file FlexUser.pm . Please take a look at it. Most of the fields of the fields structure are self-explanatory. Some may not be as clear, so here are some comments about them:

The DISPLAY_SETS parameter is used for form generation and is mainly there for cosmetic purposes. Each field in the form will belong to one of the defined display sets. Form fields will then be rendered in a separately-framed set for each defined display set. Fields are assigned to display sets using the "display_set" parameter, which is defined as an index number (first defined set will be 0 and so on). Any number of sets can be defined.

Form fields are defined as an array of hashes under the FIELDS key of the data structure. The order in which they are listed in the array will be the order used to display them within the display set where they belong.

The storage type for each field defines where it is going to be stored It can take a value of "primary" and "secondary". The former will be stored in Metadot's primary (legacy) `user' table, and the latter will be stored according to a mapping in the new "extended_user" table, that we have defined for this new class. The extended_user table (please take a look at its code at the bottom of FlexUser.pm ) has room enough for a number of 255 character strings and some Text fields of 64K characters. Each "secondary" field needs to be assigned to a slot in this table, and we use the "store_at_column" parameter to define this mapping.

Note that some fields in the structure have a "field_type" set to "textarea". These will be rendered as HTML textareas instead of the default "text" boxes. If no "field_type" is included, the default will be "text". Currently, only text and textarea form fields are supported.

In order for this configuration to be available to consumer classes (i.e., the FlexRegister class, which will need it for form generation and processing) we create the get_fields_config() method. Please take a look at it. You will note that this method does some validation to make sure that some of the fields are always present in the config data structure. We require these to preserve backward compatibility with the old Default class and because those fields are required by many of the system modules.

STEP 2. Override methods for User Registration Form generation and processing.

These methods live in the Register::Default class. The methods involved are Register::Default::www_register_form (generates the registration form) and Register::Default::www_register (will process registration form).

We define methods to override both of these, in a new Register::FlexRegister class. The www_register_form method will generate the form according to the fields defined in the $FIELDS_CONFIG structure in FlexUser. www_register will process this form and will call FlexUser::add to have the new data saved. Note how these methods use the configuration data to generate the right HTML and execute the requested validation of form data.

The other two methods overridden in FlexRegister are save_modify_settings_form and show_modify_settings_form . These methods are in charge of displaying and processing the same user data forms but for the case of users or administrators modifying existing users data.

STEP 3. Override methods for user data saving and retrieving.

In order to allow for customizable fields, we need to implement a storage backend capable of storing data according to a custom-defined mapping. We do this by having our storage table consist of a number of blank columns not tied to any particular user field. What gets stored on these columns will depend on the mapping defined in the $FIELDS_CONFIG data structure.

Given that fields are customizable, we will not use hard-wired accessors/mutators for user instances. We instead define the methods FlexUser::get_value and FlexUser::set_value for this purpose.

We override method User::Default::add , which is the method in charge of inserting new user data to the database. We do this so that custom fields that are non-legacy (i.e, those marked as "secondary" in the config data structure) can be saved in the extended_user table. Similarly, we override User::Default::save so that fields can be stored in the extended_user table when data of existing users is modified.

The new configuration data structure introduces fields for first, initial, and last names. These are required and come as substitutes of the "fullname" legacy field. Thus, in order for FlexUser to remain backward compatible with clients of the old User::Default class, we need to keep supporting the fullname field. We do this by overriding the "fullname" accessor/mutator method and by keeping the fullname field of the old user table in sync with the contents of the new first, initial and last name fields.

We also override methods www_show_users and show_user_list . These methods are used to display the user management console. We override them to provide extended functionality for searching and sorting according to first and last names, to improve on the older "fullname"-based interface.

4.2 Customizing Authentication and Session Handling classes

Authentication is the mechanism through which user-entered credentials (usually a user name and password) are validated. Session handling is the mechanism for establishing users identities across HTTP requests, independently of whether the user is authenticated or not. Metadot allows for easy customization of these functions via its Authenticator and SessionHandler classes. In this section we will explain how to customize Authenticator functionality via subclassing and we will comment on how the SessionHandler class works.

The primary classes are:

Metadot::Authenticator, and its subclasses in Metadot/Authenticator/*
Metadot::SessionHandler, and its subclasses in Metadot/SessionHandler/*

Metadot::AuthenticatorFactory
Metadot::SessionHandlerFactory
These Factory classes return the correct Authenticator or SessionHandler subclasses based on the configuration information in the site's metadot.conf file.

4.2.1 Authenticator class

The Authenticator class is in charge of authenticating users. It is invoked on every hit to establish the identity of users. It provides methods to generate and process forms for input of user credentials. What particular subclass of authenticator is used should be specified by adding a line to etc/metadot.conf as follows:

authenticator_type = UserPassAuthenticator

(if none is specified, UserPassAuthenticator is used)

The main method you should be aware of in Authenticator is Authenticator::authenticate . This method is not intended to be overridden. It acts as a template that will call some abstract methods (meant to be overridden in subclasses) to perform authentication. This method will return a User and a Session object to the framework, to be used during the life of the request.

It will be useful to refer to method Authenticator::authenticate in Authenticator.pm as we discuss it. Authentication happens as follows:

  1. 1. The method Authenticator::determine_action is called. This method will return a token that will be used for determining whether a new session must be created (if you review the concrete implementation of this method in UserPassAuthenticator::determine_action you'll see that it prescribes session creation immediately after a login form has been submitted).
  2. 2. A SessionHandler object is created. This will be used to either create or restore a new session depending on the case.
  3. 3. If a new session needs to be created, it is so by calling the create_session method (to be overridden by concrete Authenticators) and asking the SessionHandler object to keep track of it by invoking its persist_session method.
  4. 4. If a session id (in the form of a request parameter "key") is available it means that a session already exists for this user, so we restore that by invoking the restore_session method (with the session id as parameter) in the session handle object.
  5. 5. If a session has neither been created nor restored, we first ask the session handler for one, and if it fails to do so we assume this is the first request of an anonymous user and consequently create a a new session for her, restore the profile of the anonymous user (a system default user) and return these.
  6. 6. At this point we certainly have a session object available and use it to restore the user object associated with it.
  7. 7. If the user is logged in then we refresh her session at this point. Refreshing means resetting the countdown timer for session expiration.
  8. 8. We return the User and Session objects of the authenticated user.

At this point you may be asking: But where are the username and password verified? This step should happen on session creation and should be implemented by concrete Authenticators in method create_session . For examples of this please look at the concrete implementations of create_session in classes UserPassAuthenticator and LDAPAuthenticator .

Other important methods in Authenticator are the ones used for generating and processing login forms. We include this function inside Authenticator because any given authenticator may take different input parameters as login credentials (e.g., a smartcard authenticator may only take a single one-time-valid authentication code). The relevant methods to be overridden for this purpose are show_login_form , show_my_page_login_box (a login box formatted as a MyPage element) and process_login_form . Again, please look at concrete implementations of these in UserPassAuthenticator and LDAPAuthenticator for reference.

A good example of customizing the Authenticator class can be found in the LDAPAuthenticator class included with the standard Metadot distribution. LDAPAuthenticator is a subclass of the standard UserPassAuthenticator . LDAPAuthenticator modifies the parent class to have authentication take place against an LDAP server, instead of the statically stored username and password of Metadot's default class.

4.2.2 SessionHandler class

The SessionHandler class implements a session handling strategy. That is, it provides the means for identifying users across HTTP requests. Currently we only provide a browser-cookie implementation for this. Other session tracking strategies (such as URL rewriting) should be implemented by subclassing SessionHandler . Note that other strategies would probably require making changes in other parts of the framework as well (URL rewriting, for instance, would require that session ids be encoded in all URLs generated in the system, something that is not currently doable from within the scope of SessionHandler ).

Methods in SessionHandler that must be overridden by subclasses are persist_session (will start tracking a session across HTTP requests); delete_session (will stop tracking a session) and restore_session (will restore a Session object previously being tracked by the session handler). Please refer to CookieSessionHandler.pm for a concrete implementation of the SessionHandler class.

In order to substitute a different subclass of SessionHandler , the following line should be added to <metadot>/etc/metadot.conf :

session_handler_type = CookieSessionHandler

(if none is specified, CookieSessionHandler will be used)