summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorEvan Prodromou <evan@status.net>2010-02-27 17:07:57 -0500
committerEvan Prodromou <evan@status.net>2010-02-27 17:07:57 -0500
commitf00e8bbf47e3ea93578c5bfaba8d1d0460526086 (patch)
treec60faae494a2e43bdf45e7508c4ac2bd763399b5 /plugins
parentb701f5648d944cbb74748c48ea399b226eafc525 (diff)
parent04c4facba9230f40726c5891dcac21d928fbb2ab (diff)
Merge branch 'testing' into 0.9.x
Diffstat (limited to 'plugins')
-rw-r--r--plugins/OStatus/classes/HubSub.php2
-rw-r--r--plugins/OStatus/classes/Magicsig.php20
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php6
-rw-r--r--plugins/OStatus/lib/magicenvelope.php22
-rw-r--r--plugins/OStatus/lib/salmon.php16
-rw-r--r--plugins/OStatus/lib/salmonaction.php27
-rw-r--r--plugins/RegisterThrottle/RegisterThrottlePlugin.php249
-rw-r--r--plugins/RegisterThrottle/Registration_ip.php124
8 files changed, 437 insertions, 29 deletions
diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php
index 1ac181fee..e599d83a9 100644
--- a/plugins/OStatus/classes/HubSub.php
+++ b/plugins/OStatus/classes/HubSub.php
@@ -99,7 +99,7 @@ class HubSub extends Memcached_DataObject
return array_keys($this->keyTypes());
}
- function sequenceKeys()
+ function sequenceKey()
{
return array(false, false, false);
}
diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php
index 751527c81..96900d876 100644
--- a/plugins/OStatus/classes/Magicsig.php
+++ b/plugins/OStatus/classes/Magicsig.php
@@ -50,7 +50,11 @@ class Magicsig extends Memcached_DataObject
public /*static*/ function staticGet($k, $v=null)
{
$obj = parent::staticGet(__CLASS__, $k, $v);
- return Magicsig::fromString($obj->keypair);
+ if (!empty($obj)) {
+ return Magicsig::fromString($obj->keypair);
+ }
+
+ return $obj;
}
@@ -84,6 +88,10 @@ class Magicsig extends Memcached_DataObject
return array('user_id' => 'K');
}
+ function sequenceKey() {
+ return array(false, false, false);
+ }
+
function insert()
{
$this->keypair = $this->toString();
@@ -173,14 +181,15 @@ class Magicsig extends Memcached_DataObject
switch ($this->alg) {
case 'RSA-SHA256':
- return 'sha256';
+ return 'magicsig_sha256';
}
}
public function sign($bytes)
{
- $sig = $this->_rsa->createSign($bytes, null, 'sha256');
+ $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());
@@ -192,7 +201,8 @@ class Magicsig extends Memcached_DataObject
public function verify($signed_bytes, $signature)
{
- $result = $this->_rsa->validateSign($signed_bytes, $signature, null, 'sha256');
+ $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());
@@ -205,7 +215,7 @@ class Magicsig extends Memcached_DataObject
// Define a sha256 function for hashing
// (Crypt_RSA should really be updated to use hash() )
-function sha256($bytes)
+function magicsig_sha256($bytes)
{
return hash('sha256', $bytes);
}
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 35539bff7..7b1aec76b 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -1288,9 +1288,9 @@ class Ostatus_profile extends Memcached_DataObject
$disco = new Discovery();
- $result = $disco->lookup($addr);
-
- if (!$result) {
+ try {
+ $result = $disco->lookup($addr);
+ } catch (Exception $e) {
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null);
return null;
}
diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php
index f33119b8f..230d81ba1 100644
--- a/plugins/OStatus/lib/magicenvelope.php
+++ b/plugins/OStatus/lib/magicenvelope.php
@@ -83,6 +83,28 @@ class MagicEnvelope
}
+ public function toXML($env) {
+ $dom = new DOMDocument();
+
+ $envelope = $dom->createElementNS(MagicEnvelope::NS, 'me:env');
+ $envelope->setAttribute('xmlns:me', MagicEnvelope::NS);
+ $data = $dom->createElementNS(MagicEnvelope::NS, 'me:data', $env['data']);
+ $data->setAttribute('type', $env['data_type']);
+ $envelope->appendChild($data);
+ $enc = $dom->createElementNS(MagicEnvelope::NS, 'me:encoding', $env['encoding']);
+ $envelope->appendChild($enc);
+ $alg = $dom->createElementNS(MagicEnvelope::NS, 'me:alg', $env['alg']);
+ $envelope->appendChild($alg);
+ $sig = $dom->createElementNS(MagicEnvelope::NS, 'me:sig', $env['sig']);
+ $envelope->appendChild($sig);
+
+ $dom->appendChild($envelope);
+
+
+ return $dom->saveXML();
+ }
+
+
public function unfold($env)
{
$dom = new DOMDocument();
diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php
index 6e2459544..3d3341bc6 100644
--- a/plugins/OStatus/lib/salmon.php
+++ b/plugins/OStatus/lib/salmon.php
@@ -48,11 +48,14 @@ class Salmon
return false;
}
- if (!common_config('ostatus', 'skip_signatures')) {
+ try {
$xml = $this->createMagicEnv($xml, $actor);
+ } catch (Exception $e) {
+ common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage());
+ return false;
}
- $headers = array('Content-Type: application/atom+xml');
+ $headers = array('Content-Type: application/magic-envelope+xml');
try {
$client = new HTTPClient();
@@ -72,7 +75,6 @@ class Salmon
public function createMagicEnv($text, $actor)
{
- common_log(LOG_DEBUG, "Got actor as : ". print_r($actor, true));
$magic_env = new MagicEnvelope();
$user = User::staticGet('id', $actor->id);
@@ -84,7 +86,6 @@ class Salmon
$magickey = new Magicsig();
$magickey->generate($user->id);
}
- common_log(LOG_DEBUG, "Salmon: Loaded key for ". $user->id);
} else {
throw new Exception("Salmon invalid actor for signing");
}
@@ -92,18 +93,17 @@ class Salmon
try {
$env = $magic_env->signMessage($text, 'application/atom+xml', $magickey->toString());
} catch (Exception $e) {
- common_log(LOG_ERR, "Salmon signing failed: ". $e->getMessage());
return $text;
}
- return $magic_env->unfold($env);
+ return $magic_env->toXML($env);
}
- public function verifyMagicEnv($dom)
+ public function verifyMagicEnv($text)
{
$magic_env = new MagicEnvelope();
- $env = $magic_env->fromDom($dom);
+ $env = $magic_env->parse($text);
return $magic_env->verify($env);
}
diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php
index a03169101..fa9dc3b1d 100644
--- a/plugins/OStatus/lib/salmonaction.php
+++ b/plugins/OStatus/lib/salmonaction.php
@@ -41,29 +41,32 @@ class SalmonAction extends Action
$this->clientError(_m('This method requires a POST.'));
}
- if (empty($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/atom+xml') {
- $this->clientError(_m('Salmon requires application/atom+xml'));
+ if (empty($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/magic-envelope+xml') {
+ $this->clientError(_m('Salmon requires application/magic-envelope+xml'));
}
$xml = file_get_contents('php://input');
- $dom = DOMDocument::loadXML($xml);
+ // Check the signature
+ $salmon = new Salmon;
+ if (!$salmon->verifyMagicEnv($xml)) {
+ common_log(LOG_DEBUG, "Salmon signature verification failed.");
+ $this->clientError(_m('Salmon signature verification failed.'));
+ } else {
+ $magic_env = new MagicEnvelope();
+ $env = $magic_env->parse($xml);
+ $xml = $magic_env->unfold($env);
+ }
+
+
+ $dom = DOMDocument::loadXML($xml);
if ($dom->documentElement->namespaceURI != Activity::ATOM ||
$dom->documentElement->localName != 'entry') {
common_log(LOG_DEBUG, "Got invalid Salmon post: $xml");
$this->clientError(_m('Salmon post must be an Atom entry.'));
}
- // Check the signature
- $salmon = new Salmon;
- if (!common_config('ostatus', 'skip_signatures')) {
- if (!$salmon->verifyMagicEnv($dom)) {
- common_log(LOG_DEBUG, "Salmon signature verification failed.");
- $this->clientError(_m('Salmon signature verification failed.'));
- }
- }
-
$this->act = new Activity($dom->documentElement);
return true;
}
diff --git a/plugins/RegisterThrottle/RegisterThrottlePlugin.php b/plugins/RegisterThrottle/RegisterThrottlePlugin.php
new file mode 100644
index 000000000..05709b780
--- /dev/null
+++ b/plugins/RegisterThrottle/RegisterThrottlePlugin.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * Throttle registration by IP address
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Spam
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Throttle registration by IP address
+ *
+ * We a) record IP address of registrants and b) throttle registrations.
+ *
+ * @category Spam
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class RegisterThrottlePlugin extends Plugin
+{
+ /**
+ * Array of time spans in seconds to limits.
+ *
+ * Default is 3 registrations per hour, 5 per day, 10 per week.
+ */
+
+ public $regLimits = array(604800 => 10, // per week
+ 86400 => 5, // per day
+ 3600 => 3); // per hour
+
+ /**
+ * Database schema setup
+ *
+ * We store user registrations in a table registration_ip.
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+
+ // For storing user-submitted flags on profiles
+
+ $schema->ensureTable('registration_ip',
+ array(new ColumnDef('user_id', 'integer', null,
+ false, 'PRI'),
+ new ColumnDef('ipaddress', 'varchar', 15, false, 'MUL'),
+ new ColumnDef('created', 'timestamp', null, false, 'MUL')));
+
+ return true;
+ }
+
+ /**
+ * Load related modules when needed
+ *
+ * @param string $cls Name of the class to be loaded
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+
+ function onAutoload($cls)
+ {
+ $dir = dirname(__FILE__);
+
+ switch ($cls)
+ {
+ case 'Registration_ip':
+ include_once $dir . '/'.$cls.'.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Called when someone tries to register.
+ *
+ * We check the IP here to determine if it goes over any of our
+ * configured limits.
+ *
+ * @param Action $action Action that is being executed
+ *
+ * @return boolean hook value
+ *
+ */
+
+ function onStartRegistrationTry($action)
+ {
+ $ipaddress = $this->_getIpAddress();
+
+ if (empty($ipaddress)) {
+ throw new ServerException(_m('Cannot find IP address.'));
+ }
+
+ foreach ($this->regLimits as $seconds => $limit) {
+
+ $this->debug("Checking $seconds ($limit)");
+
+ $reg = $this->_getNthReg($ipaddress, $limit);
+
+ if (!empty($reg)) {
+ $this->debug("Got a {$limit}th registration.");
+ $regtime = strtotime($reg->created);
+ $now = time();
+ $this->debug("Comparing {$regtime} to {$now}");
+ if ($now - $regtime < $seconds) {
+ throw new Exception(_("Too many registrations. Take a break and try again later."));
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Called after someone registers.
+ *
+ * We record the successful registration and IP address.
+ *
+ * @param Action $action Action that is being executed
+ *
+ * @return boolean hook value
+ *
+ */
+
+ function onEndRegistrationTry($action)
+ {
+ $ipaddress = $this->_getIpAddress();
+
+ if (empty($ipaddress)) {
+ throw new ServerException(_m('Cannot find IP address.'));
+ }
+
+ $user = common_current_user();
+
+ if (empty($user)) {
+ throw new ServerException(_m('Cannot find user after successful registration.'));
+ }
+
+ $reg = new Registration_ip();
+
+ $reg->user_id = $user->id;
+ $reg->ipaddress = $ipaddress;
+
+ $result = $reg->insert();
+
+ if (!$result) {
+ common_log_db_error($reg, 'INSERT', __FILE__);
+ // @todo throw an exception?
+ }
+
+ return true;
+ }
+
+ /**
+ * Check the version of the plugin.
+ *
+ * @param array &$versions Version array.
+ *
+ * @return boolean hook value
+ */
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'RegisterThrottle',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:RegisterThrottle',
+ 'description' =>
+ _m('Throttles excessive registration from a single IP.'));
+ return true;
+ }
+
+ /**
+ * Gets the current IP address.
+ *
+ * @return string IP address or null if not found.
+ */
+
+ private function _getIpAddress()
+ {
+ $keys = array('HTTP_X_FORWARDED_FOR',
+ 'CLIENT-IP',
+ 'REMOTE_ADDR');
+
+ foreach ($keys as $k) {
+ if (!empty($_SERVER[$k])) {
+ return $_SERVER[$k];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the Nth registration with the given IP address.
+ *
+ * @param string $ipaddress Address to key on
+ * @param integer $n Nth address
+ *
+ * @return Registration_ip nth registration or null if not found.
+ */
+
+ private function _getNthReg($ipaddress, $n)
+ {
+ $reg = new Registration_ip();
+
+ $reg->ipaddress = $ipaddress;
+
+ $reg->orderBy('created DESC');
+ $reg->limit($n - 1, 1);
+
+ if ($reg->find(true)) {
+ return $reg;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/plugins/RegisterThrottle/Registration_ip.php b/plugins/RegisterThrottle/Registration_ip.php
new file mode 100644
index 000000000..7e61d089e
--- /dev/null
+++ b/plugins/RegisterThrottle/Registration_ip.php
@@ -0,0 +1,124 @@
+<?php
+/**
+ * Data class for storing IP addresses of new registrants.
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Data class for storing IP addresses of new registrants.
+ *
+ * @category Spam
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ */
+
+class Registration_ip extends Memcached_DataObject
+{
+ public $__table = 'registration_ip'; // table name
+ public $user_id; // int(4) primary_key not_null
+ public $ipaddress; // varchar(15)
+ public $created; // timestamp
+
+ /**
+ * Get an instance by key
+ *
+ * @param string $k Key to use to lookup (usually 'user_id' for this class)
+ * @param mixed $v Value to lookup
+ *
+ * @return User_greeting_count object found, or null for no hits
+ *
+ */
+
+ function staticGet($k, $v=null)
+ {
+ return Memcached_DataObject::staticGet('Registration_ip', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'ipaddress' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'created' => DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL);
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has; this function
+ * defines them.
+ *
+ * @return array key definitions
+ */
+
+ function keys()
+ {
+ return array('user_id' => 'K');
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them.
+ *
+ * @return array key definitions
+ */
+
+ function keyTypes()
+ {
+ return $this->keys();
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * If a table has a single integer column as its primary key, DB_DataObject
+ * assumes that the column is auto-incrementing and makes a sequence table
+ * to do this incrementation. Since we don't need this for our class, we
+ * overload this method and return the magic formula that DB_DataObject needs.
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+
+ function sequenceKey()
+ {
+ return array(false, false, false);
+ }
+}