diff --git a/include/ajax.admin.php b/include/ajax.admin.php
index ca34433480a46eb6091730a9d9068de46d6bdc1f..0e2f18f67bb430792874aa22a798dab5e7fad7fe 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 0000000000000000000000000000000000000000..871fe9db7fd6846fbdc3c0f5748930adda85ae40
--- /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 3c898f26a61ef5bb02fba8bc96df1e8638b6f109..c0fbd9040f3a296af2d3c0e3341e2e810f887543 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 5d686dc113914a35f02d45de77728a38c4677450..8b3a087ca60a97b930f2b3995716989412716d27 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 659ab6873c2ba23f7d68ac8de189a95143ca58d4..533eb4f935102ee2072b1d999a4f7b1f94b75fac 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 0000000000000000000000000000000000000000..bc3d66ff50fc8e2d870b11e255c2c38a3a2ad287
--- /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 55dee8d6d19035b46fac75e0cd440c1f27a52877..58e153a6a921e8d74fcbc818b6dc25895e192007 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 42afb11c70fda8aa85eda07d8931f75a0f1955d9..65c4b41f4fc467f5c3e782185f8c4bd4794c39ba 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;
+}