Commit 557f3504 authored by Leigh Stoller's avatar Leigh Stoller

Deal with issues described by Jon in issues #203.

The main point of the changes are to require that the old password is
provided when changing your password. This holds true even for admins
changing their own password in red-dot. Note though, that when an admin
changes another user's password in red-dot, old password is not
required, which is somewhat in conflict with the overall goal, but hey,
we want to be practical too.

I ended up removing password modification from the profile page, and
use the already existing changepswd page, which I cleaned up and turned
into a first class ajax citizen to make the page operate smoother.
parent c9444a4e
<?php
#
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
chdir("..");
include_once("webtask.php");
chdir("apt");
# We set this in CheckPageArgs
$target_user = null;
#
# Need to check the permission, since we allow admins to edit
# other accounts.
#
function CheckPageArgs()
{
global $this_user, $target_user;
global $ajax_args;
global $APTMAIL;
if (!isset($ajax_args["formfields"])) {
SPITAJAX_ERROR(-1, "Missing formfields");
return -1;
}
$formfields = $ajax_args["formfields"];
if (!isset($formfields["uid"])) {
SPITAJAX_ERROR(-1, "Missing target uid");
return -1;
}
$uid = $formfields["uid"];
if (!TBvalid_uid($uid)) {
SPITAJAX_ERROR(-1, "Invalid target uid");
return -1;
}
$target_user = User::LookupByUid($uid);
if (!$target_user) {
SPITAJAX_ERROR(-1, "Unknown target uid");
return -1;
}
# Key based change (forgot password), no current user to check.
if (isset($formfields["key"])) {
if (isset($this_user)) {
SPITAJAX_ERROR(-1, "Why are you here?");
return -1;
}
return;
}
if ($uid == $this_user->uid())
return 0;
if (!ISADMIN()) {
SPITAJAX_ERROR(-1, "Not enough permission");
return -1;
}
return 0;
}
#
# The change password page.
#
function Do_ChangePassword()
{
global $this_user, $target_user;
global $TBMAINSITE, $ELABINELAB;
global $ajax_args;
if (CheckPageArgs() < 0) {
return;
}
$formfields = $ajax_args["formfields"];
$target_uid = $target_user->uid();
if (isset($formfields["key"])) {
if ($formfields["key"] == "") {
SPITAJAX_ERROR(3, "Missing key");
return;
}
if (!$target_user->chpasswd_key() ||
!$target_user->chpasswd_expires()) {
SPITAJAX_ERROR(3, "Why are you here?");
return;
}
if ($target_user->chpasswd_key() != $formfields["key"]) {
SPITAJAX_ERROR(3, "Invalid key");
return;
}
if (time() > $target_user->chpasswd_expires()) {
SPITUSERERROR("Your key has expired. Please request a
<a href='forgotpswd.php'>new key</a>.");
return;
}
}
else {
#
# admins do not need to provide an old password when changing another
# user password, but they need it to change their own password.
#
if (!ISADMIN() || $target_uid == $this_user->uid()) {
if (!isset($formfields["oldpassword"]) ||
$formfields["oldpassword"] == "") {
$errors["oldpassword"] = "Missing Field";
}
elseif (VERIFYPASSWD($target_uid, $formfields["oldpassword"]) != 0){
$errors["oldpassword"] = "Incorrect Password";
}
}
}
if (!isset($formfields["password1"]) || $formfields["password1"] == "") {
$errors["password1"] = "Missing Field";
}
elseif ($formfields["password1"] != $formfields["password2"]) {
$errors["password2"] = "Does not match";
}
elseif (! CHECKPASSWORD($target_uid,
$formfields["password1"],
$target_user->name(),
$target_user->email(), $checkerror)) {
$errors["password1"] = "$checkerror";
}
# Present these errors before we call out to do anything else.
if (count($errors)) {
SPITAJAX_ERROR(2, $errors);
return -1;
}
# Allow for form precheck only. So JS code knows it will be fast.
if (isset($ajax_args["checkonly"]) && $ajax_args["checkonly"]) {
SPITAJAX_RESPONSE(0);
return;
}
if ($TBMAINSITE || $ELABINELAB) {
$salt = "\$5\$" . substr(GENHASH(), 0, 16) . "\$";
}
else {
$salt = "\$1\$" . substr(GENHASH(), 0, 8) . "\$";
}
$encoding = crypt($formfields["password1"], $salt);
$safe_encoding = escapeshellarg($encoding);
if (!HASREALACCOUNT($target_uid)) {
$retval = SUEXEC("nobody", "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_CONTINUE);
}
else {
$retval = SUEXEC($target_uid, "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_CONTINUE);
}
if ($retval) {
SPITAJAX_ERROR(-1, "Could not reset password on SSL private key");
return;
}
#
# Change the passphrase on the SSL key. If not an active user, it will
# happen when the user is approved.
#
if ($target_user->IsActive()) {
$safe_password = escapeshellarg($formfields["password1"]);
# Do not send email, mkusercert sends email and hides the password.
$retval = SUEXEC($target_uid, "nobody",
"webmkusercert -C -p $safe_password $target_uid",
SUEXEC_ACTION_IGNORE);
if ($retval) {
SPITAJAX_ERROR(-1, "Could not reset password on SSL private key");
return;
}
}
SPITAJAX_RESPONSE(0);
}
# Local Variables:
# mode:php
# End:
?>
<?php
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -33,11 +33,7 @@ RedirectSecure();
# Verify page arguments.
#
$optargs = OptionalPageArguments("user", PAGEARG_USER,
"key", PAGEARG_STRING,
"password1", PAGEARG_STRING,
"password2", PAGEARG_STRING,
"reset", PAGEARG_STRING);
"key", PAGEARG_STRING);
#
# We use this page for both resetting a forgotten password, and for
......@@ -83,6 +79,8 @@ if (isset($key)) {
<a href='forgotpswd.php'>new key</a>.");
return;
}
$needold = 0;
$key = "'$key'";
}
else {
#
......@@ -99,177 +97,31 @@ else {
SPITUSERERROR("Not enough permission to reset password for user");
return;
}
#
# admins do not need to provide an old password when changing another
# user password, but they need it to change their own password.
#
$needold = (!ISADMIN() || $this_user->SameUser($user) ? 1 : 0);
$key = "null";
}
$uid = $user->uid();
function SPITFORM($password1, $password2, $errors)
{
global $keyB, $user;
$user_uid = $user->uid();
# XSS prevention.
$password1 = CleanString($password1);
$password2 = CleanString($password2);
# XSS prevention.
if ($errors) {
while (list ($key, $val) = each ($errors)) {
# Skip internal error, we want the html in those errors
# and we know it is safe.
if ($key == "error") {
continue;
}
$errors[$key] = CleanString($val);
}
}
$formatter = function($field, $html) use ($errors) {
$class = "form-group";
if ($errors && array_key_exists($field, $errors)) {
$class .= " has-error";
}
echo "<div class='$class'>\n";
echo " $html\n";
if ($errors && array_key_exists($field, $errors)) {
echo "<label class='control-label' for='inputError'>" .
$errors[$field] . "</label>\n";
}
echo "</div>\n";
};
SPITHEADER(1);
REQUIRE_SUP();
SPITNULLREQUIRE();
echo "<div class='row'>
<div class='col-lg-4 col-lg-offset-4
col-md-4 col-md-offset-4
col-sm-6 col-sm-offset-3
col-xs-10 col-xs-offset-1'>\n";
echo "<form id='quickvm_form' role='form'
method='post' action='changepswd.php?user=$user_uid'>\n";
echo "<div class='panel panel-default'>
<div class='panel-heading'>
<h3 class='panel-title'>
<center>Change Your Password</center></h3>
</div>
<div class='panel-body'>\n";
$formatter("password1",
"<input name='password1'
value='$password1'
class='form-control'
placeholder='Your new password'
autofocus type='password'>");
$formatter("password2",
"<input name='password2'
type='password'
value='$password2'
class='form-control'
placeholder='Confirm password'>");
echo "<center>
<button class='btn btn-primary'
type='submit' name='reset'>Reset Password</button><center>\n";
if (isset($keyB)) {
echo "<input type='hidden' name='key' value='$keyB'>\n";
}
echo " </div>\n";
echo "</div>\n";
echo "</form>\n";
echo "</div>\n";
echo "</div>\n";
SPITFOOTER();
}
#
# If not clicked, then put up a form.
#
if (! isset($reset)) {
SPITFORM("", "", null);
return;
}
$errors = array();
#
# Reset clicked. Verify a proper password.
#
if (!isset($password1) || $password1 == "") {
$errors["password1"] = "Missing Field";
}
if (!isset($password2) || $password2 == "") {
$errors["password2"] = "Missing Field";
}
if (!count($errors) && $password1 != $password2) {
$errors["password2"] = "Passwords do not match";
}
if (!count($errors) &&
! CHECKPASSWORD($user->uid(),
$password1, $user->name(), $user->email(), $checkerror)) {
$errors["password1"] = $checkerror;
}
if (count($errors)) {
SPITFORM($password1, $password2, $errors);
return;
}
if ($TBMAINSITE || $ELABINELAB) {
$salt = "\$5\$" . substr(GENHASH(), 0, 16) . "\$";
}
else {
$salt = "\$1\$" . substr(GENHASH(), 0, 8) . "\$";
}
$encoding = crypt("$password1", $salt);
$safe_encoding = escapeshellarg($encoding);
#
# Clear this for forgotten password.
#
if (isset($key)) {
setcookie($TBAUTHCOOKIE, "", 1, "/", $WWWHOST, $TBSECURECOOKIES);
}
# Header after cookie.
SPITHEADER(1);
SpitWaitModal("waitwait");
echo "<script>\n";
echo "window.NEEDOLD = $needold;\n";
echo "window.KEY = $key;\n";
echo "window.USER = '$uid';\n";
echo "</script>\n";
echo "<div id='page-body'></div>\n";
echo "<div id='oops_div'></div>\n";
echo "<div id='waitwait_div'></div>\n";
echo "</script>\n";
REQUIRE_UNDERSCORE();
REQUIRE_SUP();
SPITNULLREQUIRE();
echo "<script>ShowWaitModal('waitwait');</script>\n";
flush();
#
# Invoke backend to deal with this.
#
$target_uid = $user->uid();
if (!HASREALACCOUNT($target_uid)) {
$retval = SUEXEC("nobody", "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_CONTINUE);
}
else {
$retval = SUEXEC($target_uid, "nobody",
"webtbacct passwd $target_uid $safe_encoding",
SUEXEC_ACTION_CONTINUE);
if (!$retval) {
# Change the passphrase on the SSL key.
$safe_password = escapeshellarg($password1);
# Do not send email, mkusercert sends email and hides the password.
$retval = SUEXEC($target_uid, "nobody",
"webmkusercert -C -p $safe_password $target_uid",
SUEXEC_ACTION_IGNORE);
}
}
echo "<script>HideWaitModal('waitwait');</script>\n";
flush();
if ($retval) {
SPITUSERERROR("Oops, error changing password");
}
else {
echo "Your password has been changed.\n";
}
REQUIRE_APTFORMS();
SPITREQUIRE("js/changepswd.js");
AddTemplateList(array("changepswd", "oops-modal", "waitwait-modal"));
SPITFOOTER();
?>
$(function ()
{
'use strict';
var templates = APT_OPTIONS.fetchTemplateList(['changepswd',
'oops-modal',
'waitwait-modal']);
var mainTemplate = _.template(templates['changepswd']);
function initialize()
{
window.APT_OPTIONS.initialize(sup);
$('#oops_div').html(templates['oops-modal']);
$('#waitwait_div').html(templates['waitwait-modal']);
var html = aptforms.FormatFormFields(mainTemplate({
needold : window.NEEDOLD,
key : window.KEY,
user : window.USER,
}));
$('#page-body').html(html);
$('#submit-button').click(function (event) {
event.preventDefault();
SubmitForm();
return false;
});
}
//
// Submit the form.
//
function SubmitForm()
{
var submit_callback = function(json) {
if (json.code) {
sup.SpitOops("oops", json.value);
return;
}
sup.ShowModal("#success-modal");
setTimeout(function f() {
window.location.replace("user-dashboard.php");
}, 2000);
};
var checkonly_callback = function(json) {
if (json.code) {
if (json.code != 2) {
sup.SpitOops("oops", json.value);
}
return;
}
aptforms.SubmitForm('#changepswd-form',
"changepswd", "changepswd",
submit_callback);
};
aptforms.CheckForm('#changepswd-form',
"changepswd", "changepswd",
checkonly_callback);
}
$(document).ready(initialize);
});
......@@ -36,7 +36,6 @@ $(function ()
var myaccount = aptforms.FormatFormFields(myaccountTemplate({
formfields: formfields,
verify_modal: verify,
nopassword: window.APT_OPTIONS.nopassword,
}));
$('#page-body').html(myaccount);
......
<?php
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -56,7 +56,6 @@ function CheckPageArgs()
}
$target_user = User::LookupByUid($uid);
if (!$target_user) {
sleep(2);
SPITAJAX_ERROR(-1, "Unknown target uid");
return -1;
}
......@@ -64,7 +63,7 @@ function CheckPageArgs()
return 0;
if (!ISADMIN()) {
SPITAJAX_ERROR(-1, "Not enough permissin");
SPITAJAX_ERROR(-1, "Not enough permission");
return -1;
}
return 0;
......@@ -140,21 +139,6 @@ function CheckForm()
elseif (! TBvalid_city($formfields["city"])) {
$errors["city"] = TBFieldErrorString();
}
if (isset($formfields["password1"]) && $formfields["password1"] != "") {
if (!isset($formfields["password2"]) ||
strcmp($formfields["password2"], "") == 0) {
$errors["password2"] = "Missing Field";
}
elseif (strcmp($formfields["password1"], $formfields["password2"])) {
$errors["password2"] = "Does not match password";
}
elseif (! CHECKPASSWORD($formfields["uid"],
$formfields["password1"],
$formfields["name"],
$formfields["email"], $checkerror)) {
$errors["password1"] = "$checkerror";
}
}
# Present these errors before we call out to do anything else.
if (count($errors)) {
......@@ -254,34 +238,12 @@ function Do_Update()
if ($formfields["affiliation"] != $target_user->affil()) {
$args["affiliation"] = $formfields["affiliation"];
}
if (isset($formfields["password1"]) && $formfields["password1"] != "") {
$args["password1"] = $formfields["password1"];
$args["password2"] = $formfields["password2"];
}
if (count($args) &&
!User::ModUserInfo($target_user,
$target_user->uid(), $args, $errors)) {
SPITAJAX_ERROR(2, $errors);
return;
}
#
# Change the passphrase on the SSL key. If not an active user, it will
# happen when the user is approved.
#
if ($target_user->IsActive() &&
isset($formfields["password1"]) && $formfields["password1"] != "") {
$safe_password = escapeshellarg($formfields["password1"]);
# Do not send email, mkusercert sends email and hides the password.
$retval = SUEXEC($target_uid, "nobody",
"webmkusercert -C -p $safe_password $target_uid",
SUEXEC_ACTION_IGNORE);
if ($retval) {
SPITAJAX_ERROR(-1, "Could not reset password on SSL private key");
return;
}
}
#
# The user can change their email on this pass, but the backend
# does not allow that, so have to call tbacct directly with an
......@@ -301,7 +263,7 @@ function Do_Update()
SUEXEC_ACTION_CONTINUE);
}
if ($retval) {
$errors["error"] = "Internal error changing password";
$errors["error"] = "Internal error changing email";
SPITAJAX_ERROR(2, $errors);
return;
}
......
<?php
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -70,21 +70,8 @@ $defaults["state"] = $target_user->state();
$defaults["country"] = $target_user->country();
$defaults["affiliation"] = $target_user->affil();
#
# See the comment in signup.php about "promoting" geni users to
# local users that can start projects. These users do not get a
# password change box, we do not want them coming in that way,
# they have to use the trusted signer. As noted in signup.php,
# we need a flag for this kind of user.
#
$nopassword = 0;
if (!$target_user->country() || $target_user->country() == "") {
$nopassword = 1;
}
SPITHEADER(1);
echo "<script>\n";
echo "window.APT_OPTIONS.nopassword = $nopassword;\n";
echo "</script>\n";
echo "<link rel='stylesheet' href='css/bootstrap-formhelpers.min.css'>\n";
echo "<div id='page-body'></div>\n";
......
<?php
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -268,6 +268,7 @@ $PAGEHEADER_FUNCTION = function($thinheader = 0, $ignore1 = NULL,
<li><a href='ssh-keys.php'>Manage SSH Keys</a></li>";
}
echo " <li><a href='myaccount.php'>Manage Account</a></li>
<li><a href='changepswd.php'>Change Password</a></li>
<li><a href='signup.php'>Start/Join Project</a></li>";
if ($login_user->IsActive()) {
echo " <li class='divider'></li>
......
......@@ -214,7 +214,14 @@ $routing = array("myprofiles" =>
"guest" => false,
"unapproved" => true,
"methods" => array("update" =>
"Do_Update")),
"Do_Update")),
"changepswd" =>
array("file" => "changepswd.ajax",
"guest" => false,
"unapproved" => true,
"notloggedinokay" => true,
"methods" => array("changepswd" =>
"Do_ChangePassword")),
"lists" =>
array("file" => "lists.ajax",
"guest" => false,
......@@ -361,6 +368,7 @@ function CheckLoginForAjax($route)
global $this_user, $check_status;
$guestokay = false;
$unapprovedokay = false;
$notloggedinokay = false;
if (array_key_exists("guest", $route)) {
$guestokay = $route["guest"];
......@@ -368,6 +376,9 @@ function CheckLoginForAjax($route)
if (array_key_exists("unapproved", $route)) {
$unapprovedokay = $route["unapproved"];
}
if (array_key_exists("notloggedinokay", $route)) {
$notloggedinokay = $route["notloggedinokay"];
}
# Known user, but timed out.
if ($check_status & CHECKLOGIN_TIMEDOUT) {
......@@ -406,7 +417,7 @@ function CheckLoginForAjax($route)
}
return;
}
if (!$guestokay) {
if (!($guestokay || $notloggedinokay)) {
SPITAJAX_ERROR(2, "You are not logged in");
exit(2);
}
......
<div class='row'>
<div class='col-lg-4 col-lg-offset-4
col-md-4 col-md-offset-4
col-sm-6 col-sm-offset-3
col-xs-10 col-xs-offset-1'>
<form id='changepswd-form' role='form' method='post'>
<div class='panel panel-default'>
<div class='panel-heading'>
<h3 class='panel-title'>
<center>Change Your Password</center></h3>
</div>
<div class='panel-body'>
<% if (needold) { %>
<div class="form-group">
<input name='oldpassword' autofocus
value=''
class="form-control format-me"
data-key="oldpassword"
placeholder="Current Password" type="password">
</div>
<% } %>
<div class="form-group">
<input name='password1' <% if (!needold) { %>autofocus<% } %>
value=''
data-key="password1"
class='form-control format-me'
placeholder='Your new password'
type='password'>
</div>
<div class="form-group">
<input name='password2'
data-key="password2"
type='password'
value=''
class='form-control format-me'
placeholder='Confirm password'>
</div>
<center>
<button class='btn btn-primary' id="submit-button"
type='submit' name='reset'>Reset Password</button>
</center>
<% if (key) { %>
<input type='hidden' name='key' value='<%- key %>'>
<% } %>
<input type='hidden' name='uid' value='<%- user %>'>
</div>
</div>
</form>
</div>
</div>
<!-- Finished -->
<div id='success-modal' class='modal fade'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-body'>
<button type='button' class='close' data-dismiss='modal'
aria-hidden='true'>&times;</button>
<center><h4>Your password has been changed</h4></center>
</div>
</div>
</div>
</div>
......@@ -72,24 +72,6 @@
type="text">