From: Mark Wells Date: Wed, 21 Dec 2016 23:37:36 +0000 (-0800) Subject: Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into 3.x X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=0fa98e9782d550147a68a00d7ee75079ad331666;hp=89d30bbd2b79d9b1de0890eec86234bc9d3ce330 Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into 3.x --- diff --git a/FS-Test/bin/freeside-test-stop b/FS-Test/bin/freeside-test-stop index 5e221a85b..ad355c3de 100755 --- a/FS-Test/bin/freeside-test-stop +++ b/FS-Test/bin/freeside-test-stop @@ -22,7 +22,7 @@ if (sudo grep -q '^test:' /usr/local/etc/freeside/htpasswd); then oldhtpasswd=$( cd /usr/local/etc/freeside; \ ls |grep -P 'htpasswd_\d{8}' | \ sort -nr |head -1 ) - if [ -f $oldhtpasswd ]; then + if [ -f /usr/local/etc/freeside/$oldhtpasswd ]; then echo "Renaming $oldhtpasswd to htpasswd." sudo mv /usr/local/etc/freeside/$oldhtpasswd \ /usr/local/etc/freeside/htpasswd diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index de3e55d05..0420d1a13 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -176,6 +176,7 @@ sub ss2clientapi { 'reset_passwd' => 'MyAccount/reset_passwd', 'check_reset_passwd' => 'MyAccount/check_reset_passwd', 'process_reset_passwd' => 'MyAccount/process_reset_passwd', + 'validate_passwd' => 'MyAccount/validate_passwd', 'list_tickets' => 'MyAccount/list_tickets', 'create_ticket' => 'MyAccount/create_ticket', 'get_ticket' => 'MyAccount/get_ticket', diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm index cac26c1f5..f09291920 100644 --- a/FS/FS/Setup.pm +++ b/FS/FS/Setup.pm @@ -84,6 +84,12 @@ sub enable_encryption { $conf->set('encryptionpublickey', $rsa->get_public_key_string ); $conf->set('encryptionprivatekey', $rsa->get_private_key_string ); + # reload Record globals, false laziness with FS::Record + $FS::Record::conf_encryption = $conf->exists('encryption'); + $FS::Record::conf_encryptionmodule = $conf->config('encryptionmodule'); + $FS::Record::conf_encryptionpublickey = join("\n",$conf->config('encryptionpublickey')); + $FS::Record::conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey')); + } sub populate_numbering { diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index d06b7d8a7..576cdf072 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -332,7 +332,8 @@ sub upgrade_data { #fix whitespace - before cust_main 'cust_location' => [], - #cust_main (remove paycvv from history) + #cust_main (tokenizes cards, remove paycvv from history, locations, cust_payby, etc) + # (handles payinfo encryption/tokenization across all relevant tables) 'cust_main' => [], #msgcat diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 0165bc479..2343fc67b 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -5734,6 +5734,90 @@ sub _upgrade_data { #class method $class->_upgrade_otaker(%opts); + # turn on encryption as part of regular upgrade, so all new records are immediately encrypted + # existing records will be encrypted in queueable_upgrade (below) + unless ($conf->exists('encryptionpublickey') || $conf->exists('encryptionprivatekey')) { + eval "use FS::Setup"; + die $@ if $@; + FS::Setup::enable_encryption(); + } + +} + +sub queueable_upgrade { + my $class = shift; + + ### encryption gets turned on in _upgrade_data, above + + eval "use FS::upgrade_journal"; + die $@ if $@; + + # prior to 2013 (commit f16665c9) payinfo was stored in history if not encrypted, + # clear that out before encrypting/tokenizing anything else + if (!FS::upgrade_journal->is_done('clear_payinfo_history')) { + foreach my $table ('cust_main','cust_pay_pending','cust_pay','cust_pay_void','cust_refund') { + my $sql = 'UPDATE h_'.$table.' SET payinfo = NULL WHERE payinfo IS NOT NULL'; + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + } + FS::upgrade_journal->set_done('clear_payinfo_history'); + } + + # encrypt old records + if ($conf->exists('encryption') && !FS::upgrade_journal->is_done('encryption_check')) { + + # allow replacement of closed cust_pay/cust_refund records + local $FS::payinfo_Mixin::allow_closed_replace = 1; + + # because it looks like nothing's changing + local $FS::Record::no_update_diff = 1; + + # commit everything immediately + local $FS::UID::AutoCommit = 1; + + # encrypt what's there + foreach my $table ('cust_main','cust_pay_pending','cust_pay','cust_pay_void','cust_refund') { + my $tclass = 'FS::'.$table; + my $lastrecnum = 0; + my @recnums = (); + while (my $recnum = _upgrade_next_recnum(dbh,$table,\$lastrecnum,\@recnums)) { + my $record = $tclass->by_key($recnum); + next unless $record; # small chance it's been deleted, that's ok + next unless grep { $record->payby eq $_ } @FS::Record::encrypt_payby; + # window for possible conflict is practically nonexistant, + # but just in case... + $record = $record->select_for_update; + my $error = $record->replace; + die $error if $error; + } + } + + FS::upgrade_journal->set_done('encryption_check'); + } + +} + +# not entirely false laziness w/ Billing_Realtime::_token_check_next_recnum +# cust_payby might get deleted while this runs +# not a method! +sub _upgrade_next_recnum { + my ($dbh,$table,$lastrecnum,$recnums) = @_; + my $recnum = shift @$recnums; + return $recnum if $recnum; + my $tclass = 'FS::'.$table; + my $sql = 'SELECT '.$tclass->primary_key. + ' FROM '.$table. + ' WHERE '.$tclass->primary_key.' > '.$$lastrecnum. + ' ORDER BY '.$tclass->primary_key.' LIMIT 500';; + my $sth = $dbh->prepare($sql) or die $dbh->errstr; + $sth->execute() or die $sth->errstr; + my @recnums; + while (my $rec = $sth->fetchrow_hashref) { + push @$recnums, $rec->{$tclass->primary_key}; + } + $sth->finish(); + $$lastrecnum = $$recnums[-1]; + return shift @$recnums; } =back diff --git a/FS/FS/part_export/broadband_sqlradius.pm b/FS/FS/part_export/broadband_sqlradius.pm index e58c641cf..2d6681e74 100644 --- a/FS/FS/part_export/broadband_sqlradius.pm +++ b/FS/FS/part_export/broadband_sqlradius.pm @@ -133,6 +133,8 @@ sub radius_check_suspended { sub _export_suspend { my( $self, $svc_broadband ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 67f0c5c91..99cd61509 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -26,6 +26,10 @@ tie %options, 'Tie::IxHash', type => 'select', options => [qw( usergroup radusergroup ) ], }, + 'skip_provisioning' => { + type => 'checkbox', + label => 'Skip provisioning records to this database' + }, 'ignore_accounting' => { type => 'checkbox', label => 'Ignore accounting records from this database' @@ -154,6 +158,8 @@ sub radius_check { #override for other svcdb sub _export_insert { my($self, $svc_x) = (shift, shift); + return '' if $self->option('skip_provisioning'); + foreach my $table (qw(reply check)) { my $method = "radius_$table"; my %attrib = $self->$method($svc_x); @@ -179,6 +185,8 @@ sub _export_insert { sub _export_replace { my( $self, $new, $old ) = (shift, shift, shift); + return '' if $self->option('skip_provisioning'); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; @@ -289,6 +297,8 @@ sub _export_replace { sub _export_suspend { my( $self, $svc_acct ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + my $new = $svc_acct->clone_suspended; local $SIG{HUP} = 'IGNORE'; @@ -360,6 +370,8 @@ sub _export_suspend { sub _export_unsuspend { my( $self, $svc_x ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; @@ -399,6 +411,8 @@ sub _export_unsuspend { sub _export_delete { my( $self, $svc_x ) = (shift, shift); + return '' if $self->option('skip_provisioning'); + my $jobnum = ''; my $usergroup = $self->option('usergroup') || 'usergroup'; diff --git a/FS/t/suite/15-activate_encryption.t b/FS/t/suite/15-activate_encryption.t new file mode 100755 index 000000000..e81ddf48e --- /dev/null +++ b/FS/t/suite/15-activate_encryption.t @@ -0,0 +1,110 @@ +#!/usr/bin/perl + +use strict; +use FS::Test; +use Test::More tests => 14; +use FS::Conf; +use FS::UID qw( dbh ); +use DateTime; +use FS::cust_main; # to load all other tables + +my $fs = FS::Test->new( user => 'admin' ); +my $conf = FS::Conf->new; +my $err; +my @tables = qw(cust_main cust_pay_pending cust_pay cust_pay_void cust_refund); + +### can only run on test database (company name "Freeside Test") +like( $conf->config('company_name'), qr/^Freeside Test/, 'using test database' ) or BAIL_OUT(''); + +### upgrade test db schema +$err = system('freeside-upgrade','-s','admin'); +ok( !$err, 'schema upgrade ran' ) or BAIL_OUT('Error string: '.$!); + +### we need to unencrypt our test db before we can test turning it on + +# temporarily load all payinfo into memory +my %payinfo = (); +foreach my $table (@tables) { + $payinfo{$table} = {}; + foreach my $record ($fs->qsearch({ table => $table })) { + next unless grep { $record->payby eq $_ } @FS::Record::encrypt_payby; + $payinfo{$table}{$record->get($record->primary_key)} = $record->get('payinfo'); + } +} + +# turn off encryption +foreach my $config ( qw(encryption encryptionmodule encryptionpublickey encryptionprivatekey) ) { + $conf->delete($config); + ok( !$conf->exists($config), "deleted $config" ) or BAIL_OUT(''); +} +$FS::Record::conf_encryption = $conf->exists('encryption'); +$FS::Record::conf_encryptionmodule = $conf->config('encryptionmodule'); +$FS::Record::conf_encryptionpublickey = join("\n",$conf->config('encryptionpublickey')); +$FS::Record::conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey')); + +# save unencrypted values +foreach my $table (@tables) { + local $FS::payinfo_Mixin::allow_closed_replace = 1; + local $FS::Record::no_update_diff = 1; + local $FS::UID::AutoCommit = 1; + my $tclass = 'FS::'.$table; + foreach my $key (keys %{$payinfo{$table}}) { + my $record = $tclass->by_key($key); + $record->payinfo($payinfo{$table}{$key}); + $err = $record->replace; + last if $err; + } +} +ok( !$err, "save unencrypted values" ) or BAIL_OUT($err); + +# make sure it worked +CHECKDECRYPT: +foreach my $table (@tables) { + my $tclass = 'FS::'.$table; + foreach my $key (sort {$a <=> $b} keys %{$payinfo{$table}}) { + my $sql = 'SELECT * FROM '.$table. + ' WHERE payinfo LIKE \'M%\''. + ' AND char_length(payinfo) > 80'. + ' AND '.$tclass->primary_key.' = '.$key; + my $sth = dbh->prepare($sql) or BAIL_OUT(dbh->errstr); + $sth->execute or BAIL_OUT($sth->errstr); + if (my $hashrec = $sth->fetchrow_hashref) { + $err = $table.' '.$key.' encrypted'; + last CHECKDECRYPT; + } + } +} +ok( !$err, "all values unencrypted" ) or BAIL_OUT($err); + +### now, run upgrade +$err = system('freeside-upgrade','admin'); +ok( !$err, 'upgrade ran' ) or BAIL_OUT('Error string: '.$!); + +# check that confs got set +foreach my $config ( qw(encryption encryptionmodule encryptionpublickey encryptionprivatekey) ) { + ok( $conf->exists($config), "$config was set" ) or BAIL_OUT(''); +} + +# check that known records got encrypted +CHECKENCRYPT: +foreach my $table (@tables) { + my $tclass = 'FS::'.$table; + foreach my $key (sort {$a <=> $b} keys %{$payinfo{$table}}) { + my $sql = 'SELECT * FROM '.$table. + ' WHERE payinfo LIKE \'M%\''. + ' AND char_length(payinfo) > 80'. + ' AND '.$tclass->primary_key.' = '.$key; + my $sth = dbh->prepare($sql) or BAIL_OUT(dbh->errstr); + $sth->execute or BAIL_OUT($sth->errstr); + unless ($sth->fetchrow_hashref) { + $err = $table.' '.$key.' not encrypted'; + last CHECKENCRYPT; + } + } +} +ok( !$err, "all values encrypted" ) or BAIL_OUT($err); + +exit; + +1; + diff --git a/httemplate/view/directions.html b/httemplate/view/directions.html index 8377d129a..1c99cda94 100644 --- a/httemplate/view/directions.html +++ b/httemplate/view/directions.html @@ -62,7 +62,8 @@ function show_route() { if ( status == google.maps.DirectionsStatus.OK ) { directionsDisplay.setDirections(result); } else { - document.body.innerHTML = ('

Directions lookup failed with the following error: '+status+'

'); + document.body.innerHTML = ('

Directions lookup failed with the following error: '+status+'

') + + <% include('/elements/google_maps_api_key.html' ) |js_string%>; } }); } diff --git a/ng_selfservice/elements/add_password_validation.php b/ng_selfservice/elements/add_password_validation.php new file mode 100644 index 000000000..6938437eb --- /dev/null +++ b/ng_selfservice/elements/add_password_validation.php @@ -0,0 +1,51 @@ + diff --git a/ng_selfservice/images/error.png b/ng_selfservice/images/error.png new file mode 100644 index 000000000..628cf2dae Binary files /dev/null and b/ng_selfservice/images/error.png differ diff --git a/ng_selfservice/images/tick.png b/ng_selfservice/images/tick.png new file mode 100644 index 000000000..a9925a06a Binary files /dev/null and b/ng_selfservice/images/tick.png differ diff --git a/ng_selfservice/password.php b/ng_selfservice/password.php index 41296ed2d..a6e67951a 100644 --- a/ng_selfservice/password.php +++ b/ng_selfservice/password.php @@ -1,5 +1,92 @@ -Chagne password +myaccount_passwd(array( + 'session_id' => $_COOKIE['session_id'], + 'svcnum' => $_POST['svcnum'], + 'new_password' => $_POST['new_password'], + 'new_password2' => $_POST['new_password2'], + )); + + if ($pwd_change_result['error']) { + $error = $pwd_change_result['error']; + } else { + $pwd_change_success = true; + } +} + +if ($pwd_change_success) { +?> + +

Password changed for .

+ +list_svcs(array( + 'session_id' => $_COOKIE['session_id'], + 'svcdb' => 'svc_acct', + )); + if (isset($pwd_change_svcs['error'])) { + $error = $error || $pwd_change_svcs['error']; + } + if (!isset($pwd_change_svcs['svcs'])) { + $pwd_change_svcs['svcs'] = $pwd_change_svcs['svcs']; + $error = $error || 'Unknown error loading services'; + } + if ($error) { + include('elements/error.php'); + } +?> + +
+ + + + + + + + + + + + + + + + +
Change password for account: + +
New password: + +
+ + +
Re-enter new password:
+
+ + + +
+ + + diff --git a/ng_selfservice/xmlrpc_validate_passwd.php b/ng_selfservice/xmlrpc_validate_passwd.php new file mode 100644 index 000000000..5632dc3f1 --- /dev/null +++ b/ng_selfservice/xmlrpc_validate_passwd.php @@ -0,0 +1,15 @@ + $_POST['fieldid'], + check_password => $_POST['check_password'], + svcnum => $_POST['svcnum'], + session_id => $_COOKIE['session_id'] +); + +$result = $freeside->validate_passwd($xmlrpc_args); +echo json_encode($result); + +?>