File Coverage

blib/lib/WE/DB/ComplexUser.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # $Id: ComplexUser.pm,v 2.21 2005/05/10 11:13:22 eserte Exp $
5             # Author: Olaf Mätzner, Slaven Rezic
6             #
7             # Copyright (C) 2001 Online Office Berlin. All rights reserved.
8             # Copyright (C) 2002,2004 Slaven Rezic.
9             # This is free software; you can redistribute it and/or modify it under the
10             # terms of the GNU General Public License, see the file COPYING.
11              
12             #
13             # Mail: slaven@rezic.de
14             # WWW: http://we-framework.sourceforge.net
15             #
16              
17             package WE::DB::ComplexUser;
18              
19 8     8   35277 use base qw(WE::DB::Base WE::DB::User Class::Accessor);
  8         18  
  8         4374  
20             __PACKAGE__->mk_accessors(qw(DB CryptMode InvalidChars InvalidGroupChars
21             DBFile DBTieArgs ErrorType ErrorMsg));
22              
23             use strict;
24             use vars qw($VERSION);
25             $VERSION = sprintf("%d.%02d", q$Revision: 2.21 $ =~ /(\d+)\.(\d+)/);
26              
27             use MLDBM;
28             use Fcntl;
29              
30             {
31             package WE::EntityObj;
32             use base qw(Class::Accessor);
33             __PACKAGE__->mk_accessors(qw(Id));
34             sub new {
35             my $self = bless {}, shift;
36             my %args = @_;
37             while(my($k,$v) = each %args) {
38             $self->$k($v);
39             }
40             $self;
41             }
42             }
43              
44             {
45             package WE::UserObj;
46             use base qw(WE::EntityObj);
47             # AuthType should be "" or "userdb" for using local passwords
48             __PACKAGE__->mk_accessors
49             (qw(Username Password Realname Groups Roles Email
50             Homedirectory Shell Language AuthType));
51             sub name { shift->Username }
52             }
53              
54             sub UserObjClass { "WE::UserObj" }
55              
56             {
57             package WE::GroupObj;
58             use base qw(WE::EntityObj);
59             __PACKAGE__->mk_accessors
60             (qw(Groupname Description));
61             sub name { shift->Groupname }
62             }
63              
64             sub GroupObjClass { "WE::GroupObj" }
65              
66             {
67             # this will be written to the database and should not be used otherwise
68             package WE::DB::ComplexUser::DBInfo;
69             use base qw(Class::Accessor);
70             __PACKAGE__->mk_accessors
71             (qw(CryptMode InvalidChars InvalidGroupChars));
72             sub new { bless {}, $_[0] }
73             }
74              
75             use constant ERROR_NOT_ACCEPTED => 0;
76             use constant ERROR_OK => 1;
77             use constant ERROR_INVALID_CHAR => 2;
78             use constant ERROR_GROUP_EXISTS => 3;
79             use constant ERROR_USER_EXISTS => 4;
80              
81             use constant NEXT_ID_KEY => "__NEXT_ID__";
82             use constant GROUPS_KEY => "__GROUPS__";
83              
84             use constant ERROR_TYPE_DIE => 0;
85             use constant ERROR_TYPE_RETURN => 1;
86              
87             sub new {
88             my($class, $root, $file, %args) = @_;
89              
90             $args{-db} = "DB_File" unless defined $args{-db};
91             $args{-serializer} = "Data::Dumper" unless defined $args{-serializer};
92             $args{-locking} = 0 unless defined $args{-locking};
93             $args{-readonly} = 0 unless defined $args{-readonly};
94             $args{-writeonly} = 0 unless defined $args{-writeonly};
95             $args{-connect} = 1 unless defined $args{-connect};
96              
97             my $self = {};
98             bless $self, $class;
99              
100             my @tie_args;
101             if ($args{-readonly}) {
102             push @tie_args, O_RDONLY;
103             } elsif ($args{-writeonly}) {
104             push @tie_args, O_RDWR;
105             } else {
106             push @tie_args, O_RDWR|O_CREAT;
107             }
108              
109             push @tie_args, $args{-db} eq 'Tie::TextDir' ? 0770 : 0660;
110              
111             if ($args{-db} eq 'DB_File') {
112             require DB_File;
113             push @tie_args, $DB_File::DB_HASH;
114             if ($args{-locking}) {
115             $MLDBM::UseDB = 'DB_File::Lock';
116             push @tie_args, $args{-readonly} ? "read" : "write";
117             } else {
118             $MLDBM::UseDB = 'DB_File';
119             }
120             } else {
121             $MLDBM::UseDB = $args{-db};
122             }
123              
124             $MLDBM::Serializer = $args{-serializer};
125              
126             $self->DBFile($file);
127             $self->DBTieArgs(\@tie_args);
128              
129             $self->Root($root);
130             $self->Connected(0);
131             $self->ErrorType(ERROR_TYPE_DIE);
132              
133             $self->connect_if_necessary(sub {
134             # read database information
135             my $db_info = $self->DB->{__DBINFO__};
136             if (!defined $db_info) {
137             $db_info = $self->DB->{__DBINFO__} = new WE::DB::ComplexUser::DBInfo;
138             }
139             # sync members with DBINFO
140             if ($db_info) {
141             $self->CryptMode($db_info->CryptMode);
142             $self->InvalidChars($db_info->InvalidChars);
143             $self->InvalidGroupChars($db_info->InvalidGroupChars);
144             }
145             # set %args
146             if (!$self->CryptMode) {
147             $self->CryptMode(defined $args{-crypt} ? $args{-crypt} : 'crypt');
148             $db_info->CryptMode($self->CryptMode) if $db_info;
149             }
150             if (!$self->InvalidChars) {
151             $self->InvalidChars(defined $args{-invalidchars} ? $args{-invalidchars} : '');
152             $db_info->InvalidChars($self->InvalidChars) if $db_info;
153             }
154             if (!$self->InvalidGroupChars) {
155             $self->InvalidGroupChars(defined $args{-invalidgroupchars} ? $args{-invalidgroupchars} : '');
156             $db_info->InvalidGroupChars($self->InvalidGroupChars) if $db_info;
157             }
158             # write back database information
159             if (!$args{-readonly}) {
160             $self->DB->{__DBINFO__} = $db_info;
161             }
162             });
163              
164             if ($args{-connect} && $args{-connect} ne 'never') {
165             $self->connect;
166             }
167              
168             $self;
169             }
170              
171             sub connect {
172             my $self = shift;
173             tie %{ $self->{DB} }, 'MLDBM', $self->DBFile, @{$self->DBTieArgs}
174             or require Carp, Carp::confess("Can't tie MLDBM database @{[$self->DBFile]} with args <@{$self->DBTieArgs}>, db <$MLDBM::UseDB> and serializer <$MLDBM::Serializer>: $!");
175             $self->Connected(1);
176             }
177              
178             sub _next_id {
179             my($self) = @_;
180             $self->connect_if_necessary
181             (sub {
182             my $id = $self->{DB}->{NEXT_ID_KEY()} || 0;
183             $self->{DB}->{NEXT_ID_KEY()}++;
184             $id;
185             });
186             }
187              
188             sub identify {
189             my($self, $user, $password) = @_;
190             $self->identify_object($user, $password) ? ERROR_OK : ERROR_NOT_ACCEPTED;
191             }
192              
193             sub identify_object {
194             my($self, $user, $password) = @_;
195             my $u;
196             $self->connect_if_necessary(sub {
197             if ($self->user_exists($user)) {
198             $u = $self->get_user_object($user);
199             my $authtype = $u->AuthType;
200             TRY_AUTH: {
201             if ($authtype && $authtype ne "userdb") {
202             my $method = "identify_${authtype}";
203             my $code = qq{use mixin 'WE::DB::ComplexUser::Auth${authtype}'; \$self->can('${method}');} ;
204             if (!eval $code) {
205             warn "$code: $@";
206             last TRY_AUTH;
207             }
208             my $ret = $self->$method($u, $password);
209             if ($ret != ERROR_OK) {
210             undef $u;
211             }
212             last TRY_AUTH;
213             }
214              
215             my $cryptpw = $self->DB->{$user}->Password;
216             my $crypt = $self->_decrypt($password, $cryptpw);
217             if ($crypt ne $cryptpw) {
218             undef $u;
219             };
220             }
221             }
222             });
223             $u;
224             }
225              
226             sub get_fullname {
227             my($self, $user) = @_;
228             my $ret = 0;
229             $self->connect_if_necessary(sub {
230             if ( $self->user_exists($user) ) {
231             my $fullname = $self->DB->{$user}->Realname;
232             if (defined $fullname) { $ret = $fullname } else { $ret = "" }
233             }
234             });
235             return $ret;
236             }
237              
238             sub user_exists {
239             my($self, $user) = @_;
240             $self->connect_if_necessary(sub {
241             exists $self->DB->{$user} ? 1 : 0;
242             });
243             }
244              
245             sub add_user {
246             my($self, $user, $password, $fullname) = @_;
247             $self->connect_if_necessary(sub {
248             if ( $self->user_exists($user) ) {
249             $self->ErrorMsg("User <$user> exists already");
250             return ERROR_USER_EXISTS;
251             }
252             if ( $user =~ /^_/) {
253             my $msg = "Usernames starting with `_' are not allowed";
254             if ($self->ErrorType eq ERROR_TYPE_RETURN) {
255             $self->ErrorMsg($msg);
256             return ERROR_INVALID_CHAR;
257             } else {
258             die $msg;
259             }
260             }
261             if ( $self->InvalidChars ne '' ) {
262             my $rcrx = "[" . quotemeta($self->InvalidChars) . "]";
263             if ($user =~ /$rcrx/) {
264             my $msg = "Invalid characters (some of @{[ $self->InvalidChars ]} in user name";
265             if ($self->ErrorType eq ERROR_TYPE_RETURN) {
266             $self->ErrorMsg($msg);
267             return ERROR_INVALID_CHAR;
268             } else {
269             die $msg;
270             }
271             }
272             }
273             my $o = $self->UserObjClass->new;
274             $o->Username($user);
275             $o->Password($self->_encrypt($password));
276             if (!$fullname) {$fullname="new user"};
277             $o->Realname($fullname);
278             $self->DB->{$user} = $o;
279             ERROR_OK;
280             });
281             }
282              
283             # This is inefficient, but safe:
284             sub add_user_object {
285             my($self, $user_object) = @_;
286             my $user = $user_object->Username;
287             if (!defined $user) {
288             die "Username is empty in user object";
289             }
290             my $password = $user_object->Password;
291             my $ret = $self->add_user($user, $password, undef);
292             return $ret if $ret != ERROR_OK;
293             my $id = $self->_next_id;
294             $user_object->Id($id);
295             my $new_user_object = $self->get_user_object($user);
296             $user_object->Password($new_user_object->Password); # the password is maybe encrypted now
297             $self->set_user_object($user, $user_object);
298             ERROR_OK;
299             }
300              
301             sub update_user {
302             my($self, $user, $password, $fullname, $groups) = @_;
303             $self->connect_if_necessary(sub {
304             if ( !$self->user_exists($user) ) {
305             return 0;
306             }
307             my $o = $self->DB->{$user};
308             if (defined $password) {
309             $password = $self->_encrypt($password);
310             $o->Password($password);
311             }
312             if (defined $fullname) {
313             $o->Realname($fullname);
314             }
315             if (defined $groups) {
316             $o->Groups($groups);
317             }
318             $self->DB->{$user} = $o;
319             1;
320             });
321             }
322              
323             sub delete_user {
324             my($self, $user) = @_;
325             $self->connect_if_necessary(sub {
326             my $ret = 0;
327             if (!$self->user_exists($user)) {
328             return 0;
329             }
330             delete $self->DB->{$user};
331             1;
332             });
333             }
334              
335             sub is_in_group {
336             my($self, $user, $group) = @_;
337             $self->connect_if_necessary(sub {
338             if ( $self->user_exists($user) ) {
339             my $o = $self->DB->{$user};
340             return 0 if !defined $o->{Groups};
341             if (!ref $o->{Groups} eq 'ARRAY') {
342             return $o->{Groups} eq $group;
343             } else {
344             foreach (@{ $o->{Groups} }) {
345             return 1 if ($_ eq $group);
346             }
347             }
348             }
349             0;
350             });
351             }
352              
353             sub get_groups {
354             my($self, $user) = @_;
355             $self->connect_if_necessary(sub {
356             my @groups;
357             if ($self->user_exists($user)) {
358             my $o = $self->DB->{$user};
359             if (ref $o->{Groups} eq 'ARRAY') {
360             return @{ $o->{Groups} };
361             } else {
362             return (defined $o->{Groups} ? $o->{Groups} : ());
363             }
364             }
365             ();
366             });
367             }
368              
369             sub get_user {
370             my($self, $user) = @_;
371             $self->connect_if_necessary(sub {
372             if ($self->user_exists($user)) {
373             my $o = $self->DB->{$user};
374             my @groups = $self->get_groups($user);
375             my $ret = +{
376             'groups' => \@groups,
377             'username' => $user,
378             'password' => $o->Password,
379             'fullname' => $o->Realname,
380             'email' => $o->Email,
381             };
382             foreach my $key (keys %$o) {
383             next if $key =~ /^(Username|Password|Realname|Groups|Email)$/;
384             $ret->{$key} = $o->{$key}
385             unless exists $ret->{$key}; # do not override key entries
386             }
387             return $ret;
388             } else {return 0}
389             });
390             }
391              
392             sub get_user_object {
393             my($self, $user) = @_;
394             $self->connect_if_necessary(sub {
395             my $o = $self->DB->{$user};
396             $o;
397             });
398             }
399              
400             sub set_user_object {
401             my $self = shift;
402             my($user, $o);
403             if (@_ == 1) {
404             $o = $_[0];
405             $user = $o->Username;
406             } else {
407             ($user, $o) = @_;
408             }
409             $self->connect_if_necessary(sub {
410             die "\$o is not an WE::UserObj object"
411             if !UNIVERSAL::isa($o, $self->UserObjClass);
412             die "Not allowed: Username in \$o was changed to @{[ $o->Username ]}, but must be $user"
413             if $user ne $o->Username;
414             $self->DB->{$user} = $o;
415             $o;
416             });
417             }
418              
419             sub add_group {
420             my($self, $user, $group) = @_;
421              
422             if ( $self->InvalidGroupChars ne '' ) {
423             my $rcrx = "[" . quotemeta($self->InvalidGroupChars) . "]";
424             if ($group =~ /$rcrx/) {
425             my $msg = "Invalid characters (some of @{[ $self->InvalidGroupChars ]} in group name";
426             if ($self->ErrorType eq ERROR_TYPE_RETURN) {
427             $self->ErrorMsg($msg);
428             return ERROR_INVALID_CHAR;
429             } else {
430             die $msg;
431             }
432             }
433             }
434              
435             $self->connect_if_necessary(sub {
436             my $ret=0;
437             if ($self->is_in_group($user,$group)) { return 0;}
438             if ( $self->user_exists($user)) {
439             my $o = $self->DB->{$user};
440             if (ref $o->Groups ne 'ARRAY') {
441             if (defined $o->Groups) {
442             $o->Groups([$o->Groups, $group]);
443             } else {
444             $o->Groups([$group]);
445             }
446             } else {
447             my $groups = $o->Groups;
448             push @$groups, $group;
449             }
450             $self->DB->{$user} = $o;
451             $ret=1;
452             }
453             return $ret;
454             });
455             }
456              
457             sub set_groups {
458             my($self, $user, @groups) = @_;
459              
460             if ( $self->InvalidGroupChars ne '' ) {
461             my $rcrx = "[" . quotemeta($self->InvalidGroupChars) . "]";
462             for my $group (@groups) {
463             if ($group =~ /$rcrx/) {
464             die "Invalid characters (some of @{[ $self->InvalidGroupChars ]} in group name";
465             }
466             }
467             }
468              
469             $self->connect_if_necessary(sub {
470             my $ret = 0;
471             if ($self->user_exists($user)) {
472             my $o = $self->DB->{$user};
473             $o->Groups(\@groups);
474             $self->DB->{$user} = $o;
475             $ret = 1;
476             }
477             return $ret;
478             });
479             }
480              
481             sub delete_group {
482             my($self, $user, $delgroup) = @_;
483             $self->connect_if_necessary(sub {
484             my $ret=0;
485             if ( $self->user_exists($user) && $self->is_in_group($user,$delgroup)) {
486             my $o = $self->DB->{$user};
487             my @groups = $self->get_groups($user);
488             my @newgroups;
489             foreach my $g (@groups) {
490             if ($g ne $delgroup) { push(@newgroups,$g) }
491             }
492             $o->Groups(\@newgroups);
493             $self->DB->{$user} = $o;
494             $ret=1;
495             }
496             return $ret;
497             });
498             }
499              
500             sub get_users_of_group {
501             my($self, $group) = @_;
502             $self->connect_if_necessary(sub {
503             my @users;
504             foreach my $usr (keys %{$self->DB}) {
505             next if $usr =~ /^__/; # skip special keys
506             if ( $self->is_in_group($usr,$group) ) { push(@users,$usr); }
507             }
508             return @users;
509             });
510             }
511              
512             sub get_all_users {
513             my($self) = @_;
514             $self->connect_if_necessary(sub {
515             my @allusers;
516             foreach my $usr (keys %{$self->DB}) {
517             next if $usr =~ /^__/; # skip special keys
518             push(@allusers, $usr);
519             }
520             return @allusers;
521             });
522             }
523              
524             sub get_all_groups {
525             my($self) = @_;
526             $self->connect_if_necessary(sub {
527             $self->_init_groups;
528             sort keys %{ $self->DB->{GROUPS_KEY()} };
529             });
530             }
531              
532             sub _crypt {
533             my($password, $salt) = @_;
534             $password = "" if !defined $password;
535             my $crypt;
536             eval {
537             local $SIG{__DIE__};
538             $crypt = crypt($password, $salt);
539             };
540             if ($@) { $crypt = $password };
541             $crypt;
542             }
543              
544             sub _encrypt {
545             my($self, $password) = @_;
546             if ($self->CryptMode eq 'none') {
547             $password;
548             } else {
549             _crypt($password, &salt);
550             }
551             }
552              
553             sub _decrypt {
554             my($self, $checkit, $old_password) = @_;
555             if ($self->CryptMode eq 'none') {
556             if ($checkit eq $old_password) {
557             $old_password;
558             } else {
559             $checkit.$old_password."DUMMY"; # construct a wrong result
560             }
561             } else {
562             _crypt($checkit, $old_password);
563             }
564             }
565              
566             sub salt {
567             my($salt) = ''; # initialization
568             my($i, $rand) = (0, 0);
569             my(@itoa64) = ( '.', '/', 0 .. 9, 'a' .. 'z', 'A' .. 'Z' ); # 0 .. 63
570              
571             # to64
572             for ($i = 0; $i < 8; $i++) {
573             srand(time + $rand + $$);
574             $rand = rand(25*29*17 + $rand);
575             $salt .= $itoa64[$rand & $#itoa64];
576             }
577             #warn "Salt is: $salt\n";
578              
579             return $salt;
580             }
581              
582             sub error {
583             my($self, $errorcode) = @_;
584             my @errtxt = ("not accepted", # 0
585             "ok",
586             "invalid character",
587             "group already exists",
588             );
589              
590             if ( $errtxt[$errorcode] ) {
591             return $errtxt[$errorcode];
592             } else {
593             return "unknown error.";
594             }
595             return 0;
596             }
597              
598             # Return 1 if this file is really a WE::DB::ComplexUser file
599             sub check_data_format {
600             my $self = shift;
601             if (scalar keys %{ $self->DB } == 0) {
602             return 1; # empty
603             }
604             exists $self->DB->{__DBINFO__} ? 1 : 0;
605             }
606              
607             sub _init_groups {
608             my $self = shift;
609             $self->connect_if_necessary(sub {
610             if (!exists $self->DB->{GROUPS_KEY()}) {
611             $self->DB->{GROUPS_KEY()} =
612             {
613             map {
614             my $o = $self->GroupObjClass->new;
615             $o->Groupname($_);
616             ($_ => $o);
617             } $self->_predefined_groups
618             };
619             }
620             });
621             }
622              
623             sub delete_all_groups {
624             my $self = shift;
625             $self->connect_if_necessary(sub {
626             $self->DB->{GROUPS_KEY()} = { };
627             for my $u_name ($self->get_all_users) {
628             $self->DB->{$u_name}{Groups} = [];
629             }
630             });
631             }
632              
633             sub _predefined_groups {
634             qw(editor chiefeditor admin);
635             }
636              
637             sub delete_group_definition {
638             my($self, $group) = @_;
639             $self->connect_if_necessary(sub {
640             my $groups = $self->DB->{GROUPS_KEY()};
641             delete $groups->{$group};
642             $self->DB->{GROUPS_KEY()} = $groups;
643             for my $u_name ($self->get_all_users) {
644             my @groups = $self->get_groups($u_name);
645             my $before = scalar @groups;
646             @groups = grep { $_ ne $group } @groups;
647             if ($before != scalar @groups) { # a group was deleted
648             my $u = $self->DB->{$u_name};
649             $u->{Groups} = \@groups;
650             $self->DB->{$u_name} = $u;
651             }
652             }
653             return ERROR_OK;
654             });
655             }
656              
657             sub add_group_definition {
658             my($self, $group) = @_;
659             my $group_object;
660             if (UNIVERSAL::isa($group, $self->GroupObjClass)) {
661             $group_object = $group;
662             $group = $group_object->Groupname;
663             if (!defined $group) {
664             die "Groupname is empty in group object";
665             }
666             } else {
667             $group_object = $self->GroupObjClass->new;
668             $group_object->Groupname($group);
669             }
670             $self->connect_if_necessary(sub {
671             $self->_init_groups;
672             my @groups = keys %{ $self->DB->{GROUPS_KEY()} };
673             if (grep { $_ eq $group } @groups) {
674             return ERROR_GROUP_EXISTS;
675             }
676             my $groups = $self->DB->{GROUPS_KEY()};
677             $groups->{$group} = $group_object;
678             $self->DB->{GROUPS_KEY()} = $groups;
679             return ERROR_OK;
680             });
681             }
682              
683             sub get_group_definition {
684             my($self, $group) = @_;
685             $self->connect_if_necessary(sub {
686             $self->_init_groups;
687             my $groupdef = $self->DB->{GROUPS_KEY()}{$group};
688             $groupdef;
689             });
690             }
691              
692             sub set_group_definition {
693             my $self = shift;
694             my($group, $o);
695             if (@_ == 1) {
696             $o = $_[0];
697             $group = $o->Groupname;
698             } else {
699             ($group, $o) = @_;
700             }
701             $self->connect_if_necessary(sub {
702             my $groups = $self->DB->{GROUPS_KEY()};
703             $groups->{$group} = $o;
704             $self->DB->{GROUPS_KEY()} = $groups;
705             $o;
706             });
707             }
708              
709             sub set_password {
710             my($self, $entity_obj, $password) = @_;
711             my $crypted_password = $self->_encrypt($password);
712             $entity_obj->Password($crypted_password);
713             }
714              
715             1;
716              
717             __END__