File: Session.pm

package info (click to toggle)
psp 0.5.5-3
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k, sarge
  • size: 4,820 kB
  • ctags: 2,333
  • sloc: perl: 21,074; ansic: 4,553; sh: 2,407; makefile: 461; php: 11; pascal: 6
file content (348 lines) | stat: -rw-r--r-- 13,371 bytes parent folder | download | duplicates (2)
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