1 # Vend::UserDB - Interchange user database functions
3 # $Id: UserDB.pm,v 2.66 2009-05-01 13:50:01 pajamian Exp $
5 # Copyright (C) 2002-2008 Interchange Development Group
6 # Copyright (C) 1996-2002 Red Hat, Inc.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
20 $VERSION = substr(q$Revision: 2.66 $, 10);
24 @S_FIELDS @B_FIELDS @P_FIELDS @I_FIELDS
33 no warnings qw(uninitialized numeric);
35 my $ready = new Vend::Safe;
46 ::logGlobal("SHA passwords disabled: $@");
49 # The object encryption methods take three arguments: object, password, and
50 # mystery meat. If called in the context of new_account(), the mystery meat
51 # is the salt (which is not always used). If called in the context of
52 # login(), then the mystery meat is the entire password field from the
53 # database (with salt, if applicable).
55 default => \&enc_default,
57 md5_salted => \&enc_md5_salted,
63 my ($pwd, $salt) = @_;
64 return crypt($pwd, $salt);
69 return Digest::MD5::md5_hex(shift);
72 # This particular md5_salted encryption stores the salt with the password
73 # in colon-separated format: /.+:(..)/. It is compatible with Zen Cart.
74 # Detecting context based on the length of the mystery meat is a little
75 # hokey; it would be more ideal to specify or detect the context
76 # explicitly in/from the object itself (or as a named/separate parameter).
78 my ($obj, $password, $mystery_meat) = @_;
82 my $mystery_meat_length = length $mystery_meat;
83 if ($mystery_meat_length == 35) {
84 # Extract only the salt; we don't need the database password here.
85 my (undef, $db_salt) = split(':', $mystery_meat);
86 $encrypted = Digest::MD5::md5_hex($db_salt . $password);
87 $return_salt = $db_salt;
90 if ($mystery_meat_length != 2) {
91 # Assume the mystery meat is a salt and soldier on anyway.
92 ::logError("Unrecognized salt for md5_salted encryption.");
94 $return_salt = $mystery_meat;
95 $encrypted = Digest::MD5::md5_hex($return_salt . $password);
98 return "$encrypted:$return_salt";
104 $obj->log_either('SHA passwords unavailable. Is Digest::SHA installed?');
107 return Digest::SHA::sha1_hex(shift);
110 # Maps the length of the encrypted data to the algorithm that
111 # produces it. This method will have to be re-evaluated if competing
112 # algorithms are introduced which produce the same-length value.
122 UserDB.pm -- Interchange User Database Functions
126 userdb $function, %options
130 The Interchange user database saves information for users, including shipping,
131 billing, and preference information. It allows the user to return to a
132 previous session without the requirement for a "cookie" or other persistent
135 It is object-oriented and called via the [userdb] usertag, which calls the
138 It restores and manipulates the form values normally stored in the user session
139 values -- the ones set in forms and read through the C<[value variable]> tags.
140 A special function allows saving of shopping cart contents.
142 The preference, billing, and shipping information is keyed so that different
143 sets of information may be saved, providing and "address_book" function that
144 can save more than one shipping and/or billing address. The set to restore
145 is selected by the form values C<s_nickname>, C<b_nickname>, and C<p_nickname>.
153 $obj->login(); # Form values are
154 # mv_username, mv_password
158 $obj->new_account(); # Form values are
159 # mv_username, mv_password, mv_verify
163 $obj->change_pass(); # Form values are
164 # mv_username, mv_password_old, mv_password, mv_verify(new)
166 Get, set user information:
170 $obj->clear_values();
172 Save, restore filed user information:
174 $obj->get_shipping();
175 $obj->set_shipping();
180 $obj->get_preferences();
181 $obj->set_preferences();
186 =head2 Shipping Address Book
188 The shipping address book saves information relevant to shipping the
189 order. In its simplest form, this can be the only address book needed.
190 By default these form values are included:
202 The values are saved with the $obj->set_shipping() method and restored
203 with $obj->get_shipping. A list of the keys available is kept in the
204 form value C<address_book>, suitable for iteration in an HTML select
205 box or in a set of links.
231 The accounts book saves information relevant to billing the
232 order. By default these form values are included:
243 mv_credit_card_exp_month
244 mv_credit_card_exp_year
245 mv_credit_card_reference
247 The values are saved with the $obj->set_billing() method and restored
248 with $obj->get_billing. A list of the keys available is kept in the
249 form value C<accounts>, suitable for iteration in an HTML select
250 box or in a set of links.
271 mv_credit_card_exp_month
272 mv_credit_card_exp_year
273 mv_credit_card_reference
279 Preferences are miscellaneous session information. They include
280 by default the fields C<email>, C<fax>, C<phone_night>,
281 and C<fax_order>. The field C<p_nickname> acts as a key to select
286 There are several database locations that have special purposes. These
287 fields are not saved as user values.
291 =item USERNAME default: username
293 The username or key field of the database table.
295 =item BILLING default: accounts
297 Billing address hash field.
299 =item SHIPPING default: address_book
301 Shipping address hash field.
303 =item PREFERENCES default: preferences
305 Miscellaneous information hash field.
307 =item FEEDBACK default: feedback
309 Customer feedback hash field.
311 =item PRICING default: price_level
313 Customer pricing level marker.
315 =item CARTS default: carts
317 Saved carts hash field.
319 =item PASSWORD default: password
321 Customer password info. If C<crypt> is set, may be encrypted.
323 =item LAST default: mod_time
327 =item EXPIRATION default: expiration
329 Expiration of account.
331 =item OUTBOARD_KEY default: (none)
333 Key information for linking to another table of address or other info.
335 =item GROUPS default: groups
337 Groups they should be logged into.
339 =item SUPER default: super
341 Whether they are a superuser (admin).
343 =item ACL default: acl
345 =item FILE_ACL default: file_acl
347 =item DB_ACL default: db_acl
349 Location of access control information.
351 =item CREATED_DATE_ISO default: (none)
353 =item CREATED_DATE_UNIX default: (none)
355 =item UPDATED_DATE_ISO default: (none)
357 =item UPDATED_DATE_UNIX default: (none)
361 =item MERGED_USER default: (none)
363 The user id of another account this was merged into. If present, and data (should
364 be a valid user id) is present in the field, the user will be logged as that username.
370 # user name and password restrictions
371 $USERNAME_GOOD_CHARS = '[-A-Za-z0-9_@.]';
373 @P_FIELDS = qw ( p_nickname email fax email_copy phone_night mail_list fax_order );
377 s_nickname b_nickname
388 @B_to_S{values %S_to_B} = keys %S_to_B;
392 my ($class, %options) = @_;
395 if( $Vend::Cfg->{UserDB} ) {
396 if( $options{profile} ) {
397 $loc = $Vend::Cfg->{UserDB_repository}{$options{profile}};
400 $options{profile} = 'default';
401 $loc = $Vend::Cfg->{UserDB};
403 $loc = {} unless $loc;
405 while ( ($k,$v) = each %$loc) {
406 $options{$k} = $v unless defined $options{$k};
410 if($options{billing}) {
411 $options{billing} =~ s/[,\s]+$//;
412 $options{billing} =~ s/^[,\s]+//;
413 @B_FIELDS = split /[\s,]+/, $options{billing};
415 if($options{shipping}) {
416 $options{shipping} =~ s/[,\s]+$//;
417 $options{shipping} =~ s/^[,\s]+//;
418 @S_FIELDS = split /[\s,]+/, $options{shipping};
420 if($options{preferences}) {
421 $options{preferences} =~ s/[,\s]+$//;
422 $options{preferences} =~ s/^[,\s]+//;
423 @P_FIELDS = split /[\s,]+/, $options{preferences};
425 if($options{ignore}) {
426 $options{ignore} =~ s/[,\s]+$//;
427 $options{ignore} =~ s/^[,\s]+//;
428 @I_FIELDS = split /[\s,]+/, $options{ignore};
431 USERNAME => $options{username} ||
433 $CGI::values{mv_username} ||
435 OLDPASS => $options{oldpass} || $CGI::values{mv_password_old} || '',
436 PASSWORD => $options{password} || $CGI::values{mv_password} || '',
437 VERIFY => $options{verify} || $CGI::values{mv_verify} || '',
438 NICKNAME => $options{nickname} || '',
439 PROFILE => $options{profile} || '',
441 USERMINLEN => $options{userminlen} || 2,
442 PASSMINLEN => $options{passminlen} || 4,
443 VALIDCHARS => $options{validchars} ? ('[' . $options{validchars} . ']') : $USERNAME_GOOD_CHARS,
444 CRYPT => defined $options{'crypt'}
446 : ! $::Variable->{MV_NO_CRYPT},
447 CGI => ( defined $options{cgi} ? is_yes($options{cgi}) : 1),
449 DB_ID => $options{database} || 'userdb',
450 OPTIONS => \%options,
451 OUTBOARD => $options{outboard} || '',
453 USERNAME => $options{user_field} || 'username',
454 BILLING => $options{bill_field} || 'accounts',
455 SHIPPING => $options{addr_field} || 'address_book',
456 PREFERENCES => $options{pref_field} || 'preferences',
457 FEEDBACK => $options{feedback_field} || 'feedback',
458 PRICING => $options{pricing_field} || 'price_level',
459 ORDERS => $options{ord_field} || 'orders',
460 CARTS => $options{cart_field} || 'carts',
461 PASSWORD => $options{pass_field} || 'password',
462 LAST => $options{time_field} || 'mod_time',
463 EXPIRATION => $options{expire_field} || 'expiration',
464 OUTBOARD_KEY=> $options{outboard_key_col},
465 GROUPS => $options{groups_field}|| 'groups',
466 MERGED_USER => $options{merged_user},
467 SUPER => $options{super_field}|| 'super',
468 ACL => $options{acl} || 'acl',
469 FILE_ACL => $options{file_acl} || 'file_acl',
470 DB_ACL => $options{db_acl} || 'db_acl',
471 CREATED_DATE_ISO => $options{created_date_iso},
472 CREATED_DATE_UNIX => $options{created_date_epoch},
473 UPDATED_DATE_ISO => $options{updated_date_iso},
474 UPDATED_DATE_UNIX => $options{updated_date_epoch},
482 return $self if $options{no_open};
484 set_db($self) or die errmsg("user database %s does not exist.", $self->{DB_ID}) . "\n";
486 return $Vend::user_object = $self;
491 my $user = new Vend::UserDB no_open => 1, %options;
494 push @out, $user->{LOCATION}{USERNAME};
495 push @out, $user->{LOCATION}{PASSWORD};
496 push @out, $user->{LOCATION}{LAST};
497 push @out, @S_FIELDS, @B_FIELDS, @P_FIELDS;
498 push @out, $user->{LOCATION}{ORDERS};
499 push @out, $user->{LOCATION}{SHIPPING};
500 push @out, $user->{LOCATION}{BILLING};
501 push @out, $user->{LOCATION}{PREFERENCES};
504 my $delimiter = $options{delimiter} || "\t";
505 if($delimiter =~ /csv|comma/i) {
509 my $separator = $options{separator} || "\n";
512 print join $delimiter, @out;
515 if ($options{verbose}) {
518 if(length $delimiter == 1) {
519 $msg .= sprintf '\0%o', ord($delimiter);
525 $msg .= "Separator=";
526 if(length $separator == 1) {
527 $msg .= sprintf '\0%o', ord($separator);
532 $msg .= "\nNicknames: ";
533 $msg .= "SHIPPING=$S_FIELDS[0] ";
534 $msg .= "BILLING=$B_FIELDS[0] ";
535 $msg .= "PREFERENCES=$P_FIELDS[0] ";
536 $msg .= "\nFields:\n";
537 $msg .= join "\n", @out;
542 $type = 4, $ext = '.csv', last SWITCH if $csv;
543 $type = 6, last SWITCH if $delimiter eq "\t";
544 $type = 5, last SWITCH if $delimiter eq "|";
545 $type = 3, last SWITCH
546 if $delimiter eq "\n%%\n" && $separator eq "\n%%%\n";
547 $type = 2, last SWITCH
548 if $delimiter eq "\n" && $separator eq "\n\n";
552 my $id = $user->{DB_ID};
553 $msg .= "Database line in catalog.cfg should be:\n\n";
554 $msg .= "Database $id $id.txt $type";
564 if(! $self->{OPTIONS}{logfile}) {
565 return logError($msg);
573 my $time = $self->{OPTIONS}{unix_time} ? time() :
574 POSIX::strftime("%Y%m%d%H%M", localtime());
576 logData( ($self->{OPTIONS}{logfile} || $Vend::Cfg->{LogFile}),
579 $CGI::remote_host || $CGI::remote_addr,
586 my ($self,%options) = @_;
588 if(! defined $self->{PRESENT}{$self->{LOCATION}{ACL}}) {
589 $self->{ERROR} = errmsg('No ACL field present.');
593 if(not $options{location}) {
594 $self->{ERROR} = errmsg('No location to check.');
598 my $acl = $self->{DB}->field($self->{USERNAME}, $self->{LOCATION}{ACL});
599 $acl =~ /(\s|^)$options{location}(\s|$)/;
604 my ($self,%options) = @_;
606 if(!$self->{PRESENT}{$self->{LOCATION}{ACL}}) {
607 $self->{ERROR} = errmsg('No ACL field present.');
611 if(!$options{location}) {
612 $self->{ERROR} = errmsg('No location to set.');
616 my $acl = $self->{DB}->field($self->{USERNAME}, $self->{LOCATION}{ACL});
617 if($options{'delete'}) {
618 $acl =~ s/(\s|^)$options{location}(\s|$)/$1$2/;
621 $acl =~ s/(\s|^)$options{location}(\s|$)/$1$2/;
622 $acl .= " $options{location}";
625 $self->{DB}->set_field( $self->{USERNAME}, $self->{LOCATION}{ACL}, $acl);
626 return $acl if $options{show};
631 my ($self, $loc, %options) = @_;
632 return undef unless $options{location};
633 $options{mode} = 'r' if ! defined $options{mode};
634 my $acl = $self->{DB}->field( $self->{USERNAME}, $loc);
635 my $f = $ready->reval($acl);
636 return undef unless exists $f->{$options{location}};
637 return 1 if ! $options{mode};
638 if($options{mode} =~ /^\s*expire\b/i) {
639 my $cmp = $f->{$options{location}};
640 return $cmp < time() ? '' : 1;
642 return 1 if $f->{$options{location}} =~ /$options{mode}/i;
647 my ($self, $loc, %options) = @_;
648 return undef unless $self->{OPTIONS}{location};
649 if($options{mode} =~ /^\s*expires?\s+(.*)/i) {
650 $options{mode} = adjust_time($1);
652 my $acl = $self->{DB}->field( $self->{USERNAME}, $loc );
653 my $f = $ready->reval($acl) || {};
654 if($options{'delete'}) {
655 delete $f->{$options{location}};
658 $f->{$options{location}} = $options{mode} || 'rw';
660 my $return = $self->{DB}->set_field( $self->{USERNAME}, $loc, uneval_it($f) );
661 return $return if $options{show};
667 return $self->_set_acl($self->{LOCATION}{FILE_ACL}, @_);
672 return $self->_set_acl($self->{LOCATION}{DB_ACL}, @_);
677 return $self->_check_acl($self->{LOCATION}{FILE_ACL}, @_);
682 return $self->_check_acl($self->{LOCATION}{DB_ACL}, @_);
686 my($self, $database) = @_;
688 $database = $self->{DB_ID} unless $database;
690 $Vend::WriteDatabase{$database} = 1;
692 my $db = database_exists_ref($database);
693 return undef unless defined $db;
696 my @fields = $db->columns();
705 if($self->{OPTIONS}{username_email}) {
706 $ignore{$self->{OPTIONS}{username_email_field} || 'email'} = 1;
709 for(values %{$self->{LOCATION}}) {
713 if($self->{OPTIONS}{force_lower}) {
714 @fields = map { lc $_ } @fields;
719 $self->{PRESENT}->{$_} = 1;
725 $self->{DB_FIELDS} = \@final;
729 # Sets location map, returns old value
731 my ($self, $location, $field) = @_;
732 if(! defined $field) {
733 return $self->{LOCATION}->{$location};
736 my $old = $self->{LOCATION}->{$field};
737 $self->{LOCATION}->{$location} = $field;
743 my($self, @fields) = @_;
745 @fields = @{ $self->{DB_FIELDS} } unless @fields;
751 if($self->{OPTIONS}->{constant}) {
752 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{constant} ;
754 my ($k, $v) = split /=/, $_;
760 if($self->{OPTIONS}->{scratch}) {
761 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{scratch} ;
763 my ($k, $v) = split /=/, $_;
769 if($self->{OPTIONS}->{session_hash}) {
770 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{session_hash} ;
772 my ($k, $v) = split /=/, $_;
774 $session_hash{$k} = $v;
779 if(my $s = $scratch{$_}) {
780 if (exists $Vend::Cfg->{ScratchDefault}->{$s}) {
781 $::Scratch->{$s} = $Vend::Cfg->{ScratchDefault}->{$s};
784 delete $::Scratch->{$s};
787 elsif($constant{$_}) {
788 delete $Vend::Session->{constant}{$constant{$_}};
790 elsif($session_hash{$_}) {
791 delete $Vend::Session->{$session_hash{$_}};
794 if (exists $Vend::Cfg->{ValuesDefault}->{$_}) {
795 $::Values->{$_} = $Vend::Cfg->{ValuesDefault}->{$_};
798 delete $::Values->{$_};
800 delete $CGI::values{$_};
808 my($self, $valref, $scratchref) = @_;
810 $valref = $::Values unless ref($valref);
811 $scratchref = $::Scratch unless ref($scratchref);
812 my $constref = $Vend::Session->{constant} ||= {};
814 my @fields = @{ $self->{DB_FIELDS} };
816 if($self->{OPTIONS}{username_email}) {
817 push @fields, $self->{OPTIONS}{username_email_field} || 'email';
821 or die errmsg("No user database found.");
823 unless ( $db->record_exists($self->{USERNAME}) ) {
824 $self->{ERROR} = errmsg("username %s does not exist.", $self->{USERNAME});
833 for(values %{$self->{LOCATION}}) {
838 if($self->{OUTBOARD}) {
839 %outboard = split /[\s=,]+/, $self->{OUTBOARD};
840 push @fields, keys %outboard;
843 if($self->{OPTIONS}->{constant}) {
844 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{constant} ;
846 my ($k, $v) = split /=/, $_;
850 #::logDebug("constant ones: " . join " ", @s);
853 if($self->{OPTIONS}->{session_hash}) {
854 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{session_hash} ;
856 my ($k, $v) = split /=/, $_;
858 $session_hash{$k} = $v;
860 #::logDebug("session_hash ones: " . join " ", @s);
863 if($self->{OPTIONS}->{scratch}) {
864 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{scratch} ;
866 my ($k, $v) = split /=/, $_;
870 #::logDebug("scratch ones: " . join " ", @s);
874 my $row = $db->row_hash($self->{USERNAME});
875 my $outkey = $self->{LOCATION}->{OUTBOARD_KEY}
876 ? $row->{$self->{LOCATION}->{OUTBOARD_KEY}}
879 if(my $ef = $self->{OPTIONS}->{extra_fields}) {
880 my @s = grep /\w/, split /[\s,]+/, $ef;
881 my $field = $self->{LOCATION}{PREFERENCES};
882 my $loc = $self->{OPTIONS}{extra_selector} || 'default';
883 my $hash = get_option_hash($row->{$field});
884 if($hash and $hash = $hash->{$loc} and ref($hash) eq 'HASH') {
886 $::Values->{$_} = $hash->{$_};
893 $self->{PRESENT}->{$_} = 1;
898 my ($t, $c, $k) = split /:+/, $outboard{$_};
899 $val = ::tag_data($t, ($c || $_), $outkey, { foreign => $k });
906 if($k = $scratch{$_}) {
907 $scratchref->{$k} = $val;
910 elsif($k = $constant{$_}) {
911 $constref->{$k} = $val;
914 elsif($k = $session_hash{$_}) {
915 $Vend::Session->{$k} = string_to_ref($val) || {};
918 $valref->{$_} = $val;
923 foreach $area (qw!SHIPPING BILLING PREFERENCES CARTS!) {
924 my $f = $self->{LOCATION}->{$area};
925 if ($self->{PRESENT}->{$f}) {
926 my $s = $self->get_hash($area);
927 die errmsg("Bad structure in %s: %s", $f, $@) if $@;
928 $::Values->{$f} = join "\n", sort keys %$s;
936 my($self, $valref, $scratchref) = @_;
938 $valref = $::Values unless ref($valref);
939 $scratchref = $::Scratch unless ref($scratchref);
941 my $user = $self->{USERNAME};
943 my @fields = @{$self->{DB_FIELDS}};
945 my $db = $self->{DB};
947 unless ( $db->record_exists($self->{USERNAME}) ) {
948 $self->{ERROR} = errmsg("username %s does not exist.", $self->{USERNAME});
955 if($self->{OPTIONS}->{scratch}) {
956 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{scratch} ;
958 my ($k, $v) = split /=/, $_;
964 if($self->{OPTIONS}->{constant}) {
965 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{constant} ;
967 my ($k, $v) = split /=/, $_;
973 if($self->{OPTIONS}->{session_hash}) {
974 my (@s) = grep /\w/, split /[\s,]+/, $self->{OPTIONS}{session_hash} ;
976 my ($k, $v) = split /=/, $_;
978 $session_hash{$k} = $v;
984 if($self->{OUTBOARD}) {
985 %outboard = split /[\s=,]+/, $self->{OUTBOARD};
986 push @fields, keys %outboard;
996 if(my $ef = $self->{OPTIONS}->{extra_fields}) {
997 my $row = $db->row_hash($user);
998 my @s = grep /\w/, split /[\s,]+/, $ef;
999 my $field = $self->{LOCATION}{PREFERENCES};
1000 my $loc = $self->{OPTIONS}{extra_selector} || 'default';
1001 my $hash = get_option_hash( $row->{$field} ) || {};
1003 my $subhash = $hash->{$loc} ||= {};
1005 $subhash->{$_} = $valref->{$_};
1008 push @extra, $field;
1009 push @extra, uneval_it($hash);
1013 #::logDebug("set_values saving $_ as $valref->{$_}\n");
1016 if ($k = $scratch{$_}) {
1017 $val = $scratchref->{$k}
1018 if defined $scratchref->{$k};
1020 elsif ($constant{$_}) {
1021 # we never store constants
1024 elsif ($k = $session_hash{$_}) {
1025 $val = uneval_it($Vend::Session->{$k});
1028 $val = $valref->{$_}
1029 if defined $valref->{$_};
1032 next if ! defined $val;
1035 my ($t, $c, $k) = split /:+/, $outboard{$_};
1036 ::tag_data($t, ($c || $_), $self->{USERNAME}, { value => $val, foreign => $k });
1038 elsif ($db->test_column($_)) {
1044 "cannot set unknown userdb field %s to: %s",
1054 if($dfield = $self->{OPTIONS}{updated_date_iso}) {
1055 if($self->{OPTIONS}{updated_date_gmtime}) {
1056 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%SZ', gmtime());
1058 elsif($self->{OPTIONS}{updated_date_showzone}) {
1059 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S %z', localtime());
1062 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime());
1065 elsif($dfield = $self->{OPTIONS}{updated_date_epoch}) {
1069 if($dfield and $dstring) {
1070 if($db->test_column($dfield)) {
1071 push @bfields, $dfield;
1072 push @bvals, $dstring;
1075 my $msg = errmsg("updated field %s doesn't exist", $dfield);
1076 Vend::Tags->warnings($msg);
1081 push @bfields, shift @extra;
1082 push @bvals, shift @extra;
1085 #::logDebug("bfields=" . ::uneval(\@bfields));
1086 #::logDebug("bvals=" . ::uneval(\@bvals));
1088 $db->set_slice($user, \@bfields, \@bvals);
1093 my $msg = errmsg("error saving values in userdb: %s", $@);
1094 $self->{ERROR} = $msg;
1099 # Changes made to support Accounting Interface.
1101 if(my $l = $Vend::Cfg->{Accounting}) {
1104 while ($indexvar <= (scalar @bfields)) {
1105 $hashvar{ $bfields[$indexvar] } = $bvals[$indexvar];
1109 my $class = $l->{Class};
1116 "Failed to save customer data with accounting system %s: %s",
1121 my $returnval = $obj->save_customer_data($user, \%hashvar);
1129 my $ref = $self->set_hash('BILLING', @B_FIELDS );
1135 my $ref = $self->set_hash('SHIPPING', @S_FIELDS );
1139 sub set_preferences {
1141 my $ref = $self->set_hash('PREFERENCES', @P_FIELDS );
1147 my $ref = $self->get_hash('SHIPPING', @S_FIELDS );
1153 my $ref = $self->get_hash('BILLING', @B_FIELDS );
1157 sub get_preferences {
1159 my $ref = $self->get_hash('PREFERENCES', @P_FIELDS );
1163 sub get_shipping_names {
1165 my $ref = $self->get_hash('SHIPPING');
1166 return undef unless ref $ref;
1167 $::Values->{$self->{LOCATION}{SHIPPING}} = join "\n", sort keys %$ref;
1168 return $::Values->{$self->{LOCATION}{SHIPPING}} if $self->{OPTIONS}{show};
1172 sub get_shipping_hashref {
1174 my $ref = $self->get_hash('SHIPPING');
1175 return $ref if ref($ref) eq 'HASH';
1179 sub get_billing_names {
1181 my $ref = $self->get_hash('BILLING');
1182 return undef unless ref $ref;
1183 $::Values->{$self->{LOCATION}{BILLING}} = join "\n", sort keys %$ref;
1184 return $::Values->{$self->{LOCATION}{BILLING}} if $self->{OPTIONS}{show};
1188 sub get_billing_hashref {
1190 my $ref = $self->get_hash('BILLING');
1191 return $ref if ref($ref) eq 'HASH';
1195 sub get_preferences_names {
1197 my $ref = $self->get_hash('PREFERENCES');
1198 return undef unless ref $ref;
1199 $::Values->{$self->{LOCATION}{PREFERENCES}} = join "\n", sort keys %$ref;
1200 return $::Values->{$self->{LOCATION}{PREFERENCES}} if $self->{OPTIONS}{show};
1204 sub get_cart_names {
1206 my $ref = $self->get_hash('CARTS');
1207 return undef unless ref $ref;
1208 $::Values->{$self->{LOCATION}{CARTS}} = join "\n", sort keys %$ref;
1209 return $::Values->{$self->{LOCATION}{CARTS}} if $self->{OPTIONS}{show};
1213 sub delete_billing {
1215 $self->delete_nickname('BILLING', @B_FIELDS );
1221 $self->delete_nickname('CARTS', $self->{NICKNAME});
1225 sub delete_shipping {
1227 $self->delete_nickname('SHIPPING', @S_FIELDS );
1231 sub delete_preferences {
1233 $self->delete_nickname('PREFERENCES', @P_FIELDS );
1237 sub delete_nickname {
1238 my($self, $name, @fields) = @_;
1240 die errmsg("no fields?") unless @fields;
1241 die errmsg("no name?") unless $name;
1243 $self->get_hash($name) unless ref $self->{$name};
1245 my $nick_field = shift @fields;
1246 my $nick = $self->{NICKNAME} || $::Values->{$nick_field};
1248 delete $self->{$name}{$nick};
1250 my $field_name = $self->{LOCATION}->{$name};
1251 unless($self->{PRESENT}->{$field_name}) {
1252 $self->{ERROR} = errmsg('%s field not present to set %s', $field_name, $name);
1256 my $s = uneval_it($self->{$name});
1258 $self->{DB}->set_field( $self->{USERNAME}, $field_name, $s);
1260 return ($s, $self->{$name});
1264 my($self, $name, @fields) = @_;
1266 die errmsg("no fields?") unless @fields;
1267 die errmsg("no name?") unless $name;
1269 $self->get_hash($name) unless ref $self->{$name};
1271 my $nick_field = shift @fields;
1272 my $nick = $self->{NICKNAME} || $::Values->{$nick_field};
1273 $nick =~ s/^[\0\s]+//;
1274 $nick =~ s/[\0\s]+.*//;
1275 $::Values->{$nick_field} = $nick;
1276 $CGI::values{$nick_field} = $nick if $self->{CGI};
1278 die errmsg("no nickname?") unless $nick;
1280 $self->{$name}{$nick} = {} unless $self->{OPTIONS}{keep}
1281 and defined $self->{$name}{$nick};
1284 $self->{$name}{$nick}{$_} = $::Values->{$_}
1285 if defined $::Values->{$_};
1288 my $field_name = $self->{LOCATION}->{$name};
1289 unless($self->{PRESENT}->{$field_name}) {
1290 $self->{ERROR} = errmsg('%s field not present to set %s', $field_name, $name);
1294 my $s = uneval_it($self->{$name});
1296 $self->{DB}->set_field( $self->{USERNAME}, $field_name, $s);
1298 return ($s, $self->{$name});
1302 my($self, $name, @fields) = @_;
1304 my $field_name = $self->{LOCATION}->{$name};
1308 die errmsg("no name?") unless $name;
1309 die errmsg("%s field not present to get %s", $field_name, $name) . "\n"
1310 unless $self->{PRESENT}->{$field_name};
1312 $s = $self->{DB}->field( $self->{USERNAME}, $field_name);
1315 $self->{$name} = string_to_ref($s);
1316 die errmsg("Bad structure in %s: %s", $field_name, $@) if $@;
1319 $self->{$name} = {};
1322 die errmsg("eval failed?") . "\n" unless ref $self->{$name};
1326 $self->{ERROR} = $@;
1330 return $self->{$name} unless @fields;
1333 my $nick_field = shift @fields;
1334 $nick = $self->{NICKNAME} || $::Values->{$nick_field};
1335 $nick =~ s/^[\0\s]+//;
1336 $nick =~ s/[\0\s]+.*//;
1337 $::Values->{$nick_field} = $nick;
1338 $CGI::values{$nick_field} = $nick if $self->{CGI};
1339 die errmsg("no nickname?") unless $nick;
1343 $self->{ERROR} = $@;
1347 $self->{$name}->{$nick} = {} unless defined $self->{$name}{$nick};
1350 delete $::Values->{$_};
1351 $::Values->{$_} = $self->{$name}{$nick}{$_}
1352 if defined $self->{$name}{$nick}{$_};
1353 next unless $self->{CGI};
1354 $CGI::values{$_} = $::Values->{$_};
1356 ::update_user() if $self->{CGI};
1357 return $self->{$name}{$nick};
1367 my ($user_data, $pw);
1369 # Show this generic error message on login page to avoid
1370 # helping would-be intruders
1371 my $stock_error = errmsg("Invalid user name or password.");
1375 $self = new Vend::UserDB %options;
1378 if($Vend::Cfg->{CookieLogin}) {
1379 $self->{USERNAME} = Vend::Util::read_cookie('MV_USERNAME')
1380 if ! $self->{USERNAME};
1381 $self->{PASSWORD} = Vend::Util::read_cookie('MV_PASSWORD')
1382 if ! $self->{PASSWORD};
1385 if ($self->{VALIDCHARS} !~ / /) {
1386 # If space isn't a valid character in usernames,
1387 # be nice and strip leading and trailing whitespace.
1388 $self->{USERNAME} =~ s/^\s+//;
1389 $self->{USERNAME} =~ s/\s+$//;
1392 if ($self->{OPTIONS}{ignore_case}) {
1393 $self->{PASSWORD} = lc $self->{PASSWORD};
1394 $self->{USERNAME} = lc $self->{USERNAME};
1397 # We specifically check for login attempts with group names to see if
1398 # anyone is trying to exploit a former vulnerability in the demo catalog.
1399 if ($self->{USERNAME} =~ /^:/) {
1400 $self->log_either(errmsg("Denied attempted login with group name '%s'",
1401 $self->{USERNAME}));
1402 die $stock_error, "\n";
1405 # Username must be long enough
1406 if (length($self->{USERNAME}) < $self->{USERMINLEN}) {
1407 $self->log_either(errmsg("Denied attempted login for user name '%s'; must have at least %s characters",
1408 $self->{USERNAME}, $self->{USERMINLEN}));
1409 die $stock_error, "\n";
1412 # Username must contain only valid characters
1413 if ($self->{USERNAME} !~ m{^$self->{VALIDCHARS}+$}) {
1414 $self->log_either(errmsg("Denied attempted login for user name '%s' with illegal characters",
1415 $self->{USERNAME}));
1416 die $stock_error, "\n";
1419 # Fail if password is too short
1420 if (length($self->{PASSWORD}) < $self->{PASSMINLEN}) {
1421 $self->log_either(errmsg("Denied attempted login with user name '%s' and password less than %s characters",
1422 $self->{USERNAME}, $self->{PASSMINLEN}));
1423 die $stock_error, "\n";
1426 my $udb = $self->{DB};
1427 my $foreign = $self->{OPTIONS}{indirect_login};
1430 my $uname = ($self->{PASSED_USERNAME} ||= $self->{USERNAME});
1431 my $ufield = $self->{LOCATION}{USERNAME};
1432 $uname = $udb->quote($uname);
1433 my $q = "select $ufield from $self->{DB_ID} where $foreign = $uname";
1434 #::logDebug("indirect login query: $q");
1435 my $ary = $udb->query($q)
1437 my $msg = errmsg( "Database access error for query: %s", $q);
1442 $self->log_either(errmsg(
1443 @$ary ? "Denied attempted login with ambiguous (indirect from %s) user name %s" : "Denied attempted login with nonexistent (indirect from %s) user name %s",
1448 die $stock_error, "\n";
1450 $self->{USERNAME} = $ary->[0][0];
1453 # If not superuser, an entry must exist in access database
1454 unless ($Vend::superuser) {
1455 unless ($udb->record_exists($self->{USERNAME})) {
1456 $self->log_either(errmsg("Denied attempted login with nonexistent user name '%s'",
1457 $self->{USERNAME}));
1458 die $stock_error, "\n";
1460 unless ($user_data = $udb->row_hash($self->{USERNAME})) {
1461 $self->log_either(errmsg("Login denied after failed fetch of user data for user '%s'",
1462 $self->{USERNAME}));
1463 die $stock_error, "\n";
1465 my $db_pass = $user_data->{ $self->{LOCATION}{PASSWORD} };
1467 $self->log_either(errmsg("Refusing to use blank password from '%s' database for user '%s'", $self->{DB_ID}, $self->{USERNAME}));
1468 die $stock_error, "\n";
1470 $pw = $self->{PASSWORD};
1472 if ( $self->{CRYPT} && $self->{OPTIONS}{promote} ) {
1473 my ($cur_method) = grep { $self->{OPTIONS}{ $_ } } keys %enc_subs;
1474 $cur_method ||= 'default';
1476 my $stored_by = $enc_id{ length($db_pass) };
1479 $cur_method ne $stored_by
1481 $db_pass eq $enc_subs{$stored_by}->($self, $pw, $db_pass)
1484 my $newpass = $enc_subs{$cur_method}->($self, $pw, $db_pass);
1485 my $db_newpass = eval {
1486 $self->{DB}->set_field(
1488 $self->{LOCATION}{PASSWORD},
1493 if ($db_newpass ne $newpass) {
1494 # Usually, an error in the update will cause $db_newpass to be set to a
1495 # useful error string. The usefulness is dependent on DB store itself, though.
1496 my $err_msg = qq{Could not update database "%s" field "%s" with promoted password due to error:\n}
1498 . qq{Check that field "%s" is at least %s characters wide.\n};
1499 $err_msg = ::errmsg(
1502 $self->{LOCATION}{PASSWORD},
1504 $self->{LOCATION}{PASSWORD},
1507 ::logError($err_msg);
1510 $db_pass = $newpass;
1514 if ($self->{CRYPT}) {
1515 $self->{PASSWORD} = $self->do_crypt($pw, $db_pass);
1518 $db_pass = lc $db_pass if $self->{OPTIONS}{ignore_case};
1520 #::logDebug(errmsg("crypt: %s", $self->{CRYPT}));
1521 #::logDebug(errmsg("ignore_case: %s", $self->{OPTIONS}{ignore_case}));
1522 #::logDebug(errmsg("given password: %s", $self->{PASSWORD}));
1523 #::logDebug(errmsg("stored password: %s", $db_pass));
1524 unless ($self->{PASSWORD} eq $db_pass) {
1525 $self->log_either(errmsg("Denied attempted login by user '%s' with incorrect password",
1526 $self->{USERNAME}));
1527 die $stock_error, "\n";
1529 $self->log_either(errmsg("Successful login by user '%s'", $self->{USERNAME}));
1532 if($self->{PRESENT}->{ $self->{LOCATION}{EXPIRATION} } ) {
1535 $cmp = POSIX::strftime("%Y%m%d%H%M", localtime($now))
1536 unless $self->{OPTIONS}->{unix_time};
1537 my $exp = $udb->field(
1539 $self->{LOCATION}{EXPIRATION},
1541 die errmsg("Expiration date not set.") . "\n"
1542 if ! $exp and $self->{EMPTY_EXPIRE_FATAL};
1543 if($exp and $exp < $cmp) {
1544 die errmsg("Expired %s.", $exp) . "\n";
1548 if($self->{PRESENT}->{ $self->{LOCATION}{MERGED_USER} } ) {
1549 my $old = $self->{USERNAME};
1550 my $new = $udb->field(
1552 $self->{LOCATION}{MERGED_USER},
1555 $self->{USERNAME} = $new;
1556 my $msg = errmsg('%s logged in as user %s, merged.', $old, $new);
1557 Vend::Tags->warnings($msg);
1558 $self->log_either($msg);
1562 if($self->{PRESENT}->{ $self->{LOCATION}{GROUPS} } ) {
1564 = $Vend::Session->{groups}
1567 $self->{LOCATION}{GROUPS},
1571 username_cookies($self->{PASSED_USERNAME} || $self->{USERNAME}, $pw)
1572 if $Vend::Cfg->{CookieLogin};
1574 if ($self->{LOCATION}{LAST} ne 'none') {
1577 unless($self->{OPTIONS}{null_time}) {
1578 $login_time = $self->{OPTIONS}{iso_time}
1579 ? POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime($now))
1583 $udb->set_field( $self->{USERNAME},
1584 $self->{LOCATION}{LAST},
1589 my $msg = errmsg("Failed to record timestamp in UserDB: %s", $@);
1594 $self->log('login') if $options{'log'};
1596 $self->get_values() unless $self->{OPTIONS}{no_get};
1603 $self->{ERROR} = $@;
1606 logError( "Vend::UserDB error: %s\n", $@ );
1614 unless $self->{LOCATION}{PRICING}
1615 and $pprof = $user_data->{ $self->{LOCATION}{PRICING} };
1617 Vend::Interpolate::tag_profile(
1619 { tag => $self->{OPTIONS}{profile} },
1623 $Vend::login_table = $Vend::Session->{login_table} = $self->{DB_ID};
1624 $Vend::username = $Vend::Session->{username} = $self->{USERNAME};
1625 $Vend::Session->{logged_in} = 1;
1627 if (my $macros = $self->{OPTIONS}{postlogin_action}) {
1629 Vend::Dispatch::run_macro $macros;
1632 logError("UserDB postlogin_action execution error: %s\n", $@);
1640 for(qw/ mv_password mv_verify mv_password_old /) {
1641 delete $CGI::values{$_};
1642 delete $::Values->{$_};
1647 my $self = shift or return undef;
1650 my $opt = $self->{OPTIONS};
1652 if( is_yes($opt->{clear}) ) {
1653 $self->clear_values();
1656 Vend::Interpolate::tag_profile("", { restore => 1 });
1669 delete $Vend::Session->{$_};
1670 undef ${"Vend::$_"};
1673 delete $CGI::values{mv_username};
1674 delete $::Values->{mv_username};
1675 $self->log('logout') if $opt->{log};
1676 $self->{MESSAGE} = errmsg('Logged out.');
1677 if ($opt->{clear_cookie}) {
1678 my @cookies = split /[\s,\0]+/, $opt->{clear_cookie};
1681 Vend::Util::set_cookie($_, '', $exp);
1684 if ($opt->{clear_session}) {
1685 Vend::Session::init_session();
1692 my ($self, $original_self);
1699 if ($self->{OPTIONS}{ignore_case}) {
1700 $self->{USERNAME} = lc $self->{USERNAME};
1701 $self->{OLDPASS} = lc $self->{OLDPASS};
1702 $self->{PASSWORD} = lc $self->{PASSWORD};
1703 $self->{VERIFY} = lc $self->{VERIFY};
1707 # Create copies so that ignore_case doesn't lc the originals.
1708 my $vend_username = $Vend::username;
1709 my $cgi_mv_username = $CGI::values{mv_username};
1710 if ($self->{OPTIONS}{ignore_case}) {
1711 $vend_username = lc $vend_username;
1712 $cgi_mv_username = lc $cgi_mv_username
1713 if defined $cgi_mv_username;
1716 # Database operations still use the mixed-case original.
1717 my $super = $Vend::superuser || (
1719 $self->{DB}->field($Vend::username, $self->{LOCATION}{SUPER})
1722 if ($self->{USERNAME} ne $vend_username or
1723 defined $cgi_mv_username and
1724 $self->{USERNAME} ne $cgi_mv_username
1727 if ($cgi_mv_username and
1728 $cgi_mv_username ne $self->{USERNAME}) {
1729 $original_self = $self;
1730 $options{username} = $cgi_mv_username;
1734 errmsg("Unprivileged user '%s' attempted to change password of user '%s'",
1735 $vend_username, $self->{USERNAME}) if $options{log};
1736 die errmsg("You are not allowed to change another user's password.");
1741 $self = new Vend::UserDB %options;
1744 die errmsg("Bad object.") unless defined $self;
1746 die errmsg("'%s' not a user.", $self->{USERNAME}) . "\n"
1747 unless $self->{DB}->record_exists($self->{USERNAME});
1749 unless ($super and $self->{USERNAME} ne $Vend::username) {
1750 my $db_pass = $self->{DB}->field($self->{USERNAME}, $self->{LOCATION}{PASSWORD});
1751 if ($self->{CRYPT}) {
1752 $self->{OLDPASS} = $self->do_crypt($self->{OLDPASS}, $db_pass);
1754 die errmsg("Must have old password.") . "\n"
1755 if $self->{OLDPASS} ne $db_pass;
1758 die errmsg("Must enter at least %s characters for password.",
1759 $self->{PASSMINLEN}) . "\n"
1760 if length($self->{PASSWORD}) < $self->{PASSMINLEN};
1761 die errmsg("Password and check value don't match.") . "\n"
1762 unless $self->{PASSWORD} eq $self->{VERIFY};
1764 if ( $self->{CRYPT} ) {
1765 $self->{PASSWORD} = $self->do_crypt(
1767 Vend::Util::random_string(2),
1771 my $pass = $self->{DB}->set_field(
1773 $self->{LOCATION}{PASSWORD},
1776 die errmsg("Database access error.") . "\n" unless defined $pass;
1777 $self->log(errmsg('change password')) if $options{'log'};
1782 $self = $original_self if $original_self;
1786 $self->{ERROR} = $@;
1787 $self->log(errmsg('change password failed')) if $options{'log'};
1790 logError( "Vend::UserDB error: %s", $@ );
1798 sub assign_username {
1800 my $file = shift || $self->{OPTIONS}{counter};
1801 my $start = $self->{OPTIONS}{username} || 'U00000';
1802 $file = './etc/username.counter' if ! $file;
1804 my $o = { start => $start, sql => $self->{OPTIONS}{sql_counter} };
1808 if(my $l = $Vend::Cfg->{Accounting}) {
1810 my $class = $l->{Class};
1812 my $assign = defined $l->{assign_username} ? $l->{assign_username} : 1;
1815 #::logDebug("Accounting class is $class");
1820 #::logDebug("Accounting object is $obj");
1824 "Failed to assign new customer number with accounting system %s",
1828 $custno = $obj->assign_customer_number();
1830 #::logDebug("assigned new customer number $custno");
1833 return $custno || Vend::Interpolate::tag_counter($file, $o);
1847 $self = new Vend::UserDB %options;
1850 delete $Vend::Session->{auto_created_user};
1852 die errmsg("Bad object.") . "\n" unless defined $self;
1854 die errmsg("Already logged in. Log out first.") . "\n"
1855 if $Vend::Session->{logged_in} and ! $options{no_login};
1856 die errmsg("Sorry, reserved user name.") . "\n"
1857 if $self->{OPTIONS}{username_mask}
1858 and $self->{USERNAME} =~ m!$self->{OPTIONS}{username_mask}!;
1859 die errmsg("Sorry, user name must be an email address.") . "\n"
1860 if $self->{OPTIONS}{username_email}
1861 and $self->{USERNAME} !~ m!^[[:alnum:]]([.]?([[:alnum:]._-]+)*)?@([[:alnum:]\-_]+\.)+[a-zA-Z]{2,4}$!;
1862 die errmsg("Must enter at least %s characters for password.",
1863 $self->{PASSMINLEN}) . "\n"
1864 if length($self->{PASSWORD}) < $self->{PASSMINLEN};
1865 die errmsg("Password and check value don't match.") . "\n"
1866 unless $self->{PASSWORD} eq $self->{VERIFY};
1868 if ($self->{OPTIONS}{ignore_case}) {
1869 $self->{PASSWORD} = lc $self->{PASSWORD};
1870 $self->{USERNAME} = lc $self->{USERNAME};
1873 my $pw = $self->{PASSWORD};
1874 if($self->{CRYPT}) {
1876 $pw = $self->do_crypt($pw, Vend::Util::random_string(2));
1880 my $udb = $self->{DB};
1882 if($self->{OPTIONS}{assign_username}) {
1883 $self->{PASSED_USERNAME} = $self->{USERNAME};
1884 $self->{USERNAME} = $self->assign_username();
1885 $self->{USERNAME} = lc $self->{USERNAME}
1886 if $self->{OPTIONS}{ignore_case};
1888 # plain error message without user-supplied username
1889 # to avoid XSS exploit (RT #306)
1890 die errmsg("Username contains illegal characters.") . "\n"
1891 if $self->{USERNAME} !~ m{^$self->{VALIDCHARS}+$};
1892 die errmsg("Must have at least %s characters in username.",
1893 $self->{USERMINLEN}) . "\n"
1894 if length($self->{USERNAME}) < $self->{USERMINLEN};
1896 if($self->{OPTIONS}{captcha}) {
1897 my $status = Vend::Tags->captcha( { function => 'check' });
1898 die errmsg("Must input captcha code correctly.") . "\n"
1902 # Here we put the username in a non-primary key field, checking
1904 my $foreign = $self->{OPTIONS}{indirect_login};
1906 my $uname = ($self->{PASSED_USERNAME} ||= $self->{USERNAME});
1907 $uname = $udb->quote($uname);
1908 my $q = "select $foreign from $self->{DB_ID} where $foreign = $uname";
1909 my $ary = $udb->query($q)
1911 my $msg = errmsg( "Database access error for query: %s", $q);
1916 my $msg = errmsg( "Username already exists (indirect).");
1921 if ($udb->record_exists($self->{USERNAME})) {
1922 die errmsg("Username already exists.") . "\n";
1929 $self->{PASSED_USERNAME},
1931 or die errmsg("Database access error.");
1934 my $pass = $udb->set_field(
1936 $self->{LOCATION}{PASSWORD},
1940 die errmsg("Database access error.") . "\n" unless defined $pass;
1942 if($self->{OPTIONS}{username_email}) {
1943 my $field_name = $self->{OPTIONS}{username_email_field} || 'email';
1944 $::Values->{$field_name} ||= $self->{USERNAME};
1950 or die errmsg("Database access error: %s", $udb->errstr) . "\n";
1955 if($dfield = $self->{OPTIONS}{created_date_iso}) {
1956 if($self->{OPTIONS}{created_date_gmtime}) {
1957 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%SZ', gmtime());
1959 elsif($self->{OPTIONS}{created_date_showzone}) {
1960 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S %z', localtime());
1963 $dstring = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime());
1966 elsif($dfield = $self->{OPTIONS}{created_date_epoch}) {
1970 if($dfield and $dstring) {
1977 my $msg = errmsg('Failed to set new account creation date: %s', $udb->errstr);
1978 Vend::Tags->warnings($msg);
1982 if($options{no_login}) {
1983 $Vend::Session->{auto_created_user} = $self->{USERNAME};
1986 $self->set_values() unless $self->{OPTIONS}{no_set};
1987 $self->{USERNAME} = $foreign if $foreign;
1988 username_cookies($self->{USERNAME}, $pw)
1989 if $Vend::Cfg->{CookieLogin};
1991 $self->log('new account') if $options{'log'};
1994 "Cannot log in after new account creation: %s",
2004 $self->{ERROR} = $@;
2007 logError( "Vend::UserDB error: %s\n", $@ );
2015 sub username_cookies {
2016 my ($user, $pw) = @_;
2018 $CGI::values{mv_cookie_password} or
2019 $CGI::values{mv_cookie_username} or
2020 Vend::Util::read_cookie('MV_PASSWORD') or
2021 Vend::Util::read_cookie('MV_USERNAME');
2022 $::Instance->{Cookies} = [] unless defined $::Instance->{Cookies};
2023 my $exp = time() + $Vend::Cfg->{SaveExpire};
2024 push @{$::Instance->{Cookies}},
2025 ['MV_USERNAME', $user, $exp];
2027 $CGI::values{mv_cookie_password} or
2028 Vend::Util::read_cookie('MV_PASSWORD');
2029 push @{$::Instance->{Cookies}},
2030 ['MV_PASSWORD', $pw, $exp];
2035 my($self, %options) = @_;
2037 my $from = $self->{NICKNAME};
2040 my $opt = $self->{OPTIONS};
2042 if ($opt->{target}) {
2043 $to = ($::Carts->{$opt->{target}} ||= []);
2049 #::logDebug ("to=$to nick=$opt->{target} from=$from cart=" . ::uneval_it($from));
2051 my $field_name = $self->{LOCATION}->{CARTS};
2055 die errmsg("no from cart name?") unless $from;
2056 die errmsg("%s field not present to get %s", $field_name, $from) . "\n"
2057 unless $self->{PRESENT}->{$field_name};
2059 my $s = $self->{DB}->field( $self->{USERNAME}, $field_name);
2061 die errmsg("no saved carts.") . "\n" unless $s;
2063 my @carts = split /\0/, $from;
2064 my $d = string_to_ref($s);
2065 #::logDebug ("saved carts=" . ::uneval_it($d));
2067 die errmsg("eval failed?") unless ref $d;
2070 die errmsg("source cart '%s' does not exist.", $from) . "\n" unless ref $d->{$_};
2071 push @$cart, @{$d->{$_}};
2077 $self->{ERROR} = $@;
2080 #::logDebug ("to=$to nick=$opt->{target} from=$from cart=" . ::uneval_it($cart));
2083 $to = [] unless ref $to;
2090 next unless $master = $_->{mv_mi};
2092 $max = $master if $master > $max;
2101 next unless $master = $_->{mv_mi};
2102 next unless $used{$master};
2105 $alias{$master} = $max++;
2106 $_->{mv_mi} = $alias{$master};
2109 $_->{mv_mi} = $alias{$master};
2122 my($self, %options) = @_;
2125 my $to = $self->{NICKNAME};
2127 my $opt = $self->{OPTIONS};
2129 if ($opt->{source}) {
2130 $from = $::Carts->{$opt->{source}} || [];
2133 $from = $Vend::Items;
2136 my $field_name = $self->{LOCATION}->{CARTS};
2140 die errmsg("no to cart name?") . "\n" unless $to;
2141 die errmsg('%s field not present to set %s', $field_name, $from) . "\n"
2142 unless $self->{PRESENT}->{$field_name};
2144 $d = string_to_ref( $self->{DB}->field( $self->{USERNAME}, $field_name) );
2148 die errmsg("eval failed?") unless ref $d;
2151 $d->{$to} = [] unless ref $d->{$to};
2152 push(@{$d->{$to}}, @{$from});
2164 $self->{ERROR} = $@;
2168 $self->{DB}->set_field( $self->{USERNAME}, $field_name, $s);
2173 my $function = shift;
2182 %options = ($opt, @_);
2188 my $module = $Vend::Cfg->{UserControl} ? 'Vend::UserControl' : 'Vend::UserDB';
2190 if($function eq 'login') {
2191 $Vend::Session->{logged_in} = 0;
2192 delete $Vend::Session->{username};
2193 delete $Vend::Session->{groups};
2194 undef $Vend::username;
2195 undef $Vend::groups;
2197 $user = $module->new(%options);
2198 unless (defined $user) {
2199 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2202 if ($status = $user->login(%options) ) {
2203 if( $Vend::ReadOnlyCfg->{AdminUserDB}{$user->{PROFILE}} ) {
2209 elsif($function eq 'new_account') {
2210 $user = $module->new(%options);
2211 unless (defined $user) {
2212 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2215 $status = $user->new_account(%options);
2216 if($status and ! $options{no_login}) {
2217 $Vend::Session->{logged_in} = 1;
2218 $Vend::Session->{username} = $user->{USERNAME};
2221 elsif($function eq 'logout') {
2222 $user = $module->new(%options)
2224 $Vend::Session->{failure} = errmsg("Unable to create user object.");
2229 elsif (! $Vend::Session->{logged_in}) {
2230 $Vend::Session->{failure} = errmsg("Not logged in.");
2233 elsif($function eq 'save') {
2234 $user = $module->new(%options);
2235 unless (defined $user) {
2236 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2239 $status = $user->set_values();
2241 elsif($function eq 'load') {
2242 $user = $module->new(%options);
2243 unless (defined $user) {
2244 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2247 $status = $user->get_values();
2250 $user = $module->new(%options);
2251 unless (defined $user) {
2252 $Vend::Session->{failure} = errmsg("Unable to access user database.");
2256 $status = $user->$function(%options);
2258 $user->{ERROR} = $@ if $@;
2261 if(defined $status) {
2262 delete $Vend::Session->{failure};
2263 $Vend::Session->{success} = $user->{MESSAGE};
2264 if($options{show_message}) {
2265 $status = $user->{MESSAGE};
2269 $Vend::Session->{failure} = $user->{ERROR};
2270 if($options{show_message}) {
2271 $status = $user->{ERROR};
2274 return $status unless $options{hide};
2279 my ($self, $password, $salt) = @_;
2280 my $sub = $self->{ENCSUB};
2282 for (grep { $self->{OPTIONS}{$_} } keys %enc_subs) {
2283 $sub = $enc_subs{$_};
2286 $self->{ENCSUB} = $sub ||= $enc_subs{default};
2288 return $sub->($self, $password, $salt);