oops
ivan
ivan at 420.am
Fri Mar 18 13:41:19 PST 2005
pbowen's recent commit didn't make it to the list; sorry about that.
files changed: FS/FS/Record.pm FS/FS/cust_bill.pm
httemplate/docs/upgrade10.html FS/FS/Conf.pm FS/FS/cust_main.pm
FS/bin/freeside-setup
here's the diff:
Index: FS/FS/Record.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Record.pm,v
retrieving revision 1.95
retrieving revision 1.97
diff -u -r1.95 -r1.97
--- FS/FS/Record.pm 4 Mar 2005 12:34:55 -0000 1.95
+++ FS/FS/Record.pm 18 Mar 2005 19:21:28 -0000 1.97
@@ -13,6 +13,7 @@
use FS::UID qw(dbh getotaker datasrc driver_name);
use FS::SearchCache;
use FS::Msgcat qw(gettext);
+use FS::Conf;
use FS::part_virtual_field;
@@ -24,6 +25,12 @@
$DEBUG = 0;
$me = '[FS::Record]';
+my $conf;
+my $rsa_module;
+my $rsa_loaded;
+my $rsa_encrypt;
+my $rsa_decrypt;
+
#ask FS::UID to run this stuff for us later
$FS::UID::callback{'FS::Record'} = sub {
$File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc;
@@ -379,32 +386,42 @@
}
}
}
-
+ my @return;
if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
if ( eval 'FS::'. $table. '->can(\'new\')' eq \&new ) {
#derivied class didn't override new method, so this optimization is safe
if ( $cache ) {
- map {
+ @return = map {
new_or_cached( "FS::$table", { %{$_} }, $cache )
} values(%result);
} else {
- map {
+ @return = map {
new( "FS::$table", { %{$_} } )
} values(%result);
}
} else {
warn "untested code (class FS::$table uses custom new method)";
- map {
+ @return = map {
eval 'FS::'. $table. '->new( { %{$_} } )';
} values(%result);
}
+
+ # Check for encrypted fields and decrypt them.
+ if ($conf->exists('encryption') && eval 'defined(@FS::'. $table . '::encrypted_fields)') {
+ foreach my $record (@return) {
+ foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
+ # Set it directly... This may cause a problem in the future...
+ $record->setfield($field, $record->decrypt($record->getfield($field)));
+ }
+ }
+ }
} else {
cluck "warning: FS::$table not loaded; returning FS::Record objects";
- map {
+ @return = map {
FS::Record->new( $table, { %{$_} } );
} values(%result);
}
-
+ return @return;
}
=item jsearch TABLE, HASHREF, SELECT, EXTRA_SQL, PRIMARY_TABLE, PRIMARY_KEY
@@ -598,6 +615,7 @@
sub insert {
my $self = shift;
+ my $saved = {};
my $error = $self->check;
return $error if $error;
@@ -628,6 +646,17 @@
}
my $table = $self->table;
+
+
+ # Encrypt before the database
+ if ($conf->exists('encryption') && defined(eval '@FS::'. $table . 'encrypted_fields')) {
+ foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
+ $self->{'saved'} = $self->getfield($field);
+ $self->setfield($field, $self->enrypt($self->getfield($field)));
+ }
+ }
+
+
#false laziness w/delete
my @real_fields =
grep defined($self->getfield($_)) && $self->getfield($_) ne "",
@@ -741,6 +770,12 @@
dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
+ # Now that it has been saved, reset the encrypted fields so that $new
+ # can still be used.
+ foreach my $field (keys %{$saved}) {
+ $self->setfield($field, $saved->{$field});
+ }
+
'';
}
@@ -845,11 +880,11 @@
sub replace {
my $new = shift;
+ my $old = shift;
- my $old;
- if ( @_ ) {
- $old = shift;
- } else {
+ my $saved = {};
+
+ if (!defined($old)) {
warn "[debug]$me replace called with no arguments; autoloading old record\n"
if $DEBUG;
my $primary_key = $new->dbdef_table->primary_key;
@@ -873,6 +908,14 @@
my $error = $new->check;
return $error if $error;
+
+ # Encrypt for replace
+ if ($conf->exists('encryption') && defined(eval '@FS::'. $new->table . 'encrypted_fields')) {
+ foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') {
+ $saved->{$field} = $new->getfield($field);
+ $new->setfield($field, $new->encrypt($new->getfield($field)));
+ }
+ }
#my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
my %diff = map { ($new->getfield($_) ne $old->getfield($_))
@@ -1002,6 +1045,12 @@
dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
+ # Now that it has been saved, reset the encrypted fields so that $new
+ # can still be used.
+ foreach my $field (keys %{$saved}) {
+ $new->setfield($field, $saved->{$field});
+ }
+
'';
}
@@ -1653,6 +1702,70 @@
join("\n", map {
"$_: ". $self->getfield($_). "|"
} (fields($self->table)) );
+}
+
+sub encrypt {
+ my ($self, $value) = @_;
+ my $encrypted;
+ if ($conf->exists('encryption') && !$self->is_encrypted($value)) {
+ $self->loadRSA;
+ if (ref($rsa_encrypt) =~ /::RSA/) { # We Can Encrypt
+ # RSA doesn't like the empty string so let's pack it up
+ # The database doesn't like the RSA data so uuencode it
+ my $length = length($value)+1;
+ $encrypted = pack("u*",$rsa_encrypt->encrypt(pack("Z$length",$value)));
+ }
+ }
+ return $encrypted;
+}
+
+sub is_encrypted {
+ my ($self, $value) = @_;
+ # Possible Bug - Some work may be required here....
+
+ if (length($value) > 80) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub decrypt {
+ my ($self,$value) = @_;
+ my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
+ if ($conf->exists('encryption') && $self->is_encrypted($value)) {
+ $self->loadRSA;
+ if (ref($rsa_decrypt) =~ /::RSA/) {
+ my $encrypted = unpack ("u*", $value);
+ $decrypted = unpack("Z*", $rsa_decrypt->decrypt($encrypted));
+ }
+ }
+ return $decrypted;
+}
+
+sub loadRSA {
+ my $self = shift;;
+ #Initialize the Module
+ if (!$conf->exists('encryptionmodule')) {
+ carp "warning: There is no Encryption Module Defined!";
+ return;
+ }
+ $rsa_module = $conf->config('encryptionmodule');
+ if (!$rsa_loaded) {
+ eval ("require $rsa_module"); # No need to import the namespace
+ $rsa_loaded++;
+ }
+ # Initialize Encryption
+ if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') {
+ my $public_key = join("\n",$conf->config('encryptionpublickey'));
+ $rsa_encrypt = $rsa_module->new_public_key($public_key);
+ }
+
+ # Intitalize Decryption
+ if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') {
+ my $private_key = join("\n",$conf->config('encryptionprivatekey'));
+ $rsa_decrypt = $rsa_module->new_private_key($private_key);
+ }
}
sub DESTROY { return; }
Index: FS/FS/cust_bill.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill.pm,v
retrieving revision 1.103
retrieving revision 1.106
diff -u -r1.103 -r1.106
--- FS/FS/cust_bill.pm 16 Feb 2005 02:53:54 -0000 1.103
+++ FS/FS/cust_bill.pm 18 Mar 2005 19:21:28 -0000 1.106
@@ -681,7 +756,7 @@
'state' => $cust_main->getfield('state'),
'zip' => $cust_main->getfield('zip'),
'country' => $cust_main->getfield('country'),
- 'cardnum' => $cust_main->getfield('payinfo'),
+ 'cardnum' => $cust_main->payinfo,
'exp' => $cust_main->getfield('paydate'),
'payname' => $cust_main->getfield('payname'),
'amount' => $self->owed,
Index: httemplate/docs/upgrade10.html
===================================================================
RCS file: /home/cvs/cvsroot/freeside/httemplate/docs/upgrade10.html,v
retrieving revision 1.44
diff -u -r1.44 upgrade10.html
--- httemplate/docs/upgrade10.html 12 Mar 2005 14:31:45 -0000 1.44
+++ httemplate/docs/upgrade10.html 18 Mar 2005 21:15:21 -0000
@@ -10,8 +10,8 @@
INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' );
@@ -278,8 +278,12 @@
ALTER TABLE h_agent ADD username varchar(80) NULL;
ALTER TABLE agent ADD _password varchar(80) NULL;
ALTER TABLE h_agent ADD _password varchar(80) NULL;
-ALTER TABLE cust_main ADD paycvv varchar(4) NULL;
-ALTER TABLE h_cust_main ADD paycvv varchar(4) NULL;
+ALTER TABLE cust_main ADD paycvv varchar(512) NULL;
+ALTER TABLE h_cust_main ADD paycvv varchar(512) NULL;
+ALTER TABLE cust_main ALTER COLUMN payinfo varchar(512) NULL;
+ALTER TABLE h_cust_main ALTER COLUMN payinfo varchar(512) NULL;
+ALTER TABLE cust_main ADD paymask varchar(80) NULL;
+ALTER TABLE h_cust_main ADD paymask varchar(80) NULL;
ALTER TABLE part_referral ADD disabled char(1) NULL;
ALTER TABLE h_part_referral ADD disabled char(1) NULL;
CREATE INDEX part_referral1 ON part_referral ( disabled );
Index: FS/FS/Conf.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Conf.pm,v
retrieving revision 1.120
retrieving revision 1.122
diff -u -r1.120 -r1.122
--- FS/FS/Conf.pm 8 Feb 2005 20:22:46 -0000 1.120
+++ FS/FS/Conf.pm 18 Mar 2005 19:21:28 -0000 1.122
@@ -302,6 +302,34 @@
},
{
+ 'key' => 'encryption',
+ 'section' => 'billing',
+ 'description' => 'Enable encryption of credit cards.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'encryptionmodule',
+ 'section' => 'billing',
+ 'description' => 'Use which module for encryption?',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'encryptionpublickey',
+ 'section' => 'billing',
+ 'description' => 'Your RSA Public Key - Required if Encryption is turned on.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'encryptionprivatekey',
+ 'section' => 'billing',
+ 'description' => 'Your RSA Private Key - Including this will enable the "Bill Now" feature. However if the system is compromised, a hacker can use this key to decode the stored credit card information. This is generally not a good idea.',
+ 'type' => 'textarea',
+ },
+
+ {
'key' => 'business-onlinepayment',
'section' => 'billing',
'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
Index: FS/FS/cust_main.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_main.pm,v
retrieving revision 1.174
retrieving revision 1.175
diff -u -r1.174 -r1.175
--- FS/FS/cust_main.pm 13 Mar 2005 11:47:29 -0000 1.174
+++ FS/FS/cust_main.pm 18 Mar 2005 19:21:29 -0000 1.175
@@ -1,7 +1,7 @@
package FS::cust_main;
use strict;
-use vars qw( @ISA @EXPORT_OK $conf $DEBUG $import );
+use vars qw( @ISA @EXPORT_OK $conf $DEBUG $import @encrypted_fields);
use vars qw( $realtime_bop_decline_quiet ); #ugh
use Safe;
use Carp;
@@ -53,6 +53,8 @@
$import = 0;
+ at encrypted_fields = ('payinfo', 'paycvv');
+
#ask FS::UID to run this stuff for us later
#$FS::UID::callback{'FS::cust_main'} = sub {
install_callback FS::UID sub {
@@ -175,11 +177,84 @@
=item ship_fax - phone (optional)
-=item payby - I<CARD> (credit card - automatic), I<DCRD> (credit card - on-demand), I<CHEK> (electronic check - automatic), I<DCHK> (electronic check - on-demand), I<LECB> (Phone bill billing), I<BILL> (billing), I<COMP> (free), or I<PREPAY> (special billing type: applies a payment from a prepaid card - see L<FS::prepay_credit> - and sets billing type to I<BILL>)
+=item payby
+
+I<CARD> (credit card - automatic), I<DCRD> (credit card - on-demand), I<CHEK> (electronic check - automatic), I<DCHK> (electronic check - on-demand), I<LECB> (Phone bill billing), I<BILL> (billing), I<COMP> (free), or I<PREPAY> (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>)
+
+=item payinfo
+
+Card Number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
+
+=cut
+
+sub payinfo {
+ my($self,$payinfo) = @_;
+ if ( defined($payinfo) ) {
+ $self->paymask($payinfo);
+ $self->setfield('payinfo', $payinfo); # This is okay since we are the 'setter'
+ } else {
+ $payinfo = $self->getfield('payinfo'); # This is okay since we are the 'getter'
+ return $payinfo;
+ }
+}
+
+
+=item paycvv
+
+Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
+
+=cut
+
+=item paymask - Masked payment type
+
+=over 4
+
+=item Credit Cards
+
+Mask all but the last four characters.
+
+=item Checks
+
+Mask all but last 2 of account number and bank routing number.
+
+=item Others
+
+Do nothing, return the unmasked string.
+
+=back
+
+=cut
+
+sub paymask {
+ my($self,$value)=@_;
+
+ # If it doesn't exist then generate it
+ my $paymask=$self->getfield('paymask');
+ if (!defined($value) && (!defined($paymask) || $paymask eq '')) {
+ $value = $self->payinfo;
+ }
+
+ if ( defined($value) && !$self->is_encrypted($value)) {
+ my $payinfo = $value;
+ my $payby = $self->payby;
+ if ($payby eq 'CARD' || $payby eq 'DCARD') { # Credit Cards (Show last four)
+ $paymask = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
+ } elsif ($payby eq 'CHEK' ||
+ $payby eq 'DCHK' ) { # Checks (Show last 2 @ bank)
+ my( $account, $aba ) = split('@', $payinfo );
+ $paymask = 'x'x(length($account)-2). substr($account,(length($account)-2))."@".$aba;
+ } else { # Tie up loose ends
+ $paymask = $payinfo;
+ }
+ $self->setfield('paymask', $paymask); # This is okay since we are the 'setter'
+ } else {
+ $paymask = 'N/A';
+ }
+ return $paymask;
+}
+
-=item payinfo - card number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
-=item paycvv - Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
=item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
@@ -602,6 +677,16 @@
local $SIG{TSTP} = 'IGNORE';
local $SIG{PIPE} = 'IGNORE';
+ # If the mask is blank then try to set it - if we can...
+ if (!defined($self->paymask) && $self->paymask eq '') {
+ $self->paymask($self->payinfo);
+ }
+
+ # We absolutely have to have an old vs. new record to make this work.
+ if (!defined($old)) {
+ $old = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+ }
+
if ( $self->payby eq 'COMP' && $self->payby ne $old->payby
&& $conf->config('users-allow_comp') ) {
return "You are not permitted to create complimentary accounts."
@@ -695,7 +780,7 @@
Checks all fields to make sure this is a valid customer record. If there is
an error, returns the error, otherwise returns false. Called by the insert
-and repalce methods.
+and replace methods.
=cut
@@ -826,9 +911,19 @@
$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY)$/
or return "Illegal payby: ". $self->payby;
+
+ # If it is encrypted and the private key is not availaible then we can't
+ # check the credit card.
+
+ my $check_payinfo = 1;
+
+ if ($self->is_encrypted($self->payinfo)) {
+ $check_payinfo = 0;
+ }
+
$self->payby($1);
- if ( $self->payby eq 'CARD' || $self->payby eq 'DCRD' ) {
+ if ( $check_payinfo && ($self->payby eq 'CARD' || $self->payby eq 'DCRD')) {
my $payinfo = $self->payinfo;
$payinfo =~ s/\D//g;
@@ -856,7 +951,7 @@
}
}
- } elsif ( $self->payby eq 'CHEK' || $self->payby eq 'DCHK' ) {
+ } elsif ($check_payinfo && ( $self->payby eq 'CHEK' || $self->payby eq 'DCHK' )) {
my $payinfo = $self->payinfo;
$payinfo =~ s/[^\d\@]//g;
@@ -2478,15 +2573,17 @@
=item payinfo_masked
-Returns a "masked" payinfo field with all but the last four characters replaced
-by 'x'es. Useful for displaying credit cards.
+Returns a "masked" payinfo field appropriate to the payment type. Masked characters are replaced by 'x'es. Use this to display publicly accessable account Information.
+
+Credit Cards - Mask all but the last four characters.
+Checks - Mask all but last 2 of account number and bank routing number.
+Others - Do nothing, return the unmasked string.
=cut
sub payinfo_masked {
my $self = shift;
- my $payinfo = $self->payinfo;
- 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
+ return $self->paymask;
}
=item invoicing_list [ ARRAYREF ]
Index: FS/bin/freeside-setup
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/bin/freeside-setup,v
retrieving revision 1.48
retrieving revision 1.49
diff -u -r1.48 -r1.49
--- FS/bin/freeside-setup 12 Mar 2005 14:31:47 -0000 1.48
+++ FS/bin/freeside-setup 18 Mar 2005 19:21:30 -0000 1.49
@@ -487,8 +487,9 @@
'ship_night', 'varchar', 'NULL', 20,
'ship_fax', 'varchar', 'NULL', 12,
'payby', 'char', '', 4,
- 'payinfo', 'varchar', 'NULL', $char_d,
- 'paycvv', 'varchar', 'NULL', 4,
+ 'payinfo', 'varchar', 'NULL', 512,
+ 'paycvv', 'varchar', 'NULL', 512,
+ 'paymask', 'varchar', 'NULL', $char_d,
#'paydate', @date_type,
'paydate', 'varchar', 'NULL', 10,
'payname', 'varchar', 'NULL', $char_d,
--
_ivan
More information about the freeside-commits
mailing list