We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit 2a59453d authored by Evan Prodromou's avatar Evan Prodromou

Merge branch 'testing' into moveaccount

parents fb681990 a3c08fad
......@@ -66,6 +66,12 @@ class ApiGroupMembershipAction extends ApiPrivateAuthAction
parent::prepare($args);
$this->group = $this->getTargetGroup($this->arg('id'));
if (empty($this->group)) {
// TRANS: Client error displayed trying to show group membership on a non-existing group.
$this->clientError(_('Group not found.'), 404, $this->format);
return false;
}
$this->profiles = $this->getProfiles();
return true;
......@@ -84,12 +90,6 @@ class ApiGroupMembershipAction extends ApiPrivateAuthAction
{
parent::handle($args);
if (empty($this->group)) {
// TRANS: Client error displayed trying to show group membership on a non-existing group.
$this->clientError(_('Group not found.'), 404, $this->format);
return false;
}
// XXX: RSS and Atom
switch($this->format) {
......
......@@ -377,7 +377,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction
function supported($cmd)
{
static $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand',
'FavCommand', 'OnCommand', 'OffCommand');
'FavCommand', 'OnCommand', 'OffCommand', 'JoinCommand', 'LeaveCommand');
if (in_array(get_class($cmd), $cmdlist)) {
return true;
......
......@@ -200,8 +200,6 @@ class NewgroupAction extends Action
}
}
$mainpage = common_local_url('showgroup', array('nickname' => $nickname));
$cur = common_current_user();
// Checked in prepare() above
......@@ -215,7 +213,6 @@ class NewgroupAction extends Action
'location' => $location,
'aliases' => $aliases,
'userid' => $cur->id,
'mainpage' => $mainpage,
'local' => true));
common_redirect($group->homeUrl(), 303);
......
......@@ -100,8 +100,6 @@ class SubscribersAction extends GalleryAction
}
}
$subscribers->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'subscribers',
array('nickname' => $this->user->nickname));
......
......@@ -106,8 +106,6 @@ class SubscriptionsAction extends GalleryAction
}
}
$subscriptions->free();
$this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
$this->page, 'subscriptions',
array('nickname' => $this->user->nickname));
......
......@@ -487,6 +487,7 @@ class User_group extends Memcached_DataObject
}
// MAGICALLY put fields into current scope
// @fixme kill extract(); it makes debugging absurdly hard
extract($fields);
......@@ -498,6 +499,9 @@ class User_group extends Memcached_DataObject
// fill in later...
$uri = null;
}
if (empty($mainpage)) {
$mainpage = common_local_url('showgroup', array('nickname' => $nickname));
}
$group->nickname = $nickname;
$group->fullname = $fullname;
......
......@@ -182,6 +182,9 @@ class Activity
$actorEl = $this->_child($entry, self::ACTOR);
if (!empty($actorEl)) {
// Standalone <activity:actor> elements are a holdover from older
// versions of ActivityStreams. Newer feeds should have this data
// integrated straight into <atom:author>.
$this->actor = new ActivityObject($actorEl);
......@@ -196,19 +199,24 @@ class Activity
$this->actor->id = $authorObj->id;
}
}
} else if (!empty($feed) &&
$subjectEl = $this->_child($feed, self::SUBJECT)) {
$this->actor = new ActivityObject($subjectEl);
} else if ($authorEl = $this->_child($entry, self::AUTHOR, self::ATOM)) {
// An <atom:author> in the entry overrides any author info on
// the surrounding feed.
$this->actor = new ActivityObject($authorEl);
} else if (!empty($feed) && $authorEl = $this->_child($feed, self::AUTHOR,
self::ATOM)) {
// If there's no <atom:author> on the entry, it's safe to assume
// the containing feed's authorship info applies.
$this->actor = new ActivityObject($authorEl);
} else if (!empty($feed) &&
$subjectEl = $this->_child($feed, self::SUBJECT)) {
// Feed subject is used for things like groups.
// Should actually possibly not be interpreted as an actor...?
$this->actor = new ActivityObject($subjectEl);
}
$contextEl = $this->_child($entry, self::CONTEXT);
......
......@@ -1437,41 +1437,23 @@ class ApiAction extends Action
{
if (empty($id)) {
if (self::is_decimal($this->arg('id'))) {
return User_group::staticGet($this->arg('id'));
return User_group::staticGet('id', $this->arg('id'));
} else if ($this->arg('id')) {
$nickname = common_canonical_nickname($this->arg('id'));
$local = Local_group::staticGet('nickname', $nickname);
if (empty($local)) {
return null;
} else {
return User_group::staticGet('id', $local->id);
}
return User_group::getForNickname($this->arg('id'));
} else if ($this->arg('group_id')) {
// This is to ensure that a non-numeric user_id still
// overrides screen_name even if it doesn't get used
// This is to ensure that a non-numeric group_id still
// overrides group_name even if it doesn't get used
if (self::is_decimal($this->arg('group_id'))) {
return User_group::staticGet('id', $this->arg('group_id'));
}
} else if ($this->arg('group_name')) {
$nickname = common_canonical_nickname($this->arg('group_name'));
$local = Local_group::staticGet('nickname', $nickname);
if (empty($local)) {
return null;
} else {
return User_group::staticGet('id', $local->group_id);
}
return User_group::getForNickname($this->arg('group_name'));
}
} else if (self::is_decimal($id)) {
return User_group::staticGet($id);
return User_group::staticGet('id', $id);
} else {
$nickname = common_canonical_nickname($id);
$local = Local_group::staticGet('nickname', $nickname);
if (empty($local)) {
return null;
} else {
return User_group::staticGet('id', $local->group_id);
}
return User_group::getForNickname($id);
}
}
......
......@@ -76,6 +76,10 @@ class ArrayWrapper
function __call($name, $args)
{
$item =& $this->_items[$this->_i];
if (!is_object($item)) {
common_log(LOG_ERR, "Invalid entry " . var_export($item, true) . " at index $this->_i of $this->N; calling $name()");
throw new ServerException("Internal error: bad entry in array wrapper list.");
}
return call_user_func_array(array($item, $name), $args);
}
}
......@@ -95,7 +95,11 @@ function _have_config()
return StatusNet::haveConfig();
}
function __autoload($cls)
/**
* Wrapper for class autoloaders.
* This used to be the special function name __autoload(), but that causes bugs with PHPUnit 3.5+
*/
function autoload_sn($cls)
{
if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) {
require_once(INSTALLDIR.'/classes/' . $cls . '.php');
......@@ -111,6 +115,8 @@ function __autoload($cls)
}
}
spl_autoload_register('autoload_sn');
// XXX: how many of these could be auto-loaded on use?
// XXX: note that these files should not use config options
// at compile time since DB config options are not yet loaded.
......
......@@ -121,7 +121,7 @@ function mail_notify_from()
$domain = mail_domain();
$notifyfrom = '"'.common_config('site', 'name') .'" <noreply@'.$domain.'>';
$notifyfrom = '"'. str_replace('"', '\\"', common_config('site', 'name')) .'" <noreply@'.$domain.'>';
}
return $notifyfrom;
......
......@@ -16,6 +16,12 @@ jQuery(document).ready(function($){
contentSelector : "#notices_primary ol.notices",
itemSelector : "#notices_primary ol.notices li"
},function(){
SN.Init.Notices();
// Reply button and attachment magic need to be set up
// for each new notice.
// DO NOT run SN.Init.Notices() which will duplicate stuff.
$(this).find('.notice').each(function() {
SN.U.NoticeReplyTo($(this));
SN.U.NoticeWithAttachment($(this));
});
});
});
......@@ -21,7 +21,7 @@
// grab each selector option and see if any fail.
function areSelectorsValid(opts){
for (var key in opts){
if (key.indexOf && key.indexOf('Selector') && $(opts[key]).length === 0){
if (key.indexOf && (key.indexOf('Selector') != -1) && $(opts[key]).length === 0){
debug('Your ' + key + ' found no elements.');
return false;
}
......
......@@ -39,11 +39,41 @@ class Magicsig extends Memcached_DataObject
public $__table = 'magicsig';
/**
* Key to user.id/profile.id for the local user whose key we're storing.
*
* @var int
*/
public $user_id;
/**
* Flattened string representation of the key pair; callers should
* usually use $this->publicKey and $this->privateKey directly,
* which hold live Crypt_RSA key objects.
*
* @var string
*/
public $keypair;
/**
* Crypto algorithm used for this key; currently only RSA-SHA256 is supported.
*
* @var string
*/
public $alg;
/**
* Public RSA key; gets serialized in/out via $this->keypair string.
*
* @var Crypt_RSA
*/
public $publicKey;
/**
* PrivateRSA key; gets serialized in/out via $this->keypair string.
*
* @var Crypt_RSA
*/
public $privateKey;
public function __construct($alg = 'RSA-SHA256')
......@@ -51,6 +81,13 @@ class Magicsig extends Memcached_DataObject
$this->alg = $alg;
}
/**
* Fetch a Magicsig object from the cache or database on a field match.
*
* @param string $k
* @param mixed $v
* @return Magicsig
*/
public /*static*/ function staticGet($k, $v=null)
{
$obj = parent::staticGet(__CLASS__, $k, $v);
......@@ -103,6 +140,14 @@ class Magicsig extends Memcached_DataObject
return array(false, false, false);
}
/**
* Save this keypair into the database.
*
* Overloads default insert behavior to encode the live key objects
* as a flat string for storage.
*
* @return mixed
*/
function insert()
{
$this->keypair = $this->toString();
......@@ -110,6 +155,14 @@ class Magicsig extends Memcached_DataObject
return parent::insert();
}
/**
* Generate a new keypair for a local user and store in the database.
*
* Warning: this can be very slow on systems without the GMP module.
* Runtimes of 20-30 seconds are not unheard-of.
*
* @param int $user_id id of local user we're creating a key for
*/
public function generate($user_id)
{
$rsa = new Crypt_RSA();
......@@ -128,6 +181,12 @@ class Magicsig extends Memcached_DataObject
$this->insert();
}
/**
* Encode the keypair or public key as a string.
*
* @param boolean $full_pair set to false to leave out the private key.
* @return string
*/
public function toString($full_pair = true)
{
$mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes());
......@@ -140,6 +199,13 @@ class Magicsig extends Memcached_DataObject
return 'RSA.' . $mod . '.' . $exp . $private_exp;
}
/**
* Decode a string representation of an RSA public key or keypair
* as a Magicsig object which can be used to sign or verify.
*
* @param string $text
* @return Magicsig
*/
public static function fromString($text)
{
$magic_sig = new Magicsig();
......@@ -168,6 +234,14 @@ class Magicsig extends Memcached_DataObject
return $magic_sig;
}
/**
* Fill out $this->privateKey or $this->publicKey with a Crypt_RSA object
* representing the give key (as mod/exponent pair).
*
* @param string $mod base64-encoded
* @param string $exp base64-encoded exponent
* @param string $type one of 'public' or 'private'
*/
public function loadKey($mod, $exp, $type = 'public')
{
common_log(LOG_DEBUG, "Adding ".$type." key: (".$mod .', '. $exp .")");
......@@ -186,11 +260,22 @@ class Magicsig extends Memcached_DataObject
}
}
/**
* Returns the name of the crypto algorithm used for this key.
*
* @return string
*/
public function getName()
{
return $this->alg;
}
/**
* Returns the name of a hash function to use for signing with this key.
*
* @return string
* @fixme is this used? doesn't seem to be called by name.
*/
public function getHash()
{
switch ($this->alg) {
......@@ -200,24 +285,48 @@ class Magicsig extends Memcached_DataObject
}
}
/**
* Generate base64-encoded signature for the given byte string
* using our private key.
*
* @param string $bytes as raw byte string
* @return string base64-encoded signature
*/
public function sign($bytes)
{
$sig = $this->privateKey->sign($bytes);
return Magicsig::base64_url_encode($sig);
}
/**
*
* @param string $signed_bytes as raw byte string
* @param string $signature as base64
* @return boolean
*/
public function verify($signed_bytes, $signature)
{
$signature = Magicsig::base64_url_decode($signature);
return $this->publicKey->verify($signed_bytes, $signature);
}
/**
* URL-encoding-friendly base64 variant encoding.
*
* @param string $input
* @return string
*/
public static function base64_url_encode($input)
{
return strtr(base64_encode($input), '+/', '-_');
}
/**
* URL-encoding-friendly base64 variant decoding.
*
* @param string $input
* @return string
*/
public static function base64_url_decode($input)
{
return base64_decode(strtr($input, '-_', '+/'));
......
......@@ -331,6 +331,7 @@ class Ostatus_profile extends Memcached_DataObject
* an acceptable response from the remote site.
*
* @param mixed $entry XML string, Notice, or Activity
* @param Profile $actor
* @return boolean success
*/
public function notifyActivity($entry, $actor)
......@@ -1778,8 +1779,10 @@ class Ostatus_profile extends Memcached_DataObject
case 'mailto':
$rest = $match[2];
$oprofile = Ostatus_profile::ensureWebfinger($rest);
break;
default:
common_log("Unrecognized URI protocol for profile: $protocol ($uri)");
common_log(LOG_WARNING,
"Unrecognized URI protocol for profile: $protocol ($uri)");
break;
}
}
......
......@@ -80,21 +80,53 @@ class MagicEnvelope
throw new Exception(_m('Unable to locate signer public key.'));
}
/**
* The current MagicEnvelope spec as used in StatusNet 0.9.7 and later
* includes both the original data and some signing metadata fields as
* the input plaintext for the signature hash.
*
* @param array $env
* @return string
*/
public function signingText($env) {
return implode('.', array($env['data'], // this field is pre-base64'd
Magicsig::base64_url_encode($env['data_type']),
Magicsig::base64_url_encode($env['encoding']),
Magicsig::base64_url_encode($env['alg'])));
}
/**
*
* @param <type> $text
* @param <type> $mimetype
* @param <type> $keypair
* @return array: associative array of envelope properties
* @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around
*/
public function signMessage($text, $mimetype, $keypair)
{
$signature_alg = Magicsig::fromString($keypair);
$armored_text = Magicsig::base64_url_encode($text);
return array(
$env = array(
'data' => $armored_text,
'encoding' => MagicEnvelope::ENCODING,
'data_type' => $mimetype,
'sig' => $signature_alg->sign($armored_text),
'sig' => '',
'alg' => $signature_alg->getName()
);
$env['sig'] = $signature_alg->sign($this->signingText($env));
return $env;
}
/**
* Create an <me:env> XML representation of the envelope.
*
* @param array $env associative array with envelope data
* @return string representation of XML document
* @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around
*/
public function toXML($env) {
$xs = new XMLStringer();
$xs->startXML();
......@@ -110,6 +142,16 @@ class MagicEnvelope
return $string;
}
/**
* Extract the contained XML payload, and insert a copy of the envelope
* signature data as an <me:provenance> section.
*
* @param array $env associative array with envelope data
* @return string representation of modified XML document
*
* @fixme in case of XML parsing errors, this will spew to the error log or output
* @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around
*/
public function unfold($env)
{
$dom = new DOMDocument();
......@@ -136,6 +178,14 @@ class MagicEnvelope
return $dom->saveXML();
}
/**
* Find the author URI referenced in the given Atom entry.
*
* @param string $text string containing Atom entry XML
* @return mixed URI string or false if XML parsing fails, or null if no author URI can be found
*
* @fixme XML parsing failures will spew to error logs/output
*/
public function getAuthor($text) {
$doc = new DOMDocument();
if (!$doc->loadXML($text)) {
......@@ -153,11 +203,30 @@ class MagicEnvelope
}
}
/**
* Check if the author in the Atom entry fragment claims to match
* the given identifier URI.
*
* @param string $text string containing Atom entry XML
* @param string $signer_uri
* @return boolean
*/
public function checkAuthor($text, $signer_uri)
{
return ($this->getAuthor($text) == $signer_uri);
}
/**
* Attempt to verify cryptographic signing for parsed envelope data.
* Requires network access to retrieve public key referenced by the envelope signer.
*
* Details of failure conditions are dumped to output log and not exposed to caller.
*
* @param array $env array representation of magic envelope data, as returned from MagicEnvelope::parse()
* @return boolean
*
* @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around
*/
public function verify($env)
{
if ($env['alg'] != 'RSA-SHA256') {
......@@ -187,15 +256,35 @@ class MagicEnvelope
return false;
}
return $verifier->verify($env['data'], $env['sig']);
return $verifier->verify($this->signingText($env), $env['sig']);
}
/**
* Extract envelope data from an XML document containing an <me:env> or <me:provenance> element.
*
* @param string XML source
* @return mixed associative array of envelope data, or false on unrecognized input
*
* @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around
* @fixme will spew errors to logs or output in case of XML parse errors
* @fixme may give fatal errors if some elements are missing or invalid XML
* @fixme calling DOMDocument::loadXML statically triggers warnings in strict mode
*/
public function parse($text)
{
$dom = DOMDocument::loadXML($text);
return $this->fromDom($dom);
}
/**
* Extract envelope data from an XML document containing an <me:env> or <me:provenance> element.
*
* @param DOMDocument $dom
* @return mixed associative array of envelope data, or false on unrecognized input
*
* @fixme it might be easier to work with storing envelope data these in the object instead of passing arrays around
* @fixme may give fatal errors if some elements are missing
*/
public function fromDom($dom)
{
$env_element = $dom->getElementsByTagNameNS(MagicEnvelope::NS, 'env')->item(0);
......@@ -218,3 +307,24 @@ class MagicEnvelope
);
}
}
/**
* Variant of MagicEnvelope using the earlier signature form listed in the MagicEnvelope
* spec in early 2010; this was used in StatusNet up through 0.9.6, so for backwards compatiblity
* we still need to accept and sometimes send this format.
*/
class MagicEnvelopeCompat extends MagicEnvelope {
/**
* StatusNet through 0.9.6 used an earlier version of the MagicEnvelope spec
* which used only the input data, without the additional fields, as the plaintext
* for signing.
*
* @param array $env
* @return string
*/
public function signingText($env) {
return $env['data'];
}
}
......@@ -38,10 +38,12 @@ class Salmon