/[soft]/identity/CatDap/trunk/lib/CatDap/Controller/user.pm
ViewVC logotype

Contents of /identity/CatDap/trunk/lib/CatDap/Controller/user.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 44 - (show annotations) (download)
Wed Nov 3 09:34:45 2010 UTC (13 years, 5 months ago) by buchan
File size: 12535 byte(s)
Update cookie expiry on successful re-auth

1 package CatDap::Controller::user;
2 use Moose;
3 use namespace::autoclean;
4 use Net::LDAP;
5 use Net::LDAP::Schema;
6 use Net::LDAP::Extension::SetPassword;
7 use Net::LDAP::Control::PasswordPolicy 0.02;
8 use Crypt::CBC;
9 use Data::UUID;
10 use Data::Dumper;
11
12 BEGIN {extends 'Catalyst::Controller'; }
13
14 =head1 NAME
15
16 CatDap::Controller::user - Catalyst Controller
17
18 =head1 DESCRIPTION
19
20 Catalyst Controller.
21
22 =head1 METHODS
23
24 =cut
25
26 =head2 auto
27
28 Ensure the user is logged in. In order to bind as the user, we use
29 CatDap::Model::User, which uses Catalyst::Model::LDAP::FromAuthentication,
30 which effectively requires calling $c->authenticate on every request.
31
32 To do this, we keep the password, encrypted with blowfish, using the
33 (for now), first 3 octets of IPv4 request address and a UUID string (stored in
34 a cookie) as the key. To access the password, an attacker would need:
35 - the first 3 octets of IPv4 request (not stored anywhere, but accessible
36 in server logs)
37 - the encrpyted password (only available server-side in the session variable)
38 - the UUID key portion (only available on the browser-side in a cookie)
39
40 So, if the user does "not exist", we authenticate them, if it succeeds we encrypt
41 the password and store it in the session.
42
43 If the user is logged in, we get the encrypted password from the session, decrypt
44 it (we need to handle failure to decrypt it better)
45
46 =cut
47
48 sub auto : Private {
49 my ( $self, $c ) = @_;
50 my $cipher;
51 my $password;
52 my $mesg;
53 my $dn;
54 my $keyprefix = sprintf("%02x%02x%02x",split /\./,$c->req->address);
55 if (! defined $c->user) {
56 $c->log->info("No session, logging user in");
57 if (! $c->authenticate({ username => $c->req->param('username'),
58 password => $c->req->param('password') || $c->req->param('key')}) ) {
59
60 #TODO: ppolicy ....
61 $c->stash(errors => ['Incorrect username or password']);
62 $c->stash(template => 'index.tt');
63
64 #$c->forward('/index');
65 $c->detach('/user/login');
66 } else {
67
68 #if (defined $c->user->pwdReset) {
69 # $c->res->redirect('/user');
70 #}
71 #$c->persist_user;
72 $c->log->info('Logging user in to LDAP');
73
74 my $ug = Data::UUID->new;
75 my $key = $ug->create_str();
76 $cipher = Crypt::CBC->new( -key => $keyprefix . $key,
77 -cipher => 'Blowfish'
78 ) or die $!;
79 $c->session->{enc_password} = $cipher->encrypt($c->req->param('password') || $c->req->param('key'));
80 $c->response->cookies->{'key'} = { value => $key, expires => '+10m' };
81 $c->stash(pages => roles2pages($c->user->roles));
82 $c->session->{dn} = $c->user->ldap_entry->dn;
83 $c->session->{user} = $c->req->param('username');
84 $password = $c->req->param('password') || $c->req->param('key');
85 return 1;
86 }
87
88 } else {
89 my $key = $c->req->cookie('key')->value;
90 $cipher = Crypt::CBC->new( -key => $keyprefix . $key,
91 -cipher => 'Blowfish'
92 ) or die $!;
93 $password = $cipher->decrypt($c->session->{enc_password});
94 $c->log->info("Re-authenticating user " . $c->session->{user});
95 $c->authenticate({username => $c->session->{user},password => $password});
96 $c->res->cookies->{'key'} = {value => $key, expires => '+10m'};
97
98 $c->stash(pages => roles2pages($c->user->roles));
99 $c->log->info($@) if $@;
100 return 1;
101 }
102
103 }
104
105 =head2 index
106
107 =cut
108
109 sub index :Path :Args(0) {
110 my ( $self, $c ) = @_;
111 my $cipher;
112 my $password;
113 my $mesg;
114 my $dn;
115
116 if (not defined $c->user ) {
117 $c->stash(template => 'index.tt');
118 $c->forward('/index');
119 $c->detach;
120 }
121 my $schemaldap = Net::LDAP->new(${$c->config}{'Model::Proxy'}{'host'}) or warn "LDAP bind failed: $!";
122 $schemaldap->start_tls if ${$c->config}{'Model::Proxy'}{'start_tls'};
123 $schemaldap->bind;
124 my $schema = $schemaldap->schema or die ("Searching schema failed: $!");
125 my $attrdef;
126
127 my $user = $c->user->username;
128 my $entry;
129 $c->log->info("Searching for user $user");
130 $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
131 $entry = $mesg->entry;
132 my %mods;
133 my %params = %{$c->req->parameters};
134 my $update = 0;
135 foreach my $req (keys %params) {
136 next if $req !~ /(.+)_new/;
137 my $attrname = $1;
138 next if $params{$attrname . '_new'} eq $params{$attrname . '_old'};
139 $c->log->info("Received update request for attribute $attrname");
140 $update = 1;
141 $attrdef = $schema->attribute($attrname) or die ("getting schema failed: $!");
142 if ($$attrdef{'single-value'}) {
143 $entry->replace($attrname => $params{$attrname . '_new' }) or $c->log->info($!);
144 } else {
145 $entry->delete($attrname => $params{$attrname . '_old'});
146 $entry->add($attrname => $params{$attrname . '_new'});
147 }
148 if ($update) {
149 $mesg = $entry->update;
150 push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code;
151 }
152 }
153
154 $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
155 $c->log->info($mesg->error) if $mesg->code;
156 $entry = $mesg->entry;
157 $c->log->info($mesg->error) if $mesg->code;
158
159 my @values;
160 my @attributes = $entry->attributes;
161 my @may;
162 my @addable_attrs = @attributes;
163 my @ocs;
164 my @must;
165 @ocs = $entry->get_value("objectClass");
166 foreach my $oc (@ocs) {
167 foreach my $attr ($schema->must($oc)) {
168 push @must,$$attr{'name'} if not grep /$$attr{'name'}/,@must;
169 }
170 }
171
172 foreach my $attr (sort @attributes) {
173 next if ($attr eq "objectClass");
174 next if grep /$attr/,@{${$c->config}{'Controller::User'}{'skip_attrs'}};
175 my @vals = $entry->get_value($attr);
176 $attrdef = $schema->attribute($attr) or die ("getting schema failed: $!");
177 my %valhash = (
178 name => $attr,
179 values => \@vals,
180 desc => $$attrdef{'desc'},
181 );
182 if (! grep /^$attr$/, @{${$c->config}{'Controller::User'}{'uneditable_attrs'}}) {
183 $valhash{'editable'} = 1;
184 }
185 if (! $$attrdef{'single-value'} && $valhash{'editable'}) { $valhash{'addable'} = 1; }
186 if (! grep /$attr/,@must) { $valhash{'removable'} = 1; }
187 push @values, \%valhash;
188 }
189 foreach my $oc (@ocs) {
190 foreach my $attrdef ($schema->may($oc)) {
191 my $attrname = $$attrdef{'name'};
192 grep /$attrname/,@may or
193 grep /$attrname/,@attributes or
194 grep /$attrname/,@{${$c->config}{'Controller::User'}{'uneditable_attrs'}} or
195 grep /$attrname/,@{${$c->config}{'Controller::User'}{'skip_attrs'}} or
196 push @may, $attrname;
197 }
198 }
199 @may = sort @may;
200 $c->stash({ username => $user,
201 values => \@values,
202 attrdef => $attrdef,
203 may => \@may,
204 must => \@must,
205 });
206 $c->stash(subpages => gensubpages());
207 }
208
209 sub add : Local {
210 my ( $self, $c) = @_;
211 my ($mesg,$entry,$user,$attr,$value);
212 $attr = $c->req->param('attribute');
213 $value = $c->req->param('value');
214 $user = $c->user->username;
215 $c->log->info("Searching for user $user");
216 $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
217 $entry = $mesg->entry;
218 $entry->add( $attr => $value);
219 $c->log->info("Adding $attr = $value to user $user");
220 $entry->update;
221 push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code;
222 $c->log->info($mesg->error);
223 $c->res->redirect('/user');
224 }
225
226 sub delete : Local : Args(2) {
227 my ( $self, $c, $attrname,$attrvalue) = @_;
228 my ($mesg,$entry,$user);
229 $user = $c->user->username;
230 $c->log->info("Searching for user $user");
231 $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
232 $entry = $mesg->entry;
233 $c->log->info("Deleting $attrname = $attrvalue from user $user");
234 $entry->delete($attrname => $attrvalue);
235 $entry->update;
236 push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code;
237 $c->log->info($mesg->error);
238 $c->res->redirect('/user');
239 }
240
241 sub password : Local {
242 my ( $self, $c) = @_;
243 my ($mesg,$newpass,$cipher);
244 $c->stash(subpages => gensubpages());
245 if ( not defined $c->req->param('password') or not defined $c->req->param('newpassword1') or not defined $c->req->param('newpassword2')) {
246
247 #if ( not defined $c->req->param('newpassword1') or not defined $c->req->param('newpassword2')) {
248
249 $c->detach;
250 }
251 if ($c->req->param('newpassword1') eq $c->req->param('newpassword2')) {
252 $newpass = $c->req->param('newpassword1');
253 } else {
254 push @{${$c->stash}{'errors'}},"New passwords dont match";
255 }
256 my $pp = Net::LDAP::Control::PasswordPolicy->new;
257 $mesg = $c->model('User')->set_password(
258 oldpasswd => $c->req->param('password'),
259 newpasswd => $newpass,
260 control => [ $pp ],
261 );
262 if ($mesg->code) {
263 my $perror = $mesg->error;
264 push @{${$c->stash}{'errors'}},"Password change failed: $perror";
265 $c->detach;
266 } else {
267
268 # re-encrypt the new password and forward to user view
269 my $keyprefix = sprintf("%02x%02x%02x",split /\./,$c->req->address);
270 $cipher = Crypt::CBC->new( -key => $keyprefix . $c->sessionid,
271 -cipher => 'Blowfish'
272 ) or die $!;
273 $c->session->{enc_password} = $cipher->encrypt($newpass);
274 push @{${$c->stash}{'errors'}},"Password change succeeded";
275 $c->res->redirect('/user');
276 }
277
278 }
279
280 sub firstlogin : Local {
281 my ( $self, $c ) = @_;
282 my ($mesg,$newpass,$cipher);
283
284 if (! $c->authenticate({
285 username => $c->req->param('username'),
286 password => $c->req->param('key')}) ) {
287 $c->stash(errors => ['An error occurred']);
288 $c->res->redirect('/user');
289 }
290
291 if ( not defined $c->req->param('newpassword1') or not defined $c->req->param('newpassword2')) {
292 $c->detach;
293 }
294 if ($c->req->param('newpassword1') eq $c->req->param('newpassword2')) {
295 $newpass = $c->req->param('newpassword1');
296 } else {
297 push @{${$c->stash}{'errors'}},"New passwords dont match";
298 }
299 my $pp = Net::LDAP::Control::PasswordPolicy->new;
300 $mesg = $c->model('User')->set_password(
301
302 #oldpasswd => $c->req->param('password'),
303 newpasswd => $newpass,
304 control => [ $pp ],
305 );
306 if ($mesg->code) {
307 my $perror = $mesg->error;
308 push @{${$c->stash}{'errors'}},"Password change failed: $perror";
309 $c->detach;
310 } else {
311
312 # re-encrypt the new password and forward to user view
313 my $keyprefix = sprintf("%02x%02x%02x",split /\./,$c->req->address);
314 $cipher = Crypt::CBC->new( -key => $keyprefix . $c->sessionid,
315 -cipher => 'Blowfish'
316 ) or die $!;
317 $c->session->{enc_password} = $cipher->encrypt($newpass);
318 push @{${$c->stash}{'errors'}},"Password change succeeded";
319 $c->res->redirect('/user');
320 }
321
322 }
323
324 sub login : Local {
325 my ( $self, $c ) = @_;
326 if ($c->authenticate({ username => $c->req->param('username'),
327 password => $c->req->param('password') || $c->req->param('key')}) ) {
328 $c->res->redirect('/user');
329 } else {
330
331 #TODO: ppolicy ....
332 $c->stash(errors => ['Incorrect username or password']);
333 $c->stash(template => 'index.tt');
334 $c->forward('/index');
335 }
336 return $c->error;
337 }
338
339 sub logout : Local {
340 my ( $self, $c ) = @_;
341 $c->delete_session;
342 $c->res->redirect('/');
343 }
344
345 sub roles2pages : Private {
346 my @roles = @_;
347 my @pages;
348 foreach my $role (sort @roles) {
349 if ($role =~ /^(\w+) ?(\w*) (Admin)s$/) {
350 my $page = lc("/$3/$1$2");
351 push @pages,{ page => lc("/$3/$1$2"), title => "$1 $2 $3"};
352 }
353 }
354 return \@pages;
355 }
356
357 sub gensubpages : Private {
358 my ($type) = @_;
359 my @subpagenames;
360 @subpagenames = (
361 { page => './', title => "Edit"},
362 { page => 'password', title => "Change password"},
363 );
364 return \@subpagenames;
365 }
366
367 =head1 AUTHOR
368
369 Buchan Milne
370
371 =head1 LICENSE
372
373 This library is free software. You can redistribute it and/or modify
374 it under the same terms as Perl itself.
375
376 =cut
377
378 __PACKAGE__->meta->make_immutable;
379
380 1;

  ViewVC Help
Powered by ViewVC 1.1.30