From 8dd29246741cc5afc56594802b1fa3fc38b9367e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 19 May 2010 21:00:15 +0000 Subject: [PATCH 1/8] Hotpatch to add additional debug statements to FacebookPlugin's facebook posting code. --- plugins/Facebook/facebookutil.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/Facebook/facebookutil.php b/plugins/Facebook/facebookutil.php index 83664995ac..ab2d427264 100644 --- a/plugins/Facebook/facebookutil.php +++ b/plugins/Facebook/facebookutil.php @@ -104,9 +104,13 @@ function facebookBroadcastNotice($notice) $status = "$prefix $notice->content"; + common_debug("FacebookPlugin - checking for publish_stream permission for user $user->id"); + $can_publish = $facebook->api_client->users_hasAppPermission('publish_stream', $fbuid); + common_debug("FacebookPlugin - checking for status_update permission for user $user->id"); + $can_update = $facebook->api_client->users_hasAppPermission('status_update', $fbuid); if (!empty($attachments) && $can_publish == 1) { @@ -114,15 +118,15 @@ function facebookBroadcastNotice($notice) $facebook->api_client->stream_publish($status, $fbattachment, null, null, $fbuid); common_log(LOG_INFO, - "Posted notice $notice->id w/attachment " . + "FacebookPlugin - Posted notice $notice->id w/attachment " . "to Facebook user's stream (fbuid = $fbuid)."); } elseif ($can_update == 1 || $can_publish == 1) { $facebook->api_client->users_setStatus($status, $fbuid, false, true); common_log(LOG_INFO, - "Posted notice $notice->id to Facebook " . + "FacebookPlugin - Posted notice $notice->id to Facebook " . "as a status update (fbuid = $fbuid)."); } else { - $msg = "Not sending notice $notice->id to Facebook " . + $msg = "FacebookPlugin - Not sending notice $notice->id to Facebook " . "because user $user->nickname hasn't given the " . 'Facebook app \'status_update\' or \'publish_stream\' permission.'; common_log(LOG_WARNING, $msg); @@ -138,7 +142,7 @@ function facebookBroadcastNotice($notice) $code = $e->getCode(); - $msg = "Facebook returned error code $code: " . + $msg = "FacebookPlugin - Facebook returned error code $code: " . $e->getMessage() . ' - ' . "Unable to update Facebook status (notice $notice->id) " . "for $user->nickname (user id: $user->id)!"; From 223795a2e430544e9702b1a6a5680fa4b8dfbb76 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 19 May 2010 15:12:39 -0700 Subject: [PATCH 2/8] Add config option for RequireValidatedEmail plugin to skip the check for folks with a trusted OpenID association. Also added an event that other plugins or local config can use to override the checks. --- plugins/RequireValidatedEmail/README | 14 ++++++ .../RequireValidatedEmailPlugin.php | 50 +++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/plugins/RequireValidatedEmail/README b/plugins/RequireValidatedEmail/README index 46ee24d5fe..84b1485b25 100644 --- a/plugins/RequireValidatedEmail/README +++ b/plugins/RequireValidatedEmail/README @@ -12,6 +12,20 @@ registered prior to that timestamp. addPlugin('RequireValidatedEmail', array('grandfatherCutoff' => 'Dec 7, 2009'); +You can also exclude the validation checks from OpenID accounts +connected to a trusted provider, by providing a list of regular +expressions to match their provider URLs. + +For example, to trust WikiHow and Wikipedia users: + + addPlugin('RequireValidatedEmailPlugin', array( + 'trustedOpenIDs' => array( + '!^http://\w+\.wikihow\.com/!', + '!^http://\w+\.wikipedia\.org/!', + ), + )); + + Todo: * add a more visible indicator that validation is still outstanding diff --git a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php index ccefa14f62..009a2f78e1 100644 --- a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php +++ b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php @@ -37,6 +37,20 @@ class RequireValidatedEmailPlugin extends Plugin // without the validation requirement. public $grandfatherCutoff=null; + // If OpenID plugin is installed, users with a verified OpenID + // association whose provider URL matches one of these regexes + // will be considered to be sufficiently valid for our needs. + // + // For example, to trust WikiHow and Wikipedia OpenID users: + // + // addPlugin('RequireValidatedEmailPlugin', array( + // 'trustedOpenIDs' => array( + // '!^http://\w+\.wikihow\.com/!', + // '!^http://\w+\.wikipedia\.org/!', + // ), + // )); + public $trustedOpenIDs=array(); + function __construct() { parent::__construct(); @@ -90,13 +104,17 @@ class RequireValidatedEmailPlugin extends Plugin */ protected function validated($user) { - if ($this->grandfathered($user)) { - return true; - } - // The email field is only stored after validation... // Until then you'll find them in confirm_address. - return !empty($user->email); + $knownGood = !empty($user->email) || + $this->grandfathered($user) || + $this->hasTrustedOpenID($user); + + // Give other plugins a chance to override, if they can validate + // that somebody's ok despite a non-validated email. + Event::handle('RequireValidatedEmailPlugin_Override', array($user, &$knownGood)); + + return $knownGood; } /** @@ -118,6 +136,28 @@ class RequireValidatedEmailPlugin extends Plugin return false; } + /** + * Override for RequireValidatedEmail plugin. If we have a user who's + * not validated an e-mail, but did come from a trusted provider, + * we'll consider them ok. + */ + function hasTrustedOpenID($user) + { + if ($this->trustedOpenIDs && class_exists('User_openid')) { + foreach ($this->trustedOpenIDs as $regex) { + $oid = new User_openid(); + $oid->user_id = $user->id; + $oid->find(); + while ($oid->fetch()) { + if (preg_match($regex, $oid->canonical)) { + return true; + } + } + } + } + return false; + } + function onPluginVersion(&$versions) { $versions[] = array('name' => 'Require Validated Email', From 708d22848ecffdb80ca2cd9e5f4a7f84d5ae3189 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 19 May 2010 16:19:06 -0700 Subject: [PATCH 3/8] Quick fix for creating OpenID accounts authenticating against a MediaWiki site; trim the 'User:' etc from the final path segment before generating a nickname from it. Avoids ending up with nicks like 'userbrion' on your first OpenID login! --- lib/util.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/util.php b/lib/util.php index 597da22c09..59d5132ec6 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1925,6 +1925,15 @@ function common_url_to_nickname($url) $path = preg_replace('@/$@', '', $parts['path']); $path = preg_replace('@^/@', '', $path); $path = basename($path); + + // Hack for MediaWiki user pages, in the form: + // http://example.com/wiki/User:Myname + // ('User' may be localized.) + if (strpos($path, ':')) { + $parts = array_filter(explode(':', $path)); + $path = $parts[count($parts) - 1]; + } + if ($path) { return common_nicknamize($path); } From 68305d4b6848cec6afe887ee2a5735515060770e Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 20 May 2010 12:46:36 -0700 Subject: [PATCH 4/8] Added block link to subscription notification emails; block action can now take a profile ID on the URL; added profile details to block page so there's an indication of who you're blocking before you pull the trigger. Fixed typo in RedirectingAction when no return-to data provided in form submission. RedirectingAction::returnToArgs() has been renamed to returnToPrevious() to avoid conflict with Action::returnToArgs() which returns arguments to be passed to other actions as return-to arguments. All callers should now be updated. More profile settings actions will now redirect through a login form if visited as a GET request, as would be expected from a bookmark, link sent in e-mail etc. --- actions/block.php | 46 ++++++++++++++++++++++++++++++-- actions/deleteuser.php | 4 +-- actions/groupblock.php | 4 +-- lib/mail.php | 10 +++++-- lib/profileformaction.php | 13 +++++++-- lib/redirectingaction.php | 9 ++++--- lib/router.php | 5 ++++ plugins/UserFlag/clearflag.php | 2 +- plugins/UserFlag/flagprofile.php | 2 +- 9 files changed, 79 insertions(+), 16 deletions(-) diff --git a/actions/block.php b/actions/block.php index 7f609c253b..239a50868d 100644 --- a/actions/block.php +++ b/actions/block.php @@ -87,13 +87,15 @@ class BlockAction extends ProfileFormAction { if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($this->arg('no')) { - $this->returnToArgs(); + $this->returnToPrevious(); } elseif ($this->arg('yes')) { $this->handlePost(); - $this->returnToArgs(); + $this->returnToPrevious(); } else { $this->showPage(); } + } else { + $this->showPage(); } } @@ -118,6 +120,12 @@ class BlockAction extends ProfileFormAction */ function areYouSureForm() { + // @fixme if we ajaxify the confirmation form, skip the preview on ajax hits + $profile = new ArrayWrapper(array($this->profile)); + $preview = new ProfileList($profile, $this); + $preview->show(); + + $id = $this->profile->id; $this->elementStart('form', array('id' => 'block-' . $id, 'method' => 'post', @@ -175,4 +183,38 @@ class BlockAction extends ProfileFormAction $this->autofocus('form_action-yes'); } + /** + * Override for form session token checks; on our first hit we're just + * requesting confirmation, which doesn't need a token. We need to be + * able to take regular GET requests from email! + * + * @throws ClientException if token is bad on POST request or if we have + * confirmation parameters which could trigger something. + */ + function checkSessionToken() + { + if ($_SERVER['REQUEST_METHOD'] == 'POST' || + $this->arg('yes') || + $this->arg('no')) { + + return parent::checkSessionToken(); + } + } + + /** + * If we reached this form without returnto arguments, return to the + * current user's subscription list. + * + * @return string URL + */ + function defaultReturnTo() + { + $user = common_current_user(); + if ($user) { + return common_local_url('subscribers', + array('nickname' => $user->nickname)); + } else { + return common_local_url('public'); + } + } } diff --git a/actions/deleteuser.php b/actions/deleteuser.php index 42ef4b9f51..c0a8b20e2c 100644 --- a/actions/deleteuser.php +++ b/actions/deleteuser.php @@ -92,10 +92,10 @@ class DeleteuserAction extends ProfileFormAction { if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($this->arg('no')) { - $this->returnToArgs(); + $this->returnToPrevious(); } elseif ($this->arg('yes')) { $this->handlePost(); - $this->returnToArgs(); + $this->returnToPrevious(); } else { $this->showPage(); } diff --git a/actions/groupblock.php b/actions/groupblock.php index fc95c0e669..2e06dc3249 100644 --- a/actions/groupblock.php +++ b/actions/groupblock.php @@ -117,7 +117,7 @@ class GroupblockAction extends RedirectingAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($this->arg('no')) { - $this->returnToArgs(); + $this->returnToPrevious(); } elseif ($this->arg('yes')) { $this->blockProfile(); } elseif ($this->arg('blockto')) { @@ -195,7 +195,7 @@ class GroupblockAction extends RedirectingAction return false; } - $this->returnToArgs(); + $this->returnToPrevious(); } /** diff --git a/lib/mail.php b/lib/mail.php index a4065e8d50..ab5742e33d 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -245,6 +245,11 @@ function mail_subscribe_notify_profile($listenee, $other) $other->getBestName(), common_config('site', 'name')); + $blocklink = sprintf(_("If you believe this account is being used abusively, " . + "you can block them from your subscribers list and " . + "report as spam to site administrators at %s"), + common_local_url('block', array('profileid' => $other->id))); + // TRANS: Main body of new-subscriber notification e-mail $body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n". "\t".'%3$s'."\n\n". @@ -264,9 +269,10 @@ function mail_subscribe_notify_profile($listenee, $other) ($other->homepage) ? // TRANS: Profile info line in new-subscriber notification e-mail sprintf(_("Homepage: %s"), $other->homepage) . "\n" : '', - ($other->bio) ? + (($other->bio) ? // TRANS: Profile info line in new-subscriber notification e-mail - sprintf(_("Bio: %s"), $other->bio) . "\n\n" : '', + sprintf(_("Bio: %s"), $other->bio) . "\n" : '') . + "\n\n" . $blocklink . "\n", common_config('site', 'name'), common_local_url('emailsettings')); diff --git a/lib/profileformaction.php b/lib/profileformaction.php index 0ffafe5fb8..51c89a922e 100644 --- a/lib/profileformaction.php +++ b/lib/profileformaction.php @@ -60,7 +60,16 @@ class ProfileFormAction extends RedirectingAction $this->checkSessionToken(); if (!common_logged_in()) { - $this->clientError(_('Not logged in.')); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->clientError(_('Not logged in.')); + } else { + // Redirect to login. + common_set_returnto($this->selfUrl()); + $user = common_current_user(); + if (Event::handle('RedirectToLogin', array($this, $user))) { + common_redirect(common_local_url('login'), 303); + } + } return false; } @@ -97,7 +106,7 @@ class ProfileFormAction extends RedirectingAction if ($_SERVER['REQUEST_METHOD'] == 'POST') { $this->handlePost(); - $this->returnToArgs(); + $this->returnToPrevious(); } } diff --git a/lib/redirectingaction.php b/lib/redirectingaction.php index f115852742..3a358f891c 100644 --- a/lib/redirectingaction.php +++ b/lib/redirectingaction.php @@ -53,12 +53,13 @@ class RedirectingAction extends Action * * To be called only after successful processing. * - * @fixme rename this -- it obscures Action::returnToArgs() which - * returns a list of arguments, and is a bit confusing. + * Note: this was named returnToArgs() up through 0.9.2, which + * caused problems because there's an Action::returnToArgs() + * already which does something different. * * @return void */ - function returnToArgs() + function returnToPrevious() { // Now, gotta figure where we go back to $action = false; @@ -77,7 +78,7 @@ class RedirectingAction extends Action if ($action) { common_redirect(common_local_url($action, $args, $params), 303); } else { - $url = $this->defaultReturnToUrl(); + $url = $this->defaultReturnTo(); } common_redirect($url, 303); } diff --git a/lib/router.php b/lib/router.php index a9d07276f3..afe44f92ad 100644 --- a/lib/router.php +++ b/lib/router.php @@ -136,6 +136,11 @@ class Router $m->connect('main/'.$a, array('action' => $a)); } + // Also need a block variant accepting ID on URL for mail links + $m->connect('main/block/:profileid', + array('action' => 'block'), + array('profileid' => '[0-9]+')); + $m->connect('main/sup/:seconds', array('action' => 'sup'), array('seconds' => '[0-9]+')); diff --git a/plugins/UserFlag/clearflag.php b/plugins/UserFlag/clearflag.php index bd6732e2da..f032527ed6 100644 --- a/plugins/UserFlag/clearflag.php +++ b/plugins/UserFlag/clearflag.php @@ -81,7 +81,7 @@ class ClearflagAction extends ProfileFormAction if ($_SERVER['REQUEST_METHOD'] == 'POST') { $this->handlePost(); if (!$this->boolean('ajax')) { - $this->returnToArgs(); + $this->returnToPrevious(); } } } diff --git a/plugins/UserFlag/flagprofile.php b/plugins/UserFlag/flagprofile.php index 2d0f0abb90..018c1e8ac9 100644 --- a/plugins/UserFlag/flagprofile.php +++ b/plugins/UserFlag/flagprofile.php @@ -87,7 +87,7 @@ class FlagprofileAction extends ProfileFormAction if ($_SERVER['REQUEST_METHOD'] == 'POST') { $this->handlePost(); if (!$this->boolean('ajax')) { - $this->returnToArgs(); + $this->returnToPrevious(); } } } From 2c12d837c693a816541d32dd044de5277a46336d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 21 May 2010 10:12:39 -0700 Subject: [PATCH 5/8] Disable SSL peer/hostname verification for HTTPClient unless we've configured a trusted CA bundle like this: $config['http']['ssl_cafile'] = '/usr/lib/ssl/certs/ca-certificates.crt'; The previous state was failing on all HTTPS hits due to HTTP_Request2 library turning on the validation check but not specifying a CA file. --- lib/default.php | 3 +++ lib/httpclient.php | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/default.php b/lib/default.php index ab5f294ded..950c6018d8 100644 --- a/lib/default.php +++ b/lib/default.php @@ -304,4 +304,7 @@ $default = array('subscribers' => true, 'members' => true, 'peopletag' => true), + 'http' => // HTTP client settings when contacting other sites + array('ssl_cafile' => false // To enable SSL cert validation, point to a CA bundle (eg '/usr/lib/ssl/certs/ca-certificates.crt') + ), ); diff --git a/lib/httpclient.php b/lib/httpclient.php index 384626ae06..b69f718e5f 100644 --- a/lib/httpclient.php +++ b/lib/httpclient.php @@ -132,7 +132,19 @@ class HTTPClient extends HTTP_Request2 // ought to be investigated to see if we can handle // it gracefully in that case as well. $this->config['protocol_version'] = '1.0'; - + + // Default state of OpenSSL seems to have no trusted + // SSL certificate authorities, which breaks hostname + // verification and means we have a hard time communicating + // with other sites' HTTPS interfaces. + // + // Turn off verification unless we've configured a CA bundle. + if (common_config('http', 'ssl_cafile')) { + $this->config['ssl_cafile'] = common_config('http', 'ssl_cafile'); + } else { + $this->config['ssl_verify_peer'] = false; + } + parent::__construct($url, $method, $config); $this->setHeader('User-Agent', $this->userAgent()); } From cbf2e7cfea6c4360f9cc9037b242f2508964ccac Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 21 May 2010 10:18:13 -0700 Subject: [PATCH 6/8] Avoid PHP notice about undefined array index when no avatar photo available from Google profile --- plugins/OStatus/lib/discoveryhints.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index ca54a0f5f5..34c9be2777 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -84,7 +84,7 @@ class DiscoveryHints { $hints['fullname'] = implode(' ', $hcard['n']); } - if (array_key_exists('photo', $hcard)) { + if (array_key_exists('photo', $hcard) && count($hcard['photo'])) { $hints['avatar'] = $hcard['photo'][0]; } From bbfd6eff0c69f038d151d3bf6c8bf9b45a64716f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 21 May 2010 10:29:28 -0700 Subject: [PATCH 7/8] Add TweetDeck to notice sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index f9c5256791..fbcdd6568e 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -54,6 +54,7 @@ VALUES ('tr.im','tr.im','http://tr.im/', now()), ('triklepost', 'Tricklepost', 'http://github.com/zcopley/tricklepost/tree/master', now()), ('tweenky','Tweenky','http://beta.tweenky.com/', now()), + ('TweetDeck', 'TweetDeck', 'http://www.tweetdeck.com/', now()), ('twhirl','Twhirl','http://www.twhirl.org/', now()), ('twibble','twibble','http://www.twibble.de/', now()), ('Twidge','Twidge','http://software.complete.org/twidge', now()), From afd81a540a556ef04bdc326a26268dc82b0dc5f6 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 21 May 2010 10:29:28 -0700 Subject: [PATCH 8/8] Add TweetDeck to notice sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index f9c5256791..fbcdd6568e 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -54,6 +54,7 @@ VALUES ('tr.im','tr.im','http://tr.im/', now()), ('triklepost', 'Tricklepost', 'http://github.com/zcopley/tricklepost/tree/master', now()), ('tweenky','Tweenky','http://beta.tweenky.com/', now()), + ('TweetDeck', 'TweetDeck', 'http://www.tweetdeck.com/', now()), ('twhirl','Twhirl','http://www.twhirl.org/', now()), ('twibble','twibble','http://www.twibble.de/', now()), ('Twidge','Twidge','http://software.complete.org/twidge', now()),