File Coverage

blib/lib/Schema/RDBMS/AUS/User.pm
Criterion Covered Total %
statement 24 298 8.0
branch 0 96 0.0
condition 0 39 0.0
subroutine 8 83 9.6
pod 18 21 85.7
total 50 537 9.3


line stmt bran cond sub pod time code
1             #!perl
2              
3             package Schema::RDBMS::AUS::User;
4              
5 3     3   1998 use strict;
  3         5  
  3         100  
6 3     3   15 use warnings;
  3         6  
  3         104  
7 3     3   17 use Carp qw(croak);
  3         7  
  3         174  
8 3     3   15 use DBIx::Transaction;
  3         5  
  3         107  
9 3     3   506101 use URI;
  3         26182  
  3         127  
10 3     3   2621 use URI::QueryParam;
  3         2555  
  3         89  
11 3     3   18 use Schema::RDBMS::AUS;
  3         7  
  3         83  
12              
13 3     3   18 use vars qw(%ENV);
  3         7  
  3         15044  
14              
15             return 1;
16              
17             # constructors
18              
19             sub login {
20 0     0 1   my($class, $user, $password, %login_info) = @_;
21 0           %login_info = (%login_info, name => $user, password => $password);
22 0           my $self;
23 0 0         if($self = eval { $class->_login(%login_info) }) {
  0            
24 0   0 0     my $txn = delete($login_info{_post_login}) || sub { return shift };
  0            
25             return $self->dbh->transaction(sub {
26 0     0     $self->used;
27 0 0         $self->log('login', %login_info) or return;
28 0           $txn->($self);
29 0           });
30             } else {
31 0           my $err = $@;
32 0           $self = $class->load(%login_info); # will die if user doesnt exist
33 0           $login_info{error} = $err;
34 0           $self->log('login_failure', %login_info);
35 0           die $err;
36             }
37             }
38              
39             sub _login {
40 0     0     my($class, %login_info) = @_;
41              
42 0 0         if(my $self = $class->load(%login_info)) {
43 0 0         die qq{Can not log in as group #$self->{id} "$login_info{name}"\n}
44             if($self->{is_group});
45            
46 0 0         die qq{Account #$self->{id} "$login_info{name}" is disabled.\n}
47             if($self->flag('Disabled'));
48            
49 0 0         if($self->check_password($login_info{password})) {
50 0           return $self;
51             } else {
52 0           die qq{Bad password for user #$self->{id} "$login_info{name}"\n};
53             }
54             } else {
55 0           die qq{No such user "$login_info{name}"\n};
56             }
57             }
58              
59             sub load {
60 0     0 1   my $class = shift;
61 0           return $class->_new(@_)->_LOAD_user;
62             }
63              
64             sub create {
65 0     0 1   my($class, %args) = @_;
66 0           my $self = $class->_new(%args);
67              
68 0 0         $self->{password_crypt} = $self->default_password_crypt
69             unless $self->{password_crypt};
70              
71             return $self->dbh->transaction(sub {
72 0 0   0     $self->_CREATE_user or die "Failed to create user.\n";
73 0 0         $self->log('create', %args) or die "Failed to log user creation.\n";
74 0 0         return $self unless defined $self->{_password};
75            
76 0           $self->{password} =
77             $self->crypt($self->password(delete $self->{_password}));
78            
79 0           $self->save;
80 0           });
81             }
82              
83             # methods
84              
85             sub password {
86 0     0 1   my($self, $password) = @_;
87 0 0         if($self->{_validate_password}->($password)) {
88 0           return $password;
89             } else {
90 0           die "Invalid password.\n";
91             }
92             }
93              
94             sub reset_password {
95 0     0 1   my($self, $password) = splice(@_, 0, 2);
96 0 0         if($self->password($password)) {
97 0           return $self->_reset_password($password, @_);
98             }
99             }
100              
101             sub _reset_password {
102 0     0     my($self, $password, $reason, %log_params) = @_;
103 0   0       $reason ||= 'reset_password';
104 0           $self->{password} = $self->crypt($password);
105             return $self->dbh->transaction(sub {
106 0 0   0     $self->save &&
107             $self->log($reason, %log_params);
108 0           });
109             }
110              
111             sub change_password {
112 0     0 1   my($self, $old, $new, %log_params) = @_;
113 0 0         if($self->_check_password($old)) {
114 0           return $self->reset_password($new, 'change_password', %log_params);
115             } else {
116 0           die "Old password does not match.\n";
117             }
118             }
119              
120             sub save {
121 0     0 1   my $self = shift;
122             return $self->dbh->transaction(sub {
123 0 0 0 0     if($self->_UPDATE_user && $self->_save_flags) {
124 0           return $self;
125             } else {
126 0           die "Failed to save user.\n";
127             }
128 0           });
129             }
130              
131             sub used {
132 0     0 1   my $self = shift;
133 0           return $self->{time_used} = $self->_UPDATE_used;
134             }
135              
136             sub log {
137 0     0 1   my($self, $event, %args) = @_;
138 0           delete @args{qw(password id _dbh)};
139 0           my $uri = URI->new;
140 0           $uri->query_form_hash(\%args);
141 0           my $data = $uri->query;
142 0           return $self->_INSERT_user_log($event, $data);
143             }
144              
145             sub crypt {
146 0     0 1   my $self = shift;
147 0           return $self->{_crypt_class}->crypt(@_);
148             }
149              
150             sub check_password {
151 0     0 0   my($self, $password) = @_;
152 0 0 0       return unless defined $self->{password} && length $self->{password} &&
      0        
      0        
153             defined $password && length $password;
154 0           return $self->_check_password($password);
155             }
156              
157             sub flag {
158 0     0 1   my($self, $flag) = @_;
159 0           return $self->{_flags}->{lc $flag};
160             }
161              
162             sub permission {
163 0     0 1   my($self, $perm) = @_;
164 0           return $self->{_permissions}->{lc $perm};
165             }
166              
167             sub set_flag {
168 0     0 1   my($self, $flag, $value, $create) = @_;
169 0 0         if(!defined $value) {
170 0           $value = 1;
171             } else {
172 0   0       $value = (!!$value) || 0;
173             }
174 0 0         $self->_SELECT_or_INSERT_flag($flag) if $create;
175 0           return $self->{_flags}->{lc $flag} = $value;
176             }
177              
178             sub clear_flag {
179 0     0 1   my($self, $flag) = @_;
180 0           delete $self->{_flags}->{lc $flag};
181             }
182              
183             sub add_to_group {
184 0     0 1   my($self, $group) = @_;
185            
186             return $self->dbh->transaction(sub {
187 0 0   0     $group = ref($self)->load(name => $group, _dbh => $self->{_dbh})
188             unless ref $group;
189            
190             return
191 0   0       $self->_INSERT_membership($group->{id}) &&
192             $self->_refresh_permissions &&
193             $self->_refresh_membership;
194 0           });
195             }
196              
197             sub remove_from_group {
198 0     0 1   my($self, $group) = @_;
199            
200             return $self->dbh->transaction(sub {
201 0 0   0     $group = ref($self)->load(name => $group, _dbh => $self->{_dbh})
202             unless ref $group;
203            
204             return
205 0   0       $self->_DELETE_membership($group->{id}) &&
206             $self->_refresh_permissions &&
207             $self->_refresh_membership;
208 0           });
209             }
210              
211             sub refresh {
212 0     0 1   my $self = shift;
213 0   0       return $self->_refresh_meta && $self->_refresh_user;
214             }
215              
216             # accessors
217              
218 0     0 1   sub dbh { return $_[0]->{_dbh}; }
219              
220 0     0 0   sub default_password_crypt { return "SHA1"; }
221              
222             # driver constructor
223              
224             sub driver_new {
225 0     0 0   my($class, $driver, %args) = @_;
226            
227 0   0       $args{_flags} ||= {};
228 0   0       $args{_permissions} ||= {};
229 0   0       $args{_membership} ||= {};
230              
231 0           return bless \%args, $class;
232             }
233              
234             # private class methods
235              
236             sub _connect_cached {
237 0     0     my($class, %args) = @_;
238            
239 0 0         if(my $dbh = Schema::RDBMS::AUS->dbh(
240             @args{qw(_db_dsn _db_user _db_pass _db_opts)}
241             )) {
242 0           return $dbh;
243             } else {
244 0           croak(qq{_connect_cached() failed: }, DBI->errstr);
245             }
246             }
247              
248             # private class/object methods
249              
250             sub _new {
251 0     0     my($self, %args) = @_;
252            
253 0           my $class;
254 0 0         if($class = ref($self)) {
255 0           %args = (%$self, %args);
256             } else {
257 0           $class = $self;
258             }
259              
260 0 0         $args{_dbh} = $self->_connect_cached(%args)
261             unless $args{_dbh};
262              
263 0 0         croak "a database handle (_dbh) is required for $class"
264             unless($args{_dbh});
265            
266 0 0         croak "_dbh does not seem to be a DBIx::Transaction object"
267             unless($args{_dbh}->isa('DBIx::Transaction::db'));
268            
269 0 0         $args{_dbh_driver} = $args{_dbh}->{Driver}->{Name}
270             unless $args{_dbh_driver};
271            
272 0     0     $args{_validate_password} = sub { return length $_[0]; }
273 0 0         unless $args{_validate_password};
274              
275 0           return $class->driver_new($args{_dbh_driver}, %args);
276             }
277              
278             sub _use_crypt_class {
279 0     0     my $self = shift;
280            
281 0 0         eval "use $self->{_crypt_class}; 1"
282             or croak "Failed to load $self->{_crypt_class}: $@";
283            
284 0           return $self;
285             }
286              
287             sub _refresh_user {
288 0     0     my $self = shift;
289 0 0         if(my $row = $self->_SELECT_user) {
290 0           %$self = (%$self, %$row);
291 0           return $self;
292             } else {
293 0           die "Refreshing user failed!";
294             }
295             }
296              
297             sub _refresh_meta {
298 0     0     my $self = shift;
299             return
300 0 0 0       $self->_refresh_flags &&
301             $self->_refresh_permissions &&
302             $self->_refresh_membership
303             or die "Refreshing metadata failed!";
304             }
305              
306             # low-level queries
307              
308             sub _LOAD_user {
309 0     0     my($self, %args) = @_;
310             return $self->dbh->transaction(sub {
311 0 0   0     if(my $row = $self->_SELECT_user(%args)) {
312 0           %$self = (%$self, %$row);
313 0           $self->_refresh_flags;
314 0           $self->_refresh_permissions;
315 0           $self->_refresh_membership;
316 0           return $self->_use_crypt_class;
317             } else {
318 0           die "User not found.\n";
319             }
320 0           });
321             }
322              
323             sub _check_password {
324 0     0     my($self, $password) = @_;
325 0           return $self->crypt($password) eq $self->{password};
326             }
327              
328             sub _CREATE_user {
329 0     0     my($self, %args) = @_;
330             return $self->dbh->transaction(sub {
331 0 0   0     if($self->_INSERT_user(%args)) {
332 0           my $id = $self->dbh->last_insert_id(undef, undef, 'aus_user', undef);
333 0           $self->{id} = $id;
334 0           return $self->_LOAD_user;
335             } else {
336 0           return;
337             }
338 0           });
339             }
340              
341             sub _sql_UPDATE_used {
342 0     0     my $self = shift;
343 0           return('UPDATE aus_user SET time_used = now() WHERE id = ?', $self->{id});
344             }
345              
346             sub _sql_SELECT_used {
347 0     0     my $self = shift;
348 0           return('SELECT time_used FROM aus_user WHERE id = ?', $self->{id});
349             }
350              
351             sub _UPDATE_used {
352 0     0     my $self = shift;
353 0           my $dbh = $self->dbh;
354             return $dbh->transaction(sub {
355 0     0     my($query, $id) = $self->_sql_UPDATE_used;
356 0 0         $dbh->do($query, {}, $id)
357             or die "Failed to update last used time: ", $dbh->errstr;
358            
359 0           ($query, $id) = $self->_sql_SELECT_used;
360 0 0         return $dbh->selectrow_array($query, {}, $id)
361             or die "Failed to fetch last used time: ", $dbh->errstr;
362 0           });
363             }
364              
365             sub _FIELDS_user {
366 0     0     return qw(id name password password_crypt is_group time_used);
367             }
368              
369             sub _sql_SELECT_user {
370 0     0     my($self, %args) = @_;
371              
372 0           my($k, $v);
373 0           my @fields = (
374 0           (map { "aus_user.$_ AS $_" } $self->_FIELDS_user),
375             "aus_password_crypt.class AS _crypt_class"
376             );
377            
378             {
379 0           local $" = ", ";
  0            
380 0           $k = qq{
381             SELECT @fields FROM aus_user
382             LEFT OUTER JOIN
383             aus_password_crypt
384             ON
385             aus_user.password_crypt = aus_password_crypt.id
386             };
387             }
388              
389 0 0         if($args{id}) {
    0          
390 0           $k .= "WHERE aus_user.id = ?";
391 0           $v = $args{id};
392             } elsif($args{name}) {
393 0           $k .= "WHERE aus_user.name = ?";
394 0           $v = $args{name};
395             } else {
396 0           croak q{Neither "name" or "id" were specified to _SELECT_user!};
397             }
398              
399 0           return($k, $v);
400             }
401              
402             sub _SELECT_user {
403 0     0     my($self, %args) = @_;
404 0           %args = (%$self, %args);
405            
406 0           my($query, $id) = $self->_sql_SELECT_user(%args);
407 0 0         if(my $sth = $self->dbh->prepare($query)) {
408 0 0         if($sth->execute($id)) {
409 0           my $rv = $sth->fetchrow_hashref;
410 0           $sth->finish;
411 0           return $rv;
412             }
413             }
414              
415 0           die "Query $query failed: ", $self->dbh->errstr;
416             }
417              
418             sub _SELECT_or_INSERT_flag {
419 0     0     my($self, $flag) = @_;
420 0           $flag = lc $flag;
421             return $self->dbh->transaction(sub {
422 0     0     my $sth = $self->dbh->prepare_cached(
423             "SELECT name FROM aus_flag WHERE name = ?"
424             );
425            
426 0 0         $sth->execute($flag) or die $sth->errstr;
427 0 0         if($sth->fetchrow_array) {
428 0           $sth->finish;
429 0           return $flag;
430             } else {
431 0           $sth->finish;
432 0 0         $self->dbh->do("INSERT INTO aus_flag (name) VALUES (?)", {}, $flag)
433             or die $self->dbh->errstr;
434 0           return $flag;
435             }
436 0           });
437             }
438              
439             sub _sql_INSERT_user {
440 0     0     my($self, %args) = @_;
441            
442 0           my @keys = sort grep($args{$_}, $self->_FIELDS_user);
443 0           my @qs = (("?") x scalar @keys);
444              
445 0           local $" = ", ";
446 0           return(qq{INSERT INTO aus_user (@keys) VALUES (@qs)}, @keys);
447             }
448              
449             sub _INSERT_user {
450 0     0     my($self, %args) = @_;
451            
452 0           %args = (%$self, %args);
453 0 0         croak "Can't INSERT a user that already has an id" if($args{id});
454            
455             return $self if($self->dbh->transaction(sub {
456 0     0     my($query, @keys) = $self->_sql_INSERT_user(%args);
457 0           $self->dbh->do($query, {}, @args{@keys});
458 0 0         }));
459            
460 0           return;
461             }
462              
463             sub _INSERT_user_log {
464 0     0     my($self, $event, $data) = @_;
465             return $self->dbh->transaction(sub {
466 0     0     $self->dbh->do(
467             "INSERT INTO aus_user_log (user_id, event, data) VALUES (?, ?, ?)",
468             {},
469             $self->{id}, $event, $data
470             );
471 0           });
472             }
473              
474             sub _sql_UPDATE_user {
475 0     0     my $self = shift;
476 0   0       my @fields = grep($_ ne 'id' && $_ ne 'time_used', $self->_FIELDS_user);
477 0           my @updates = map { "$_ = ?" } @fields;
  0            
478 0           local $" = ", ";
479 0           my $sql = "UPDATE aus_user SET @updates WHERE id = ?";
480 0           return($sql, @fields, 'id');
481             }
482              
483             sub _UPDATE_user {
484 0     0     my $self = shift;
485 0           my($query, @params) = $self->_sql_UPDATE_user;
486              
487             return $self->dbh->transaction(
488 0     0     sub { $self->dbh->do($query, {}, @{$self}{@params}); }
  0            
489 0           );
490             }
491              
492             sub _sql_DELETE_flags {
493 0     0     my $self = shift;
494             return(
495 0           q{DELETE FROM aus_user_flags WHERE user_id = ?},
496             'id'
497             );
498             }
499              
500             sub _sql_INSERT_flag {
501 0     0     my $self = shift;
502             return(
503 0           q{INSERT INTO aus_user_flags (user_id, flag_name, enabled) VALUES (?, ?, ?)},
504             'id'
505             );
506             }
507              
508             sub _sql_SELECT_flags {
509 0     0     my $self = shift;
510             return(
511 0           q{SELECT flag_name, enabled FROM aus_user_flags WHERE user_id = ?},
512             'id'
513             );
514             }
515              
516             sub _sql_SELECT_permissions {
517 0     0     my $self = shift;
518             return(
519 0           q{SELECT flag_name, enabled FROM aus_all_user_flags WHERE user_id = ?},
520             'id'
521             );
522             }
523              
524             sub _sql_SELECT_membership {
525 0     0     my $self = shift;
526             return(
527 0           q{
528             SELECT
529             ancestor, min(degree) AS degree
530             FROM aus_user_ancestors
531             WHERE user_id = ?
532             GROUP BY user_id, ancestor
533             },
534             'id'
535             );
536             }
537              
538             sub _fetch_flags {
539 0     0     my($self, $sth) = @_;
540 0           my %rv;
541 0           while(my @row = $sth->fetchrow_array) {
542 0           $rv{lc $row[0]} = $row[1];
543             }
544 0           return %rv;
545             }
546              
547             sub _SELECT_flags {
548 0     0     my($self, $query, @params) = @_;
549 0           my %rv;
550             $self->dbh->transaction(sub {
551 0     0     my $sth = $self->dbh->prepare_cached($query);
552 0 0         if($sth->execute(@{$self}{@params})) {
  0            
553 0           %rv = $self->_fetch_flags($sth);
554 0           $sth->finish;
555             } else {
556 0           die "fetching flags failed: ", $sth->errstr;
557             }
558 0           });
559 0           return %rv;
560             }
561              
562             sub _INSERT_membership {
563 0     0     my($self, $gid) = @_;
564             return $self->dbh->transaction(sub {
565 0     0     my $sth = $self->dbh->prepare_cached(
566             q{INSERT INTO aus_user_membership (user_id, member_of) VALUES (?, ?)}
567             );
568            
569 0 0         $sth->execute($self->{id}, $gid)
570             or die $sth->errstr;
571            
572 0           return 1;
573 0           });
574             }
575              
576             sub _DELETE_membership {
577 0     0     my($self, $gid) = @_;
578             return $self->dbh->transaction(sub {
579 0     0     my $sth = $self->dbh->prepare_cached(
580             q{DELETE FROM aus_user_membership WHERE user_id = ? AND member_of = ?}
581             );
582            
583 0 0         $sth->execute($self->{id}, $gid)
584             or die $sth->errstr;
585            
586 0           return 1;
587 0           });
588             }
589              
590             sub _refresh_flags {
591 0     0     my $self = shift;
592 0           my %rv = $self->_SELECT_flags($self->_sql_SELECT_flags);
593 0           $self->{_flags} = \%rv;
594 0           return $self->{_flags};
595             }
596              
597             sub _refresh_permissions {
598 0     0     my $self = shift;
599 0           my %rv = $self->_SELECT_flags($self->_sql_SELECT_permissions);
600 0           $self->{_permissions} = \%rv;
601 0           return $self->{_permissions};
602             }
603              
604             sub _refresh_membership {
605 0     0     my $self = shift;
606 0           my %rv = $self->_SELECT_flags($self->_sql_SELECT_membership);
607 0           $self->{_membership} = \%rv;
608 0           return $self->{_membership};
609             }
610              
611             sub _save_flags {
612 0     0     my $self = shift;
613             return $self->dbh->transaction(sub {
614 0     0     my($q, @p) = $self->_sql_DELETE_flags;
615            
616 0 0         $self->dbh->do($q, {}, @{$self}{@p}) or die
  0            
617             "do($q): ", $self->dbh->errstr;
618            
619 0           ($q, @p) = $self->_sql_INSERT_flag;
620            
621 0           while(my($k, $v) = each(%{$self->{_flags}})) {
  0            
622 0 0         $self->dbh->do($q, {}, @{$self}{@p}, $k, $v) or
  0            
623             die "do($q, $k, $v): ", $self->dbh->errstr;
624             }
625            
626 0           $self->_refresh_permissions;
627 0           return 1;
628 0           });
629             }