From 1d401ede2eca0ec15c6c5d3b565ebd1dcff0cb2d Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Tue, 30 Jun 2015 23:54:50 -0500 Subject: [PATCH] Add password reset dialog for admins --- include/ajax.admin.php | 6 ++ include/ajax.staff.php | 61 +++++++++++++++++++ include/class.forms.php | 37 +++++------ include/class.staff.php | 60 +++++++++++++++++- include/staff/staff.inc.php | 5 +- include/staff/templates/set-password.tmpl.php | 22 +++++++ scp/ajax.php | 3 + scp/css/scp.css | 8 +++ 8 files changed, 178 insertions(+), 24 deletions(-) create mode 100644 include/ajax.staff.php create mode 100644 include/staff/templates/set-password.tmpl.php diff --git a/include/ajax.admin.php b/include/ajax.admin.php index ca3443348..0e2f18f67 100644 --- a/include/ajax.admin.php +++ b/include/ajax.admin.php @@ -16,12 +16,15 @@ class AdminAjaxAPI extends AjaxController { * * Throws: * 403 - Not logged in + * 403 - Not an administrator */ function addDepartment() { global $ost, $thisstaff; if (!$thisstaff) Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); $form = new DepartmentQuickAddForm($_POST); @@ -60,12 +63,15 @@ class AdminAjaxAPI extends AjaxController { * * Throws: * 403 - Not logged in + * 403 - Not an adminitrator */ function addTeam() { global $ost, $thisstaff; if (!$thisstaff) Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); $form = new TeamQuickAddForm($_POST); diff --git a/include/ajax.staff.php b/include/ajax.staff.php new file mode 100644 index 000000000..871fe9db7 --- /dev/null +++ b/include/ajax.staff.php @@ -0,0 +1,61 @@ +<?php + +require_once(INCLUDE_DIR . 'class.staff.php'); + +class StaffAjaxAPI extends AjaxController { + + /** + * Ajax: GET /staff/<id>/set-password + * + * Uses a dialog to add a new department + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an administrator + * 404 - No such agent exists + */ + function setPassword($id) { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if (!$id || !($staff = Staff::lookup($id))) + Http::response(404, 'No such agent'); + + $form = new PasswordResetForm($_POST); + + if ($_POST && $form->isValid()) { + $clean = $form->getClean(); + try { + if ($clean['email']) { + $staff->sendResetEmail(); + } + else { + $staff->setPassword($clean['passwd1'], null); + if ($clean['temporary']) + $staff->change_passwd = 1; + } + if ($staff->save()) + Http::response(201, 'Successfully updated'); + } + catch (BadPassword $ex) { + $passwd1 = $form->getField('passwd1'); + $passwd1->addError($ex->getMessage()); + } + catch (PasswordUpdateFailed $ex) { + // TODO: Add a warning banner or crash the update + } + } + + $title = __("Set Agent Password"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/set-password.tmpl.php'; + } +} diff --git a/include/class.forms.php b/include/class.forms.php index 3c898f26a..c0fbd9040 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -99,12 +99,7 @@ class Form { function isValid($include=false) { if (!isset($this->_errors)) { $this->_errors = array(); - $this->getClean(); - // Validate the whole form so that errors can be added to the - // individual fields and collected below. - foreach ($this->validators as $V) { - $V($this); - } + $this->validate($this->getClean()); foreach ($this->getFields() as $field) if ($field->errors() && (!$include || $include($field))) $this->_errors[$field->get('id')] = $field->errors(); @@ -112,6 +107,14 @@ class Form { return !$this->_errors; } + function validate($clean_data) { + // Validate the whole form so that errors can be added to the + // individual fields and collected below. + foreach ($this->validators as $V) { + $V($this); + } + } + function getClean() { if (!$this->_clean) { $this->_clean = array(); @@ -428,11 +431,11 @@ class FormField { $vs, array($this, $this->_clean)); } - if ($this->isVisible()) - $this->validateEntry($this->_clean); - if (!isset($this->_clean) && ($d = $this->get('default'))) $this->_clean = $d; + + if ($this->isVisible()) + $this->validateEntry($this->_clean); } return $this->_clean; } @@ -504,7 +507,6 @@ class FormField { * field is visible and should be considered for validation */ function isVisible() { - $config = $this->getConfiguration(); if ($this->get('visibility') instanceof VisibilityConstraint) { return $this->get('visibility')->isVisible($this); } @@ -3038,7 +3040,8 @@ class CheckboxWidget extends Widget { $classes = 'class="'.$config['classes'].'"'; ?> <div <?php echo implode(' ', array_filter(array($classes))); ?>> - <input id="<?php echo $this->id; ?>" style="vertical-align:top;" + <input id="<?php echo $this->id; ?>" style="vertical-align:top; height:100%" + class="pull-left clearfix" type="checkbox" name="<?php echo $this->name; ?>[]" <?php if ($this->value) echo 'checked="checked"'; ?> value="<?php echo $this->field->get('id'); ?>"/> @@ -3054,9 +3057,7 @@ class CheckboxWidget extends Widget { $data = $this->field->getSource(); if (count($data)) { if (!isset($data[$this->name])) - // Indeterminite. Likely false, but consider current field - // value - return null; + return false; return @in_array($this->field->get('id'), $data[$this->name]); } return parent::getValue(); @@ -3613,10 +3614,6 @@ class AssignmentForm extends Form { return !$this->errors(); } - function getClean() { - return parent::getClean(); - } - function render($options) { switch(strtolower($options['template'])) { @@ -3708,10 +3705,6 @@ class TransferForm extends Form { return !$this->errors(); } - function getClean() { - return parent::getClean(); - } - function render($options) { switch(strtolower($options['template'])) { diff --git a/include/class.staff.php b/include/class.staff.php index 5d686dc11..8b3a087ca 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -1067,4 +1067,62 @@ class StaffDeptAccess extends VerySimpleModel { $this->setFlag(self::FLAG_ALERTS, $value); } } -?> + +/** + * This form is used to administratively change the password. The + * ChangePasswordForm is used for an agent to change their own password. + */ +class PasswordResetForm +extends AbstractForm { + function buildFields() { + return array( + 'email' => new BooleanField(array( + 'default' => true, + 'configuration' => array( + 'desc' => __('Send the agent a password reset email'), + ), + )), + 'passwd1' => new PasswordField(array( + 'placeholder' => __('New Password'), + 'required' => true, + 'configuration' => array( + 'classes' => 'span12', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + 'passwd2' => new PasswordField(array( + 'placeholder' => __('Confirm Password'), + 'required' => true, + 'configuration' => array( + 'classes' => 'span12', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + 'temporary' => new BooleanField(array( + 'configuration' => array( + 'desc' => __('Require password change at next login'), + 'classes' => 'form footer', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + ); + } + + function validate($clean) { + if ($clean['passwd1'] != $clean['passwd2']) + $this->getField('passwd1')->addError(__('Passwords do not match')); + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } +} diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index 659ab6873..533eb4f93 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -95,9 +95,12 @@ $info = Format::htmlchars($info); <input type="text" size="40" style="width:300px" class="staff-username typeahead" name="username" value="<?php echo $info['username']; ?>" /> - <button type="button" class="action-button"> +<?php if (!($bk = $staff->getAuthBackend()) || $bk->supportsPasswordChange()) { ?> + <button type="button" class="action-button" onclick="javascript: + $.dialog('ajax.php/staff/'+<?php echo $info['id']; ?>+'/set-password', 201);"> <i class="icon-refresh"></i> <?php echo __('Set Password'); ?> </button> +<?php } ?> <i class="offset help-tip icon-question-sign" href="#username"></i> <div class="error"><?php echo $errors['username']; ?></div> </td> diff --git a/include/staff/templates/set-password.tmpl.php b/include/staff/templates/set-password.tmpl.php new file mode 100644 index 000000000..bc3d66ff5 --- /dev/null +++ b/include/staff/templates/set-password.tmpl.php @@ -0,0 +1,22 @@ +<h3 class="drag-handle"><?php echo $title ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<form method="post" action="#<?php echo $path; ?>"> + <div class="inset"> + <?php $form->render(); ?> + </div> + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>" /> + <input type="button" name="cancel" class="close" + value="<?php echo __('Cancel'); ?>" /> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php + echo $verb ?: __('Update'); ?>" /> + </span> + </p> + <div class="clear"></div> +</form> diff --git a/scp/ajax.php b/scp/ajax.php index 55dee8d6d..58e153a6a 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -230,6 +230,9 @@ $dispatcher = patterns('', url('^/department$', 'addDepartment'), url('^/team', 'addTeam') )) + )), + url('^/staff/(?P<id>\d+)', patterns('ajax.staff.php:StaffAjaxAPI', + url('^/set-password$', 'setPassword') )) ); diff --git a/scp/css/scp.css b/scp/css/scp.css index 42afb11c7..65c4b41f4 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2524,3 +2524,11 @@ form .inset { .form.footer { margin-top: 50px; } +.clearfix:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; +} -- GitLab