/[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 42 - (show annotations) (download)
Wed Nov 3 08:04:04 2010 UTC (13 years, 5 months ago) by buchan
File size: 11956 byte(s)
Indenting fixes by perltidy

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

  ViewVC Help
Powered by ViewVC 1.1.30