summaryrefslogtreecommitdiff
path: root/plugins/OStatus/classes
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/OStatus/classes')
-rw-r--r--plugins/OStatus/classes/FeedSub.php3
-rw-r--r--plugins/OStatus/classes/HubSub.php2
-rw-r--r--plugins/OStatus/classes/Magicsig.php114
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php281
4 files changed, 185 insertions, 215 deletions
diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php
index b848b6b1d..80ba37bc1 100644
--- a/plugins/OStatus/classes/FeedSub.php
+++ b/plugins/OStatus/classes/FeedSub.php
@@ -110,7 +110,7 @@ class FeedSub extends Memcached_DataObject
/*size*/ null,
/*nullable*/ false,
/*key*/ 'PRI',
- /*default*/ '0',
+ /*default*/ null,
/*extra*/ null,
/*auto_increment*/ true),
new ColumnDef('uri', 'varchar',
@@ -450,3 +450,4 @@ class FeedSub extends Memcached_DataObject
}
}
+
diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php
index c420b3eef..cdace3c1f 100644
--- a/plugins/OStatus/classes/HubSub.php
+++ b/plugins/OStatus/classes/HubSub.php
@@ -77,7 +77,7 @@ class HubSub extends Memcached_DataObject
new ColumnDef('topic', 'varchar',
/*size*/255,
/*nullable*/false,
- /*key*/'KEY'),
+ /*key*/'MUL'),
new ColumnDef('callback', 'varchar',
255, false),
new ColumnDef('secret', 'text',
diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php
index 5a46aeeb6..5705ecc11 100644
--- a/plugins/OStatus/classes/Magicsig.php
+++ b/plugins/OStatus/classes/Magicsig.php
@@ -40,8 +40,9 @@ class Magicsig extends Memcached_DataObject
public $keypair;
public $alg;
- private $_rsa;
-
+ public $publicKey;
+ public $privateKey;
+
public function __construct($alg = 'RSA-SHA256')
{
$this->alg = $alg;
@@ -70,9 +71,9 @@ class Magicsig extends Memcached_DataObject
static function schemaDef()
{
return array(new ColumnDef('user_id', 'integer',
- null, true, 'PRI'),
- new ColumnDef('keypair', 'varchar',
- 255, false),
+ null, false, 'PRI'),
+ new ColumnDef('keypair', 'text',
+ false, false),
new ColumnDef('alg', 'varchar',
64, false));
}
@@ -99,17 +100,20 @@ class Magicsig extends Memcached_DataObject
return parent::insert();
}
- public function generate($user_id, $key_length = 512)
+ public function generate($user_id)
{
- PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $rsa = new Crypt_RSA();
+
+ $keypair = $rsa->createKey();
- $keypair = new Crypt_RSA_KeyPair($key_length);
- $params['public_key'] = $keypair->getPublicKey();
- $params['private_key'] = $keypair->getPrivateKey();
+ $rsa->loadKey($keypair['privatekey']);
- $this->_rsa = new Crypt_RSA($params);
- PEAR::popErrorHandling();
+ $this->privateKey = new Crypt_RSA();
+ $this->privateKey->loadKey($keypair['privatekey']);
+ $this->publicKey = new Crypt_RSA();
+ $this->publicKey->loadKey($keypair['publickey']);
+
$this->user_id = $user_id;
$this->insert();
}
@@ -117,14 +121,11 @@ class Magicsig extends Memcached_DataObject
public function toString($full_pair = true)
{
- $public_key = $this->_rsa->_public_key;
- $private_key = $this->_rsa->_private_key;
-
- $mod = base64_url_encode($public_key->getModulus());
- $exp = base64_url_encode($public_key->getExponent());
+ $mod = base64_url_encode($this->publicKey->modulus->toBytes());
+ $exp = base64_url_encode($this->publicKey->exponent->toBytes());
$private_exp = '';
- if ($full_pair && $private_key->getExponent()) {
- $private_exp = '.' . base64_url_encode($private_key->getExponent());
+ if ($full_pair && $this->privateKey->exponent->toBytes()) {
+ $private_exp = '.' . base64_url_encode($this->privateKey->exponent->toBytes());
}
return 'RSA.' . $mod . '.' . $exp . $private_exp;
@@ -132,8 +133,6 @@ class Magicsig extends Memcached_DataObject
public static function fromString($text)
{
- PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
-
$magic_sig = new Magicsig();
// remove whitespace
@@ -144,35 +143,40 @@ class Magicsig extends Memcached_DataObject
return false;
}
- $mod = base64_url_decode($matches[1]);
- $exp = base64_url_decode($matches[2]);
+ $mod = $matches[1];
+ $exp = $matches[2];
if (!empty($matches[4])) {
- $private_exp = base64_url_decode($matches[4]);
+ $private_exp = $matches[4];
} else {
$private_exp = false;
}
- $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public');
- if ($params['public_key']->isError()) {
- $error = $params['public_key']->getLastError();
- common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
- return false;
- }
+ $magic_sig->loadKey($mod, $exp, 'public');
if ($private_exp) {
- $params['private_key'] = new Crypt_RSA_KEY($mod, $private_exp, 'private');
- if ($params['private_key']->isError()) {
- $error = $params['private_key']->getLastError();
- common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
- return false;
- }
+ $magic_sig->loadKey($mod, $private_exp, 'private');
}
- $magic_sig->_rsa = new Crypt_RSA($params);
- PEAR::popErrorHandling();
-
return $magic_sig;
}
+ public function loadKey($mod, $exp, $type = 'public')
+ {
+ common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")");
+
+ $rsa = new Crypt_RSA();
+ $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
+ $rsa->setHash('sha256');
+ $rsa->modulus = new Math_BigInteger(base64_url_decode($mod), 256);
+ $rsa->k = strlen($rsa->modulus->toBytes());
+ $rsa->exponent = new Math_BigInteger(base64_url_decode($exp), 256);
+
+ if ($type == 'private') {
+ $this->privateKey = $rsa;
+ } else {
+ $this->publicKey = $rsa;
+ }
+ }
+
public function getName()
{
return $this->alg;
@@ -183,45 +187,25 @@ class Magicsig extends Memcached_DataObject
switch ($this->alg) {
case 'RSA-SHA256':
- return 'magicsig_sha256';
+ return 'sha256';
}
}
public function sign($bytes)
{
- $hash = $this->getHash();
- $sig = $this->_rsa->createSign($bytes, null, $hash);
- if ($this->_rsa->isError()) {
- $error = $this->_rsa->getLastError();
- common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
- return false;
- }
-
- return $sig;
+ $sig = $this->privateKey->sign($bytes);
+ return base64_url_encode($sig);
}
public function verify($signed_bytes, $signature)
{
- $hash = $this->getHash();
- $result = $this->_rsa->validateSign($signed_bytes, $signature, null, $hash);
- if ($this->_rsa->isError()) {
- $error = $this->keypair->getLastError();
- common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
- return false;
- }
- return $result;
+ $signature = base64_url_decode($signature);
+ return $this->publicKey->verify($signed_bytes, $signature);
}
}
-// Define a sha256 function for hashing
-// (Crypt_RSA should really be updated to use hash() )
-function magicsig_sha256($bytes)
-{
- return hash('sha256', $bytes);
-}
-
function base64_url_encode($input)
{
return strtr(base64_encode($input), '+/', '-_');
@@ -230,4 +214,4 @@ function base64_url_encode($input)
function base64_url_decode($input)
{
return base64_decode(strtr($input, '-_', '+/'));
-} \ No newline at end of file
+}
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 6145080fc..e0e0223b8 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -195,52 +195,6 @@ class Ostatus_profile extends Memcached_DataObject
}
/**
- * Subscribe a local user to this remote user.
- * PuSH subscription will be started if necessary, and we'll
- * send a Salmon notification to the remote server if available
- * notifying them of the sub.
- *
- * @param User $user
- * @return boolean success
- * @throws FeedException
- */
- public function subscribeLocalToRemote(User $user)
- {
- if ($this->isGroup()) {
- throw new ServerException("Can't subscribe to a remote group");
- }
-
- if ($this->subscribe()) {
- if ($user->subscribeTo($this->localProfile())) {
- $this->notify($user->getProfile(), ActivityVerb::FOLLOW, $this);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Mark this remote profile as subscribing to the given local user,
- * and send appropriate notifications to the user.
- *
- * This will generally be in response to a subscription notification
- * from a foreign site to our local Salmon response channel.
- *
- * @param User $user
- * @return boolean success
- */
- public function subscribeRemoteToLocal(User $user)
- {
- if ($this->isGroup()) {
- throw new ServerException("Remote groups can't subscribe to local users");
- }
-
- Subscription::start($this->localProfile(), $user->getProfile());
-
- return true;
- }
-
- /**
* Send a subscription request to the hub for this feed.
* The hub will later send us a confirmation POST to /main/push/callback.
*
@@ -708,18 +662,122 @@ class Ostatus_profile extends Memcached_DataObject
* @return Ostatus_profile
* @throws FeedSubException
*/
- public static function ensureProfile($profile_uri, $hints=array())
+
+ public static function ensureProfileURL($profile_url, $hints=array())
{
- // Get the canonical feed URI and check it
+ $oprofile = self::getFromProfileURL($profile_url);
+
+ if (!empty($oprofile)) {
+ return $oprofile;
+ }
+
+ $hints['profileurl'] = $profile_url;
+
+ // Fetch the URL
+ // XXX: HTTP caching
+
+ $client = new HTTPClient();
+ $client->setHeader('Accept', 'text/html,application/xhtml+xml');
+ $response = $client->get($profile_url);
+
+ if (!$response->isOk()) {
+ return null;
+ }
+
+ // Check if we have a non-canonical URL
+
+ $finalUrl = $response->getUrl();
+
+ if ($finalUrl != $profile_url) {
+
+ $hints['profileurl'] = $finalUrl;
+
+ $oprofile = self::getFromProfileURL($finalUrl);
+
+ if (!empty($oprofile)) {
+ return $oprofile;
+ }
+ }
+
+ // Try to get some hCard data
+
+ $body = $response->getBody();
+
+ $hcardHints = DiscoveryHints::hcardHints($body, $finalUrl);
+
+ if (!empty($hcardHints)) {
+ $hints = array_merge($hints, $hcardHints);
+ }
+
+ // Check if they've got an LRDD header
+
+ $lrdd = LinkHeader::getLink($response, 'lrdd', 'application/xrd+xml');
+
+ if (!empty($lrdd)) {
+
+ $xrd = Discovery::fetchXrd($lrdd);
+ $xrdHints = DiscoveryHints::fromXRD($xrd);
+
+ $hints = array_merge($hints, $xrdHints);
+ }
+
+ // If discovery found a feedurl (probably from LRDD), use it.
+
+ if (array_key_exists('feedurl', $hints)) {
+ return self::ensureFeedURL($hints['feedurl'], $hints);
+ }
+
+ // Get the feed URL from HTML
+
$discover = new FeedDiscovery();
- if (isset($hints['feedurl'])) {
- $feeduri = $hints['feedurl'];
- $feeduri = $discover->discoverFromFeedURL($feeduri);
- } else {
- $feeduri = $discover->discoverFromURL($profile_uri);
- $hints['feedurl'] = $feeduri;
+
+ $feedurl = $discover->discoverFromHTML($finalUrl, $body);
+
+ if (!empty($feedurl)) {
+ $hints['feedurl'] = $feedurl;
+
+ return self::ensureFeedURL($feedurl, $hints);
+ }
+ }
+
+ static function getFromProfileURL($profile_url)
+ {
+ $profile = Profile::staticGet('profileurl', $profile_url);
+
+ if (empty($profile)) {
+ return null;
+ }
+
+ // Is it a known Ostatus profile?
+
+ $oprofile = Ostatus_profile::staticGet('profile_id', $profile->id);
+
+ if (!empty($oprofile)) {
+ return $oprofile;
}
+ // Is it a local user?
+
+ $user = User::staticGet('id', $profile->id);
+
+ if (!empty($user)) {
+ throw new Exception("'$profile_url' is the profile for local user '{$user->nickname}'.");
+ }
+
+ // Continue discovery; it's a remote profile
+ // for OMB or some other protocol, may also
+ // support OStatus
+
+ return null;
+ }
+
+ public static function ensureFeedURL($feed_url, $hints=array())
+ {
+ $discover = new FeedDiscovery();
+
+ $feeduri = $discover->discoverFromFeedURL($feed_url);
+ $hints['feedurl'] = $feeduri;
+
$huburi = $discover->getAtomLink('hub');
$hints['hub'] = $huburi;
$salmonuri = $discover->getAtomLink(Salmon::NS_REPLIES);
@@ -1306,7 +1364,7 @@ class Ostatus_profile extends Memcached_DataObject
}
}
- // First, look it up
+ // Try looking it up
$oprofile = Ostatus_profile::staticGet('uri', 'acct:'.$addr);
@@ -1320,7 +1378,7 @@ class Ostatus_profile extends Memcached_DataObject
$disco = new Discovery();
try {
- $result = $disco->lookup($addr);
+ $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?
@@ -1330,38 +1388,26 @@ class Ostatus_profile extends Memcached_DataObject
$hints = array('webfinger' => $addr);
- foreach ($result->links as $link) {
- switch ($link['rel']) {
- case Discovery::PROFILEPAGE:
- $hints['profileurl'] = $profileUrl = $link['href'];
- break;
- case Salmon::NS_REPLIES:
- $hints['salmon'] = $salmonEndpoint = $link['href'];
- break;
- case Discovery::UPDATESFROM:
- $hints['feedurl'] = $feedUrl = $link['href'];
- break;
- case Discovery::HCARD:
- $hcardUrl = $link['href'];
- break;
- default:
- common_log(LOG_NOTICE, "Don't know what to do with rel = '{$link['rel']}'");
- break;
- }
- }
+ $dhints = DiscoveryHints::fromXRD($xrd);
+
+ $hints = array_merge($hints, $dhints);
+
+ // If there's an Hcard, let's grab its info
- if (isset($hcardUrl)) {
- $hcardHints = self::slurpHcard($hcardUrl);
- // Note: Webfinger > hcard
- $hints = array_merge($hcardHints, $hints);
+ 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 (isset($feedUrl)) {
+ if (array_key_exists('feedurl', $hints)) {
try {
- common_log(LOG_INFO, "Discovery on acct:$addr with feed URL $feedUrl");
- $oprofile = self::ensureProfile($feedUrl, $hints);
+ 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) {
@@ -1372,10 +1418,10 @@ class Ostatus_profile extends Memcached_DataObject
// If we got a profile page, try that!
- if (isset($profileUrl)) {
+ if (array_key_exists('profileurl', $hints)) {
try {
common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl");
- $oprofile = self::ensureProfile($profileUrl, $hints);
+ $oprofile = self::ensureProfileURL($hints['profileurl'], $hints);
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri);
return $oprofile;
} catch (Exception $e) {
@@ -1387,7 +1433,9 @@ class Ostatus_profile extends Memcached_DataObject
// XXX: try hcard
// XXX: try FOAF
- if (isset($salmonEndpoint)) {
+ 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
@@ -1467,67 +1515,4 @@ class Ostatus_profile extends Memcached_DataObject
return $file;
}
-
- protected static function slurpHcard($url)
- {
- set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/plugins/OStatus/extlib/hkit/');
- require_once('hkit.class.php');
-
- $h = new hKit;
-
- // Google Buzz hcards need to be tidied. Probably others too.
-
- $h->tidy_mode = 'proxy'; // 'proxy', 'exec', 'php' or 'none'
-
- // Get by URL
- $hcards = $h->getByURL('hcard', $url);
-
- if (empty($hcards)) {
- return array();
- }
-
- // @fixme more intelligent guess on multi-hcard pages
- $hcard = $hcards[0];
-
- $hints = array();
-
- $hints['profileurl'] = $url;
-
- if (array_key_exists('nickname', $hcard)) {
- $hints['nickname'] = $hcard['nickname'];
- }
-
- if (array_key_exists('fn', $hcard)) {
- $hints['fullname'] = $hcard['fn'];
- } else if (array_key_exists('n', $hcard)) {
- $hints['fullname'] = implode(' ', $hcard['n']);
- }
-
- if (array_key_exists('photo', $hcard)) {
- $hints['avatar'] = $hcard['photo'];
- }
-
- if (array_key_exists('note', $hcard)) {
- $hints['bio'] = $hcard['note'];
- }
-
- if (array_key_exists('adr', $hcard)) {
- if (is_string($hcard['adr'])) {
- $hints['location'] = $hcard['adr'];
- } else if (is_array($hcard['adr'])) {
- $hints['location'] = implode(' ', $hcard['adr']);
- }
- }
-
- if (array_key_exists('url', $hcard)) {
- if (is_string($hcard['url'])) {
- $hints['homepage'] = $hcard['url'];
- } else if (is_array($hcard['url'])) {
- // HACK get the last one; that's how our hcards look
- $hints['homepage'] = $hcard['url'][count($hcard['url'])-1];
- }
- }
-
- return $hints;
- }
}