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.
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.
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.
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.
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.
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:
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).
SessionHandler
object is created. This will be used to either create or restore a new session depending on the case.
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.
restore_session
method (with the session id as parameter) in the session handle object.
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.
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