[freeside-commits] branch master updated. ecf67652f26a147ef405659c98832535155325ba

Jonathan Prykop jonathan at 420.am
Wed Dec 2 03:03:37 PST 2015


The branch, master has been updated
       via  ecf67652f26a147ef405659c98832535155325ba (commit)
      from  ec4b7d78854b287347eb08a8f99d18c5d41065f5 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ecf67652f26a147ef405659c98832535155325ba
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Wed Dec 2 05:02:17 2015 -0600

    RT#29354: Password Security in Email [xmlhttp validation for selfservice]

diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 9847e5f..33a8e61 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -3308,6 +3308,45 @@ sub process_reset_passwd {
 
 }
 
+sub validate_passwd {
+  my $p = shift;
+
+  my %result;
+  %result = ( 'fieldid' => $p->{'fieldid'} )
+    if $p->{'fieldid'} =~ /^\w+$/;
+
+  return { %result, 'password_invalid' => 'Enter new password' }
+    unless length($p->{'check_password'});
+
+  my $svc_acct;
+  if ($p->{'svcnum'}) {
+    # false laziness with myaccount_passwd
+    my($context, $session, $custnum) = _custoragent_session_custnum($p);
+    return { %result, 'error' => $session } if $context eq 'error';
+
+    $custnum =~ /^(\d+)$/ or die "illegal custnum";
+    my $search = " AND custnum = $1";
+    $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent';
+
+    $svc_acct = qsearchs( {
+      'table'     => 'svc_acct',
+      'addl_from' => 'LEFT JOIN cust_svc  USING ( svcnum  ) '.
+                     'LEFT JOIN cust_pkg  USING ( pkgnum  ) '.
+                     'LEFT JOIN cust_main USING ( custnum ) ',
+      'hashref'   => { 'svcnum' => $p->{'svcnum'}, },
+      'extra_sql' => $search, #important
+    } )
+      or return { %result, 'error' => "Service not found" };
+    # end false laziness
+  }
+
+  $svc_acct ||= new FS::svc_acct {};
+
+  my $error = $svc_acct->is_password_allowed($p->{'check_password'});
+  return { %result, 'password_invalid' => $error } if $error;
+  return { %result, 'password_valid' => 1 };
+}
+
 sub list_tickets {
   my $p = shift;
   my($context, $session, $custnum) = _custoragent_session_custnum($p);
diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm
index f4b47b2..4023aa8 100644
--- a/fs_selfservice/FS-SelfService/SelfService.pm
+++ b/fs_selfservice/FS-SelfService/SelfService.pm
@@ -99,6 +99,7 @@ $socket .= '.'.$tag if defined $tag && length($tag);
   '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_selfservice/FS-SelfService/cgi/add_password_validation.html b/fs_selfservice/FS-SelfService/cgi/add_password_validation.html
new file mode 100644
index 0000000..e349fd7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/add_password_validation.html
@@ -0,0 +1,36 @@
+<SCRIPT>
+function add_password_validation (fieldid) {
+  var inputfield = document.getElementById(fieldid);
+  inputfield.onchange = function () {
+    var fieldid = this.id+'_result';
+    var resultfield = document.getElementById(fieldid);
+    var svcnum = '';
+    var svcfield = document.getElementById(this.id+'_svcnum');
+    if (svcfield) {
+      svcnum = svcfield.options[svcfield.selectedIndex].value;
+    }
+    if (this.value) {
+      resultfield.innerHTML = '<SPAN STYLE="color: blue;">Validating password...</SPAN>';
+      send_xmlhttp('selfservice.cgi',
+        ['action','validate_password','fieldid',fieldid,'svcnum',svcnum,'check_password',this.value],
+        function (result) {
+          result = JSON.parse(result);
+          var resultfield = document.getElementById(result.fieldid);
+          if (resultfield) {
+            if (result.valid) {
+              resultfield.innerHTML = '<SPAN STYLE="color: green;">Password valid!</SPAN>';
+            } else if (result.error) {
+              resultfield.innerHTML = '<SPAN STYLE="color: red;">'+result.error+'</SPAN>';
+            } else {
+              result.syserror = result.syserror || 'Server error';
+              resultfield.innerHTML = '<SPAN STYLE="color: red;">'+result.syserror+'</SPAN>';
+            }
+          }
+        }
+      );
+    } else {
+      resultfield.innerHTML = '';
+    }
+  };
+}
+</SCRIPT>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_password.html b/fs_selfservice/FS-SelfService/cgi/change_password.html
index 22d8973..ef66554 100644
--- a/fs_selfservice/FS-SelfService/cgi/change_password.html
+++ b/fs_selfservice/FS-SelfService/cgi/change_password.html
@@ -12,7 +12,7 @@
   <TR>
     <TH ALIGN="right">Change password for account: </TH>
     <TD>
-      <SELECT NAME="svcnum">
+      <SELECT ID="new_password_svcnum" NAME="svcnum">
         <%= foreach my $svc ( @svcs ) {
               $OUT .= '<OPTION VALUE="'. $svc->{'svcnum'}. '"'.
                         ( $svc->{'svcnum'} eq $svcnum ? ' SELECTED' : '' ). '>'.
@@ -25,7 +25,15 @@
 
   <TR>
     <TH ALIGN="right">New password: </TH>
-    <TD><INPUT TYPE="password" NAME="new_password" SIZE="18"></TD>
+    <TD>
+      <INPUT ID="new_password" TYPE="password" NAME="new_password" SIZE="18">
+      <DIV ID="new_password_result"></DIV>
+<%= include('send_xmlhttp') %>
+<%= include('add_password_validation') %>
+<SCRIPT>
+add_password_validation('new_password');
+</SCRIPT>
+    </TD>
   </TR>
 
   <TR>
diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
index b2ebaef..5845122 100755
--- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
+++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
@@ -23,6 +23,7 @@ use FS::SelfService qw(
   mason_comp port_graph
   start_thirdparty finish_thirdparty
   reset_passwd check_reset_passwd process_reset_passwd
+  validate_passwd
   billing_history
 );
 
@@ -85,6 +86,7 @@ my @actions = ( qw(
   process_suspend_pkg
   switch_cust
   history
+  validate_password
 ));
 
 my @nologin_actions = (qw(
@@ -109,7 +111,6 @@ if ( $cgi->param('action') =~ /^process_forgot_password_session_(\w+)$/ ) {
     warn "WARNING: unrecognized action '$1'\n";
   }
 }
-
 unless ( $nologin_actions{$action} ) {
 
   my %cookies = CGI::Cookie->fetch;
@@ -1123,6 +1124,14 @@ sub do_process_forgot_password {
   );
 }
 
+sub validate_password {
+  validate_passwd(
+    'session_id' => $session_id,
+    map { $_ => scalar($cgi->param($_)) }
+      qw( fieldid svcnum check_password )
+  )
+}
+
 #--
 
 sub do_template {
diff --git a/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html b/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html
new file mode 100644
index 0000000..ac85cb2
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html
@@ -0,0 +1,45 @@
+<SCRIPT>
+function rs_init_object () {
+  var A;
+  try {
+    A=new ActiveXObject("Msxml2.XMLHTTP");
+  } catch (e) {
+    try {
+      A=new ActiveXObject("Microsoft.XMLHTTP");
+    } catch (oc) {
+      A=null;
+    }
+  }
+  if(!A && typeof XMLHttpRequest != "undefined")
+    A = new XMLHttpRequest();
+  if (!A)
+    alert("Can't create XMLHttpRequest object");
+  return A;
+}
+
+function send_xmlhttp (url,args,callback) {
+  args = args || [];
+  callback = callback || function (data) { return data };
+  var content = '';
+  for (var i = 0; i < args.length; i = i + 2) {
+    content = content + "&" + args[i] + "=" + escape(args[i+1]);
+  }
+  content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs 
+
+  var xmlhttp = rs_init_object();
+  xmlhttp.open("POST", url, true);
+
+  xmlhttp.onreadystatechange = function() {
+    if (xmlhttp.readyState != 4) 
+      return;
+    if (xmlhttp.status == 200) {
+      var data = xmlhttp.responseText;
+      callback(data);
+    }
+  };
+
+  xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+  xmlhttp.send(content);
+}
+</SCRIPT>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/signup.cgi b/fs_selfservice/FS-SelfService/cgi/signup.cgi
index 817fdd3..072ce96 100755
--- a/fs_selfservice/FS-SelfService/cgi/signup.cgi
+++ b/fs_selfservice/FS-SelfService/cgi/signup.cgi
@@ -508,3 +508,31 @@ use FS::SelfService qw( regionselector expselect popselector domainselector
                         didselector
                       );
 
+sub add_password_validation {
+  my $fieldid = shift;
+  my $out = '';
+  if ((-e './send_xmlhttp.html') && (-e './add_password_validation.html')) {
+    my $template = new Text::Template( TYPE   => 'FILE',
+                                       SOURCE => "./send_xmlhttp.html",
+                                       DELIMITERS => [ '<%=', '%>' ],
+                                       UNTAINT => 1,                   
+                                     )
+      or die $Text::Template::ERROR;
+    $out .= $template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi' );
+    $template = new Text::Template( TYPE   => 'FILE',
+                                       SOURCE => "./add_password_validation.html",
+                                       DELIMITERS => [ '<%=', '%>' ],
+                                       UNTAINT => 1,                   
+                                     )
+      or die $Text::Template::ERROR;
+    $out .= $template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi' );
+    $out .= <<ENDOUT;
+<SCRIPT>
+add_password_validation('$fieldid');
+</SCRIPT>
+ENDOUT
+  }
+  return $out;
+}
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/signup.html b/fs_selfservice/FS-SelfService/cgi/signup.html
index 2bc59ca..5900ba6 100755
--- a/fs_selfservice/FS-SelfService/cgi/signup.html
+++ b/fs_selfservice/FS-SelfService/cgi/signup.html
@@ -336,7 +336,8 @@ HTML::Widgets::SelectLayers->new(
 <FORM name="signup_form" action="<%= $self_url %>" METHOD="POST" onsubmit="return fixup_form();"><BR><FONT SIZE="+1"><B>First package</B></FONT>
 <INPUT TYPE="hidden" NAME="promo_code" VALUE="<%= $promo_code %>">
 <INPUT TYPE="hidden" NAME="reg_code" VALUE="<%= $reg_code %>">
-<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<DIV STYLE="background: <%= $box_bgcolor %>; width: 100%">
+<TABLE BGCOLOR="<%= $box_bgcolor || '#c0c0c0' %>" BORDER=0 CELLSPACING=0>
 <TR>
   <TD COLSPAN=2><SELECT NAME="pkgpart">
 
@@ -383,7 +384,15 @@ ENDOUT
     $OUT .= <<ENDOUT;
 <TR>
   <TD ALIGN="right">Password</TD>
-  <TD><INPUT TYPE="password" NAME="_password" VALUE="$_password"></TD>
+  <TD>
+    <INPUT ID="new_password" TYPE="password" NAME="_password" VALUE="$_password">
+    <DIV ID="new_password_result"></DIV>
+ENDOUT
+
+   $OUT .= add_password_validation('new_password');
+
+   $OUT .= <<ENDOUT;
+  </TD>
 </TR>
 <TR>
   <TD ALIGN="right">Re-enter Password</TD>
@@ -433,6 +442,7 @@ NOMADIX
 %>
 
 </TABLE>
+</DIV>
 
 <%= 
 if ( @optional_packages ) { 
diff --git a/fs_selfservice/FS-SelfService/cgi/validate_password.html b/fs_selfservice/FS-SelfService/cgi/validate_password.html
new file mode 100644
index 0000000..5cc3167
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/validate_password.html
@@ -0,0 +1,9 @@
+<%= use JSON; 
+    encode_json({
+      'valid'    => $password_valid ? 1 : 0,
+      'error'    => $password_invalid,
+      'syserror' => $error,
+      'fieldid'  => $fieldid,
+      'password_debug' => $password_debug,
+    }); %>
+
diff --git a/httemplate/elements/change_password.html b/httemplate/elements/change_password.html
index 7d8daae..2b40ec1 100644
--- a/httemplate/elements/change_password.html
+++ b/httemplate/elements/change_password.html
@@ -8,7 +8,7 @@
   display: none;
 }
 </STYLE>
-<A ID="<%$pre%>link" HREF="#" onclick="<%$pre%>toggle(true)">(<% mt('change') %>)</A>
+<A ID="<%$pre%>link" HREF="javascript:void(0)" onclick="<%$pre%>toggle(true)">(<% mt('change') %>)</A>
 <DIV ID="<%$pre%>form" CLASS="passwordbox">
   <FORM METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html">
     <INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svc_acct->svcnum |h%>">

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/ClientAPI/MyAccount.pm                       |   39 +++++++++++++++++
 fs_selfservice/FS-SelfService/SelfService.pm       |    1 +
 .../cgi/add_password_validation.html               |   36 +++-------------
 .../FS-SelfService/cgi/change_password.html        |   12 +++++-
 fs_selfservice/FS-SelfService/cgi/selfservice.cgi  |   11 ++++-
 .../FS-SelfService/cgi/send_xmlhttp.html           |   45 ++++++++++++++++++++
 fs_selfservice/FS-SelfService/cgi/signup.cgi       |   28 ++++++++++++
 fs_selfservice/FS-SelfService/cgi/signup.html      |   14 +++++-
 .../FS-SelfService/cgi/validate_password.html      |    9 ++++
 httemplate/elements/change_password.html           |    2 +-
 10 files changed, 162 insertions(+), 35 deletions(-)
 copy httemplate/elements/validate_password.html => fs_selfservice/FS-SelfService/cgi/add_password_validation.html (58%)
 create mode 100644 fs_selfservice/FS-SelfService/cgi/send_xmlhttp.html
 create mode 100644 fs_selfservice/FS-SelfService/cgi/validate_password.html




More information about the freeside-commits mailing list