From 40cde2f7109cace8f37cd36c31420c38ad475d40 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 15 Mar 2010 22:10:32 +0000 Subject: Initial Twitpic-like media upload endpoint /api/statusnet/media/upload --- lib/router.php | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/router.php b/lib/router.php index 706120e0b..a48ee875e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -628,6 +628,12 @@ class Router array('action' => 'ApiTimelineTag', 'format' => '(xmljson|rss|atom)')); + // media related + $m->connect( + 'api/statusnet/media/upload', + array('action' => 'ApiMediaUpload') + ); + // search $m->connect('api/search.atom', array('action' => 'twitapisearchatom')); $m->connect('api/search.json', array('action' => 'twitapisearchjson')); -- cgit v1.2.3-54-g00ecf From 441e52718e4db4eb45bd5c76c5af446496f56f96 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 15 Mar 2010 15:08:16 -0700 Subject: Background deletion of user accounts. Notices are deleted in chunks, then the user itself when they're all gone. While deletion is in progress, the account is locked with the 'deleted' role, which disables all actions with rights control. Todo: * Pretty up the notice on the profile page about the pending delete. Show status? * Possibly more thorough account disabling, such as disallowing all use for login and access. * Improve error recovery; worst case is that an account gets left locked in 'deleted' state but the queue jobs have gotten dropped out. This would leave the username in use and any undeleted notices in place. --- actions/deleteuser.php | 10 ++++- classes/Profile.php | 3 ++ classes/Profile_role.php | 1 + lib/deluserqueuehandler.php | 95 +++++++++++++++++++++++++++++++++++++++++++++ lib/queuemanager.php | 3 ++ lib/userprofile.php | 11 ++++++ 6 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 lib/deluserqueuehandler.php (limited to 'lib') diff --git a/actions/deleteuser.php b/actions/deleteuser.php index c4f84fad2..4e6b27395 100644 --- a/actions/deleteuser.php +++ b/actions/deleteuser.php @@ -162,7 +162,15 @@ class DeleteuserAction extends ProfileFormAction function handlePost() { if (Event::handle('StartDeleteUser', array($this, $this->user))) { - $this->user->delete(); + // Mark the account as deleted and shove low-level deletion tasks + // to background queues. Removing a lot of posts can take a while... + if (!$this->user->hasRole(Profile_role::DELETED)) { + $this->user->grantRole(Profile_role::DELETED); + } + + $qm = QueueManager::get(); + $qm->enqueue($this->user, 'deluser'); + Event::handle('EndDeleteUser', array($this, $this->user)); } } diff --git a/classes/Profile.php b/classes/Profile.php index 91f6e4692..eded1ff71 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -732,6 +732,9 @@ class Profile extends Memcached_DataObject function hasRight($right) { $result = false; + if ($this->hasRole(Profile_role::DELETED)) { + return false; + } if (Event::handle('UserRightsCheck', array($this, $right, &$result))) { switch ($right) { diff --git a/classes/Profile_role.php b/classes/Profile_role.php index d0a0b31f0..e7aa1f0f0 100644 --- a/classes/Profile_role.php +++ b/classes/Profile_role.php @@ -53,6 +53,7 @@ class Profile_role extends Memcached_DataObject const ADMINISTRATOR = 'administrator'; const SANDBOXED = 'sandboxed'; const SILENCED = 'silenced'; + const DELETED = 'deleted'; // Pending final deletion of notices... public static function isValid($role) { diff --git a/lib/deluserqueuehandler.php b/lib/deluserqueuehandler.php new file mode 100644 index 000000000..4a1233a5e --- /dev/null +++ b/lib/deluserqueuehandler.php @@ -0,0 +1,95 @@ +. + */ + +/** + * Background job to delete prolific users without disrupting front-end too much. + * + * Up to 50 messages are deleted on each run through; when all messages are gone, + * the actual account is deleted. + * + * @package QueueHandler + * @maintainer Brion Vibber + */ + +class DelUserQueueHandler extends QueueHandler +{ + const DELETION_WINDOW = 50; + + public function transport() + { + return 'deluser'; + } + + public function handle($user) + { + if (!($user instanceof User)) { + common_log(LOG_ERR, "Got a bogus user, not deleting"); + return true; + } + + $user = User::staticGet('id', $user->id); + if (!$user) { + common_log(LOG_INFO, "User {$user->nickname} was deleted before we got here."); + return true; + } + + if (!$user->hasRole(Profile_role::DELETED)) { + common_log(LOG_INFO, "User {$user->nickname} is not pending deletion; aborting."); + return true; + } + + $notice = $this->getNextBatch($user); + if ($notice->N) { + common_log(LOG_INFO, "Deleting next {$notice->N} notices by {$user->nickname}"); + while ($notice->fetch()) { + $del = clone($notice); + $del->delete(); + } + + // @todo improve reliability in case we died during the above deletions + // with a fatal error. If the job is lost, we should perform some kind + // of garbage collection later. + + // Queue up the next batch. + $qm = QueueManager::get(); + $qm->enqueue($user, 'deluser'); + } else { + // Out of notices? Let's finish deleting this guy! + $user->delete(); + common_log(LOG_INFO, "User $user->id $user->nickname deleted."); + return true; + } + + return true; + } + + /** + * Fetch the next self::DELETION_WINDOW messages for this user. + * @return Notice + */ + protected function getNextBatch(User $user) + { + $notice = new Notice(); + $notice->profile_id = $user->id; + $notice->limit(self::DELETION_WINDOW); + $notice->find(); + return $notice; + } + +} diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 87bd356aa..0829c8a8b 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -264,6 +264,9 @@ abstract class QueueManager extends IoManager $this->connect('sms', 'SmsQueueHandler'); } + // Background user management tasks... + $this->connect('deluser', 'DelUserQueueHandler'); + // Broadcasting profile updates to OMB remote subscribers $this->connect('profile', 'ProfileQueueHandler'); diff --git a/lib/userprofile.php b/lib/userprofile.php index 8464c2446..2c3b1ea45 100644 --- a/lib/userprofile.php +++ b/lib/userprofile.php @@ -228,6 +228,17 @@ class UserProfile extends Widget function showEntityActions() { + if ($this->profile->hasRole(Profile_role::DELETED)) { + $this->out->elementStart('div', 'entity_actions'); + $this->out->element('h2', null, _('User actions')); + $this->out->elementStart('ul'); + $this->out->elementStart('p', array('class' => 'profile_deleted')); + $this->out->text(_('User deletion in progress...')); + $this->out->elementEnd('p'); + $this->out->elementEnd('ul'); + $this->out->elementEnd('div'); + return; + } if (Event::handle('StartProfilePageActionsSection', array(&$this->out, $this->profile))) { $cur = common_current_user(); -- cgit v1.2.3-54-g00ecf From b994d529f4de53df6350e12b5e81889cee17f317 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 15 Mar 2010 19:06:06 -0700 Subject: Throw an exception if we receive a document instead of a feed's root element --- lib/activity.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/activity.php b/lib/activity.php index ae65fe36f..d84eabf7c 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -1083,15 +1083,11 @@ class Activity $this->entry = $entry; - // @fixme Don't send in a DOMDocument + // Insist on a feed's root DOMElement; don't allow a DOMDocument if ($feed instanceof DOMDocument) { - common_log( - LOG_WARNING, - 'Activity::__construct() - ' - . 'DOMDocument passed in for feed by mistake. ' - . "Expecting a 'feed' DOMElement." + throw new ClientException( + _("Expecting a root feed element but got a whole XML document.") ); - $feed = $feed->getElementsByTagName('feed')->item(0); } $this->feed = $feed; -- cgit v1.2.3-54-g00ecf From f62b8a80cf33ac8529d0736c51dc060a9d235369 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Mar 2010 16:23:19 -0700 Subject: Pull back for now on switch of PEAR error mode to exceptions; seems to trigger out exceptions at various times we don't want them. For instance this was throwing an exception for DB_DataObject::staticGet when there's no match... definitely not what we want when all our code expects to get a nice null. Example of this causing trouble: http://gitorious.org/statusnet/mainline/merge_requests/131 Revert "Don't attempt to retrieve the current user from the DB while processing a DB error" This reverts commit 68347691b0c7fb3f81415abd7fcdc5aec85cc554. Revert "Use PHP exceptions for PEAR error handling." This reverts commit d8212977ce7f911d4f9bd6e55f94aea059a86782. --- index.php | 103 ++++++++++++++++++++++++++------------------------------- lib/common.php | 12 ------- 2 files changed, 46 insertions(+), 69 deletions(-) (limited to 'lib') diff --git a/index.php b/index.php index 65f251bcc..36ba3a0d2 100644 --- a/index.php +++ b/index.php @@ -37,6 +37,8 @@ define('INSTALLDIR', dirname(__FILE__)); define('STATUSNET', true); define('LACONICA', true); // compatibility +require_once INSTALLDIR . '/lib/common.php'; + $user = null; $action = null; @@ -66,69 +68,52 @@ function getPath($req) */ function handleError($error) { - try { - - if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { - return; - } + if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + return; + } - $logmsg = "PEAR error: " . $error->getMessage(); - if ($error instanceof PEAR_Exception && common_config('site', 'logdebug')) { - $logmsg .= " : ". $error->toText(); - } - // DB queries often end up with a lot of newlines; merge to a single line - // for easier grepability... - $logmsg = str_replace("\n", " ", $logmsg); - common_log(LOG_ERR, $logmsg); - - // @fixme backtrace output should be consistent with exception handling - if (common_config('site', 'logdebug')) { - $bt = $error->getTrace(); - foreach ($bt as $n => $line) { - common_log(LOG_ERR, formatBacktraceLine($n, $line)); - } - } - if ($error instanceof DB_DataObject_Error - || $error instanceof DB_Error - || ($error instanceof PEAR_Exception && $error->getCode() == -24) - ) { - //If we run into a DB error, assume we can't connect to the DB at all - //so set the current user to null, so we don't try to access the DB - //while rendering the error page. - global $_cur; - $_cur = null; - - $msg = sprintf( - _( - 'The database for %s isn\'t responding correctly, '. - 'so the site won\'t work properly. '. - 'The site admins probably know about the problem, '. - 'but you can contact them at %s to make sure. '. - 'Otherwise, wait a few minutes and try again.' - ), - common_config('site', 'name'), - common_config('site', 'email') - ); - } else { - $msg = _( - 'An important error occured, probably related to email setup. '. - 'Check logfiles for more info..' - ); + $logmsg = "PEAR error: " . $error->getMessage(); + if (common_config('site', 'logdebug')) { + $logmsg .= " : ". $error->getDebugInfo(); + } + // DB queries often end up with a lot of newlines; merge to a single line + // for easier grepability... + $logmsg = str_replace("\n", " ", $logmsg); + common_log(LOG_ERR, $logmsg); + + // @fixme backtrace output should be consistent with exception handling + if (common_config('site', 'logdebug')) { + $bt = $error->getBacktrace(); + foreach ($bt as $n => $line) { + common_log(LOG_ERR, formatBacktraceLine($n, $line)); } - - $dac = new DBErrorAction($msg, 500); - $dac->showPage(); - - } catch (Exception $e) { - echo _('An error occurred.'); } + if ($error instanceof DB_DataObject_Error + || $error instanceof DB_Error + ) { + $msg = sprintf( + _( + 'The database for %s isn\'t responding correctly, '. + 'so the site won\'t work properly. '. + 'The site admins probably know about the problem, '. + 'but you can contact them at %s to make sure. '. + 'Otherwise, wait a few minutes and try again.' + ), + common_config('site', 'name'), + common_config('site', 'email') + ); + } else { + $msg = _( + 'An important error occured, probably related to email setup. '. + 'Check logfiles for more info..' + ); + } + + $dac = new DBErrorAction($msg, 500); + $dac->showPage(); exit(-1); } -set_exception_handler('handleError'); - -require_once INSTALLDIR . '/lib/common.php'; - /** * Format a backtrace line for debug output roughly like debug_print_backtrace() does. * Exceptions already have this built in, but PEAR error objects just give us the array. @@ -253,6 +238,10 @@ function main() return; } + // For database errors + + PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); + // Make sure RW database is setup setupRW(); diff --git a/lib/common.php b/lib/common.php index 047dc5a7b..5d53270e3 100644 --- a/lib/common.php +++ b/lib/common.php @@ -71,7 +71,6 @@ if (!function_exists('dl')) { # global configuration object require_once('PEAR.php'); -require_once('PEAR/Exception.php'); require_once('DB/DataObject.php'); require_once('DB/DataObject/Cast.php'); # for dates @@ -129,17 +128,6 @@ require_once INSTALLDIR.'/lib/activity.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; - -//set PEAR error handling to use regular PHP exceptions -function PEAR_ErrorToPEAR_Exception($err) -{ - if ($err->getCode()) { - throw new PEAR_Exception($err->getMessage(), $err->getCode()); - } - throw new PEAR_Exception($err->getMessage()); -} -PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception'); - try { StatusNet::init(@$server, @$path, @$conffile); } catch (NoConfigException $e) { -- cgit v1.2.3-54-g00ecf From 22f827134c3be845494bebd76bda9e4a074e710b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Mar 2010 10:52:11 -0700 Subject: Workaround for HTTP authentication in the API when running PHP as CGI/FastCGI. Example rewrite lines added as comments in htaccess.sample, API tweaked to accept alternate environment var form. --- htaccess.sample | 5 +++++ lib/apiauth.php | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/htaccess.sample b/htaccess.sample index 37eb8e01e..18a868698 100644 --- a/htaccess.sample +++ b/htaccess.sample @@ -5,6 +5,11 @@ RewriteBase /mublog/ + ## Uncomment these if having trouble with API authentication + ## when PHP is running in CGI or FastCGI mode. + #RewriteCond %{HTTP:Authorization} ^(.*) + #RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1] + RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) index.php?p=$1 [L,QSA] diff --git a/lib/apiauth.php b/lib/apiauth.php index 32502399f..17f803a1c 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -294,11 +294,15 @@ class ApiAuthAction extends ApiAction function basicAuthProcessHeader() { - if (isset($_SERVER['AUTHORIZATION']) - || isset($_SERVER['HTTP_AUTHORIZATION']) - ) { - $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) - ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; + $authHeaders = array('AUTHORIZATION', + 'HTTP_AUTHORIZATION', + 'REDIRECT_HTTP_AUTHORIZATION'); // rewrite for CGI + $authorization_header = null; + foreach ($authHeaders as $header) { + if (isset($_SERVER[$header])) { + $authorization_header = $_SERVER[$header]; + break; + } } if (isset($_SERVER['PHP_AUTH_USER'])) { -- cgit v1.2.3-54-g00ecf