File: UserFactory.pm

package info (click to toggle)
libapache2-sitecontrol-perl 1.05-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, forky, sid, trixie
  • size: 208 kB
  • sloc: perl: 558; makefile: 7
file content (353 lines) | stat: -rw-r--r-- 12,597 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
349
350
351
352
353
package Apache2::SiteControl::UserFactory;

use 5.008;
use strict;
use warnings;
use Carp;
use Data::Dumper;
use Apache2::SiteControl::User;
use Crypt::CBC;

our $engine;
our $encryption_key;

sub init_engine
{
   my $cipher = shift;
   my $key = shift;

   if(!defined($engine)) {
      $engine = Crypt::CBC->new({ key => $key, cipher => $cipher });
   }
}

# Params: Apache request, username, password, other credentials...
sub makeUser
{
   my $this = shift;
   my $r = shift;
   my $username = shift;
   my $password = shift;
   my @other_cred = @_;
   my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp";
   my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp";
   my $mapdir = $r->dir_config("SiteControlUsermap") || "";
   my $debug = $r->dir_config("SiteControlDebug") || 0;
   my $savePassword = $r->dir_config("UserObjectSavePassword") || 0;
   my $cipher = $r->dir_config("UserObjectPasswordCipher") || "CAST5";
   my $key = $r->dir_config("UserObjectPasswordKey") || $encryption_key || "A not very secure key because the admin forgot to set it.";
   my $saveOther = $r->dir_config("UserObjectSaveOtherCredentials") || 0;
   my $factory = $r->dir_config("SiteControlUserFactory") || "Apache2::SiteControl::UserFactory";
   my $user = undef;
   my %session;
   my $usermap;
   my $session_removed = 0;

   $r->log_error("encryption engine using key: $key") if $debug;
   init_engine($cipher, $key) if($savePassword);

   # Proper steps:
   # 1. Check to see if session already exists for user. If so, delete it.
   # 2. Create new session for user and populate it.
   # 3. Return the new user object.
   $r->log_error("Making user object for $username.") if $debug;
   eval {
      if($mapdir && -l "$mapdir/$username") {
         $r->log_error("$username is logging in, and already had a session. Removing old session.");
         $session_removed = 1;
         my $sid = readlink "$mapdir/$username";
         unlink "$mapdir/$username"; # Remove the link
         unlink "$sid"; # Remove the session file
      }
      tie %session, 'Apache::Session::File', undef, 
         {
            Directory => $sessiondir,
            LockDirectory => $lockdir
         };
      # Remember the username to session mapping.
      $r->log_error("Making symlink from $sessiondir/$session{_session_id} to $mapdir/$username") if($mapdir);
      symlink "$sessiondir/" . $session{_session_id}, "$mapdir/$username" if($mapdir);
      $user = new Apache2::SiteControl::User($username, $session{_session_id}, $factory);
      $session{username} = $username;
      $session{manager} = $factory;
      $session{attr_password} = $engine->encrypt($password) if($savePassword);
      $session{attr_session_removed} = $session_removed;
      if(@other_cred && $saveOther) {
         my $i = 2;
         for my $c (@other_cred) {
            $r->log_error("Saving extra credential_$i with value $c") if $debug;
            $session{"attr_credential_$i"} = $c;
            $i++;
         }
      }
      $r->log_error("Created user: " . Dumper($user)) if $debug;
   };
   if($@) {
      $r->log_error("Problem making new user object: $@");
      return undef;
   }

   # Note: this is a half-baked user, but the controller only needs the session
   # id. It might be better to return the result of findUser instead.
   return $user;
}

