Merge branch 'testing' into 0.9.x
This commit is contained in:
commit
f00e8bbf47
@ -282,12 +282,6 @@ class Notice extends Memcached_DataObject
|
|||||||
|
|
||||||
$notice->content = $final;
|
$notice->content = $final;
|
||||||
|
|
||||||
if (!empty($rendered)) {
|
|
||||||
$notice->rendered = $rendered;
|
|
||||||
} else {
|
|
||||||
$notice->rendered = common_render_content($final, $notice);
|
|
||||||
}
|
|
||||||
|
|
||||||
$notice->source = $source;
|
$notice->source = $source;
|
||||||
$notice->uri = $uri;
|
$notice->uri = $uri;
|
||||||
$notice->url = $url;
|
$notice->url = $url;
|
||||||
@ -315,6 +309,12 @@ class Notice extends Memcached_DataObject
|
|||||||
$notice->location_ns = $location_ns;
|
$notice->location_ns = $location_ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($rendered)) {
|
||||||
|
$notice->rendered = $rendered;
|
||||||
|
} else {
|
||||||
|
$notice->rendered = common_render_content($final, $notice);
|
||||||
|
}
|
||||||
|
|
||||||
if (Event::handle('StartNoticeSave', array(&$notice))) {
|
if (Event::handle('StartNoticeSave', array(&$notice))) {
|
||||||
|
|
||||||
// XXX: some of these functions write to the DB
|
// XXX: some of these functions write to the DB
|
||||||
@ -944,6 +944,8 @@ class Notice extends Memcached_DataObject
|
|||||||
$reply->profile_id = $user->id;
|
$reply->profile_id = $user->id;
|
||||||
|
|
||||||
$id = $reply->insert();
|
$id = $reply->insert();
|
||||||
|
|
||||||
|
self::blow('reply:stream:%d', $user->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,7 +973,10 @@ class Notice extends Memcached_DataObject
|
|||||||
|
|
||||||
$sender = Profile::staticGet($this->profile_id);
|
$sender = Profile::staticGet($this->profile_id);
|
||||||
|
|
||||||
$mentions = common_find_mentions($this->profile_id, $this->content);
|
// @todo ideally this parser information would only
|
||||||
|
// be calculated once.
|
||||||
|
|
||||||
|
$mentions = common_find_mentions($this->content, $this);
|
||||||
|
|
||||||
$replied = array();
|
$replied = array();
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
|
|||||||
//exit with 200 response, if this is checking fancy from the installer
|
//exit with 200 response, if this is checking fancy from the installer
|
||||||
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
|
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
|
||||||
|
|
||||||
define('STATUSNET_VERSION', '0.9.0beta6');
|
define('STATUSNET_VERSION', '0.9.0beta6+bugfix1');
|
||||||
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
|
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
|
||||||
|
|
||||||
define('STATUSNET_CODENAME', 'Stand');
|
define('STATUSNET_CODENAME', 'Stand');
|
||||||
|
@ -540,22 +540,40 @@ class NoticeListItem extends Widget
|
|||||||
function showContext()
|
function showContext()
|
||||||
{
|
{
|
||||||
$hasConversation = false;
|
$hasConversation = false;
|
||||||
if( !empty($this->notice->conversation)
|
if (!empty($this->notice->conversation)) {
|
||||||
&& $this->notice->conversation != $this->notice->id){
|
$conversation = Notice::conversationStream(
|
||||||
$hasConversation = true;
|
$this->notice->conversation,
|
||||||
}else{
|
1,
|
||||||
$conversation = Notice::conversationStream($this->notice->id, 1, 1);
|
1
|
||||||
|
);
|
||||||
if ($conversation->N > 0) {
|
if ($conversation->N > 0) {
|
||||||
$hasConversation = true;
|
$hasConversation = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($hasConversation) {
|
if ($hasConversation) {
|
||||||
|
$conv = Conversation::staticGet(
|
||||||
|
'id',
|
||||||
|
$this->notice->conversation
|
||||||
|
);
|
||||||
|
$convurl = $conv->uri;
|
||||||
|
if (!empty($convurl)) {
|
||||||
$this->out->text(' ');
|
$this->out->text(' ');
|
||||||
$convurl = common_local_url('conversation',
|
$this->out->element(
|
||||||
array('id' => $this->notice->conversation));
|
'a',
|
||||||
$this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id,
|
array(
|
||||||
|
'href' => $convurl.'#notice-'.$this->notice->id,
|
||||||
'class' => 'response'),
|
'class' => 'response'),
|
||||||
_('in context'));
|
_('in context')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$msg = sprintf(
|
||||||
|
"Couldn't find Conversation ID %d to make 'in context'"
|
||||||
|
. "link for Notice ID %d",
|
||||||
|
$this->notice->conversation,
|
||||||
|
$this->notice->id
|
||||||
|
);
|
||||||
|
common_log(LOG_WARNING, $msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
lib/util.php
69
lib/util.php
@ -426,14 +426,14 @@ function common_render_content($text, $notice)
|
|||||||
{
|
{
|
||||||
$r = common_render_text($text);
|
$r = common_render_text($text);
|
||||||
$id = $notice->profile_id;
|
$id = $notice->profile_id;
|
||||||
$r = common_linkify_mentions($id, $r);
|
$r = common_linkify_mentions($r, $notice);
|
||||||
$r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
|
$r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
|
||||||
return $r;
|
return $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
function common_linkify_mentions($profile_id, $text)
|
function common_linkify_mentions($text, $notice)
|
||||||
{
|
{
|
||||||
$mentions = common_find_mentions($profile_id, $text);
|
$mentions = common_find_mentions($text, $notice);
|
||||||
|
|
||||||
// We need to go through in reverse order by position,
|
// We need to go through in reverse order by position,
|
||||||
// so our positions stay valid despite our fudging with the
|
// so our positions stay valid despite our fudging with the
|
||||||
@ -487,11 +487,11 @@ function common_linkify_mention($mention)
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
function common_find_mentions($profile_id, $text)
|
function common_find_mentions($text, $notice)
|
||||||
{
|
{
|
||||||
$mentions = array();
|
$mentions = array();
|
||||||
|
|
||||||
$sender = Profile::staticGet('id', $profile_id);
|
$sender = Profile::staticGet('id', $notice->profile_id);
|
||||||
|
|
||||||
if (empty($sender)) {
|
if (empty($sender)) {
|
||||||
return $mentions;
|
return $mentions;
|
||||||
@ -499,6 +499,30 @@ function common_find_mentions($profile_id, $text)
|
|||||||
|
|
||||||
if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
|
if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
|
||||||
|
|
||||||
|
// Get the context of the original notice, if any
|
||||||
|
|
||||||
|
$originalAuthor = null;
|
||||||
|
$originalNotice = null;
|
||||||
|
$originalMentions = array();
|
||||||
|
|
||||||
|
// Is it a reply?
|
||||||
|
|
||||||
|
if (!empty($notice) && !empty($notice->reply_to)) {
|
||||||
|
$originalNotice = Notice::staticGet('id', $notice->reply_to);
|
||||||
|
if (!empty($originalNotice)) {
|
||||||
|
$originalAuthor = Profile::staticGet('id', $originalNotice->profile_id);
|
||||||
|
|
||||||
|
$ids = $originalNotice->getReplies();
|
||||||
|
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$repliedTo = Profile::staticGet('id', $id);
|
||||||
|
if (!empty($repliedTo)) {
|
||||||
|
$originalMentions[$repliedTo->nickname] = $repliedTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
preg_match_all('/^T ([A-Z0-9]{1,64}) /',
|
preg_match_all('/^T ([A-Z0-9]{1,64}) /',
|
||||||
$text,
|
$text,
|
||||||
$tmatches,
|
$tmatches,
|
||||||
@ -514,7 +538,22 @@ function common_find_mentions($profile_id, $text)
|
|||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
|
|
||||||
$nickname = common_canonical_nickname($match[0]);
|
$nickname = common_canonical_nickname($match[0]);
|
||||||
|
|
||||||
|
// Try to get a profile for this nickname.
|
||||||
|
// Start with conversation context, then go to
|
||||||
|
// sender context.
|
||||||
|
|
||||||
|
if (!empty($originalAuthor) && $originalAuthor->nickname == $nickname) {
|
||||||
|
|
||||||
|
$mentioned = $originalAuthor;
|
||||||
|
|
||||||
|
} else if (!empty($originalMentions) &&
|
||||||
|
array_key_exists($nickname, $originalMentions)) {
|
||||||
|
|
||||||
|
$mention = $originalMentions[$nickname];
|
||||||
|
} else {
|
||||||
$mentioned = common_relative_profile($sender, $nickname);
|
$mentioned = common_relative_profile($sender, $nickname);
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($mentioned)) {
|
if (!empty($mentioned)) {
|
||||||
|
|
||||||
@ -849,7 +888,7 @@ function common_relative_profile($sender, $nickname, $dt=null)
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function common_local_url($action, $args=null, $params=null, $fragment=null)
|
function common_local_url($action, $args=null, $params=null, $fragment=null, $addSession=true)
|
||||||
{
|
{
|
||||||
$r = Router::get();
|
$r = Router::get();
|
||||||
$path = $r->build($action, $args, $params, $fragment);
|
$path = $r->build($action, $args, $params, $fragment);
|
||||||
@ -857,12 +896,12 @@ function common_local_url($action, $args=null, $params=null, $fragment=null)
|
|||||||
$ssl = common_is_sensitive($action);
|
$ssl = common_is_sensitive($action);
|
||||||
|
|
||||||
if (common_config('site','fancy')) {
|
if (common_config('site','fancy')) {
|
||||||
$url = common_path(mb_substr($path, 1), $ssl);
|
$url = common_path(mb_substr($path, 1), $ssl, $addSession);
|
||||||
} else {
|
} else {
|
||||||
if (mb_strpos($path, '/index.php') === 0) {
|
if (mb_strpos($path, '/index.php') === 0) {
|
||||||
$url = common_path(mb_substr($path, 1), $ssl);
|
$url = common_path(mb_substr($path, 1), $ssl, $addSession);
|
||||||
} else {
|
} else {
|
||||||
$url = common_path('index.php'.$path, $ssl);
|
$url = common_path('index.php'.$path, $ssl, $addSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $url;
|
return $url;
|
||||||
@ -881,7 +920,7 @@ function common_is_sensitive($action)
|
|||||||
return $ssl;
|
return $ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
function common_path($relative, $ssl=false)
|
function common_path($relative, $ssl=false, $addSession=true)
|
||||||
{
|
{
|
||||||
$pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
|
$pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
|
||||||
|
|
||||||
@ -905,7 +944,9 @@ function common_path($relative, $ssl=false)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($addSession) {
|
||||||
$relative = common_inject_session($relative, $serverpart);
|
$relative = common_inject_session($relative, $serverpart);
|
||||||
|
}
|
||||||
|
|
||||||
return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
|
return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
|
||||||
}
|
}
|
||||||
@ -1127,14 +1168,15 @@ function common_broadcast_profile(Profile $profile)
|
|||||||
|
|
||||||
function common_profile_url($nickname)
|
function common_profile_url($nickname)
|
||||||
{
|
{
|
||||||
return common_local_url('showstream', array('nickname' => $nickname));
|
return common_local_url('showstream', array('nickname' => $nickname),
|
||||||
|
null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should make up a reasonable root URL
|
// Should make up a reasonable root URL
|
||||||
|
|
||||||
function common_root_url($ssl=false)
|
function common_root_url($ssl=false)
|
||||||
{
|
{
|
||||||
$url = common_path('', $ssl);
|
$url = common_path('', $ssl, false);
|
||||||
$i = strpos($url, '?');
|
$i = strpos($url, '?');
|
||||||
if ($i !== false) {
|
if ($i !== false) {
|
||||||
$url = substr($url, 0, $i);
|
$url = substr($url, 0, $i);
|
||||||
@ -1419,7 +1461,8 @@ function common_remove_magic_from_request()
|
|||||||
|
|
||||||
function common_user_uri(&$user)
|
function common_user_uri(&$user)
|
||||||
{
|
{
|
||||||
return common_local_url('userbyid', array('id' => $user->id));
|
return common_local_url('userbyid', array('id' => $user->id),
|
||||||
|
null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function common_notice_uri(&$notice)
|
function common_notice_uri(&$notice)
|
||||||
|
@ -99,7 +99,7 @@ class HubSub extends Memcached_DataObject
|
|||||||
return array_keys($this->keyTypes());
|
return array_keys($this->keyTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
function sequenceKeys()
|
function sequenceKey()
|
||||||
{
|
{
|
||||||
return array(false, false, false);
|
return array(false, false, false);
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,13 @@ class Magicsig extends Memcached_DataObject
|
|||||||
public /*static*/ function staticGet($k, $v=null)
|
public /*static*/ function staticGet($k, $v=null)
|
||||||
{
|
{
|
||||||
$obj = parent::staticGet(__CLASS__, $k, $v);
|
$obj = parent::staticGet(__CLASS__, $k, $v);
|
||||||
|
if (!empty($obj)) {
|
||||||
return Magicsig::fromString($obj->keypair);
|
return Magicsig::fromString($obj->keypair);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function table()
|
function table()
|
||||||
{
|
{
|
||||||
@ -84,6 +88,10 @@ class Magicsig extends Memcached_DataObject
|
|||||||
return array('user_id' => 'K');
|
return array('user_id' => 'K');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sequenceKey() {
|
||||||
|
return array(false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
function insert()
|
function insert()
|
||||||
{
|
{
|
||||||
$this->keypair = $this->toString();
|
$this->keypair = $this->toString();
|
||||||
@ -173,14 +181,15 @@ class Magicsig extends Memcached_DataObject
|
|||||||
switch ($this->alg) {
|
switch ($this->alg) {
|
||||||
|
|
||||||
case 'RSA-SHA256':
|
case 'RSA-SHA256':
|
||||||
return 'sha256';
|
return 'magicsig_sha256';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sign($bytes)
|
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()) {
|
if ($this->_rsa->isError()) {
|
||||||
$error = $this->_rsa->getLastError();
|
$error = $this->_rsa->getLastError();
|
||||||
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||||
@ -192,7 +201,8 @@ class Magicsig extends Memcached_DataObject
|
|||||||
|
|
||||||
public function verify($signed_bytes, $signature)
|
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()) {
|
if ($this->_rsa->isError()) {
|
||||||
$error = $this->keypair->getLastError();
|
$error = $this->keypair->getLastError();
|
||||||
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||||
@ -205,7 +215,7 @@ class Magicsig extends Memcached_DataObject
|
|||||||
|
|
||||||
// Define a sha256 function for hashing
|
// Define a sha256 function for hashing
|
||||||
// (Crypt_RSA should really be updated to use hash() )
|
// (Crypt_RSA should really be updated to use hash() )
|
||||||
function sha256($bytes)
|
function magicsig_sha256($bytes)
|
||||||
{
|
{
|
||||||
return hash('sha256', $bytes);
|
return hash('sha256', $bytes);
|
||||||
}
|
}
|
||||||
|
@ -1288,9 +1288,9 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
|
|
||||||
$disco = new Discovery();
|
$disco = new Discovery();
|
||||||
|
|
||||||
|
try {
|
||||||
$result = $disco->lookup($addr);
|
$result = $disco->lookup($addr);
|
||||||
|
} catch (Exception $e) {
|
||||||
if (!$result) {
|
|
||||||
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null);
|
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
public function unfold($env)
|
||||||
{
|
{
|
||||||
$dom = new DOMDocument();
|
$dom = new DOMDocument();
|
||||||
|
@ -48,11 +48,14 @@ class Salmon
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!common_config('ostatus', 'skip_signatures')) {
|
try {
|
||||||
$xml = $this->createMagicEnv($xml, $actor);
|
$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 {
|
try {
|
||||||
$client = new HTTPClient();
|
$client = new HTTPClient();
|
||||||
@ -72,7 +75,6 @@ class Salmon
|
|||||||
|
|
||||||
public function createMagicEnv($text, $actor)
|
public function createMagicEnv($text, $actor)
|
||||||
{
|
{
|
||||||
common_log(LOG_DEBUG, "Got actor as : ". print_r($actor, true));
|
|
||||||
$magic_env = new MagicEnvelope();
|
$magic_env = new MagicEnvelope();
|
||||||
|
|
||||||
$user = User::staticGet('id', $actor->id);
|
$user = User::staticGet('id', $actor->id);
|
||||||
@ -84,7 +86,6 @@ class Salmon
|
|||||||
$magickey = new Magicsig();
|
$magickey = new Magicsig();
|
||||||
$magickey->generate($user->id);
|
$magickey->generate($user->id);
|
||||||
}
|
}
|
||||||
common_log(LOG_DEBUG, "Salmon: Loaded key for ". $user->id);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Salmon invalid actor for signing");
|
throw new Exception("Salmon invalid actor for signing");
|
||||||
}
|
}
|
||||||
@ -92,18 +93,17 @@ class Salmon
|
|||||||
try {
|
try {
|
||||||
$env = $magic_env->signMessage($text, 'application/atom+xml', $magickey->toString());
|
$env = $magic_env->signMessage($text, 'application/atom+xml', $magickey->toString());
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
common_log(LOG_ERR, "Salmon signing failed: ". $e->getMessage());
|
|
||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
return $magic_env->unfold($env);
|
return $magic_env->toXML($env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function verifyMagicEnv($dom)
|
public function verifyMagicEnv($text)
|
||||||
{
|
{
|
||||||
$magic_env = new MagicEnvelope();
|
$magic_env = new MagicEnvelope();
|
||||||
|
|
||||||
$env = $magic_env->fromDom($dom);
|
$env = $magic_env->parse($text);
|
||||||
|
|
||||||
return $magic_env->verify($env);
|
return $magic_env->verify($env);
|
||||||
}
|
}
|
||||||
|
@ -41,29 +41,32 @@ class SalmonAction extends Action
|
|||||||
$this->clientError(_m('This method requires a POST.'));
|
$this->clientError(_m('This method requires a POST.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/atom+xml') {
|
if (empty($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/magic-envelope+xml') {
|
||||||
$this->clientError(_m('Salmon requires application/atom+xml'));
|
$this->clientError(_m('Salmon requires application/magic-envelope+xml'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$xml = file_get_contents('php://input');
|
$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 ||
|
if ($dom->documentElement->namespaceURI != Activity::ATOM ||
|
||||||
$dom->documentElement->localName != 'entry') {
|
$dom->documentElement->localName != 'entry') {
|
||||||
common_log(LOG_DEBUG, "Got invalid Salmon post: $xml");
|
common_log(LOG_DEBUG, "Got invalid Salmon post: $xml");
|
||||||
$this->clientError(_m('Salmon post must be an Atom entry.'));
|
$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);
|
$this->act = new Activity($dom->documentElement);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
249
plugins/RegisterThrottle/RegisterThrottlePlugin.php
Normal file
249
plugins/RegisterThrottle/RegisterThrottlePlugin.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
plugins/RegisterThrottle/Registration_ip.php
Normal file
124
plugins/RegisterThrottle/Registration_ip.php
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -799,8 +799,8 @@ list-style-type:none;
|
|||||||
display:inline;
|
display:inline;
|
||||||
}
|
}
|
||||||
.entity_tags li {
|
.entity_tags li {
|
||||||
float:left;
|
display:inline;
|
||||||
margin-right:11px;
|
margin-right:7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aside .section {
|
.aside .section {
|
||||||
|
Loading…
Reference in New Issue
Block a user