"Blue.box is an open source GUI for administration and configuration of FreeSWITCH and Asterisk based VoIP system."
http://wiki.2600hz.com/display/bluebox/About+blue.box

blue.box is PHP/MySQL application, like Tiki.

In the context of suite:Tiki Suite, we'll add an integration between Tiki and blue.box. Please note that this is like Tiki's interaction with Kaltura and BigBlueButton (do not get blue.box and BigBlueButton confused as they are distinct FOSS projects), it should be as simple/built-in/native as possible and you do not have to use the full Tiki Suite.

Our existing user base should be able to upgrade to Tiki9, get a

  1. blue.box/FreeSWITCH hosting such as http://www.synapseglobal.com/bluebox_hosting.php
  2. get a phone number (ex.: http://www.synapseglobal.com/voip_termination.php)
  3. configure something in the admin panels and be ready to go.
    • For the desktop app, we'll be focusing on suite:Jitsi support.


Ideally,

  1. From my existing Tiki users/groups, I should be able to set who has what permission level in blue.box.
  2. From Tiki, there would be a one-click login to blue.box (alternatively, users connect to the blue.box interface with the same username/password as Tiki)
  3. Tiki user accounts can get a SIP account in the process (seems to be easy)
  4. Tiki user accounts can get a XMPP account in the process (needs additional work?)


Examples of integration


Questions?

  • How do we keep user data in sync between the two?
    • Tiki is the master and blue.box can't change email and password in blue.box?
    • Sync both ways?
  • blue.box has the following levels: Guest, Restricted User, User, Power User, Restricted Admin, Account Admin, System Admin
    • How do we make this to Tiki groups? tiki_p_bluebox_guest, tiki_p_bluebox_restricted_user, etc.
  • How to configure Jitsi client? suite:Tiki Suite Jitsi blue.box


Testing

Confirmed working 2011-09-19

  1. Create a user at http://bluebox.tikisuite.org/tiki/ (Tiki😎
  2. Login with those credentials at http://bluebox.tikisuite.org/bluebox/
    • user is indeed created with the level "User"
      • This is too high. User should just be able to set personal stuff (my voice mail, etc.)
  3. Login with same credentials with Jitsi (SIP) suite:Tiki Suite Jitsi blue.box

Dogfood use case

  • offer jabber & SIP accounts to all @tiki.org e-mail holders
    • by default, no out-calls, just SIP & Jabber
      • Thus, we need to figure out a scheme for permissions

Integration issues

  • blue.box uses emails, not usernames like Tiki
    • A same e-mail can be used by two different usernames in Tiki
  • When a user calls in, it finds them in tiki and displays their record as a popup
    • Should we have a phone number field type in trackers?

Robert

  • Get commits on blue.box going
  • Get blue.box/FreeSWITCH working with XMPP and jitsi
  • Solve SSO and groups/perms
  • Quick test of PluginJabber which will probably need updating because it's several years old. Let's get something in HTML5/AJAX -> http://www.ohloh.net/tags/ajax/xmpp


Marc and LP discussion

In blue.box, a restriced number of perms:
Regular user can self-configure:
Configure rules for received calls

  • Which email to fwd voice mail attachments
  • Which number to forward to (and the hours)


Admins

  • Can do anything


In blue.box, admins can set-up:
Users can make outgoing calls (and thus can cost $) (which is an attribute to set in blue.box)

Notes about blue.box

  • Groups/permission system in not a default feature, you need to install the plugin (easy to do)

Use cases

Make it easy for any SME to get a blue.box configured for their team.

  • blue.box for standard PBX functionality.
  • Jitsi as soft-phone
  • Tiki as the Intranet (which has all the logins, and sometimes connects to LDAP)

Fred, basic user

  1. Fred logins into Tiki and updates his todo list, wiki pages, etc.
  2. Fred clicks a link in Tiki which sends him to blue.box
  3. Since Tiki and blue-box have been linked/configured, blue.box knows what access level to provide Fred
  4. Since Fred is not an admin, he can't see/change the configurations for other team members.
    • You need to install and configure permission module, which is not part of blue.box core installation.


Fred should be able to configure things like

  • Forwarding voice mail to email address
  • Transfer calls
  • etc.


Fred has documentation on how to configure Jitsi to work with blue.box/FreeSWITCH

Samantha, admin

  • Same as Fred, but can manage all the accounts

Integration code from Robert

Code emailed by Robert Plummer
Copy to clipboard
<?php ini_set('error_reporting', E_ALL); ini_set('display_errors', 1); class Http_Exception extends Exception{ const NOT_MODIFIED = 304; const BAD_REQUEST = 400; const NOT_FOUND = 404; const NOT_ALOWED = 405; const CONFLICT = 409; const PRECONDITION_FAILED = 412; const INTERNAL_ERROR = 500; } class Http_Stat { private $_status = null; private $_type = null; private $_url = null; private $_params = null; private $_success = null; private $_data = null; function __construct($status, $type, $url, $params, $success = false, $data = null) { $this->_status = $status; $this->_type = $type; $this->_url = $url; $this->_params = $params; $this->_success = $success; $this->_data = $data; } function getStatus() { return $this->_status; } function getType() { return $this->_type; } function getUrl() { return $this->_url; } function getParams() { return $this->_params; } function getSuccess() { return $this->_success; } function getData() { return $this->_data; } } class Http { private $_host = null; private $_port = null; private $_user = null; private $_pass = null; private $_protocol = null; const HTTP = 'http'; const HTTPS = 'https'; private $_connMultiple = false; /** * Factory of the class. Lazy connect * * @param string $host * @param integer $port * @param string $user * @param string $pass * @return Http */ static public function connect($host, $port = 80, $protocol = self::HTTP) { return new self($host, $port, $protocol, false); } /** * * @return Http */ static public function multiConnect() { return new self(null, null, null, true); } private $_append = array(); public function add($http) { $this->_append[] = $http; return $this; } private $_silentMode = false; /** * * @param bool $mode * @return Http */ public function silentMode($mode=true) { $this->_silentMode = $mode; return $this; } protected function __construct($host, $port, $protocol, $connMultiple) { $this->_connMultiple = $connMultiple; $this->_host = $host; $this->_port = $port; $this->_protocol = $protocol; } public function setCredentials($user, $pass) { $this->_user = $user; $this->_pass = $pass; } const POST = 'POST'; const GET = 'GET'; const DELETE = 'DELETE'; private $_requests = array(); /** * @param string $url * @param array $params * @return Http */ public function post($url, $params=array()) { $this->_requests[] = array(self::POST, $this->_url($url), $params); return $this; } /** * @param string $url * @param array $params * @return Http */ public function get($url, $params=array()) { $this->_requests[] = array(self::GET, $this->_url($url), $params); return $this; } /** * @param string $url * @param array $params * @return Http */ public function delete($url, $params=array()) { $this->_requests[] = array(self::DELETE, $this->_url($url), $params); return $this; } public function _getRequests() { return $this->_requests; } /** * POST request * * @param string $url * @param array $params * @return string */ public function doPost($url, $params=array()) { return $this->_exec(self::POST, $this->_url($url), $params); } /** * GET Request * * @param string $url * @param array $params * @return string */ public function doGet($url, $params=array()) { return $this->_exec(self::GET, $this->_url($url), $params); } /** * DELETE Request * * @param string $url * @param array $params * @return string */ public function doDelete($url, $params=array()) { return $this->_exec(self::DELETE, $this->_url($url), $params); } private $_headers = array(); /** * setHeaders * * @param array $headers * @return Http */ public function setHeaders($headers) { $this->_headers = $headers; return $this; } /** * Builds absolute url * * @param unknown_type $url * @return unknown */ private function _url($url=null) { return "{$this->_protocol}://{$this->_host}:{$this->_port}/{$url}"; } const HTTP_OK = 200; const HTTP_CREATED = 201; const HTTP_ACEPTED = 202; /** * Performing the real request * * @param string $type * @param string $url * @param array $params * @return string */ private function _exec($type, $url, $params = array()) { $headers = $this->_headers; $s = curl_init(); if(!is_null($this->_user)){ curl_setopt($s, CURLOPT_USERPWD, $this->_user.':'.$this->_pass); } switch ($type) { case self::DELETE: curl_setopt($s, CURLOPT_URL, $url . '?' . http_build_query($params)); curl_setopt($s, CURLOPT_CUSTOMREQUEST, self::DELETE); break; case self::POST: curl_setopt($s, CURLOPT_URL, $url); curl_setopt($s, CURLOPT_POST, true); curl_setopt($s, CURLOPT_POSTFIELDS, $params); break; case self::GET: curl_setopt($s, CURLOPT_URL, $url . '?' . http_build_query($params)); break; } curl_setopt($s, CURLOPT_RETURNTRANSFER, true); curl_setopt($s, CURLOPT_HTTPHEADER, $headers); $_out = curl_exec($s); $status = curl_getinfo($s, CURLINFO_HTTP_CODE); curl_close($s); switch ($status) { case self::HTTP_OK: case self::HTTP_CREATED: case self::HTTP_ACEPTED: $out = $_out; break; default: if (!$this->_silentMode) { throw new Http_Exception("http error: {$status}", $status); } } return $out; } public function run() { if ($this->_connMultiple) { return $this->_runMultiple(); } else { return $this->_run(); } } private function _runMultiple() { $out= null; if (count($this->_append) > 0) { $arr = array(); foreach ($this->_append as $_append) { $arr = array_merge($arr, $_append->_getRequests()); } $this->_requests = $arr; $out = $this->_run(); } return $out; } private function _run() { $headers = $this->_headers; $curly = $result = array(); $mh = curl_multi_init(); foreach ($this->_requests as $id => $reg) { $curly[$id] = curl_init(); $type = $reg[0]; $url = $reg[1]; $params = $reg[2]; if(!is_null($this->_user)){ curl_setopt($curly[$id], CURLOPT_USERPWD, $this->_user.':'.$this->_pass); } switch ($type) { case self::DELETE: curl_setopt($curly[$id], CURLOPT_URL, $url . '?' . http_build_query($params)); curl_setopt($curly[$id], CURLOPT_CUSTOMREQUEST, self::DELETE); break; case self::POST: curl_setopt($curly[$id], CURLOPT_URL, $url); curl_setopt($curly[$id], CURLOPT_POST, true); curl_setopt($curly[$id], CURLOPT_POSTFIELDS, $params); break; case self::GET: curl_setopt($curly[$id], CURLOPT_URL, $url . '?' . http_build_query($params)); break; } curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, true); curl_setopt($curly[$id], CURLOPT_HTTPHEADER, $headers); curl_multi_add_handle($mh, $curly[$id]); } $running = null; do { curl_multi_exec($mh, $running); sleep(0.2); } while($running > 0); foreach($curly as $id => $c) { $status = curl_getinfo($c, CURLINFO_HTTP_CODE); switch ($status) { case self::HTTP_OK: case self::HTTP_CREATED: case self::HTTP_ACEPTED: $result[$id] = new Http_Stat($status, $type, $url, $params, true, curl_multi_getcontent($c)); break; default: if (!$this->_silentMode) { $result[$id] = new Http_Stat($status, $type, $url, $params, false); } } curl_multi_remove_handle($mh, $c); } curl_multi_close($mh); return $result; } } defined('SYSPATH') OR die('No direct access allowed.'); /** * Tiki Auth driver, relies on Doctrine driver * Note: this Auth driver does not support roles nor auto-login. * * $Id: File.php 3917 2009-04-02 03:06:22Z zombor $ * * @package Auth * @author Robert Plummer * @copyright (c) 2009 Darren Schreiber * @license Mozilla Public License (MPL) v1.1 */ class Auth_Tiki_Driver extends Auth_Driver { /** * VERSION */ const VERSION = '0.1'; /** * Constructor loads the user list into the class. */ public function __construct(array $config, $passwordOrig = '') { parent::__construct($config); include_once("Doctrine.php"); $this->doctrine = new Auth_Doctrine_Driver($config); if (empty($passwordOrig) && !empty($_REQUEST['login']['password'])) { $passwordOrig = $_REQUEST['login']['password']; } $this->passwordOrig = $passwordOrig; } /** * Logs a user in. * * @param string username * @param string password * @param boolean enable auto-login (not supported) * @return boolean */ public function login($username, $password, $remember, $requirePassword = TRUE) { $client = Http::connect('bluebox.tikisuite.org'); $client->setCredentials($username, $this->passwordOrig); $status = $client->get('/tiki/tiki-index.php')->run(); $tikiLogin = FALSE; if (isset($status[0])) { $tikiLogin = $status[0]->getSuccess(); } if ($tikiLogin != TRUE) return $tikiLogin; //at this point the user exists in tiki $userExists = (strlen($this->password($username)) > 0 ? TRUE : FALSE); if (empty($userExists) && !empty($password)) { $emailParts = explode('@', $username); $userId = $this->createUser($username, $password); $deviceId = $this->createDevice($userId, $emailParts[0], $username, $password); $numberId = $this->createNumber($deviceId, $emailParts[0]); } return $this->complete_login($username); //we don't need to verify password here, it was done above from tiki server //return $this->doctrine->login($username, $password, $remember, $requirePassword); } /** * Forces a user to be logged in, without specifying a password. * * @param mixed username * @return boolean */ public function force_login($username) { return $this->doctrine->force_login($username); } /** * Get the stored password for a username. * * @param mixed username * @return string */ public function password($username) { return $this->doctrine->password($username); } /** * Generate and return a token that allows for resetting of a user's password * @param string $username Username of the user to reset * @return string Returns a token/hash that should be sent to a user via a confirmed method to allow for reset */ public function resetToken($username) { return $this->doctrine->resetToken($username); } /** * Force a new password to be recorded, if a password reset token matches the stored token previously generated. * @param string $username * @param string $token Password token, generated and recorded by resetToken() * @param string $newPassword * @return boolean True if reset is successful */ public function resetPassword($username, $token, $newPassword) { return $this->doctrine->resetPassword($username, $token, $newPassword); } public function getRealIpAddr() { return $this->doctrine->getRealIpAddr(); } private function createUser($username, $password) { $user = new User(); $user['first_name'] = 'Tiki'; $user['last_name'] = 'Account'; $user['username'] = $username; $user['email_address'] = $username; $user['password'] = $password; $user['user_type'] = User::TYPE_NORMAL_USER; $user['location_id'] = 1; $user['account_id'] = 1; //save user Doctrine::getTable('User')->getRecordListener()->get('MultiTenant')->setOption('disabled', TRUE); $userId = $user->save(); Doctrine::getTable('User')->getRecordListener()->get('MultiTenant')->setOption('disabled', FALSE); return $userId; } private function createDevice($userId, $deviceName, $username, $password) { $device = new Device(); $device['name'] = $deviceName; $device['user_id'] = $userId; $device['context_id'] = 1; $device['account_id'] = 1; //save device Doctrine::getTable('Device')->getRecordListener()->get('MultiTenant')->setOption('disabled', TRUE); $deviceId = $device->save(); Doctrine::getTable('Device')->getRecordListener()->get('MultiTenant')->setOption('disabled', FALSE); return $deviceId; } private function createNumber($deviceId, $numberName) { $number = new Number(); $number['number'] = $numberName; $number['type'] = Number::TYPE_INTERNAL; $number['status'] = Number::STATUS_NORMAL; $number['location_id'] = 1; $number['account_id'] = 1; $number['foreign_id'] = $deviceId; //save number Doctrine::getTable('Number')->getRecordListener()->get('MultiTenant')->setOption('disabled', TRUE); $numberId = $number->save(); Doctrine::getTable('Number')->getRecordListener()->get('MultiTenant')->setOption('disabled', FALSE); return $numberId; } } // End Auth_File_Driver

Related links


alias