session(3)
NAME
- CGI::Session - CGI cookie authentication against an LDAP
- database
ABSTRACT
- Provides a simple API authenticate users against an LDAP
- server, and then
to cache this authentication information between invoka - tions of CGI scripts
without sending passwords subsequent to login. - The state information is maintained in a combination of
- a cookie, a database,
and a magic passkey which is sent in the contents of the - web page. Acquiring
the login thus requires stealing both the cookie and a - current copy of the
web page. - CGI::Session also contains a subclass of CGI which
- transparently injects
the passkey into forms. It is strongly suggested that - you use this class.
SYNOPSIS
Setting Things Up
use CGI::Session; use CGI;
my $cgi = new CGI::Session::CGI;
my $session = new CGI::Session( $cgi );
$cgi->session( $session );
my $session_store = new CGI::Session::CookieJar::DBI;
$session_store->set( -cookie_name=>'cookie_name',
-username=>'myuser',
-password=>'kjsdfdf',
-host=>'dbhost',
-database=>'mydb',
-cookie_table=>'cookiejar' );
$session->set( -cookie_jar => $session_store );
$session->auth_servers(
[ new CGI::Session::LDAPServer(
'ldap.server.my.domain', #
host
389, #
port
'ou=my,ou=domain', #
root
'ou=people,ou=my,ou=domain' #
base
'uid=$username,ou=people,ou=my,ou=domain' #
bind
) ] );
$session->open;
Performing the Initial Login
my $action = $cgi->param('action');
my $passkey = $cgi->param('passkey');
if ( defined $action and $action eq 'Log In' )
{
my $username = $cgi->param('username');
my $password = $cgi->param('password');
if ( $session->authenticated( $username, $password
) )
{
$session->set_passkey( $user );
$session->set_login_cookie( $user );
# Notice that we use $session->header and not
$cgi->header
#
print $session->header();
print $cgi->start_html( 'Login Succeeded' );
...
# The passkey is sent via the cgi wrapper.
#
my $passkey = $session->passkey;
print $cgi->start_form( -action=>'http://my.stupid/script.cgi' );
print ...your form here...
print $cgi->end_form;
...
print $cgi->end_html;
exit 0;
}
else
{
...
Login Failed
...
$session->close;
exit 0;
}
}
Confirming an Existing Session
my $passkey = $cgi->param('passkey');
if ( defined $passkey and !$session->confirm_userlogin( $passkey ) )
{
print $session->header();
print $cgi->start_html( 'Open Session' );
...
my $passkey = $session->passkey;
print $cgi->start_form( -action=>'http://my.stupid/script.cgi' );
print ...your form here...
print $cgi->end_form;
...
print $cgi->end_html;
$session->close;
exit 0;
}
else
{
... Authentication Failed Page ...
}
Logging out of an Existing Session
$session->set_logout_cookie;
print $session->header;
print $cgi->start_html( 'Logout Complete' );
print "You have logged out.";
print $cgi->end_html;
exit 0;
REQUIRES
CGI.pm CGI::Carp DBI (and at least one DBD) Mozilla::LDAP
Date::Format
DESCRIPTION
When a user first authenticates the LDAP database is
consulted. If the user is successfully authenticated the
information is cached. For subsequent login attempts. The
successful login is recorded in a database, and opaque
references to this information are passed back to the
client.
One of the opaque references is a cookie which is managed
by the client, and the other is a randomly chosen string
which is passed within the content of the web pages. The
random string is referred to as a passkey, and it must be
resent with every page.
On subsequent executions the cookie and the passkey are
checked. If either of these do not match the record in
the database then the user is rejected.
When the program is complete the user is logged out by
expiring the cookie.
USAGE
There are four major operations. The first is setting up
the CGIviaLDAP. Gotta do this every time. The second is
authenticating a new user/connection. The third is
authenticating an existing session. The fourth is logging
out an existing session. ( And somewhere in there you
have to send the cookie and passkey back to the client. )
Setting up the Authentication Object
- The first step is to include the necessary libraries.
These are CGI::Session.pm and CGI. - use CGI::Session::CGI;
use CGI::Session; - The second step is to create the CGI::Session object which
will be used. It requires a CGI object when it is
created. The CGI object provides the machinery to manage
cookies.
my $cgi = new CGI::Session::CGI;
my $session = new CGI::Session( $cgi );
$cgi->session( $cgi );- Now you have to tell the CGIviaLDAP several things. You
have to tell it which LDAP servers it should use for
authentication. You need to tell it how to connect to the
database. You need to describe the database table in
which it will store its information. You need to describe
the cookie that it will send to the client's web browser.
Finally, you need to describe various aspects of the login
behavior. - Setting the Authentication Servers
$session->auth_servers( new CGI::Session::LDAPServer(- -host=>'my.host.my.domain',
-port=>389, -bind=>'uid=$username,ou=people,dc=my,dc=domain' ) );
- The string '$username' within the -bind argument will be
replaced with the username when authentication occurrs. - You can also supply more than one ldap server by passing
an array of servers. The servers will be checked from
first to last in the array.
my $server1 = new CGI::Session::LDAPServer(- -host=>'ldap1.my.domain',
-port=>389,
-bind=>'uid=$username,ou=people,dc=my,dc=domain' ); - my $server2 = new CGI::Session::LDAPServer(
- -host=>'ldap2.your.domain',
-port=>389,
-bind=>'uid=$username,ou=people,dc=your,dc=domain' ); - $session->auth_servers( [ $server1, $server2 ] );
- Describing the Database Connection
- CGIviaLDAP uses perl DBI modules to access the database.
There are three items of major importance. These are the
connection DN, the the database user, and their associated
password.
$session->dbi_dn( 'dbi:mysql:my_apps_database' );
$session->dbi_username( 'my_apps_user' );
$session->dbi_password( '!CENSORED' );- Describing the Database Table
- You've now told the object how to connect to the database.
Now you need to tell it what the table it stores the
information in will look like. The most important is the
name of the table in which the information will be stored.
$session->cookie_table( 'login_cookies' );- There are three columns it expects. The first is the name
of the user; the second is the contents of the cookie; and
the third is the passkey. By default these are called,
respectively, 'user_id', 'cookie', and 'cookie', and
'passkey'. You may never need to change these. If you do
need to change them then you would write:
$session->user_column('username');
$session->cookie_column('login_cookie');
$session->passkey_column('login_passkey');- Setting Cookie Parameters
- When your program sends back a cookie, the cookie needs to
have several parameters set. These include the name of
the cookie, the path which it covers, the domain for which
it is good, and wether or not it should be used without a
secure connection.
$session->cookie_name( 'MySessionCookie123587098' ); #- The name of the cookie
$session->cookie_path( '/' );
$session->cookie_domain( '.drinktomi.com' );
$session->secure( 1 ); # 1=requires secure transport# 0=does not require securetransport - Most importantly you need describe how long the cookie
should be valid for. This is the expiration. It is given
in seconds. If using the refresh option (more on this
later) then the expiration determines how long the web
browser can sit idle. If not using the refresh option
then it determines how long the user will remain logged
in.
$session->cookie_expiration( 60*60*2 ); # Cookies will- be good for two hours.
- Setting Login Behavior
- Setting the auto refresh cookie option to 1 will the
cookie's expiration time to be updated every time a page
is sent to the client. As long as the user keeps using
the application they will never be logged out.
$session->auto_refresh_cookie(1) # 1=always refresh the- session cookie
# 0=never automaticallyrefresh the session cookie
- In some instances you only want people to log in when they
have a pre-existing database entry. In this case there
are two ways of managing things. The first is to create
an external file containing the valid user IDs. This is
kind of a hack.
$session->allowed_user_file( '/var/etc/allowed_users' );
$session->restricted_access( 1 ) # 1=use allowed user- file
# 0=do not use alloweduser file
- The second way of managing things is a little more to my
taste. Normally the auth object will register the user
(create an entry for them) in the cookie table. You can
change this so it will not log a person in unless they
already have an entry in the cookie table.
$session->register(1); # 1=automatically register- users in the cookie table.
# 0=do not automatically register users in the cookie table.
- Some day we may support check LDAP group memberships as a
third mechanism. - Sending Back a Page
- You have to do two things. The first is that you have to
generate the HTTP header using CGI::Session instead of
CGI, and the second is that you have to make sure that the
passkey gets sent back with the results of the next page. - The call CGI::Session::header is used _exactly_ like
CGI::header. The only difference is that it automatically
injects the session cookie if it needs to.
print $session->header;- The best way to get the passkey back to the user is by
using CGI::Session::CGI instead of CGI, and using the
start_form and end_form functions. These will
automatically inject the necessary html. The code looks
something like this:
print $cgi->start_form( -action=>$cgi->self_url );
print "YOUR FORM HERE";
print $cgi->end_form;- As long as you use CGI::Session::CGI then you don't have
to do anything else. - If you want to inject passkey into the document yourself
then the simplest way is to use a hidden text field. The
current passcode is contained in CGI::Session::passkey.
The code to create the form might look something like the
next snippet.
print "<form...>"
...
my $key = $session->passkey;
print "<input type=hidden name=passkey value=$key>";
...
print "</form>"- If you don't send the passkey along then confirmation of
the next session login will fail. - Authenticating a New Session
- Read the user name, and password from the incoming CGI
form, and then pass them to CGIviaLDAP::authenticated. If
the user is authenticated the we must generate a passkey
and a session cookie.
my $username = $cgi->param('username');
my $password = $cgi->param('password');
if ( $session->authenticated( $username, $password ) ){$session->set_passkey( $username );
$session->set_login_cookie( $username );
...
Successfully authenticated, send response
...}- else
{...
Login Failed
...} - Confirming an Existing Session
Read the passkey from the incoming CGI form, and thenask
CGIviaLDAP to confirm it.my $key = $cgi->param('passkey');
if ( $session->confirmed($key) ){...
Session was confirmed and this is a valid session
...}else{...
Session was not confirmed, and this is not a validsession
...}Once a session has been confirmed you can do several
things with it. You can change the passcode; you can
change the cookie identifier; or you can refresh the
cookie so that the expiration time will be reset.Changing the Passcode
if ( $session->confirmed( $key ) ){$session->set_passcode;
...
Session was confirmed and this is a valid session
...}Changing the Cookie Identifier
if ( $session->confirmed( $key ) ){$session->set_login_cookie;
...
Session was confirmed and this is a valid session
...}Refreshing the Cookie Expiration
if ( $session->confirmed( $key ) ){$session->refresh_login_cookie;
...
Session was confirmed and this is a valid session
...}Logging Out
if ( $session->confirmed( $key ) and $logout ){$session->set_logout_cookie;
...
print $session->header() # You must send back acookie using the $session
print $cgi->start_html( 'Logout Page' );
print "You have been logged out."; # Noticethat the passkey does not# need tobe sent back.print $cgi->end_html;
exit 0;}Creating the Cookie TableGuess what? Once you have configured your CGIviaLDAP there
is a function which will create the table that you have
described. It only works for MySQL at the moment, but in
the future it may work for other databases.
$session->create_cookie_table;
TO DO
1. Provide function to retreive username from the database
using the cookie.
2. Provide support for Net::LDAP
3. Clean up DBI code. (DBI provides the independence that
the old routines did.)
4. Clean up DBI connection creation. (Makes way too many
database connections.)
5. Make an 'add_cookie_table' function to alter existing
tables.
6. Date tracking and garbage collection of expired cookie
entries for auto-registered tables.
REFERENCE
- Creates a new session object. Requires at least one
argument. This argument is a CGI object of some kind. - my $cgi = new CGI::Session::CGI;
my $session = new CGI::Session( $cgi ); - You can then set values with function calls. Or, you can
use the handy-dandy '-PARAMETER=>VALUE' syntax just like
the standard module CGI.pm uses. This is in fact the
prefered method, and I strongly suggest that you use it.
my $cgi = new CGI::Session::CGI;
my $session = new CGI::Session( $cgi,
-auth_servers => [ - $ldap1, $ldap2 ],
- -dbi_dn =>
- 'dbi:mysql:stock',
-cookie_table => - 'everyones_cookies',
-dbi_username => - 'your_mythical_db_user',
-dbi_password => - 'its_password',
- -cookie_expiration
- => 900,
-cookie_name => - '1FA6FAACE01B7A2677',
-cookie_path => '/',
-cookie_domain => - '.inktomi.com',
-cookie_secure => 0, - -passkey_name =>
- 'passkey',
- -restricted_access
- => 0,
-register => 1,
-auto_refresh_cookie - => 1 );
- Parameters for a new CGI::Session
- -cookie_name
The name of the cookie that will be passed- back to the browser.
- -cookie_expiration
The lifetime of the cookie in seconds.- -cookie_path
The path of the cookie.- -cookie_domain
The domain of the cookie.- -cookie_secure
If set to 1 (-cookie_secure=>1) then SSL- will be required for this
connection. If set to 0 or undef then then - normal http can be used.
Defaults to 1. - -auth_servers
Points to either a single authentication- server, or an anonymous
array of authentication servers. Currently - authentication servers
are defined using CGI::Session::LDAPServer. - Others may be added
in the future. (At that time this will be - come a very poorly named
module.) - my $ldap1 = new CGI::Session::LDAPServer(
- -host=>'ldap.inktomi.com',
-port=>389, -bind=>'uid=$user - name,ou=People,dc=inktomi,dc=com' );
- my $ldap2 = new CGI::Session::LDAPServer(
- -host=>'mccoy.inktomi.com',
-port=>389, -bind=>'uid=$user - name,ou=People,dc=inktomi,dc=com' );
- $session => new CGI::Session( $cgi,
- -auth_servers => $ldap1 );
- --or-
- $session => new CGI::Session( $cgi,
- -auth_servers => [ $ldap1, $ldap2 ] );
- -restricted_access
This is set to either a 1 or 0 (undef is the- same as 0). If set to a
one then access will be restricted to those - users which are specfied
in the file corresponding to -allowed_us - er_file. This file contains
the names of the users which can be success - fully authenticated. One
username is listed on each line of this file. - -allowed_user_file
The full path to a file containing the user- names of the users which
can be successfully authenticated. Each line - of the file contains one
username. If a user is not specified in this - file then authentication
will fail. - This file is only consulted if -restrict
- ed_access is set to 1.
- -unikey
DANGER. The password for a back door. If- this value is set to 0
or undef then no back door exists. This is - ONLY A TESTING feature.
DO NOT SET THIS VARIABLE IN PRODUCTION CODE. - -register
If set to 1 then an entry is automatically- created in the cookie
table if one does not exist. If set to 0 - then authentication will
fail if the user does not exist. - -dbi_dn
The DBI connection string which will be used- to connect to the
database. - -dbi_username
The username which will be used to connect to- the database.
- -dbi_password
The password which will be used to connect to- the database.
- -cookie_table
The database table in which the cookie infor- mation will be stored.
- -user_column
The column in the cookie_table containing the- username.
- -passkey_column
The column in the cookie_table containing the- passkey.
- -cookie_column
The column in the cookie_table containing the- cookie value.
- -cookie_name_column
The column in the cookie_table containing the- cookie name.
- -login_expiration_column. CURRENTLY UNUSED
The column in the cookie_table containing the- session expiration time.
- -passkey_name
The name of the CGI parameter which contains- the passkey.
- -debug
Set to non-zero to generate debugging infor- mation.
- CGI::Session::open
Internal function. Opens up the cookie jar. This
function is called by methods just before they first
access a cookie jar. - $session->open;
- CGI::Session::cgi
Accessor method. The cgi to which the session is
attached. - CGI::Session::cookie
Accessor method. The value of the current cookie. - CGI::Session::passkey
Accessor method. The value of the current passkey.
Set by confirmed() and authenticated(). - CGI::Session::is_authenticated
Accessor method. Authentication state. True if the
session has been successfully authenticated. False if
it has not. - CGI::Session::cookie_name
Accessor method. The name of the login cookie. - CGI::Session::cookie_logged_out
Accessor method. Vestigial logout cookie. Unused.
Like the wings of an archeopertyx. But with no hairy
feathers. Left here for strictly archeological
reasons. - CGI::Session::cookie_expiration
Accessor method. The lifetime of the cookie specified
in seconds. - CGI::Session::cookie_path
Accessor method. The path of the cookie. - CGI::Session::cookie_domain
Accessor method. The domain of the cookie. - CGI::Session::cookie_secure
Accessor method. True if the cookie requires SSL.
False otherwise. - Authentication Behavior Variables
These are variables which affect the behavior of the
authentication mechanism. - CGI::Session::auth_servers
Accessor method. The list of authentication servers
which will be contacted. This value can either be a
single server or a reference to an array of servers. - Currently these servers are definied by
CGI::Session::LDAPServer objects. - CGI::Session::restricted_access
Accessor method. If set to a non-zero value then the
allowed_user_file is turned on. - CGI::Session::allowed_user_file
Accessor method. The full path to the
allowed_user_file. - CGI::Session::unikey
Accessor method. Boy this one sucks. This is a
backdoor value. If this is set then any user matching
this ID will be successfully authenticated. Why?
Strictly for testing. NEVER, EVER SET THIS VALUE
UNLESS YOU KNOW WHAT THE FUCK YOU ARE DOING. - CGI::Session::register
Accessor method. Login requires an entry to exist in
the cookie table for each user. If this variable is
set then an entry will automatically be created for
users which are successfully authenticated. - CGI::Session::auto_refresh_cookie
Accessor method. Normally the cookie will expire X
seconds after it is created, where X is specified by
CGI::Session::cookie_expiration. Whenever the cookie
is refreshed this timer resets. Setting this variable
to a non-zero value causes the cookie to be refreshed
every time that it is successfully verified. - CGI::Session::used_with_custom_cgi
Forget about this one. This is an internal function
used by CGI::Session and CGI::Session::CGI. Normally
set to zero. Setting CGI::Session::CGI::session
causes this value to be set. - CGI::Session::cookie_jar
# Cookiejar. This handles all cookie storage. #
Accessor method. The object encapsulating cookie
storage. - CGI::Session::passkey_name
Accessor method. The name of the passkey field in the
form is stored here. Not currently important, but it
will be if/when the table becomes a shared resource. - CGI::Session::debug
Accessor method. Turns on debugging. Currently this
doesn't do much. I need to add more instrumentation. - CGI::Session::has_passkey
True if the CGI session has a value for the parame- ter specified with
-passkey_name. - print "Session has passkey: ".( $ses
- sion->has_passkey ? "YES" : "NO" )."0;
- CGI::Session::passkey_field
The value of the CGI parameter specified by- -passkey_name.
- $passkey_field = $session->passkey_field;
- CGI::Session::confirmed
Confirms that the cookie and a passkey constitute a- valid login. If
the session confirmation succeeds then it will re - turn a true value.
If the session confirmation fails then it will re - turn a false value.
- Once this routine is called the variable of
CGI::Session::is_authenticated will contain the sta - tus of the
session. - The function may be called in one of two ways. You
- can either let
it extract the passkey value on its own, or you can - hand it the
passkey value to be checked. It is much less work - to let it extract
the passkey value. - if ( $session->confirmed )
{
Session was confirmed... - }
- If you want to handle the extraction of the passkey
- on your own...
- my $passkey = $cgi->param( 'passkey_name' );
if ( $session->confirmed( $passkey ) )
{
Session was confirmed... - }
- CGI::Session::confirm
The preferred way of confirming a valid login session.
It extracts the cookie and session key from the CGI,
checks their validity, and then sets the variable
CGI::Session::is_authenticated. Used as follows:
$session->confirm;
if ( $session->is_authenticated )
{
Authentication Succeeded- }
else
{
Authentication Failed - }
- CGI::Session::authenticate
Call the method authenticated with the username and
password that you want to check. Authenticated will
check their validity. If user was successfully
authenticated then it will return a true value. If
the user was not successfully authenticated then it
will return a false value. - Once authenticated is called then is_authenticated
will return the authentication status.
$username = $cgi->param('your_username_field');
$password = $cgi->param('your_password_field');- if ( $session->authenticated( $username, $password )
- )
{
Authentication Succeeded - }
- else
{
Authentication Failed - }
- CGI::Session::authenticate
The preferred method of authenticating a user. Call
the method authenticate with the username and password
that you want to check. Authenticate will check their
validity and then set the variable is_authenticated
with the status. For example:
$username = $cgi->param('your_username_field');
$password = $cgi->param('your_password_field');- $session->authenticate( $username, $password );
if ( $session->is_authenticated )
{
Authentication Succeeded - }
- else
{
Authentication Failed - }
- CGI::Session::header
Acts just like CGI.pm's header function, but it
injects the authentication cookie. - If you are using CGI::Session::CGI then this function
will not be used. If you are using CGI.pm directly
then call this function instead of CGI.pm's header
method.
print $session->header;
print $cgi->start_html( 'my html' );
...- CGI::Session::user_exists
Internal function. Checks the database to see if a user
has an existing record within the cookie table. True if
the cookie table contains an entry for the username, and
false if it does not.
if ( $self->user_exists( $username ) )
{
... perform action for defined user ... - }
- CGI::Session::register_user
Internal function. Creates an entry for the specified
user within the cookie table.
if ( ! $self->user_exists( $username ) )
{
$self->register_user( $username ); - }
- CGI::Session::login_cookie
Internal function. Returns the cookie string for the
current session. The expiration time is a unix timestamp
as returned by the function time(). The expiration time is not a lifetime in seconds.
my $cookie_string = $self->login_cookie( $cookie_name, - $expiration_time );
- CGI::Session::refresh_login_cookie
Resets the expiration time for the current cookie.
$self->refresh_login_cookie();CGI::Session::user($)
The cached name.
my $username = $self->user();CGI::Session::username($)
Pulls the username for the current cookie/passkey pair
from the database or local cache.
my $username = $self->username();CGI::Session::logout_cookie
Returns a login_cookie which has expired. (Expiration
date is set to epoch.)
my $cookie = $self->logout_cookie();