1<?php 2 3require_once(INCLUDE_DIR . 'class.staff.php'); 4 5class StaffAjaxAPI extends AjaxController { 6 7 /** 8 * Ajax: GET /staff/<id>/set-password 9 * 10 * Uses a dialog to add a new department 11 * 12 * Returns: 13 * 200 - HTML form for addition 14 * 201 - {id: <id>, name: <name>} 15 * 16 * Throws: 17 * 403 - Not logged in 18 * 403 - Not an administrator 19 * 404 - No such agent exists 20 */ 21 function setPassword($id) { 22 global $ost, $thisstaff; 23 24 if (!$thisstaff) 25 Http::response(403, 'Agent login required'); 26 if (!$thisstaff->isAdmin()) 27 Http::response(403, 'Access denied'); 28 if ($id && !($staff = Staff::lookup($id))) 29 Http::response(404, 'No such agent'); 30 31 $form = new PasswordResetForm($_POST); 32 $errors = array(); 33 if (!$_POST && isset($_SESSION['new-agent-passwd'])) 34 $form->data($_SESSION['new-agent-passwd']); 35 36 if ($_POST && $form->isValid()) { 37 $clean = $form->getClean(); 38 try { 39 // Validate password 40 if (!$clean['welcome_email']) 41 Staff::checkPassword($clean['passwd1'], null); 42 if ($id == 0) { 43 // Stash in the session later when creating the user 44 $_SESSION['new-agent-passwd'] = $clean; 45 Http::response(201, 'Carry on'); 46 } 47 if ($clean['welcome_email']) { 48 $staff->sendResetEmail(); 49 } 50 else { 51 $staff->setPassword($clean['passwd1'], null); 52 if ($clean['change_passwd']) 53 $staff->change_passwd = 1; 54 } 55 if ($staff->save()) 56 Http::response(201, 'Successfully updated'); 57 } 58 catch (BadPassword $ex) { 59 if ($passwd1 = $form->getField('passwd1')) 60 $passwd1->addError($ex->getMessage()); 61 } 62 catch (PasswordUpdateFailed $ex) { 63 $errors['err'] = __('Password update failed:').' '.$ex->getMessage(); 64 } 65 } 66 67 $title = __("Set Agent Password"); 68 $verb = $id == 0 ? __('Set') : __('Update'); 69 $path = ltrim($ost->get_path_info(), '/'); 70 71 include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; 72 } 73 74 function changePassword($id) { 75 global $cfg, $ost, $thisstaff; 76 77 if (!$thisstaff) 78 Http::response(403, 'Agent login required'); 79 if (!$id || $thisstaff->getId() != $id) 80 Http::response(404, 'No such agent'); 81 82 $form = new PasswordChangeForm($_POST); 83 $errors = array(); 84 85 if ($_POST && $form->isValid()) { 86 $clean = $form->getClean(); 87 if (($rtoken = $_SESSION['_staff']['reset-token'])) { 88 $_config = new Config('pwreset'); 89 if ($_config->get($rtoken) != $thisstaff->getId()) 90 $errors['err'] = 91 __('Invalid reset token. Logout and try again'); 92 elseif (!($ts = $_config->lastModified($rtoken)) 93 && ($cfg->getPwResetWindow() < (time() - strtotime($ts)))) 94 $errors['err'] = 95 __('Invalid reset token. Logout and try again'); 96 } 97 if (!$errors) { 98 try { 99 $thisstaff->setPassword($clean['passwd1'], @$clean['current']); 100 if ($thisstaff->save()) { 101 if ($rtoken) { 102 $thisstaff->cancelResetTokens(); 103 Http::response(200, $this->encode(array( 104 'redirect' => 'index.php' 105 ))); 106 } 107 Http::response(201, 'Successfully updated'); 108 } 109 } 110 catch (BadPassword $ex) { 111 if ($passwd1 = $form->getField('passwd1')) 112 $passwd1->addError($ex->getMessage()); 113 } 114 catch (PasswordUpdateFailed $ex) { 115 $errors['err'] = __('Password update failed:').' '.$ex->getMessage(); 116 } 117 } 118 } 119 120 $title = __("Change Password"); 121 $verb = __('Update'); 122 $path = ltrim($ost->get_path_info(), '/'); 123 124 include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; 125 } 126 127 function getAgentPerms($id) { 128 global $thisstaff; 129 130 if (!$thisstaff) 131 Http::response(403, 'Agent login required'); 132 if (!$thisstaff->isAdmin()) 133 Http::response(403, 'Access denied'); 134 if (!($staff = Staff::lookup($id))) 135 Http::response(404, 'No such agent'); 136 137 return $this->encode($staff->getPermissionInfo()); 138 } 139 140 function resetPermissions() { 141 global $ost, $thisstaff; 142 143 if (!$thisstaff) 144 Http::response(403, 'Agent login required'); 145 if (!$thisstaff->isAdmin()) 146 Http::response(403, 'Access denied'); 147 148 $form = new ResetAgentPermissionsForm($_POST); 149 150 if (@is_array($_GET['ids'])) { 151 $perms = new RolePermission(null); 152 $selected = Staff::objects()->filter(array('staff_id__in' => $_GET['ids'])); 153 foreach ($selected as $staff) 154 // XXX: This maybe should be intersection rather than union 155 $perms->merge($staff->getPermission()); 156 $form->getField('perms')->setValue($perms->getInfo()); 157 } 158 159 if ($_POST && $form->isValid()) { 160 $clean = $form->getClean(); 161 Http::response(201, $this->encode(array('perms' => $clean['perms']))); 162 } 163 164 $title = __("Reset Agent Permissions"); 165 $verb = __("Continue"); 166 $path = ltrim($ost->get_path_info(), '/'); 167 168 include STAFFINC_DIR . 'templates/reset-agent-permissions.tmpl.php'; 169 } 170 171 function changeDepartment() { 172 global $ost, $thisstaff; 173 174 if (!$thisstaff) 175 Http::response(403, 'Agent login required'); 176 if (!$thisstaff->isAdmin()) 177 Http::response(403, 'Access denied'); 178 179 $form = new ChangeDepartmentForm($_POST); 180 181 // Preselect reasonable dept and role based on the current settings 182 // of the received staff ids 183 if (@is_array($_GET['ids'])) { 184 $dept_id = null; 185 $role_id = null; 186 $selected = Staff::objects()->filter(array('staff_id__in' => $_GET['ids'])); 187 foreach ($selected as $staff) { 188 if (!isset($dept_id)) { 189 $dept_id = $staff->dept_id; 190 $role_id = $staff->role_id; 191 } 192 elseif ($dept_id != $staff->dept_id) 193 $dept_id = 0; 194 elseif ($role_id != $staff->role_id) 195 $role_id = 0; 196 } 197 $form->getField('dept_id')->setValue($dept_id); 198 $form->getField('role_id')->setValue($role_id); 199 } 200 201 if ($_POST && $form->isValid()) { 202 $clean = $form->getClean(); 203 Http::response(201, $this->encode($clean)); 204 } 205 206 $title = __("Change Primary Department"); 207 $verb = __("Continue"); 208 $path = ltrim($ost->get_path_info(), '/'); 209 210 include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; 211 } 212 213 function setAvatar($id) { 214 global $thisstaff; 215 216 if (!$thisstaff) 217 Http::response(403, 'Agent login required'); 218 if ($id != $thisstaff->getId() && !$thisstaff->isAdmin()) 219 Http::response(403, 'Access denied'); 220 if ($id == $thisstaff->getId()) 221 $staff = $thisstaff; 222 else 223 $staff = Staff::lookup((int) $id); 224 225 if (!($avatar = $staff->getAvatar())) 226 Http::response(404, 'User does not have an avatar'); 227 228 if ($code = $avatar->toggle()) 229 return $this->encode(array( 230 'img' => (string) $avatar, 231 // XXX: This is very inflexible 232 'code' => $code, 233 )); 234 } 235 236 function configure2FA($staffId, $id=0) { 237 global $thisstaff; 238 if (!$thisstaff) 239 Http::response(403, 'Agent login required'); 240 if ($staffId != $thisstaff->getId()) 241 Http::response(403, 'Access denied'); 242 if ($id && !($auth= Staff2FABackend::lookup($id))) 243 Http::response(404, 'Unknown 2FA'); 244 245 $staff = $thisstaff; 246 $info = array(); 247 if ($auth) { 248 // Simple state machine to manage settings and verification 249 $state = @$_POST['state'] ?: 'validate'; 250 switch ($state) { 251 case 'verify': 252 try { 253 $form = $auth->getInputForm($_POST); 254 if ($_POST && $form 255 && $form->isValid() 256 && $auth->validate($form, $staff)) { 257 // Mark the settings as verified 258 if (($config = $staff->get2FAConfig($auth->getId()))) { 259 $config['verified'] = time(); 260 $staff->updateConfig(array( 261 $auth->getId() => JsonDataEncoder::encode($config))); 262 } 263 // We're done here 264 $auth = null; 265 $info['notice'] = __('Setup completed successfully'); 266 } else { 267 $info['error'] = __('Unable to verify the token - try again!'); 268 } 269 } catch (ExpiredOTP $ex) { 270 // giving up cleanly 271 $info['error'] = $ex->getMessage(); 272 $auth = null; 273 } 274 break; 275 case 'validate': 276 default: 277 $config = $staff->get2FAConfig($auth->getId()); 278 $vars = $_POST ?: $config['config'] ?: array('email' => $staff->getEmail()); 279 $form = $auth->getSetupForm($vars); 280 if ($_POST && $form && $form->isValid()) { 281 if ($config['config'] && $config['config']['external2fa']) 282 $external2fa = true; 283 284 // Save the setting based on setup form 285 $clean = $form->getClean(); 286 if (!$external2fa) { 287 $config = ['config' => $clean, 'verified' => 0]; 288 $staff->updateConfig(array( 289 $auth->getId() => JsonDataEncoder::encode($config))); 290 } 291 292 // Send verification token to the user 293 if ($token=$auth->send($staff)) { 294 // Transition to verify state 295 $form = $auth->getInputForm($vars); 296 $state = 'verify'; 297 $info['notice'] = __('Token sent to you!'); 298 } else { 299 // Generic error TODO: better wording 300 $info['error'] = __('Error sending Token - double check entry'); 301 } 302 } 303 } 304 } 305 306 include STAFFINC_DIR . 'templates/2fas.tmpl.php'; 307 } 308 309 function reset2fA($staffId) { 310 global $thisstaff; 311 312 if (!$thisstaff) 313 Http::response(403, 'Agent login required'); 314 if (!$thisstaff->isAdmin()) 315 Http::response(403, 'Access denied'); 316 317 $default_2fa = ConfigItem::getConfigsByNamespace('staff.'.$staffId, 'default_2fa'); 318 319 if ($default_2fa) 320 $default_2fa->delete(); 321 } 322} 323