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