/** * Look up, and if necessary create, an Ostatus_profile for the remote * entity with the given webfinger address. * This should never return null -- you will either get an object or * an exception will be thrown. * * @param string $addr webfinger address * @return Ostatus_profile * @throws Exception on error conditions * @throws OStatusShadowException if this reference would obscure a local user/group */ public static function ensureWebfinger($addr) { // First, try the cache $uri = self::cacheGet(sprintf('ostatus_profile:webfinger:%s', $addr)); if ($uri !== false) { if (is_null($uri)) { // Negative cache entry // TRANS: Exception. throw new Exception(_m('Not a valid webfinger address.')); } $oprofile = Ostatus_profile::getKV('uri', $uri); if ($oprofile instanceof Ostatus_profile) { return $oprofile; } } // Try looking it up $oprofile = Ostatus_profile::getKV('uri', Discovery::normalize($addr)); if ($oprofile instanceof Ostatus_profile) { self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->getUri()); return $oprofile; } // Now, try some discovery $disco = new Discovery(); try { $xrd = $disco->lookup($addr); } catch (Exception $e) { // Save negative cache entry so we don't waste time looking it up again. // @todo FIXME: Distinguish temporary failures? self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null); // TRANS: Exception. throw new Exception(_m('Not a valid webfinger address.')); } $hints = array_merge(array('webfinger' => $addr), DiscoveryHints::fromXRD($xrd)); // If there's an Hcard, let's grab its info if (array_key_exists('hcard', $hints)) { if (!array_key_exists('profileurl', $hints) || $hints['hcard'] != $hints['profileurl']) { $hcardHints = DiscoveryHints::fromHcardUrl($hints['hcard']); $hints = array_merge($hcardHints, $hints); } } // If we got a feed URL, try that $feedUrl = null; if (array_key_exists('feedurl', $hints)) { $feedUrl = $hints['feedurl']; try { common_log(LOG_INFO, "Discovery on acct:{$addr} with feed URL " . $hints['feedurl']); $oprofile = self::ensureFeedURL($hints['feedurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->getUri()); return $oprofile; } catch (Exception $e) { common_log(LOG_WARNING, "Failed creating profile from feed URL '{$feedUrl}': " . $e->getMessage()); // keep looking } } // If we got a profile page, try that! $profileUrl = null; if (array_key_exists('profileurl', $hints)) { $profileUrl = $hints['profileurl']; try { common_log(LOG_INFO, "Discovery on acct:{$addr} with profile URL {$profileUrl}"); $oprofile = self::ensureProfileURL($hints['profileurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->getUri()); return $oprofile; } catch (OStatusShadowException $e) { // We've ended up with a remote reference to a local user or group. // @todo FIXME: Ideally we should be able to say who it was so we can // go back and refer to it the regular way throw $e; } catch (Exception $e) { common_log(LOG_WARNING, "Failed creating profile from profile URL '{$profileUrl}': " . $e->getMessage()); // keep looking // // @todo FIXME: This means an error discovering from profile page // may give us a corrupt entry using the webfinger URI, which // will obscure the correct page-keyed profile later on. } } // XXX: try hcard // XXX: try FOAF if (array_key_exists('salmon', $hints)) { $salmonEndpoint = $hints['salmon']; // An account URL, a salmon endpoint, and a dream? Not much to go // on, but let's give it a try $uri = 'acct:' . $addr; $profile = new Profile(); $profile->nickname = self::nicknameFromUri($uri); $profile->created = common_sql_now(); if (!is_null($profileUrl)) { $profile->profileurl = $profileUrl; } $profile_id = $profile->insert(); if ($profile_id === false) { common_log_db_error($profile, 'INSERT', __FILE__); // TRANS: Exception. %s is a webfinger address. throw new Exception(sprintf(_m('Could not save profile for "%s".'), $addr)); } $oprofile = new Ostatus_profile(); $oprofile->uri = $uri; $oprofile->salmonuri = $salmonEndpoint; $oprofile->profile_id = $profile_id; $oprofile->created = common_sql_now(); if (!is_null($feedUrl)) { $oprofile->feeduri = $feedUrl; } $result = $oprofile->insert(); if ($result === false) { $profile->delete(); common_log_db_error($oprofile, 'INSERT', __FILE__); // TRANS: Exception. %s is a webfinger address. throw new Exception(sprintf(_m('Could not save OStatus profile for "%s".'), $addr)); } self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->getUri()); return $oprofile; } // TRANS: Exception. %s is a webfinger address. throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr)); }
$salmonuri = null; // @fixme will bork where the URI isn't the profile URL for now $discover = new FeedDiscovery(); try { $feedurl = $discover->discoverFromURL($oprofile->uri); $salmonuri = $discover->getAtomLink(Salmon::REL_SALMON) ?: $discover->getAtomLink(Salmon::NS_REPLIES); // NS_REPLIES is deprecated if (empty($salmonuri)) { throw new FeedSubNoSalmonException('No salmon upstream URI was found'); } } catch (FeedSubException $e) { $acct = $oprofile->localProfile()->getAcctUri(); print "Could not discover feeds HTML response, trying reconstructed acct URI: {$acct}\n"; $disco = new Discovery(); $xrd = $disco->lookup($acct); $hints = DiscoveryHints::fromXRD($xrd); if (empty($feedurl) && !array_key_exists('feedurl', $hints)) { throw new FeedSubNoFeedException($acct); } $feedurl = $feedurl ?: $hints['feedurl']; $salmonuri = array_key_exists('salmon', $hints) ? $hints['salmon'] : $salmonuri; // get the hub data too and put it in the FeedDiscovery object $discover->discoverFromFeedUrl($feedurl); } $huburi = $discover->getHubLink(); print " Feed URL: {$feedurl}\n"; print " Hub URL: {$huburi}\n"; print " Salmon URL: {$salmonuri}\n"; if ($feedurl != $oprofile->feeduri || $salmonuri != $oprofile->salmonuri) { print "\n"; print "Updating...\n";
/** * Look up, and if necessary create, an Ostatus_profile for the remote * entity with the given webfinger address. * This should never return null -- you will either get an object or * an exception will be thrown. * * @param string $addr webfinger address * @return Ostatus_profile * @throws Exception on error conditions * @throws OStatusShadowException if this reference would obscure a local user/group */ public static function updateWebfinger($addr) { $disco = new Discovery(); try { $xrd = $disco->lookup($addr); } catch (Exception $e) { // Save negative cache entry so we don't waste time looking it up again. // @fixme distinguish temporary failures? self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null); // TRANS: Exception. throw new Exception(_m('Not a valid webfinger address.')); } $hints = array('webfinger' => $addr); $dhints = DiscoveryHints::fromXRD($xrd); $hints = array_merge($hints, $dhints); // If there's an Hcard, let's grab its info if (array_key_exists('hcard', $hints)) { if (!array_key_exists('profileurl', $hints) || $hints['hcard'] != $hints['profileurl']) { $hcardHints = DiscoveryHints::fromHcardUrl($hints['hcard']); $hints = array_merge($hcardHints, $hints); } } // If we got a feed URL, try that if (array_key_exists('feedurl', $hints)) { try { common_log(LOG_INFO, "Discovery on acct:{$addr} with feed URL " . $hints['feedurl']); $oprofile = self::ensureFeedURL($hints['feedurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); return $oprofile; } catch (Exception $e) { common_log(LOG_WARNING, "Failed creating profile from feed URL '{$feedUrl}': " . $e->getMessage()); // keep looking } } // If we got a profile page, try that! if (array_key_exists('profileurl', $hints)) { try { common_log(LOG_INFO, "Discovery on acct:{$addr} with profile URL {$profileUrl}"); $oprofile = self::ensureProfileURL($hints['profileurl'], $hints); self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); return $oprofile; } catch (OStatusShadowException $e) { // We've ended up with a remote reference to a local user or group. // @fixme ideally we should be able to say who it was so we can // go back and refer to it the regular way throw $e; } catch (Exception $e) { common_log(LOG_WARNING, "Failed creating profile from profile URL '{$profileUrl}': " . $e->getMessage()); // keep looking // // @fixme this means an error discovering from profile page // may give us a corrupt entry using the webfinger URI, which // will obscure the correct page-keyed profile later on. } } throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr)); }
function ensureProfiles() { try { $this->oprofile = Ostatus_profile::getActorProfile($this->activity); if (!$this->oprofile instanceof Ostatus_profile) { throw new UnknownUriException($this->activity->actor->id); } } catch (UnknownUriException $e) { // Apparently we didn't find the Profile object based on our URI, // so OStatus doesn't have it with this URI in ostatus_profile. // Try to look it up again, remote side may have changed from http to https // or maybe publish an acct: URI now instead of an http: URL. // // Steps: // 1. Check the newly received URI. Who does it say it is? // 2. Compare these alleged identities to our local database. // 3. If we found any locally stored identities, ask it about its aliases. // 4. Do any of the aliases from our known identity match the recently introduced one? // // Example: We have stored http://example.com/user/1 but this URI says https://example.com/user/1 common_debug('No local Profile object found for a magicsigned activity author URI: ' . $e->object_uri); $disco = new Discovery(); $xrd = $disco->lookup($e->object_uri); // Step 1: We got a bunch of discovery data for https://example.com/user/1 which includes // aliases https://example.com/user and hopefully our original http://example.com/user/1 too $all_ids = array_merge(array($xrd->subject), $xrd->aliases); if (!in_array($e->object_uri, $all_ids)) { common_debug('The activity author URI we got was not listed itself when doing discovery on it.'); throw $e; } // Go through each reported alias from lookup to see if we know this already foreach ($all_ids as $aliased_uri) { $oprofile = Ostatus_profile::getKV('uri', $aliased_uri); if (!$oprofile instanceof Ostatus_profile) { continue; // unknown locally, check the next alias } // Step 2: We found the alleged http://example.com/user/1 URI in our local database, // but this can't be trusted yet because anyone can publish any alias. common_debug('Found a local Ostatus_profile for "' . $e->object_uri . '" with this URI: ' . $aliased_uri); // We found an existing OStatus profile, but is it really the same? Do a callback to the URI's origin // Step 3: lookup our previously known http://example.com/user/1 webfinger etc. $xrd = $disco->lookup($oprofile->getUri()); // getUri returns ->uri, which we filtered on earlier $doublecheck_aliases = array_merge(array($xrd->subject), $xrd->aliases); common_debug('Trying to match known "' . $aliased_uri . '" against its returned aliases: ' . implode(' ', $doublecheck_aliases)); // if we find our original URI here, it is a legitimate alias // Step 4: Is the newly introduced https://example.com/user/1 URI in the list of aliases // presented by http://example.com/user/1 (i.e. do they both say they are the same identity?) if (in_array($e->object_uri, $doublecheck_aliases)) { $oprofile->updateUriKeys($e->object_uri, DiscoveryHints::fromXRD($xrd)); $this->oprofile = $oprofile; break; // don't iterate through aliases anymore } } // We might end up here after $all_ids is iterated through without a $this->oprofile value, if (!$this->oprofile instanceof Ostatus_profile) { common_debug("We do not have a local profile to connect to this activity's author. Let's create one."); // ensureActivityObjectProfile throws exception on failure $this->oprofile = Ostatus_profile::ensureActivityObjectProfile($this->activity->actor); } } assert($this->oprofile instanceof Ostatus_profile); $this->actor = $this->oprofile->localProfile(); }