diff options
author | Evan Prodromou <evan@status.net> | 2010-02-27 17:07:57 -0500 |
---|---|---|
committer | Evan Prodromou <evan@status.net> | 2010-02-27 17:07:57 -0500 |
commit | f00e8bbf47e3ea93578c5bfaba8d1d0460526086 (patch) | |
tree | c60faae494a2e43bdf45e7508c4ac2bd763399b5 /plugins | |
parent | b701f5648d944cbb74748c48ea399b226eafc525 (diff) | |
parent | 04c4facba9230f40726c5891dcac21d928fbb2ab (diff) |
Merge branch 'testing' into 0.9.x
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/OStatus/classes/HubSub.php | 2 | ||||
-rw-r--r-- | plugins/OStatus/classes/Magicsig.php | 20 | ||||
-rw-r--r-- | plugins/OStatus/classes/Ostatus_profile.php | 6 | ||||
-rw-r--r-- | plugins/OStatus/lib/magicenvelope.php | 22 | ||||
-rw-r--r-- | plugins/OStatus/lib/salmon.php | 16 | ||||
-rw-r--r-- | plugins/OStatus/lib/salmonaction.php | 27 | ||||
-rw-r--r-- | plugins/RegisterThrottle/RegisterThrottlePlugin.php | 249 | ||||
-rw-r--r-- | plugins/RegisterThrottle/Registration_ip.php | 124 |
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); + } +} |