Still some rough edges here, and delete likely doesn't work. But most of the functionality necessary to add or edit devices is in place. Editing has been lightly tested.
require_once SHOUT_BASE . '/lib/Forms/DeviceForm.php';
$action = Horde_Util::getFormData('action');
-$devices = $shout_devices->getDevices($context);
-$devid = Horde_Util::getFormData('devid');
$vars = Horde_Variables::getDefaultVariables();
//$tabs = Shout::getTabs($context, $vars);
$RENDERER = new Horde_Form_Renderer();
-$section = 'devices';
$title = _("Devices: ");
switch ($action) {
- case 'save':
- $Form = new DeviceDetailsForm($vars);
-
- // Show the list if the save was successful, otherwise back to edit.
- if ($Form->isSubmitted() && $Form->isValid()) {
- try {
- $shout_devices->saveDevice($Form->getVars());
- $notification->push(_("Device settings saved."));
- } catch (Exception $e) {
- $notification->push($e);
- }
+case 'add':
+case 'edit':
+ $vars = Horde_Variables::getDefaultVariables();
+ $vars->set('context', $context);
+ $Form = new DeviceDetailsForm($vars);
+
+ // Show the list if the save was successful, otherwise back to edit.
+ if ($Form->isSubmitted() && $Form->isValid()) {
+ // Form is Valid and Submitted
+ try {
+ $devid = Horde_Util::getFormData('devid');
+
+ $Form->execute();
+ $notification->push(_("Device information updated."),
+ 'horde.success');
$action = 'list';
break;
- } else {
- $action = 'edit';
- }
- case 'add':
- case 'edit':
- if ($action == 'add') {
- $title .= _("New Device");
- // Treat adds just like an empty edit
- $action = 'edit';
- } else {
- $title .= sprintf(_("Edit Device %s"), $extension);
+ } catch (Exception $e) {
+ $notification->push($e);
}
+ } elseif ($Form->isSubmitted()) {
+ // Submitted but not valid
+ $notification->push(_("Problem processing the form. Please check below and try again."), 'horde.warning');
+ }
+
+ // Create a new add/edit form
+ $devid = Horde_Util::getFormData('devid');
+ $devices = $shout_devices->getDevices($context);
+ $vars = new Horde_Variables($devices[$devid]);
+
+ $vars->set('action', $action);
+ $Form = new DeviceDetailsForm($vars);
+ $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post');
+ // Make sure we get the right template below.
+ $action = 'edit';
+
+ break;
+case 'delete':
+ $title .= sprintf(_("Delete Devices %s"), $extension);
+ $devid = Horde_Util::getFormData('devid');
+
+ $vars = Horde_Variables::getDefaultVariables();
+ $vars->set('context', $context);
+ $Form = new DeviceDeleteForm($vars);
+
+ $FormValid = $Form->validate($vars, true);
+
+ if ($Form->isSubmitted() && $FormValid) {
+ try {
+ $Form->execute();
+ $notification->push(_("Device Deleted."));
+ $action = 'list';
+ } catch (Exception $e) {
+ $notification->push($e);
+ }
+ } elseif ($Form->isSubmitted()) {
+ $notification->push(_("Problem processing the form. Please check below and try again."), 'horde.warning');
+ }
- $FormName = 'DeviceDetailsForm';
- $vars = new Horde_Variables($devices[$devid]);
- $Form = new DeviceDetailsForm($vars);
-
- $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post');
-
- break;
-
+ $vars = Horde_Variables::getDefaultVariables(array());
+ $vars->set('context', $context);
+ $Form = new DeviceDeleteForm($vars);
+ $Form->open($RENDERER, $vars, Horde::applicationUrl('devices.php'), 'post');
- case 'delete':
- $notification->push("Not supported.");
- break;
+ break;
- case 'list':
- default:
- $action = 'list';
- $title .= _("List Users");
+case 'list':
+default:
+ $action = 'list';
+ $title .= _("List Devices");
}
+// Fetch the (possibly updated) list of extensions
+$devices = $shout_devices->getDevices($context);
+
require SHOUT_TEMPLATES . '/common-header.inc';
require SHOUT_TEMPLATES . '/menu.inc';
$notification->notify();
-//echo $tabs->render($section);
+echo "<br>\n";
require SHOUT_TEMPLATES . '/devices/' . $action . '.inc';
}
/**
- * Save an extension to the LDAP tree
+ * Save an extension to the backend.
+ *
+ * This method is intended to be overridden by a child class. However it
+ * also implements some basic checks, so a typical backend will still
+ * call this method via parent::
*
* @param string $context Context to which the user should be added
*
throw new Shout_Exception(_("Invalid extension."));
}
- if (!Shout::checkRights("shout:contexts:$context:users",
+ if (!Shout::checkRights("shout:contexts:$context:extensions",
+ PERMS_DELETE, 1)) {
+ throw new Shout_Exception(_("Permission denied to delete extensions in this context."));
+ }
+ }
+
+ /**
+ * Save a device to the backend.
+ *
+ * This method is intended to be overridden by a child class. However it
+ * also implements some basic checks, so a typical backend will still
+ * call this method via parent::
+ *
+ * @param string $context Context to which the user should be added
+ *
+ * @param string $extension Extension to be saved
+ *
+ * @param array $details Phone numbers, PIN, options, etc to be saved
+ *
+ * @return TRUE on success, PEAR::Error object on error
+ * @throws Shout_Exception
+ */
+ public function saveDevice($context, $devid, &$details)
+ {
+ if (empty($context)) {
+ throw new Shout_Exception(_("Invalid device information."));
+ }
+
+ if (!Shout::checkRights("shout:contexts:$context:devices", PERMS_EDIT, 1)) {
+ throw new Shout_Exception(_("Permission denied to save devices in this context."));
+ }
+
+ if (empty($devid) || !empty($details['genauthtok'])) {
+ list($devid, $password) = Shout::genDeviceAuth($context);
+ $details['devid'] = $devid;
+ $details['password'] = $password;
+ }
+
+
+ }
+
+ /**
+ * Delete a device from the backend.
+ *
+ * This method is intended to be overridden by a child class. However it
+ * also implements some basic checks, so a typical backend will still
+ * call this method via parent::
+ *
+ * @param <type> $context
+ * @param <type> $devid
+ */
+ public function deleteDevice($context, $devid)
+ {
+ if (empty($context) || empty($devid)) {
+ throw new Shout_Exception(_("Invalid device."));
+ }
+
+ if (!Shout::checkRights("shout:contexts:$context:devices",
PERMS_DELETE, 1)) {
- throw new Shout_Exception(_("Permission denied to delete users in this context."));
+ throw new Shout_Exception(_("Permission denied to delete devices in this context."));
}
}
protected $_db = null;
/**
+ * Handle for the current writable database connection.
+ * @var object $_db
+ */
+ protected $_write_db = null;
+
+ /**
* Boolean indicating whether or not we're connected to the LDAP
* server.
* @var boolean $_connected
$sql = sprintf($sql, $this->_params['table']);
$vars = array();
+ $msg = 'SQL query in Shout_Driver_Sql#getContexts(): ' . $sql;
+ Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_DEBUG);
$result = $this->_db->query($sql, $vars);
if ($result instanceof PEAR_Error) {
throw Shout_Exception($result);
'FROM %s WHERE accountcode = ?';
$sql = sprintf($sql, $this->_params['table']);
$args = array($context);
- $result = $this->_db->query($sql, $args);
+ $msg = 'SQL query in Shout_Driver_Sql#getDevices(): ' . $sql;
+ Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $sth = $this->_db->prepare($sql);
+ $result = $this->_db->execute($sth, $args);
if ($result instanceof PEAR_Error) {
throw new Shout_Exception($result);
}
while ($row && !($row instanceof PEAR_Error)) {
// Asterisk uses the "name" field to indicate the registration
// identifier. We use the field "alias" to put a friendly name on
- // the device. Thus devid -> name and name => alias
+ // the device. Thus devid => name and name => alias
$devid = $row['name'];
$row['devid'] = $devid;
$row['name'] = $row['alias'];
// Trim off the context from the mailbox number
list($row['mailbox']) = explode('@', $row['mailbox']);
+ // The UI calls the 'secret' a 'password'
+ $row['password'] = $row['secret'];
+ unset($row['secret']);
+
// Hide the DB internal ID from the front-end
unset($row['id']);
* Save a device (add or edit) to the backend.
*
* @param string $context The context in which this device is valid
- * @param array $info Array of device details
+ * @param string $devid Device ID to save
+ * @param array $details Array of device details
*/
- public function saveDevice($context, $info)
+ public function saveDevice($context, $devid, &$details)
{
+ // Check permissions and possibly update the authentication tokens
+ parent::saveDevice($context, $devid, $details);
+
// See getDevices() for an explanation of these conversions
- $info['alias'] = $info['name'];
- $info['mailbox'] = $info['mailbox'] . '@' . $context;
+ $details['alias'] = $details['name'];
+ $details['name'] = $details['devid'];
+ $details['mailbox'] .= '@' . $context;
- if ($info['devid']) {
+ if (!empty($devid)) {
// This is an edit
- $info['name'] = $info['devid'];
- $sql = 'UPDATE %s SET ';
+ $details['name'] = $details['devid'];
+ $sql = 'UPDATE %s SET accountcode = ?, callerid = ?, ' .
+ 'mailbox = ?, secret = ?, alias = ?, canreinvite = "no", ' .
+ 'nat = "yes", type = "peer" WHERE name = ?';
} else {
// This is an add. Generate a new unique ID and secret
- $devid = $context . uniqid();
- $secret = md5(uniqid(mt_rand));
- $sql = 'INSERT INTO %s (name, accountcode, callerid, mailbox, ' .
- 'secret, alias, canreinvite, nat, type) ' .
- ' VALUES (?, ?, ?, ?, ?, ?, "no", "yes", "peer")';
+ $sql = 'INSERT INTO %s (accountcode, callerid, mailbox, ' .
+ 'secret, alias, name, canreinvite, nat, type) ' .
+ 'VALUES (?, ?, ?, ?, ?, ?, "no", "yes", "peer")';
+
+ }
+
+ $sql = sprintf($sql, $this->_params['table']);
+ $args = array(
+ $context,
+ $details['callerid'],
+ $details['mailbox'],
+ $details['password'],
+ $details['alias'],
+ $details['name'],
+ );
+
+ $msg = 'SQL query in Shout_Driver_Sql#saveDevice(): ' . $sql;
+ Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $sth = $this->_write_db->prepare($sql);
+ $result = $this->_write_db->execute($sth, $args);
+ if ($result instanceof PEAR_Error) {
+ $msg = $result->getMessage() . ': ' . $result->getDebugInfo();
+ Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_ERR);
+ throw new Shout_Exception(_("Internal database error. Details have been logged for the administrator."));
}
+ return true;
+ }
+
+ public function deleteDevice($context, $devid)
+ {
+ parent::deleteDevice($context, $devid);
+
+ $sql = 'DELETE FROM %s WHERE devid = ?';
+ $sql = sprintf($sql, $this->_params['table']);
+ $values = array($devid);
+
+ $msg = 'SQL query in Shout_Driver_Sql#deleteDevice(): ' . $sql;
+ Horde::logMessage($msg, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $res = $this->_write_db->query($sql);
+ if ($res instanceof PEAR_Error) {
+ throw new Shout_Exception($res->getMessage(), $res->getCode());
+ }
+
+ return true;
}
/**
$this->_write_db->disconnect();
}
}
-
+
}
parent::__construct($vars, _("$formtitle - Context: $context"));
$this->addHidden('', 'action', 'text', true);
- $vars->set('action', 'save');
if ($edit) {
$this->addHidden('', 'devid', 'text', true);
$this->addVariable(_("Device Name"), 'name', 'text', false);
$this->addVariable(_("Mailbox"), 'mailbox', 'int', false);
$this->addVariable(_("CallerID"), 'callerid', 'text', false);
-
+ $this->addVariable(_("Reset authentication token?"), 'genauthtok',
+ 'boolean', false, false);//,
+ // _("If checked, the system will generate new device ID and password. The associated device will need to be reconfigured with the new information."));
return true;
}
+ public function execute()
+ {
+ global $shout_devices;
+
+ $action = $this->_vars->get('action');
+ $context = $this->_vars->get('context');
+ $devid = $this->_vars->get('devid');
+
+ // For safety, we force the device ID and password rather than rely
+ // on the form to pass them around.
+ if ($action == 'add') {
+ // The device ID should be empty so it can be generated.
+ $devid = null;
+ $password = null;
+ } else { // $action must be 'edit'
+ $devices = $shout_devices->getDevices($context);
+ if (!isset($devices[$devid])) {
+ // The device requested doesn't already exist. This can't
+ // be a valid edit.
+ throw new Shout_Exception(_("That device does not exist."),
+ 'horde.error');
+ } else {
+ $password = $devices[$devid]['password'];
+ }
+ }
+
+ $details = array(
+ 'devid' => $devid,
+ 'name' => $this->_vars->get('name'),
+ 'mailbox' => $this->_vars->get('mailbox'),
+ 'callerid' => $this->_vars->get('callerid'),
+ 'genauthtok' => $this->_vars->get('genauthtok'),
+ 'password' => $password,
+ );
+
+ $shout_devices->saveDevice($context, $devid, $details);
+ }
+
+}
+
+class DeviceDeleteForm extends Horde_Form
+{
+ function __construct(&$vars)
+ {
+ $devid = $vars->get('devid');
+ $context = $vars->get('context');
+
+ $title = _("Delete Device %s - Context: %s");
+ $title = sprintf($title, $devid, $context);
+ parent::__construct($vars, $title);
+
+ $this->addHidden('', 'context', 'text', true);
+ $this->addHidden('', 'devid', 'text', true);
+ $this->addHidden('', 'action', 'text', true);
+ $this->setButtons(array(_("Delete"), _("Cancel")));
+ }
+
+ function execute()
+ {
+ global $shout_devices;
+ $context = $this->_vars->get('context');
+ $devid = $this->_vars->get('devid');
+ $shout_devices->deleteDevice($context, $devid);
+ }
}
\ No newline at end of file
return ($test & $permmask) == $permmask;
}
+
+ /**
+ * Generate new device authentication tokens.
+ *
+ * This method is designed to generate random strings for the
+ * authentication ID and password. The result is intended to be used
+ * for automatically generated device information. The user is prevented
+ * from specifying usernames and passwords for these reasons:
+ * 1) If a username and/or password can be easily guessed, monetary loss
+ * is likely through the fraudulent placing of telephone calls.
+ * This has been observed in the wild far too many times already.
+ *
+ * 2) The username and password are only needed to be programmed into the
+ * device once, and then stored semi-permanently. In some cases, the
+ * provisioning can be done automatically. For these reasons, having
+ * user-friendly usernames and passswords is not terribly important.
+ *
+ * @param string $context Context for this credential pair
+ *
+ * @return array Array of (string $deviceID, string $devicePassword)
+ */
+ static public function genDeviceAuth($context)
+ {
+ $devid = uniqid($context);
+ $password = uudecode(md5(uniqid(mt_rand(), true)));
+
+ // This simple password generation algorithm inspired by Jon Haworth
+ // http://www.laughing-buddha.net/jon/php/password/
+
+ // define possible characters
+ // Vowels excluded to avoid potential pronounceability
+ $possible = "0123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
+
+ $password = "";
+ $i = 0;
+ while ($i < 12) {
+ // pick a random character from the possible ones
+ $char = substr($possible, mt_rand(0, strlen($possible)-1), 1);
+
+ // we don't want this character if it's already in the password
+ if (!strstr($password, $char)) {
+ $password .= $char;
+ $i++;
+ }
+ }
+
+ return array($devid, $password);
+ }
}