Merge remote-tracking branch 'upstream/nightly' into nightly

This commit is contained in:
www-data 2016-07-04 23:37:45 +02:00
commit 14ac6e665c
17 changed files with 195 additions and 126 deletions

View File

@ -174,7 +174,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
foreach (array_unique($matches[0]) as $match) {
try {
$this->media_ids[$match] = File::getByID($match);
} catch (EmptyIdException $e) {
} catch (EmptyPkeyValueException $e) {
// got a zero from the client, at least Twidere does this on occasion
} catch (NoResultException $e) {
// File ID was not found. Do we abort and report to the client?

View File

@ -120,7 +120,7 @@ class File extends Managed_DataObject
// $args['attachment'] should always be set if action===attachment, given our routing rules
$file = File::getByID($args['attachment']);
return $file;
} catch (EmptyIdException $e) {
} catch (EmptyPkeyValueException $e) {
// ...but $args['attachment'] can also be 0...
} catch (NoResultException $e) {
// apparently this link goes to us, but is _not_ an existing attachment (File) ID?

View File

@ -383,14 +383,35 @@ abstract class Managed_DataObject extends Memcached_DataObject
static function getByID($id)
{
if (!property_exists(get_called_class(), 'id')) {
throw new ServerException('Trying to get undefined property of dataobject class.');
}
if (empty($id)) {
throw new EmptyIdException(get_called_class());
throw new EmptyPkeyValueException(get_called_class(), 'id');
}
// getByPK throws exception if id is null
// or if the class does not have a single 'id' column as primary key
return static::getByPK(array('id' => $id));
}
static function getByUri($uri)
{
if (!property_exists(get_called_class(), 'uri')) {
throw new ServerException('Trying to get undefined property of dataobject class.');
}
if (empty($uri)) {
throw new EmptyPkeyValueException(get_called_class(), 'uri');
}
$class = get_called_class();
$obj = new $class();
$obj->uri = $uri;
if (!$obj->find(true)) {
throw new NoResultException($obj);
}
return $obj;
}
/**
* Returns an ID, checked that it is set and reasonably valid
*

View File

@ -336,16 +336,6 @@ class Notice extends Managed_DataObject
}
}
public static function getByUri($uri)
{
$notice = new Notice();
$notice->uri = $uri;
if (!$notice->find(true)) {
throw new NoResultException($notice);
}
return $notice;
}
/**
* Extract #hashtags from this notice's content and save them to the database.
*/
@ -2683,7 +2673,7 @@ class Notice extends Managed_DataObject
public static function getAsTimestamp($id)
{
if (empty($id)) {
throw new EmptyIdException('Notice');
throw new EmptyPkeyValueException('Notice', 'id');
}
$timestamp = null;
@ -3091,6 +3081,44 @@ class Notice extends Managed_DataObject
$schema = Schema::get();
$schemadef = $schema->getTableDef($table);
// 2015-09-04 We move Notice location data to Notice_location
// First we see if we have to do this at all
if (isset($schemadef['fields']['lat'])
&& isset($schemadef['fields']['lon'])
&& isset($schemadef['fields']['location_id'])
&& isset($schemadef['fields']['location_ns'])) {
// Then we make sure the Notice_location table is created!
$schema->ensureTable('notice_location', Notice_location::schemaDef());
// Then we continue on our road to migration!
echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)";
$notice = new Notice();
$notice->query(sprintf('SELECT id, lat, lon, location_id, location_ns FROM %1$s ' .
'WHERE lat IS NOT NULL ' .
'OR lon IS NOT NULL ' .
'OR location_id IS NOT NULL ' .
'OR location_ns IS NOT NULL',
$schema->quoteIdentifier($table)));
print "\nFound {$notice->N} notices with location data, inserting";
while ($notice->fetch()) {
$notloc = Notice_location::getKV('notice_id', $notice->id);
if ($notloc instanceof Notice_location) {
print "-";
continue;
}
$notloc = new Notice_location();
$notloc->notice_id = $notice->id;
$notloc->lat= $notice->lat;
$notloc->lon= $notice->lon;
$notloc->location_id= $notice->location_id;
$notloc->location_ns= $notice->location_ns;
$notloc->insert();
print ".";
}
print "\n";
}
/**
* Make sure constraints are met before upgrading, if foreign keys
* are not already in use.
@ -3163,45 +3191,5 @@ class Notice extends Managed_DataObject
unset($notice);
}
}
// 2015-09-04 We move Notice location data to Notice_location
// First we see if we have to do this at all
if (!isset($schemadef['fields']['lat'])
&& !isset($schemadef['fields']['lon'])
&& !isset($schemadef['fields']['location_id'])
&& !isset($schemadef['fields']['location_ns'])) {
// We have already removed the location fields, so no need to migrate.
return;
}
// Then we make sure the Notice_location table is created!
$schema->ensureTable('notice_location', Notice_location::schemaDef());
// Then we continue on our road to migration!
echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)";
$notice = new Notice();
$notice->query(sprintf('SELECT id, lat, lon, location_id, location_ns FROM %1$s ' .
'WHERE lat IS NOT NULL ' .
'OR lon IS NOT NULL ' .
'OR location_id IS NOT NULL ' .
'OR location_ns IS NOT NULL',
$schema->quoteIdentifier($table)));
print "\nFound {$notice->N} notices with location data, inserting";
while ($notice->fetch()) {
$notloc = Notice_location::getKV('notice_id', $notice->id);
if ($notloc instanceof Notice_location) {
print "-";
continue;
}
$notloc = new Notice_location();
$notloc->notice_id = $notice->id;
$notloc->lat= $notice->lat;
$notloc->lon= $notice->lon;
$notloc->location_id= $notice->location_id;
$notloc->location_ns= $notice->location_ns;
$notloc->insert();
print ".";
}
print "\n";
}
}

View File

@ -140,16 +140,6 @@ class User extends Managed_DataObject
return $this->uri;
}
static function getByUri($uri)
{
$user = new User();
$user->uri = $uri;
if (!$user->find(true)) {
throw new NoResultException($user);
}
return $user;
}
public function getNickname()
{
return $this->getProfile()->getNickname();

View File

@ -64,6 +64,9 @@ $default =
'notice' => null, // site wide notice text
'build' => 1, // build number, for code-dependent cache
),
'security' =>
array('hash_algos' => ['sha1', 'sha256', 'sha512'], // set to null for anything that hash_hmac() can handle (and is in hash_algos())
),
'db' =>
array('database' => null, // must be set
'schema_location' => INSTALLDIR . '/classes',

View File

@ -29,10 +29,13 @@
if (!defined('GNUSOCIAL')) { exit(1); }
class EmptyIdException extends ServerException
class EmptyPkeyValueException extends ServerException
{
public function __construct($called_class)
public function __construct($called_class, $key=null)
{
parent::__construct(sprintf(_('Empty ID value was given to query for a "%s" object'), $called_class));
// FIXME: translate the 'not specified' case?
parent::__construct(sprintf(_('Empty primary key (%1$s) value was given to query for a "%2$s" object'),
is_null($key) ? 'not specified' : _ve($key),
$called_class));
}
}

View File

@ -132,7 +132,7 @@ class Fave extends Managed_DataObject
} catch (NoResultException $e) {
// In case there's some inconsistency where the profile or notice was deleted without losing the fave db entry
common_log(LOG_INFO, '"'.get_class($e->obj).'" with id=='.var_export($e->obj->id, true).' object not found when deleting favorite, ignoring...');
} catch (EmptyIdException $e) {
} catch (EmptyPkeyValueException $e) {
// Some buggy instances of GNU social have had favorites with notice id==0 stored in the database
common_log(LOG_INFO, _ve($e->getMessage()));
}

View File

@ -122,9 +122,24 @@ class Discovery
throw new Exception('Unexpected HTTP status code.');
}
$xrd->loadString($response->getBody());
switch ($response->getHeader('content-type')) {
case self::JRD_MIMETYPE_OLD:
case self::JRD_MIMETYPE:
$type = 'json';
break;
case self::XRD_MIMETYPE:
$type = 'xml';
break;
default:
// fall back to letting XML_XRD auto-detect
common_debug('No recognized content-type header for resource descriptor body.');
$type = null;
}
$xrd->loadString($response->getBody(), $type);
return $xrd;
} catch (Exception $e) {
common_log(LOG_INFO, sprintf('%s: Failed for %s: %s', _ve($class), _ve($uri), _ve($e->getMessage())));
continue;
}
}

View File

@ -1315,6 +1315,12 @@ class OStatusPlugin extends Plugin
{
if ($target->getObjectType() === ActivityObject::PERSON) {
$this->addWebFingerPersonLinks($xrd, $target);
} elseif ($target->getObjectType() === ActivityObject::GROUP) {
$xrd->links[] = new XML_XRD_Element_Link(Discovery::UPDATESFROM,
common_local_url('ApiTimelineGroup',
array('id' => $target->getGroup()->getID(), 'format' => 'atom')),
'application/atom+xml');
}
// Salmon

View File

@ -442,10 +442,10 @@ class FeedSub extends Managed_DataObject
*/
public function receive($post, $hmac)
{
common_log(LOG_INFO, __METHOD__ . ": packet for \"" . $this->getUri() . "\"! $hmac $post");
common_log(LOG_INFO, sprintf(__METHOD__.': packet for %s with HMAC %s', _ve($this->getUri()), _ve($hmac)));
if (!in_array($this->sub_state, array('active', 'nohub'))) {
common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed " . $this->getUri() . " (in state '$this->sub_state')");
common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH for inactive feed %s (in state %s)', _ve($this->getUri()), _ve($this->sub_state)));
return;
}
@ -480,7 +480,7 @@ class FeedSub extends Managed_DataObject
* shared secret that was set up at subscription time.
*
* If we don't have a shared secret, there should be no signature.
* If we we do, our the calculated HMAC should match theirs.
* If we do, our calculated HMAC should match theirs.
*
* @param string $post raw XML source as POSTed to us
* @param string $hmac X-Hub-Signature HTTP header value, or empty
@ -489,29 +489,36 @@ class FeedSub extends Managed_DataObject
protected function validatePushSig($post, $hmac)
{
if ($this->secret) {
if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) {
$their_hmac = strtolower($matches[1]);
$our_hmac = hash_hmac('sha1', $post, $this->secret);
// {3,16} because shortest hash algorithm name is 3 characters (md2,md4,md5) and longest
// is currently 11 characters, but we'll leave some margin in the end...
if (preg_match('/^([0-9a-zA-Z\-\,]{3,16})=([0-9a-fA-F]+)$/', $hmac, $matches)) {
$hash_algo = strtolower($matches[1]);
$their_hmac = strtolower($matches[2]);
common_debug(sprintf(__METHOD__ . ': PuSH from feed %s uses HMAC algorithm %s with value: %s', _ve($this->getUri()), _ve($hash_algo), _ve($their_hmac)));
if (!in_array($hash_algo, hash_algos())) {
// We can't handle this at all, PHP doesn't recognize the algorithm name ('md5', 'sha1', 'sha256' etc: https://secure.php.net/manual/en/function.hash-algos.php)
common_log(LOG_ERR, sprintf(__METHOD__.': HMAC algorithm %s unsupported, not found in PHP hash_algos()', _ve($hash_algo)));
return false;
} elseif (!is_null(common_config('security', 'hash_algos')) && !in_array($hash_algo, common_config('security', 'hash_algos'))) {
// We _won't_ handle this because there is a list of accepted hash algorithms and this one is not in it.
common_log(LOG_ERR, sprintf(__METHOD__.': Whitelist for HMAC algorithms exist, but %s is not included.', _ve($hash_algo)));
return false;
}
$our_hmac = hash_hmac($hash_algo, $post, $this->secret);
if ($their_hmac === $our_hmac) {
return true;
}
if (common_config('feedsub', 'debug')) {
$tempfile = tempnam(sys_get_temp_dir(), 'feedsub-receive');
if ($tempfile) {
file_put_contents($tempfile, $post);
}
common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed " . $this->getUri() . " on $this->huburi; saved to $tempfile");
} else {
common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed " . $this->getUri() . " on $this->huburi");
}
common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH with bad HMAC hash: got %s, expected %s for feed %s from hub %s', _ve($their_hmac), _ve($our_hmac), _ve($this->getUri()), _ve($this->huburi)));
} else {
common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH with bogus HMAC==', _ve($hmac)));
}
} else {
if (empty($hmac)) {
return true;
} else {
common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'");
common_log(LOG_ERR, sprintf(__METHOD__.': ignoring PuSH with unexpected HMAC==%s', _ve($hmac)));
}
}
return false;

View File

@ -157,10 +157,10 @@ class Magicsig extends Managed_DataObject
$keypair = $rsa->createKey($bits);
$magicsig->privateKey = new \phpseclib\Crypt\RSA();
$magicsig->privateKey->loadKey($keypair['privatekey']);
$magicsig->privateKey->load($keypair['privatekey']);
$magicsig->publicKey = new \phpseclib\Crypt\RSA();
$magicsig->publicKey->loadKey($keypair['publickey']);
$magicsig->publicKey->load($keypair['publickey']);
$magicsig->insert(); // will do $this->keypair = $this->toString(true);
$magicsig->importKeys(); // seems it's necessary to re-read keys from text keypair
@ -316,7 +316,7 @@ class Magicsig extends Managed_DataObject
public function verify($signed_bytes, $signature)
{
$signature = self::base64_url_decode($signature);
return $this->publicKey->verify($signed_bytes, $signature);
return $this->publicKey->verify($signed_bytes, $signature, \phpseclib\Crypt\RSA::PADDING_PKCS1);
}
/**

View File

@ -1842,22 +1842,28 @@ class Ostatus_profile extends Managed_DataObject
{
$orig = clone($this);
common_debug('URIFIX These identities both say they are each other: "'.$orig->uri.'" and "'.$profile_uri.'"');
common_debug('URIFIX These identities both say they are each other: '._ve($orig->uri).' and '._ve($profile_uri));
$this->uri = $profile_uri;
if (array_key_exists('feedurl', $hints)) {
if (!empty($this->feeduri)) {
common_debug('URIFIX Changing FeedSub ['.$feedsub->id.'] feeduri "'.$feedsub->uri.'" to "'.$hints['feedurl']);
$feedsub = FeedSub::getKV('uri', $this->feeduri);
if (array_key_exists('feedurl', $hints) && common_valid_http_url($hints['feedurl'])) {
try {
$feedsub = FeedSub::getByUri($this->feeduri);
common_debug('URIFIX Changing FeedSub id==['._ve($feedsub->id).'] feeduri '._ve($feedsub->uri).' to '._ve($hints['feedurl']));
$feedorig = clone($feedsub);
$feedsub->uri = $hints['feedurl'];
$feedsub->updateWithKeys($feedorig);
} else {
common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring feed: '.$hints['feedurl']);
} catch (EmptyPkeyValueException $e) {
common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring new feedurl: '._ve($hints['feedurl']));
FeedSub::ensureFeed($hints['feedurl']);
} catch (NoResultException $e) {
common_debug('URIFIX Missing FeedSub entry for the Ostatus_profile, ensuring new feedurl: '._ve($hints['feedurl']));
FeedSub::ensureFeed($hints['feedurl']);
}
$this->feeduri = $hints['feedurl'];
} elseif (array_key_exists('feedurl')) {
common_log(LOG_WARN, 'The feedurl hint we got was not a valid HTTP URL: '._ve($hints['feedurl']));
}
if (array_key_exists('salmon', $hints)) {
common_debug('URIFIX Changing Ostatus_profile salmonuri from "'.$this->salmonuri.'" to "'.$hints['salmon'].'"');
$this->salmonuri = $hints['salmon'];

View File

@ -31,6 +31,8 @@ class SalmonAction extends Action
protected $oprofile = null; // Ostatus_profile of the actor
protected $actor = null; // Profile object of the actor
var $format = 'text'; // error messages will be printed in plaintext
var $xml = null;
var $activity = null;
var $target = null;

View File

@ -18,7 +18,7 @@ class StoreRemoteMediaPlugin extends Plugin
public $domain_blacklist = array();
public $check_blacklist = false;
public $max_image_bytes = 5242880; // 5MiB max image size by default
public $max_image_bytes = 10485760; // 10MiB max image size by default
protected $imgData = array();

View File

@ -140,6 +140,20 @@ class WebFingerPlugin extends Plugin
throw $e;
}
try {
common_debug(__METHOD__.': Finding User_group URI for WebFinger lookup on resource=='._ve($resource));
$group = new User_group();
$group->whereAddIn('uri', array_keys($alt_urls), $group->columnType('uri'));
$group->limit(1);
if ($group->find(true)) {
$profile = $group->getProfile();
}
unset($group);
} catch (Exception $e) {
common_log(LOG_ERR, get_class($e).': '._ve($e->getMessage()));
throw $e;
}
// User URI did not match, so let's try our alt_urls as Profile URL values
if (!$profile instanceof Profile) {
common_debug(__METHOD__.': Finding Profile URLs for WebFinger lookup on resource=='._ve($resource));

View File

@ -23,11 +23,14 @@ class WebFingerResource_Profile extends WebFingerResource
{
$aliases = array();
try {
// Try to create an acct: URI if we're dealing with a profile
$aliases[] = $this->reconstructAcct();
} catch (WebFingerReconstructionException $e) {
common_debug("WebFinger reconstruction for Profile failed (id={$this->object->id})");
// only persons ("accounts" or "agents" actually) have acct: URIs
if ($this->object->isPerson()) {
try {
// Try to create an acct: URI if we're dealing with a profile
$aliases[] = $this->reconstructAcct();
} catch (WebFingerReconstructionException $e) {
common_debug("WebFinger reconstruction for Profile failed (id={$this->object->getID()})");
}
}
return array_merge($aliases, parent::getAliases());
@ -40,10 +43,10 @@ class WebFingerResource_Profile extends WebFingerResource
if (Event::handle('StartWebFingerReconstruction', array($this->object, &$acct))) {
// TODO: getUri may not always give us the correct host on remote users?
$host = parse_url($this->object->getUri(), PHP_URL_HOST);
if (empty($this->object->nickname) || empty($host)) {
if (empty($this->object->getNickname()) || empty($host)) {
throw new WebFingerReconstructionException($this->object);
}
$acct = mb_strtolower(sprintf('acct:%s@%s', $this->object->nickname, $host));
$acct = mb_strtolower(sprintf('acct:%s@%s', $this->object->getNickname(), $host));
Event::handle('EndWebFingerReconstruction', array($this->object, &$acct));
}
@ -55,34 +58,45 @@ class WebFingerResource_Profile extends WebFingerResource
{
if (Event::handle('StartWebFingerProfileLinks', array($xrd, $this->object))) {
$xrd->links[] = new XML_XRD_Element_Link(self::PROFILEPAGE,
$this->object->getUrl(), 'text/html');
// XFN
$xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11',
$this->object->getUrl(), 'text/html');
// FOAF
$xrd->links[] = new XML_XRD_Element_Link('describedby',
common_local_url('foaf',
array('nickname' => $this->object->nickname)),
'application/rdf+xml');
$link = new XML_XRD_Element_Link('http://apinamespace.org/atom',
common_local_url('ApiAtomService',
array('id' => $this->object->nickname)),
'application/atomsvc+xml');
// XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $this->object->nickname;
$xrd->links[] = clone $link;
if (common_config('site', 'fancy')) {
$apiRoot = common_path('api/', true);
} else {
$apiRoot = common_path('index.php/api/', true);
}
$link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot);
// XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $this->object->nickname;
$xrd->links[] = clone $link;
// Profile page, can give more metadata from Link header or HTML parsing
$xrd->links[] = new XML_XRD_Element_Link(self::PROFILEPAGE,
$this->object->getUrl(), 'text/html');
// XFN
$xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11',
$this->object->getUrl(), 'text/html');
if ($this->object->isPerson()) {
// FOAF for user
$xrd->links[] = new XML_XRD_Element_Link('describedby',
common_local_url('foaf',
array('nickname' => $this->object->getNickname())),
'application/rdf+xml');
// nickname discovery for apps etc.
$link = new XML_XRD_Element_Link('http://apinamespace.org/atom',
common_local_url('ApiAtomService',
array('id' => $this->object->getNickname())),
'application/atomsvc+xml');
// XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $this->object->getNickname();
$xrd->links[] = clone $link;
$link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot);
// XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $this->object->getNickname();
$xrd->links[] = clone $link;
} elseif ($this->object->isGroup()) {
// FOAF for group
$xrd->links[] = new XML_XRD_Element_Link('describedby',
common_local_url('foafgroup',
array('nickname' => $this->object->getNickname())),
'application/rdf+xml');
}
Event::handle('EndWebFingerProfileLinks', array($xrd, $this->object));
}