From b8381709462e2477fedda96d91ff37df0570392c Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Thu, 5 Feb 2015 10:45:40 -0600 Subject: [PATCH] cli: user: Add list, activate, lock features This adds the ability to find, activate, lock, and optionally send a password-reset email to one ore more users in the help desk. --- include/class.user.php | 6 +- setup/cli/modules/class.module.php | 11 +- setup/cli/modules/user.php | 210 ++++++++++++++++++++++++----- 3 files changed, 192 insertions(+), 35 deletions(-) diff --git a/include/class.user.php b/include/class.user.php index 0ccfaf1a7..758b5ef01 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -932,6 +932,10 @@ class UserAccount extends UserAccountModel { return static::sendUnlockEmail('registration-client') === true; } + function setPassword($new) { + $this->set('passwd', Passwd::hash($vars['passwd1'])); + } + protected function sendUnlockEmail($template) { global $ost, $cfg; @@ -1016,7 +1020,7 @@ class UserAccount extends UserAccountModel { $this->set('username', $vars['username']); if ($vars['passwd1']) { - $this->set('passwd', Passwd::hash($vars['passwd1'])); + $this->setPassword($vars['passwd1']); $this->setStatus(UserAccountStatus::CONFIRMED); } diff --git a/setup/cli/modules/class.module.php b/setup/cli/modules/class.module.php index a1647ce3c..e01e507ab 100644 --- a/setup/cli/modules/class.module.php +++ b/setup/cli/modules/class.module.php @@ -39,8 +39,14 @@ class Option { $value = null; elseif ($value) $nargs = 1; - if ($this->type == 'int') + switch ($this->type) { + case 'int': $value = (int)$value; + break; + case 'bool': + $value = strcasecmp($value, 'true') === 0 || ((int) $value); + break; + } switch ($this->action) { case 'store_true': $value = true; @@ -156,7 +162,7 @@ class Module { if ($this->arguments) { echo "\nArguments:\n"; - foreach ($this->arguments as $name=>$help) + foreach ($this->arguments as $name=>$help) { $extra = ''; if (is_array($help)) { if (isset($help['options']) && is_array($help['options'])) { @@ -169,6 +175,7 @@ class Module { echo $name . "\n " . wordwrap( preg_replace('/\s+/', ' ', $help), 76, "\n ") .$extra."\n"; + } } if ($this->epilog) { diff --git a/setup/cli/modules/user.php b/setup/cli/modules/user.php index 2d6d146a9..6fd05f778 100644 --- a/setup/cli/modules/user.php +++ b/setup/cli/modules/user.php @@ -9,8 +9,12 @@ class UserManager extends Module { 'action' => array( 'help' => 'Action to be performed', 'options' => array( - 'import' => 'Import users to the system', - 'export' => 'Export users from the system', + 'import' => 'Import users from CSV file', + 'export' => 'Export users from the system to CSV', + 'activate' => 'Create or activate an account', + 'lock' => "Lock a user's account", + 'set-password' => "Set a user's account password", + 'list' => 'List users based on search criteria', ), ), ); @@ -21,6 +25,26 @@ class UserManager extends Module { 'help' => 'File or stream to process'), 'org' => array('-O', '--org', 'metavar'=>'ORGID', 'help' => 'Set the organization ID on import'), + + 'send-mail' => array('-m', '--send-mail', + 'help' => 'Send the user an email. Used with `activate` and `set-password`', + 'default' => false, 'action' => 'store_true'), + + 'verbose' => array('-v', '--verbose', 'default'=>false, + 'action'=>'store_true', 'help' => 'Be more verbose' + ), + + // -- Search criteria + 'account' => array('-A', '--account', 'type'=>'bool', 'metavar'=>'bool', + 'help' => 'Search for users based on activation status'), + 'isconfirmed' => array('-C', '--isconfirmed', 'type'=>'bool', 'metavar'=>'bool', + 'help' => 'Search for users based on confirmation status'), + 'islocked' => array('-L', '--islocked', 'type'=>'bool', 'metavar'=>'bool', + 'help' => 'Search for users based on locked status'), + 'email' => array('-E', '--email', + 'help' => 'Search by email address'), + 'id' => array('-U', '--id', + 'help' => 'Search by user id'), ); var $stream; @@ -30,42 +54,164 @@ class UserManager extends Module { Bootstrap::connect(); switch ($args['action']) { - case 'import': - // Properly detect Macintosh style line endings - ini_set('auto_detect_line_endings', true); - - if (!$options['file']) - $this->fail('CSV file to import users from is required!'); - elseif (!($this->stream = fopen($options['file'], 'rb'))) - $this->fail("Unable to open input file [{$options['file']}]"); - - $extras = array(); - if ($options['org']) { - if (!($org = Organization::lookup($options['org']))) - $this->fail($options['org'].': Unknown organization ID'); - $extras['org_id'] = $options['org']; + case 'import': + // Properly detect Macintosh style line endings + ini_set('auto_detect_line_endings', true); + + if (!$options['file']) + $this->fail('CSV file to import users from is required!'); + elseif (!($this->stream = fopen($options['file'], 'rb'))) + $this->fail("Unable to open input file [{$options['file']}]"); + + $extras = array(); + if ($options['org']) { + if (!($org = Organization::lookup($options['org']))) + $this->fail($options['org'].': Unknown organization ID'); + $extras['org_id'] = $options['org']; + } + $status = User::importCsv($this->stream, $extras); + if (is_numeric($status)) + $this->stderr->write("Successfully imported $status clients\n"); + else + $this->fail($status); + break; + + case 'export': + $stream = $options['file'] ?: 'php://stdout'; + if (!($this->stream = fopen($stream, 'c'))) + $this->fail("Unable to open output file [{$options['file']}]"); + + fputcsv($this->stream, array('Name', 'Email')); + foreach (User::objects() as $user) + fputcsv($this->stream, + array((string) $user->getName(), $user->getEmail())); + break; + + case 'activate': + $users = $this->getQuerySet($options); + foreach ($users as $U) { + if ($options['verbose']) { + $this->stderr->write(sprintf( + "Activating %s <%s>\n", + $U->getName(), $U->getDefaultEmail() + )); } - $status = User::importCsv($this->stream, $extras); - if (is_numeric($status)) - $this->stderr->write("Successfully imported $status clients\n"); - else - $this->fail($status); + if (!($account = $U->getAccount())) { + $account = UserAccount::create(array('user' => $U)); + $U->account = $account; + $U->save(); + } + + if ($options['send-mail']) { + global $ost, $cfg; + $ost = osTicket::start(); + $cfg = $ost->getConfig(); + + if (($error = $account->sendConfirmEmail()) && $error !== true) { + $this->warn(sprintf('%s: Unable to send email: %s', + $U->getDefaultEmail(), $error->getMessage() + )); + } + } + } + + break; + + case 'lock': + $users = $this->getQuerySet($options); + $users->select_related('account'); + foreach ($users as $U) { + if (!($account = $U->getAccount())) { + $this->warn(sprintf( + '%s: User does not have a client account', + $U->getName() + )); + } + $account->setFlag(UserAccountStatus::LOCKED); + $account->save(); + } + + break; + + case 'list': + $users = $this->getQuerySet($options); + + foreach ($users as $U) { + $this->stdout->write(sprintf( + "%d %s <%s>%s\n", + $U->id, $U->getName(), $U->getDefaultEmail(), + ($O = $U->getOrganization()) ? " ({$O->getName()})" : '' + )); + } + + break; + + case 'set-password': + $this->stderr->write('Enter new password: '); + $ps1 = fgets(STDIN); + if (!function_exists('posix_isatty') || !posix_isatty(STDIN)) { + $this->stderr->write('Re-enter new password: '); + $ps2 = fgets(STDIN); + + if ($ps1 != $ps2) + $this->fail('Passwords do not match'); + } + + // Account is required + $options['account'] = true; + $users = $this->getQuerySet($options); + + $updated = 0; + foreach ($users as $U) { + $U->account->setPassword($ps1); + if ($U->account->save()) + $updated++; + } + $this->stdout->write(sprintf('Updated %d users', $updated)); + break; + + default: + $this->stderr->write('Unknown action!'); + } + @fclose($this->stream); + } + + function getQuerySet($options, $requireOne=false) { + $users = User::objects(); + foreach ($options as $O=>$V) { + if (!isset($V)) + continue; + switch ($O) { + case 'account': + $users->filter(array('account__isnull' => !$V)); break; - case 'export': - $stream = $options['file'] ?: 'php://stdout'; - if (!($this->stream = fopen($stream, 'c'))) - $this->fail("Unable to open output file [{$options['file']}]"); + case 'isconfirmed': + case 'islocked': + $flags = array( + 'isconfirmed' => UserAccountStatus::CONFIRMED, + 'islocked' => UserAccountStatus::LOCKED, + ); + $Q = new Q(array('account__status__hasbit'=>$flags[$O])); + if (!$V) + $Q->negate(); + $users->filter($Q); + break; - fputcsv($this->stream, array('Name', 'Email')); - foreach (User::objects() as $user) - fputcsv($this->stream, - array((string) $user->getName(), $user->getEmail())); + case 'org': + if (is_numeric($V)) + $users->filter(array('org__id'=>$V)); + else + $users->filter(array('org__name__contains'=>$V)); break; - default: - $this->stderr->write('Unknown action!'); + + case 'id': + $users->filter(array('id'=>$V)); + break; + } + } - @fclose($this->stream); + return $users; } } Module::register('user', 'UserManager'); -- GitLab