1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
|
package PSP::Session;
# Copyright (c) 2000, FundsXpress Financial Network, Inc.
# This library is free software released under the GNU Lesser General
# Public License, Version 2.1. Please read the important licensing and
# disclaimer information included below.
# $Id: Session.pm,v 1.1.1.2 2003/12/06 19:47:27 hartmans Exp $
use strict;
=head1 NAME
PSP::Session - Manage CGI sessions
=head1 SYNOPSIS
use PSP::Session;
session = get_session();
userid = get_user();
@headers = request_session();
@headers = request_basic();
=head1 DESCRIPTION
PSP::Session defines an object that has all of the features of a
CGI object as well as a potential session ID. This session ID will be
used by spawned commands to keep persistent state about a user. For
example, a shopping cart spawner will use this to keep track of a
user's potential purchases. PSP::Session inherits from CGI.
=head1 USAGE
=head2 get_session();
get_session takes no arguments as it acts upon self, and returns the
sessionID associated with this request (or undef if there is none.)
=head2 get_user();
get_user takes no argument as it acts upon self. It returns the Preferred
Account userid associated with this session, if the user has so authenticated
themself. It returns undef if there is no associated userid.
=head2 request_session();
request_session takes no arguments. If request_session is called, and
no session is associated with this request, it will attempt to
associate a session with the current user by setting a cookie. If there
is already an associated session, this function does nothing.
A set of additional arguments to PSP::header is returned.
=head2 request_basic();
request_basic takes no arguments. If request_session is called, and no
session is associated with this request, it will attempt to associate
a session with the current user via Basic authentication. If there is
already an associated session, this function does nothing.
=head1 SESSION IDENTIFICATION
The Session module will attempt to keep track of a session via several
different methods.
=head2 COOKIES
First, the module will use magic cookies, as supported by Netscape and
Microsoft Internet Explorer as well as other browsers at this time.
On a request, the Session module will look for a cookie previously set
by the server, by the name of SESSIONID. It will decypt this value
using a private server key to detemine the actual sessionID.
=head2 HTTP BASIC AUTHENTICATION
If cookies do not appear to be supported by the user, the module will
then attempt HTTP 'basic' authentication, as defined in RFC 1945. If
basic authentication is found, it then looks up the user in a database
to determine what sessionID he or she had been assigned.
=head2 OTHER AUTHENTICATION TOKENS
No other authentications tokens are supported at this time. In the
future, perhaps client certificates or Kerberized HTTP connections
could be used to track sessions.
=head1 EXAMPLE
The flow of this module is perhaps made most clear by an example. Let
us consider a shopping card module that uses this PSP::Session module.
A customer decides to visit "Bob's Car Parts and Floppy Disks" storefront
on the web, which happens to be housed on our server using this session
module for session tracking. From the main store page, the user requests
a catalog. A catalog is generated, by a search routine, and returned
to the user. The catalog lists 'door handles', 'radio knobs', and
'5.25" HD floppy disks', each of which has a link to 'See Details'.
The user then clicks on 'See Details' for 'radio knobs'. At this
point, the user has entered into a part of the web space handled by
the Spawner module (see Spawner.pm.) This particular page will be
generated by, say, the 'details' command. The details command takes
as input a PSP::Session object, as per Spawner.
The details routine notifies the PSP::Session object that it wishes to
establish a session (if one does not already exist.) The PSP::Session
module notes this request, and checks to see if the user already has a
sesssion assigned to them (by checking the headers for a presented
cookie or basic authentication.) If the user already presents a
session, all is well. If not, the module generates a new sessionID,
encrypts it with a private key, and adds a header to the header list
to attempt to set that as a cookie. The details routine then
generates a page describing the radio knobs, options, etc. Very
stylish and modern, available in red, grey, and chartreuse, and so
forth. At the bottom if this page is an 'order' button. The reply
completed, it is then retured to the user, along with the Set-Cookie
request in the header.
Jumping to the user, let's see what has happened at this point. The
user has received a Set-Cookie request. If the browser supports cookies,
and the user accepts the cookie, we're all set. If not, our work
is not yet done. Back to the server.
The user really likes these radio knobs, and clicks on the 'order' link,
which turns out to be a link also handled by the spawner, with the
'order' command. The order command also receives a PSP::Session object,
as per Spawner.
The order routine asks the PSP::Session object for the sessionID. If
the request contained a presented cookie, the Session object decrypts
it with the private key, and returns that to the order routine. If
there is no cookie, the Session object checks for HTTP Basic
Authentication. If this exists in the request's headers, it extracts
the key and looks up the sessionID in the database (this is described
in more detail later.) Of course, at this point in the example there's
no way that Basic Authentication could have been established yet, but
the 'get_session' routine doesn't know that. If there is no Basic
Authentication, the routine returns undef.
The order routine starts by generating a page with the ordering options
for the radio knobs, quantity, color, size, and so forth. Once it has
done this, there are two paths for it to take. The order routine then looks
at the sessionID it retrieved earlier. If it exists (not undef), the order
routine is happy and generates two buttons at the bottom of the page, one
labelled 'Order Now', the other labelled 'Add to Cart'. And, of course,
a link back to the catalog for 'Nevermind', or 'Cancel'. The details
of what happens with these buttons is outside the scope of this document,
as we have already successfully established a session.
If, on the other hand, the sessionID is undef, the order routine is not
quite as happy. At the bottom of the page, it generates three buttons.
These are 'Order Now', 'Log In and Add to Cart', and 'Sign Up!'.
'Order Now' is outside the scope of this document. To use Basic
Authentication, the user really should have a 'Mesa Preferred Shopper
Account', or some similar name that should make the customer feel warm
and fuzzy inside. To get one of these marvels, the user clicks on the
'Sign Up!' button at this stage. The details of establishing the
account are also outside the scope of this document, but result in
a (name,passwd,currsession) tuple getting entered into a database.
Once this has happened, a user can use the 'Log In and Add to Cart' button.
Which leaves us with that 'Log in...' button. This button POSTs the
form results to another URL, handled by the 'login' command. The
login routine asks the Session module for the current session. In this
case, it should be undef (since we wouldn't have gotten here anyways),
but it will gracefully handle the situation in which the user has reached
the page in error. If the current session is undef, the login routine
asks the Session module to attempt setting Basic Authenication. The
Session module adds a WWW-Authenticate header for Basic authentication,
and a realm indicating a name for the area that this cart is valid.
The login routine then returns a '401 Unauthorized' response for the
message, which is delivered to the user. The body of the message should
be somewhat descriptive of what is going on, in case the user does not
support WWW-Authenticate (which means they're violating HTTP spec, but
nevermind that.) If they don't support this, we tell them that they
cannot use a shopping cart, and return them to the order page. [Make sure
we have enough data that we can do that!]
And now, back to the user. The user (technically, the user's browser)
gets the WWW-Authenticate challenge with the 401 Unauthorized, and so
displays a password prompt. The customer, with his 'Mesa Preferred
<mumble> Account' enters his username and password. His browser then
resubmits the POST [does it? This *must* be tested with Mosaic and
lynx, at the very least] with an Authorization header for the
realm. [If they lose the POST data, we can make this a GET request. I
really don't like that idea, though. If we do lose POST data, perhaps
we should move the login button to the top of the page, before they
fill out the order form.]
At this point, we have another request routed to the 'login' command.
As before, the login routine checks for a session. When this happens,
the PSP::Session module decodes the Authorization header, and looks
up the user in the database. If the userid-passwd pair exist, it
creates a new sessionID, and adds it to the database (see the tuple
above.) This value is returned to the login routine, which is happy.
The login routine, being happy, sends off the PSP::Session POST
request to the order-handling routine, which could have been reached
above with 'Add to Cart' for a user with a session already. The
user now has a session, and the world is a joyous place outside of the
hands of this module.
But (there's always another case), there's the possibility that the
userid-passwd pair ISN'T in the database. In this case, the get_session
request by the login routine returns undef. The login routine doesn't
appreciate this and returns another 401 Unauthorized header, as above.
And that's the way it is.
=head1 OPEN ISSUES
=over
=item *
What is the webspace for which an Authorization header is sent
by the browser?
=item *
Define PSP::User which login and PSP::Session use for managing
'Mesa Preferred <mumble> Accounts'.
=back
=cut
use MesaCrypt;
use Kerberos;
@EXPORT = ();
@ISA = qw(CGI::Apache CGI::Fast CGI);
require CGI;
# Magic constants
$cookie_name = "mesas.sid";
# Counter, for further prevention of duplicate session ids.
$sid_counter = 0;
# sub get_session
# Arguments: none
# Returns: The sessionID associated with this request, undef if there
# if none
sub get_session {
# First let's check for a cookie (preferably Mint Milano)
my $self = shift;
my $cookie;
if($self->https) {
$cookie = $self->cookie("secure.$cookie_name");
} else {
$cookie = $self->cookie($cookie_name);
}
if ($cookie) {
my $sid = decrypt_sid($cookie);
return $sid;
}
# We've tried it all. They're just not presenting a session.
# Maybe they don't have one yet, of course.
return undef;
}
# request_session($self) => @cookies
sub request_session {
my $self = shift;
return () if $self->get_session;
my $cn = $self->https?"secure.$cookie_name":$cookie_name;
return $self->cookie
(-name => $cn,
-value => new_sid(),
-path => '/',
-secure => $self->https);
}
sub decrypt_sid {
my $enc_sid = shift;
return undef unless $enc_sid;
my($eblock, $out, $rc);
setup_keytab();
if ($rc = setup_eblock($MesaConf::cookie_principal, $eblock)) {
debug("Failed to setup eblock: ".error_message($rc));
return undef;
}
if($rc = decrypt($eblock, $enc_sid, $out) ) {
debug("Decrypt failed: ".error_message($rc));
return undef;
}
if ($out =~ /mesa_sid:(\d+\.\d+\.\d+)!/) {
return $1;
}
return undef;
}
sub new_sid {
my $enc_sid;
my($eblock, $out, $rc);
setup_keytab();
if ($rc = setup_eblock($MesaConf::cookie_principal, $eblock)) {
debug("Failed to setup eblock: ".error_message($rc));
return undef;
}
my $sid = "mesa_sid:".time.".".$$.".".$sid_counter++."!";
if($rc = encrypt($eblock, $sid, $enc_sid) ) {
debug("Encrypt failed: ".error_message($rc));
return undef;
}
return $enc_sid;
}
1;
__END__
=head1 BUGS
No known bugs, but this does not mean no bugs exist.
=head1 SEE ALSO
L<AtomicData>, L<HTMLIO>, L<Field>.
=head1 COPYRIGHT
PSP - Perl Server Pages
Copyright (c) 2000, FundsXpress Financial Network, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
BECAUSE THIS LIBRARY IS LICENSED FREE OF CHARGE, THIS LIBRARY IS
BEING PROVIDED "AS IS WITH ALL FAULTS," WITHOUT ANY WARRANTIES
OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
LIMITATION, ANY IMPLIED WARRANTIES OF TITLE, NONINFRINGEMENT,
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, AND THE
ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY,
AND EFFORT IS WITH THE YOU. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
=cut
|