# Params: apache request, session_key
sub findUser
{
   my $this = shift;
   my $r = shift;
   my $ses_key = shift;
   my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp";
   my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp";
   my $debug = $r->dir_config("SiteControlDebug") || 0;
   my $savePassword = $r->dir_config("UserObjectSavePassword") || 0;
   my $cipher = $r->dir_config("UserObjectPasswordCipher") || "CAST5";
   my $key = $r->dir_config("UserObjectPasswordKey") || $encryption_key || "A not very secure key because the admin forgot to set it.";
   my %session;
   my $user;

   $r->log_error("encryption engine using key: $key") if $debug;
   init_engine($cipher, $key) if($savePassword);

   eval {
      tie %session, 'Apache::Session::File', $ses_key, {
         Directory => $sessiondir,
         LockDirectory => $lockdir
         };
      # FIXME: Document the possible problems with changing user factories when
      # persistent sessions already exist.
      $user = new Apache2::SiteControl::User($session{username}, $ses_key, $session{manager});
      for my $key (keys %session) {
         next if $key !~ /^attr_/;
         my $k2 = $key;
         $k2 =~ s/^attr_//;
         if($k2 eq 'password') {
            $user->{attributes}{$k2} = $engine->decrypt($session{$key});
         } else {
            $user->{attributes}{$k2} = $session{$key};
         }
      }
      $r->log_error("Restored user: " . Dumper($user)) if $debug;
   };
   if($@) {
      # This method should fail for new logins (or login after logout), so
      # failing to find the user is not considered a "real" error
      $r->log_error("Failed to find a user with cookie $ses_key.") if $debug;
      return undef;
   }

   return $user;
}

# Apache request, user object (not name)
sub invalidate
{
   my $this = shift;
   my $r = shift;
   my $userobj = shift;
   my $debug = $r->dir_config("SiteControlDebug") || 0;
   my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp";
   my $mapdir = $r->dir_config("SiteControlUsermap") || "";
   my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp";
   my %session;

   if(!$userobj->isa("Apache2::SiteControl::User") || !defined($userobj->{sessionid})) {
      $r->log_error("Invalid user object passed to saveAttribute. Cannot remove user.");
      return 0;
   }

   $r->log_error("Logging out user: " . $userobj->getUsername) if $debug;
   eval {
      unlink "$mapdir/" . $userobj->getUsername; # Remove the MSD link
      tie %session, 'Apache::Session::File', $userobj->{sessionid}, {
         Directory => $sessiondir,
         LockDirectory => $lockdir
         };

      tied(%session)->delete;
      $r->log_error("Done with logout.") if $debug;
   };
   if($@) {
      $r->log_error("Could not delete user (logout): $@");
   }
}

# Apache request, user object, attribute name
sub saveAttribute
{
   my $this = shift;
   my $r = shift;
   my $userobj = shift;
   my $name = shift;
   my $debug = $r->dir_config("SiteControlDebug") || 0;
   my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp";
   my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp";
   my %session;

   if(!$userobj->isa("Apache2::SiteControl::User") || !defined($userobj->{sessionid})) {
      $r->log_error("Invalid user object passed to saveAttribute. Attribute not saved.");
      return 0;
   }

   eval {
      tie %session, 'Apache::Session::File', $userobj->{sessionid}, {
         Directory => $sessiondir,
         LockDirectory => $lockdir
         };

      $r->log_error("Saving attribute $name = " .
         $userobj->getAttribute($name) . 
         "using Apache::Session::File.") if $debug;
      $session{"attr_$name"} = $userobj->getAttribute($name);
      untie %session;
   };
   if($@) {
      $r->log_error("Failed to save user attribute: $@");
      return 0;
   }
}

1;

__END__

=head1 NAME

Apache2::SiteControl::UserFactory - User factory/persistence

=head1 DESCRIPTION

This class is responsible for creating user objects (see
Apache2::SiteControl::User) and managing the interfacing of those objects with a
persistent session store.  The default implementation uses
Apache::Session::File to store the various attributes of the user to disk.

If you want to do your own user management, then you should leave the User
class alone, and subclass only this factory. The following methods are
required:

=over 3

=item makeUser($$) 

