X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FRecord.pm;h=de5feeb2731a58359697fddc0b0a9bfd752f5b6e;hb=f441bdef352ddd432e305da35e80813ca30e517f;hp=e24c0eb9a83eb54eb45e155d92d8628b48253876;hpb=b1fc20ef3b68a8536163fbb17c57bca15555f3c4;p=freeside.git diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index e24c0eb9a..de5feeb27 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2,17 +2,18 @@ package FS::Record; use strict; use vars qw( $dbdef_file $dbdef $setup_hack $AUTOLOAD @ISA @EXPORT_OK $DEBUG - $me %dbdef_cache %virtual_fields_cache ); + $me %dbdef_cache %virtual_fields_cache $nowarn_identical ); use subs qw(reload_dbdef); use Exporter; use Carp qw(carp cluck croak confess); use File::CounterFile; use Locale::Country; use DBI qw(:sql_types); -use DBIx::DBSchema 0.23; +use DBIx::DBSchema 0.25; 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,8 +25,17 @@ use Tie::IxHash; $DEBUG = 0; $me = '[FS::Record]'; +$nowarn_identical = 0; + +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 { + $conf = new FS::Conf; $File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc; $dbdef_file = "/usr/local/etc/freeside/dbdef.". datasrc; &reload_dbdef unless $setup_hack; #$setup_hack needed now? @@ -379,32 +389,42 @@ sub qsearch { } } } - + 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 +618,7 @@ otherwise returns false. sub insert { my $self = shift; + my $saved = {}; my $error = $self->check; return $error if $error; @@ -628,6 +649,17 @@ sub insert { } 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 +773,12 @@ sub insert { 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 +883,9 @@ returns the error, otherwise returns false. sub replace { my $new = shift; + my $old = shift; - my $old; - if ( @_ ) { - $old = shift; - } else { + 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,13 +909,23 @@ sub replace { my $error = $new->check; return $error if $error; + + # Encrypt for replace + my $saved = {}; + 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($_)) ? ($_, $new->getfield($_)) : () } $old->fields; unless ( keys(%diff) ) { - carp "[warning]$me $new -> replace $old: records identical"; + carp "[warning]$me $new -> replace $old: records identical" + unless $nowarn_identical; return ''; } @@ -1002,6 +1048,12 @@ sub replace { 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}); + } + ''; } @@ -1051,7 +1103,9 @@ sub check { } sub _h_statement { - my( $self, $action ) = @_; + my( $self, $action, $time ) = @_; + + $time ||= time; my @fields = grep defined($self->getfield($_)) && $self->getfield($_) ne "", @@ -1062,7 +1116,7 @@ sub _h_statement { "INSERT INTO h_". $self->table. " ( ". join(', ', qw(history_date history_user history_action), @fields ). ") VALUES (". - join(', ', time, dbh->quote(getotaker()), dbh->quote($action), @values). + join(', ', $time, dbh->quote(getotaker()), dbh->quote($action), @values). ")" ; } @@ -1655,6 +1709,79 @@ sub _dump { } (fields($self->table)) ); } +sub encrypt { + my ($self, $value) = @_; + my $encrypted; + + if ($conf->exists('encryption')) { + if ($self->is_encrypted($value)) { + # Return the original value if it isn't plaintext. + $encrypted = $value; + } else { + $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))); + } else { + die ("You can't encrypt w/o a valid RSA engine - Check your installation or disable encryption"); + } + } + } + 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 + $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default + + if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') { + $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; } #sub DESTROY {