From: Evan Prodromou Date: Fri, 10 Jun 2011 19:51:30 +0000 (-0400) Subject: First pass at complete global API X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=2d72a64841ebaae889b723d5e259095cdc1b6389;p=quix0rs-gnu-social.git First pass at complete global API --- diff --git a/plugins/DomainStatusNetwork/DomainStatusNetworkPlugin.php b/plugins/DomainStatusNetwork/DomainStatusNetworkPlugin.php index 876443aa3f..efb1aae4d8 100644 --- a/plugins/DomainStatusNetwork/DomainStatusNetworkPlugin.php +++ b/plugins/DomainStatusNetwork/DomainStatusNetworkPlugin.php @@ -97,8 +97,16 @@ class DomainStatusNetworkPlugin extends Plugin switch ($cls) { + case 'GlobalregisterAction': + case 'GloballoginAction': + case 'GlobalrecoverAction': + include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; case 'DomainStatusNetworkInstaller': - include_once $dir . '/' . strtolower($cls) . '.php'; + include_once $dir . '/lib/' . strtolower($cls) . '.php'; + return false; + case 'GlobalApiAction': + include_once $dir . '/lib/' . strtolower($cls) . '.php'; return false; default: return true; @@ -138,6 +146,17 @@ class DomainStatusNetworkPlugin extends Plugin return true; } + function onRouterInitialized($m) + { + if (common_config('globalapi', 'enabled')) { + foreach (array('register', 'login', 'recover') as $method) { + $m->connect('api/statusnet/global/'.$method, + array('action' => 'global'.$method)); + } + } + return true; + } + static function nicknameForDomain($domain) { $registered = self::registeredDomain($domain); @@ -261,12 +280,16 @@ class DomainStatusNetworkPlugin extends Plugin $loginToken = Login_token::makeNew($user); if (empty($loginToken)) { - throw new ServerException(_('Cannot log in.')); + throw new ServerException(sprintf(_('Could not create new login token for user %s'), $user->nickname)); } $url = common_local_url('otp', array('user_id' => $loginToken->user_id, 'token' => $loginToken->token)); + if (empty($url)) { + throw new ServerException(sprintf(_('Could not create new OTP URL for user %s'), $user->nickname)); + } + return $url; } @@ -282,6 +305,12 @@ class DomainStatusNetworkPlugin extends Plugin StatusNet::switchSite($sn->nickname); + $user = User::staticGet('email', $email); + + if (empty($user)) { + throw new ClientException(_('No such user.')); + } + } } // The way addPlugin() works, this global variable gets disappeared. diff --git a/plugins/DomainStatusNetwork/actions/globallogin.php b/plugins/DomainStatusNetwork/actions/globallogin.php index 034d719a2f..f843392f44 100644 --- a/plugins/DomainStatusNetwork/actions/globallogin.php +++ b/plugins/DomainStatusNetwork/actions/globallogin.php @@ -35,7 +35,7 @@ if (!defined('STATUSNET')) { } /** - * Class comment + * Login to a site * * @category Action * @package StatusNet @@ -45,8 +45,10 @@ if (!defined('STATUSNET')) { * @link http://status.net/ */ -class GlobalLoginAction extends Action +class GloballoginAction extends GlobalApiAction { + var $password; + /** * For initializing members of the class. * @@ -58,6 +60,15 @@ class GlobalLoginAction extends Action function prepare($argarray) { parent::prepare($argarray); + + $password = $this->trimmed('password'); + + if (empty($password)) { + throw new ClientException(_('No password.')); + } + + $this->password = $password; + return true; } @@ -71,6 +82,15 @@ class GlobalLoginAction extends Action function handle($argarray=null) { + try { + $url = DomainStatusNetworkPlugin::login($email, $password); + $this->showSuccess(array('url' => $url)); + } catch (ClientException $ce) { + $this->showError($ce->getMessage()); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $this->showError(_('An internal error occurred.')); + } return; } } diff --git a/plugins/DomainStatusNetwork/actions/globalrecover.php b/plugins/DomainStatusNetwork/actions/globalrecover.php new file mode 100644 index 0000000000..9b688cbe70 --- /dev/null +++ b/plugins/DomainStatusNetwork/actions/globalrecover.php @@ -0,0 +1,85 @@ +. + * + * @category DomainStatusNetwork + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Recover a password + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class GlobalrecoverAction extends GlobalApiAction +{ + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + return true; + } + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * + * @return void + */ + + function handle($argarray=null) + { + try { + DomainStatusNetworkPlugin::recoverPassword($email); + $this->showSuccess(); + } catch (ClientException $ce) { + $this->showError($ce->getMessage()); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $this->showError(_('An internal error occurred.')); + } + return; + } +} diff --git a/plugins/DomainStatusNetwork/actions/globalregister.php b/plugins/DomainStatusNetwork/actions/globalregister.php index ad223808ff..42dd207f4e 100644 --- a/plugins/DomainStatusNetwork/actions/globalregister.php +++ b/plugins/DomainStatusNetwork/actions/globalregister.php @@ -35,7 +35,7 @@ if (!defined('STATUSNET')) { } /** - * Class comment + * An action to globally register a new user * * @category Action * @package StatusNet @@ -45,7 +45,7 @@ if (!defined('STATUSNET')) { * @link http://status.net/ */ -class GlobalRegisterAction extends Action +class GlobalregisterAction extends GlobalApiAction { /** * For initializing members of the class. @@ -71,48 +71,16 @@ class GlobalRegisterAction extends Action function handle($argarray=null) { - return; - } + try { + DomainStatusNetworkPlugin::registerEmail($this->email, true); + $this->showSuccess(); + } catch (ClientException $e) { + $this->showError($e->getMessage()); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $this->showError(_('An internal error occurred.')); + } - /** - * Return true if read only. - * - * MAY override - * - * @param array $args other arguments - * - * @return boolean is read only action? - */ - - function isReadOnly($args) - { - return false; - } - - /** - * Return last modified, if applicable. - * - * MAY override - * - * @return string last modified http header - */ - function lastModified() - { - // For comparison with If-Last-Modified - // If not applicable, return null - return null; - } - - /** - * Return etag, if applicable. - * - * MAY override - * - * @return string etag http header - */ - - function etag() - { - return null; + return; } } diff --git a/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php b/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php deleted file mode 100644 index b2042abe94..0000000000 --- a/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php +++ /dev/null @@ -1,318 +0,0 @@ -. - * - * @category DomainStatusNetwork - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -/** - * Installer class for domain-based multi-homing systems - * - * @category DomainStatusNetwork - * @package StatusNet - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ -class DomainStatusNetworkInstaller extends Installer -{ - protected $domain = null; - protected $rootname = null; - protected $sitedb = null; - protected $rootpass = null; - protected $nickname = null; - protected $sn = null; - - public $verbose = false; - - function __construct($domain) - { - $this->domain = $domain; - } - - /** - * Go for it! - * @return boolean success - */ - function main() - { - // We don't check prereqs. Check 'em before setting up a - // multi-home system, kthxbi - if ($this->prepare()) { - return $this->handle(); - } else { - $this->showHelp(); - return false; - } - } - - /** - * Get our input parameters... - * @return boolean success - */ - function prepare() - { - $config = $this->getConfig(); - - $this->nickname = DomainStatusNetworkPlugin::nicknameForDomain($this->domain); - - // XXX make this configurable - - $this->sitename = sprintf('The %s Status Network', $this->domain); - - $this->server = $this->nickname.'.'.$config['WILDCARD']; - $this->path = null; - $this->fancy = true; - - $datanick = $this->databaseize($this->nickname); - - $this->host = $config['DBHOSTNAME']; - $this->database = $datanick.$config['DBBASE']; - $this->dbtype = 'mysql'; // XXX: support others... someday - $this->username = $datanick.$config['USERBASE']; - - // Max size for MySQL - - if (strlen($this->username) > 16) { - $this->username = sprintf('%s%08x', substr($this->username, 0, 8), crc32($this->username)); - } - - $pwgen = $config['PWDGEN']; - - $password = `$pwgen`; - - $this->password = trim($password); - - // For setting up the database - - $this->rootname = $config['ADMIN']; - $this->rootpass = $config['ADMINPASS']; - $this->sitehost = $config['DBHOST']; - $this->sitedb = $config['SITEDB']; - - // Explicitly empty - - $this->adminNick = null; - $this->adminPass = null; - $this->adminEmail = null; - $this->adminUpdates = null; - - /** Should we skip writing the configuration file? */ - $this->skipConfig = true; - - if (!$this->validateDb()) { - return false; - } - - return true; - } - - function handle() - { - return $this->doInstall(); - } - - function setupDatabase() - { - $this->updateStatus('Creating database...'); - $this->createDatabase(); - parent::setupDatabase(); - $this->updateStatus('Creating file directories...'); - $this->createDirectories(); - $this->updateStatus('Saving status network...'); - $this->saveStatusNetwork(); - $this->updateStatus('Checking schema for plugins...'); - $this->checkSchema(); - } - - function saveStatusNetwork() - { - Status_network::setupDB($this->sitehost, - $this->rootname, - $this->rootpass, - $this->sitedb, array()); - - $sn = new Status_network(); - - $sn->nickname = $this->nickname; - $sn->dbhost = $this->host; - $sn->dbuser = $this->username; - $sn->dbpass = $this->password; - $sn->dbname = $this->database; - $sn->sitename = $this->sitename; - - $result = $sn->insert(); - - if (!$result) { - throw new ServerException("Could not create status_network: " . print_r($sn, true)); - } - - // Re-fetch; stupid auto-increment integer isn't working - - $sn = Status_network::staticGet('nickname', $sn->nickname); - - if (empty($sn)) { - throw new ServerException("Created {$this->nickname} status_network and could not find it again."); - } - - $sn->setTags(array('domain='.$this->domain)); - - $this->sn = $sn; - } - - function checkSchema() - { - $config = $this->getConfig(); - - Status_network::$wildcard = $config['WILDCARD']; - - StatusNet::switchSite($this->nickname); - - Event::handle('CheckSchema'); - } - - function getStatusNetwork() - { - return $this->sn; - } - - function createDirectories() - { - $config = $this->getConfig(); - - foreach (array('AVATARBASE', 'BACKGROUNDBASE', 'FILEBASE') as $key) { - $base = $config[$key]; - $dirname = $base.'/'.$this->nickname; - - // Make sure our bits are set - $mask = umask(0); - mkdir($dirname, 0770, true); - umask($mask); - - // If you set the setuid bit on your base dirs this should be - // unnecessary, but just in case. You must be root for this - // to work. - - if (array_key_exists('WEBUSER', $config)) { - chown($dirname, $config['WEBUSER']); - } - if (array_key_exists('WEBGROUP', $config)) { - chgrp($dirname, $config['WEBGROUP']); - } - } - } - - function createDatabase() - { - // Create the New DB - $res = mysql_connect($this->host, $this->rootname, $this->rootpass); - if (!$res) { - throw new ServerException("Cannot connect to {$this->host} as {$this->rootname}."); - } - - mysql_query("CREATE DATABASE ". mysql_real_escape_string($this->database), $res); - - $return = mysql_select_db($this->database, $res); - - if (!$return) { - throw new ServerException("Unable to connect to {$this->database} on {$this->host}."); - } - - foreach (array('localhost', '%') as $src) { - mysql_query("GRANT ALL ON " . - mysql_real_escape_string($this->database).".* TO '" . - $this->username . "'@'".$src."' ". - "IDENTIFIED BY '".$this->password."'", $res); - } - - mysql_close($res); - } - - function getConfig() - { - static $config; - - $cfg_file = "/etc/statusnet/setup.cfg"; - - if (empty($config)) { - $result = parse_ini_file($cfg_file); - - $config = array(); - foreach ($result as $key => $value) { - $key = str_replace('export ', '', $key); - $config[$key] = $value; - } - } - - return $config; - } - - function showHelp() - { - } - - function warning($message, $submessage='') - { - print $this->html2text($message) . "\n"; - if ($submessage != '') { - print " " . $this->html2text($submessage) . "\n"; - } - print "\n"; - } - - function updateStatus($status, $error=false) - { - if ($this->verbose || $error) { - if ($error) { - print "ERROR: "; - } - print $this->html2text($status); - print "\n"; - } - } - - private function html2text($html) - { - // break out any links for text legibility - $breakout = preg_replace('/+]\bhref="(.*)"[^>]*>(.*)<\/a>/', - '\2 <\1>', - $html); - return html_entity_decode(strip_tags($breakout), ENT_QUOTES, 'UTF-8'); - } - - function databaseize($nickname) - { - $nickname = str_replace('-', '_', $nickname); - return $nickname; - } -} diff --git a/plugins/DomainStatusNetwork/lib/domainstatusnetworkinstaller.php b/plugins/DomainStatusNetwork/lib/domainstatusnetworkinstaller.php new file mode 100644 index 0000000000..b2042abe94 --- /dev/null +++ b/plugins/DomainStatusNetwork/lib/domainstatusnetworkinstaller.php @@ -0,0 +1,318 @@ +. + * + * @category DomainStatusNetwork + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Installer class for domain-based multi-homing systems + * + * @category DomainStatusNetwork + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class DomainStatusNetworkInstaller extends Installer +{ + protected $domain = null; + protected $rootname = null; + protected $sitedb = null; + protected $rootpass = null; + protected $nickname = null; + protected $sn = null; + + public $verbose = false; + + function __construct($domain) + { + $this->domain = $domain; + } + + /** + * Go for it! + * @return boolean success + */ + function main() + { + // We don't check prereqs. Check 'em before setting up a + // multi-home system, kthxbi + if ($this->prepare()) { + return $this->handle(); + } else { + $this->showHelp(); + return false; + } + } + + /** + * Get our input parameters... + * @return boolean success + */ + function prepare() + { + $config = $this->getConfig(); + + $this->nickname = DomainStatusNetworkPlugin::nicknameForDomain($this->domain); + + // XXX make this configurable + + $this->sitename = sprintf('The %s Status Network', $this->domain); + + $this->server = $this->nickname.'.'.$config['WILDCARD']; + $this->path = null; + $this->fancy = true; + + $datanick = $this->databaseize($this->nickname); + + $this->host = $config['DBHOSTNAME']; + $this->database = $datanick.$config['DBBASE']; + $this->dbtype = 'mysql'; // XXX: support others... someday + $this->username = $datanick.$config['USERBASE']; + + // Max size for MySQL + + if (strlen($this->username) > 16) { + $this->username = sprintf('%s%08x', substr($this->username, 0, 8), crc32($this->username)); + } + + $pwgen = $config['PWDGEN']; + + $password = `$pwgen`; + + $this->password = trim($password); + + // For setting up the database + + $this->rootname = $config['ADMIN']; + $this->rootpass = $config['ADMINPASS']; + $this->sitehost = $config['DBHOST']; + $this->sitedb = $config['SITEDB']; + + // Explicitly empty + + $this->adminNick = null; + $this->adminPass = null; + $this->adminEmail = null; + $this->adminUpdates = null; + + /** Should we skip writing the configuration file? */ + $this->skipConfig = true; + + if (!$this->validateDb()) { + return false; + } + + return true; + } + + function handle() + { + return $this->doInstall(); + } + + function setupDatabase() + { + $this->updateStatus('Creating database...'); + $this->createDatabase(); + parent::setupDatabase(); + $this->updateStatus('Creating file directories...'); + $this->createDirectories(); + $this->updateStatus('Saving status network...'); + $this->saveStatusNetwork(); + $this->updateStatus('Checking schema for plugins...'); + $this->checkSchema(); + } + + function saveStatusNetwork() + { + Status_network::setupDB($this->sitehost, + $this->rootname, + $this->rootpass, + $this->sitedb, array()); + + $sn = new Status_network(); + + $sn->nickname = $this->nickname; + $sn->dbhost = $this->host; + $sn->dbuser = $this->username; + $sn->dbpass = $this->password; + $sn->dbname = $this->database; + $sn->sitename = $this->sitename; + + $result = $sn->insert(); + + if (!$result) { + throw new ServerException("Could not create status_network: " . print_r($sn, true)); + } + + // Re-fetch; stupid auto-increment integer isn't working + + $sn = Status_network::staticGet('nickname', $sn->nickname); + + if (empty($sn)) { + throw new ServerException("Created {$this->nickname} status_network and could not find it again."); + } + + $sn->setTags(array('domain='.$this->domain)); + + $this->sn = $sn; + } + + function checkSchema() + { + $config = $this->getConfig(); + + Status_network::$wildcard = $config['WILDCARD']; + + StatusNet::switchSite($this->nickname); + + Event::handle('CheckSchema'); + } + + function getStatusNetwork() + { + return $this->sn; + } + + function createDirectories() + { + $config = $this->getConfig(); + + foreach (array('AVATARBASE', 'BACKGROUNDBASE', 'FILEBASE') as $key) { + $base = $config[$key]; + $dirname = $base.'/'.$this->nickname; + + // Make sure our bits are set + $mask = umask(0); + mkdir($dirname, 0770, true); + umask($mask); + + // If you set the setuid bit on your base dirs this should be + // unnecessary, but just in case. You must be root for this + // to work. + + if (array_key_exists('WEBUSER', $config)) { + chown($dirname, $config['WEBUSER']); + } + if (array_key_exists('WEBGROUP', $config)) { + chgrp($dirname, $config['WEBGROUP']); + } + } + } + + function createDatabase() + { + // Create the New DB + $res = mysql_connect($this->host, $this->rootname, $this->rootpass); + if (!$res) { + throw new ServerException("Cannot connect to {$this->host} as {$this->rootname}."); + } + + mysql_query("CREATE DATABASE ". mysql_real_escape_string($this->database), $res); + + $return = mysql_select_db($this->database, $res); + + if (!$return) { + throw new ServerException("Unable to connect to {$this->database} on {$this->host}."); + } + + foreach (array('localhost', '%') as $src) { + mysql_query("GRANT ALL ON " . + mysql_real_escape_string($this->database).".* TO '" . + $this->username . "'@'".$src."' ". + "IDENTIFIED BY '".$this->password."'", $res); + } + + mysql_close($res); + } + + function getConfig() + { + static $config; + + $cfg_file = "/etc/statusnet/setup.cfg"; + + if (empty($config)) { + $result = parse_ini_file($cfg_file); + + $config = array(); + foreach ($result as $key => $value) { + $key = str_replace('export ', '', $key); + $config[$key] = $value; + } + } + + return $config; + } + + function showHelp() + { + } + + function warning($message, $submessage='') + { + print $this->html2text($message) . "\n"; + if ($submessage != '') { + print " " . $this->html2text($submessage) . "\n"; + } + print "\n"; + } + + function updateStatus($status, $error=false) + { + if ($this->verbose || $error) { + if ($error) { + print "ERROR: "; + } + print $this->html2text($status); + print "\n"; + } + } + + private function html2text($html) + { + // break out any links for text legibility + $breakout = preg_replace('/+]\bhref="(.*)"[^>]*>(.*)<\/a>/', + '\2 <\1>', + $html); + return html_entity_decode(strip_tags($breakout), ENT_QUOTES, 'UTF-8'); + } + + function databaseize($nickname) + { + $nickname = str_replace('-', '_', $nickname); + return $nickname; + } +} diff --git a/plugins/DomainStatusNetwork/lib/globalapiaction.php b/plugins/DomainStatusNetwork/lib/globalapiaction.php new file mode 100644 index 0000000000..ef77d89cfe --- /dev/null +++ b/plugins/DomainStatusNetwork/lib/globalapiaction.php @@ -0,0 +1,118 @@ +. + * + * @category DomainStatusNetwork + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * An action that requires an API key + * + * @category General + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class GlobalApiAction extends Action +{ + var $email; + + /** + * Check for an API key, and throw an exception if it's not set + * + * @param array $args URL and POST params + * + * @return boolean continuation flag + */ + + function prepare($args) + { + StatusNet::setApi(true); // reduce exception reports to aid in debugging + + parent::prepare($args); + + if (!common_config('globalapi', 'enabled')) { + throw new ClientException(_('Global API not enabled.')); + } + + $apikey = $this->trimmed('apikey'); + + if (empty($apikey)) { + throw new ClientException(_('No API key.')); + } + + $expected = common_config('globalapi', 'key'); + + if ($expected != $apikey) { + // FIXME: increment a counter by IP address to prevent brute-force + // attacks on the key. + throw new ClientException(_('Bad API key.')); + } + + $email = common_canonical_email($this->trimmed('email')); + + if (empty($email)) { + throw new ClientException(_('No email address.')); + } + + if (!Validate::email($email, common_config('email', 'check_domain'))) { + throw new ClientException(_('Invalid email address.')); + } + + $this->email = $email; + + return true; + } + + function showError($message) + { + $this->showOutput(array('error' => $message)); + } + + function showSuccess($values=null) + { + if (empty($values)) { + $values = array(); + } + $values['success'] = 1; + $this->showOutput($values); + } + + function showOutput($values) + { + header('Content-Type: application/json; charset=utf-8'); + print(json_encode($values)); + } +}