This method is called with the Apache Request object, username, password, and
all other credential_# fields from the login form.  It must create and return
an instance of Apache2::SiteControl::User (using new...See User), and store that
information (along with the session key stored in cookie format in the request)
in some sort of permanent storage.  This method is called in response to a
login, so it should invalidate any existing session for the given user name (so
that a user can be logged in only once).  This method must return the key to
use as the browser session key, or undef if it could not create the user.

=item findUser($$) 

This method is passed the apache request and the session key (which you defined
in makeUser).  This method is called every time a "logged in" user makes a
request. In other words the user objects are not persistent in memory (each
request gets a new "copy" of the state). This method uses the session key
(which was stored in a browser cookie) to figure out what user to restore. The
implementation is required to look up the user by the session key, recreate a
Apache2::SiteControl::User object and return it. It must restore all user
attributes that have been saved via saveAttribute (below). 

=item invalidate($$) 

This method is passed the apache request object and a previously created user
object. It should delete the user object from permanent store so that future
request to find that user fails unless makeUser has been called to recreate it.
The session ID (which you made up in makeUser) is available from
$user->{sessionid}.

=item saveAttribute($$$)  

This method is automatically called whenever a user has a new attribute value.
The incoming arguments are the apache request, the user object, and the name of
the attribute to save (you can read it with $user->getAttribute($name)). This
method must save the attribute in a such a way that later calls to findUser
will be able to restore the attribute to the user object that is created. The
session id you created for this user (in makeUser) is available in
$user->{sessionid}.

=back

=head1 Apache Config Directives

The following is a list of configuration variables that can be set with
apache's PerlSetVar to configure the behavior of this class:

=over 3

=item  SiteControlDebug  (default 0): 

Debug mode

=item  SiteControlLocks  (default /tmp): 

Where the locks are stored

=item  SiteControlSessions (default /tmp): 

Where the session data is stored

=item  SiteControlUsermap (default none): 

Where the usernames are mapped to session files. Required if you want multiple
session detection. If unset a single userid can be used to log in multiple
times simultaneously.

=item  SiteControlUserFactory (default: Apache2::SiteControl::UserFactory)

An implementation like this module.

=item  UserObjectSaveOtherCredentials (default: 0)

Indicates that other form data from the login screen (credential_2,
credential_3, etc.) should be saved in the session data. The keys will be
credential_2, etc.  name of the user factory to use when making user objects.
These are useful if your web application has other login choices (i.e. service,
database, etc.) that you need to know about at login.

=item  UserObjectSavePassword (default 0)

Indicates that the password should be saved in the local session data, so that
it is available to other parts of the web app (and not just the auth system).
This might be necessary if you are logging the user in and out of services on
the back end (like in webmail and database apps).

=item  UserObjectPasswordCipher (default CAST5)

The CBC cipher used for encrypting the user passwords in the session files (See
Crypt::CBC for info on allowed ciphers...this value is passed directly to
Crypt::CBC->new). If you are saving user passwords, they will be encrypted when
stored in the apache session files. This gives a little bit of added security,
and makes the apache config the only sensitive file (since that is where you
configure the key itself) instead of every random session file that is laying
around on disk. 
      
There is a global variable in this package called $encryption_key, which will
be used if this variable is not set. The suggested method is to set the
encryption key during server startup using a random value (i.e. from
/dev/random), so that all server forks will inherit the value.
      
=item  UserObjectPasswordKey

The key to use for encryption of the passwords in the session files. See
UserObjectPasswordCipher above.

=back

=head1 SEE ALSO

Apache2::SiteControl::User, Apache::SiteControl::PermissionManager,
Apache2::SiteControl::Rule, Apache::SiteControl

=head1 AUTHOR

This module was written by Tony Kay, E<lt>tkay@uoregon.eduE<gt>.

=head1 COPYRIGHT AND LICENSE

Apache2::SiteControl is covered by the GPL.

=cut