diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index d76ae060f2..3a83fd27fc 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -196,12 +196,6 @@ class ApiOauthAuthorizeAction extends Action ) ); - // XXX: Make sure we have a oauth_token_association table. The table - // is now in the main schema, but because it is being added with - // a point release, it's unlikely to be there. This code can be - // removed as of 1.0. - $this->ensureOauthTokenAssociationTable(); - $tokenAssoc = new Oauth_token_association(); $tokenAssoc->profile_id = $user->id; @@ -295,30 +289,6 @@ class ApiOauthAuthorizeAction extends Action } } - // XXX Remove this function when we hit 1.0 - function ensureOauthTokenAssociationTable() - { - $schema = Schema::get(); - - $reqTokenCols = array( - new ColumnDef('profile_id', 'integer', null, true, 'PRI'), - new ColumnDef('application_id', 'integer', null, true, 'PRI'), - new ColumnDef('token', 'varchar', 255, true, 'PRI'), - new ColumnDef('created', 'datetime', null, false), - new ColumnDef( - 'modified', - 'timestamp', - null, - false, - null, - 'CURRENT_TIMESTAMP', - 'on update CURRENT_TIMESTAMP' - ) - ); - - $schema->ensureTable('oauth_token_association', $reqTokenCols); - } - /** * Show body - override to add a special CSS class for the authorize * page's "desktop mode" (minimal display) diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index 279265a30e..33248203bc 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -289,7 +289,13 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction { $notices = array(); - $stream = new InboxNoticeStream($this->user); + $profile = null; + + if (isset($this->auth_user)) { + $profile = $this->auth_user->getProfile(); + } + + $stream = new InboxNoticeStream($this->user, $profile); $notice = $stream->getNotices(($this->page-1) * $this->count, $this->count, diff --git a/classes/Oauth_token_association.php b/classes/Oauth_token_association.php index 66be22b5d3..06c9fee1c3 100644 --- a/classes/Oauth_token_association.php +++ b/classes/Oauth_token_association.php @@ -39,4 +39,23 @@ class Oauth_token_association extends Memcached_DataObject return empty($result) ? null : $oau; } + + public static function schemaDef() + { + return array( + 'description' => 'Associate an application ID and profile ID with an OAuth token', + 'fields' => array( + 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'associated user'), + 'application_id' => array('type' => 'int', 'not null' => true, 'description' => 'the application'), + 'token' => array('type' => 'varchar', 'length' => '255', 'not null' => true, 'description' => 'token used for this association'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), + ), + 'primary key' => array('profile_id', 'application_id', 'token'), + 'foreign keys' => array( + 'oauth_token_association_profile_fkey' => array('profile_id', array('profile' => 'id')), + 'oauth_token_association_application_fkey' => array('application_id', array('application' => 'id')), + ) + ); + } } diff --git a/db/core.php b/db/core.php index 626672bf5f..fe9f4735d9 100644 --- a/db/core.php +++ b/db/core.php @@ -1110,3 +1110,5 @@ $schema['schema_version'] = array( $schema['group_join_queue'] = Group_join_queue::schemaDef(); $schema['subscription_queue'] = Subscription_queue::schemaDef(); + +$schema['oauth_token_association'] = Oauth_token_association::schemaDef(); diff --git a/lib/action.php b/lib/action.php index 3492873c59..81aa8eb12c 100644 --- a/lib/action.php +++ b/lib/action.php @@ -334,9 +334,12 @@ class Action extends HTMLOutputter // lawsuit $this->inlineScript('var _peopletagAC = "' . common_local_url('peopletagautocomplete') . '";'); $this->showScriptMessages(); - // Frame-busting code to avoid clickjacking attacks. + // Anti-framing code to avoid clickjacking attacks in older browsers. + // This will show a blank page if the page is being framed, which is + // consistent with the behavior of the 'X-Frame-Options: SAMEORIGIN' + // header, which prevents framing in newer browser. if (common_config('javascript', 'bustframes')) { - $this->inlineScript('if (window.top !== window.self) { window.top.location.href = window.self.location.href; }'); + $this->inlineScript('if (window.top !== window.self) { document.write = ""; window.top.location = window.self.location; setTimeout(function () { document.body.innerHTML = ""; }, 1); window.self.onload = function () { document.body.innerHTML = ""; }; }'); } Event::handle('EndShowStatusNetScripts', array($this)); Event::handle('EndShowLaconicaScripts', array($this)); diff --git a/lib/apiauth.php b/lib/apiauth.php index 42d32dd624..1061e6b68c 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -204,7 +204,12 @@ class ApiAuthAction extends ApiAction } } $this->auth_user = $user; - Event::handle('EndSetApiUser', array($user)); + // FIXME: setting the value returned by common_current_user() + // There should probably be a better method for this. common_set_user() + // does lots of session stuff. + global $_cur; + $_cur = $this->auth_user; + Event::handle('EndSetApiUser', array($user)); } $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . diff --git a/lib/framework.php b/lib/framework.php index b0b50fc961..9adaf7f838 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -20,7 +20,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } define('STATUSNET_BASE_VERSION', '1.0.0'); -define('STATUSNET_LIFECYCLE', 'alpha2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' +define('STATUSNET_LIFECYCLE', 'alpha5'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . STATUSNET_LIFECYCLE); define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 3b3c1913a1..e358b2be5d 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -108,6 +108,13 @@ class HTMLOutputter extends XMLOutputter header('Content-Type: '.$type); + // Output anti-framing headers to prevent clickjacking (respected by newer + // browsers). + if (common_config('javascript', 'bustframes')) { + header('X-XSS-Protection 1; mode=block'); // detect XSS Reflection attacks + header('X-Frame-Options: SAMEORIGIN'); // no rendering if origin mismatch + } + $this->extraHeaders(); if (preg_match("/.*\/.*xml/", $type)) { // Required for XML documents diff --git a/locale/eo/LC_MESSAGES/statusnet.po b/locale/eo/LC_MESSAGES/statusnet.po index f7eb731fac..aa50dc1d4a 100644 --- a/locale/eo/LC_MESSAGES/statusnet.po +++ b/locale/eo/LC_MESSAGES/statusnet.po @@ -8922,7 +8922,7 @@ msgstr "" "\n" "%4$s\n" "\n" -"Ne respondu al ĉi tiu retpoŝtadreso; respondo ne atingos lin.\n" +"Ne respondu al ĉi tiu retpoŝtadreso; respondo ne atingos lin." #. TRANS: Subject for favorite notification e-mail. #. TRANS: %1$s is the adding user's long name, %2$s is the adding user's nickname. diff --git a/plugins/Directory/locale/mk/LC_MESSAGES/Directory.po b/plugins/Directory/locale/mk/LC_MESSAGES/Directory.po index 3e493ba92c..02b7269923 100644 --- a/plugins/Directory/locale/mk/LC_MESSAGES/Directory.po +++ b/plugins/Directory/locale/mk/LC_MESSAGES/Directory.po @@ -140,6 +140,18 @@ msgstr "Пребарај групи" msgid "No groups starting with %s." msgstr "Нема групи што почнуваат на %s." +#. TRANS: Help text for searching group directory. +msgid "" +"* Make sure all words are spelled correctly.\n" +"* Try different keywords.\n" +"* Try more general keywords.\n" +"* Try fewer keywords." +msgstr "" +"* Проверете дали сите зборови се напишани како што треба.\n" +"* Обидете се со други клучни зборови.\n" +"* Обидете се со поопшти клучни зборови.\n" +"* Обидете се помалку клучни зборови." + #. TRANS: Menu item text for user directory. msgctxt "MENU" msgid "Directory" diff --git a/plugins/Directory/locale/tl/LC_MESSAGES/Directory.po b/plugins/Directory/locale/tl/LC_MESSAGES/Directory.po index 807bb044c0..05990ea3b5 100644 --- a/plugins/Directory/locale/tl/LC_MESSAGES/Directory.po +++ b/plugins/Directory/locale/tl/LC_MESSAGES/Directory.po @@ -141,6 +141,18 @@ msgstr "Hanapin sa mga pangkat" msgid "No groups starting with %s." msgstr "Walang mga pangkat na nagsisimula sa %s." +#. TRANS: Help text for searching group directory. +msgid "" +"* Make sure all words are spelled correctly.\n" +"* Try different keywords.\n" +"* Try more general keywords.\n" +"* Try fewer keywords." +msgstr "" +"* Tiyakin na tama ang pagbabanghay ng lahat ng mga salita.\n" +"* Sumubok ng ibang mga susing-salita.\n" +"* Sumubok ng mas pangkalahatang mga susing-salita.\n" +"* Sumubok ng mas kakaunting mga susing-salita." + #. TRANS: Menu item text for user directory. msgctxt "MENU" msgid "Directory" diff --git a/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php b/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php index b2042abe94..b2e988b5e1 100644 --- a/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php +++ b/plugins/DomainStatusNetwork/domainstatusnetworkinstaller.php @@ -120,6 +120,14 @@ class DomainStatusNetworkInstaller extends Installer $this->sitehost = $config['DBHOST']; $this->sitedb = $config['SITEDB']; + $tagstr = $config['TAGS']; + + if (!empty($tagstr)) { + $this->tags = preg_split('/[\s,]+/', $tagstr); + } else { + $this->tags = array(); + } + // Explicitly empty $this->adminNick = null; @@ -185,7 +193,15 @@ class DomainStatusNetworkInstaller extends Installer throw new ServerException("Created {$this->nickname} status_network and could not find it again."); } - $sn->setTags(array('domain='.$this->domain)); + // Set default tags + + $tags = $this->tags; + + // Add domain tag + + $tags[] = 'domain='.$this->domain; + + $sn->setTags($tags); $this->sn = $sn; } @@ -198,6 +214,21 @@ class DomainStatusNetworkInstaller extends Installer StatusNet::switchSite($this->nickname); + // We need to initialize the schema_version stuff to make later setup easier + + $schema = array(); + require INSTALLDIR.'/db/core.php'; + $tableDefs = $schema; + + $schema = Schema::get(); + $schemaUpdater = new SchemaUpdater($schema); + + foreach ($tableDefs as $table => $def) { + $schemaUpdater->register($table, $def); + } + + $schemaUpdater->checkSchema(); + Event::handle('CheckSchema'); } diff --git a/scripts/setup.cfg.sample b/scripts/setup.cfg.sample index 049cd3e859..3296bbe043 100644 --- a/scripts/setup.cfg.sample +++ b/scripts/setup.cfg.sample @@ -18,3 +18,4 @@ export MAILSUBJECT="Your new StatusNet site" export POSTINSTALL=/etc/statusnet/morestuff.sh export WEBUSER=www-data export WEBGROUP=www-data +export TAGS=tag1,tag2,tag3 diff --git a/scripts/update_ostatus_profiles.php b/scripts/update_ostatus_profiles.php new file mode 100644 index 0000000000..0d56423f58 --- /dev/null +++ b/scripts/update_ostatus_profiles.php @@ -0,0 +1,327 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'u:a'; +$longoptions = array('uri=', 'all'); + +$helptext = <<isGroup()) { + $self = $this->localGroup(); + } else { + $self = $this->localProfile(); + } + if (!$self) { + throw new ServerException(sprintf( + // TRANS: Server exception. %s is a URI. + _m('Tried to update avatar for unsaved remote profile %s.'), + $this->uri)); + } + + // @fixme this should be better encapsulated + // ripped from oauthstore.php (for old OMB client) + $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); + try { + if (!copy($url, $temp_filename)) { + // TRANS: Server exception. %s is a URL. + throw new ServerException(sprintf(_m('Unable to fetch avatar from %s.'), $url)); + } + + if ($this->isGroup()) { + $id = $this->group_id; + } else { + $id = $this->profile_id; + } + // @fixme should we be using different ids? + $imagefile = new ImageFile($id, $temp_filename); + $filename = Avatar::filename($id, + image_type_to_extension($imagefile->type), + null, + common_timestamp()); + rename($temp_filename, Avatar::path($filename)); + } catch (Exception $e) { + unlink($temp_filename); + throw $e; + } + // @fixme hardcoded chmod is lame, but seems to be necessary to + // keep from accidentally saving images from command-line (queues) + // that can't be read from web server, which causes hard-to-notice + // problems later on: + // + // http://status.net/open-source/issues/2663 + chmod(Avatar::path($filename), 0644); + + $self->setOriginal($filename); + + $orig = clone($this); + $this->avatar = $url; + $this->update($orig); + } + + /** + * Look up and if necessary create an Ostatus_profile for the remote entity + * with the given profile page URL. This should never return null -- you + * will either get an object or an exception will be thrown. + * + * @param string $profile_url + * @return Ostatus_profile + * @throws Exception on various error conditions + * @throws OStatusShadowException if this reference would obscure a local user/group + */ + public static function updateProfileURL($profile_url, $hints=array()) + { + $oprofile = null; + + $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()) { + // TRANS: Exception. %s is a profile URL. + throw new Exception(sprintf(_m('Could not reach profile page %s.'),$profile_url)); + } + + // Check if we have a non-canonical URL + + $finalUrl = $response->getUrl(); + + if ($finalUrl != $profile_url) { + $hints['profileurl'] = $finalUrl; + } + + // 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(); + + $feedurl = $discover->discoverFromHTML($finalUrl, $body); + + if (!empty($feedurl)) { + $hints['feedurl'] = $feedurl; + return self::ensureFeedURL($feedurl, $hints); + } + + // TRANS: Exception. %s is a URL. + throw new Exception(sprintf(_m('Could not find a feed URL for profile page %s.'),$finalUrl)); + } + + /** + * Look up, and if necessary create, an Ostatus_profile for the remote + * entity with the given webfinger address. + * This should never return null -- you will either get an object or + * an exception will be thrown. + * + * @param string $addr webfinger address + * @return Ostatus_profile + * @throws Exception on error conditions + * @throws OStatusShadowException if this reference would obscure a local user/group + */ + public static function updateWebfinger($addr) + { + $disco = new Discovery(); + + try { + $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? + self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null); + // TRANS: Exception. + throw new Exception(_m('Not a valid webfinger address.')); + } + + $hints = array('webfinger' => $addr); + + $dhints = DiscoveryHints::fromXRD($xrd); + + $hints = array_merge($hints, $dhints); + + // If there's an Hcard, let's grab its info + 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 (array_key_exists('feedurl', $hints)) { + try { + 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) { + common_log(LOG_WARNING, "Failed creating profile from feed URL '$feedUrl': " . $e->getMessage()); + // keep looking + } + } + + // If we got a profile page, try that! + if (array_key_exists('profileurl', $hints)) { + try { + common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl"); + $oprofile = self::ensureProfileURL($hints['profileurl'], $hints); + self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri); + return $oprofile; + } catch (OStatusShadowException $e) { + // We've ended up with a remote reference to a local user or group. + // @fixme ideally we should be able to say who it was so we can + // go back and refer to it the regular way + throw $e; + } catch (Exception $e) { + common_log(LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage()); + // keep looking + // + // @fixme this means an error discovering from profile page + // may give us a corrupt entry using the webfinger URI, which + // will obscure the correct page-keyed profile later on. + } + } + throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'),$addr)); + } +} + +function pullOstatusProfile($uri) { + + $oprofile = null; + + if (Validate::email($uri)) { + $oprofile = LooseOstatusProfile::updateWebfinger($uri); + } else if (Validate::uri($uri)) { + $oprofile = LooseOstatusProfile::updateProfileURL($uri); + } else { + print "Sorry, we could not reach the address: $uri\n"; + return false; + } + + return $oprofile; +} + +$quiet = have_option('q', 'quiet'); + +$lop = new LooseOstatusProfile(); + +if (have_option('u', 'uri')) { + $lop->uri = get_option_value('u', 'uri'); +} else if (!have_option('a', 'all')) { + show_help(); + exit(1); +} + +$cnt = $lop->find(); + +if (!empty($cnt)) { + if (!$quiet) { echo "Found {$cnt} OStatus profiles:\n"; } +} else { + if (have_option('u', 'uri')) { + if (!$quiet) { echo "Couldn't find an existing OStatus profile with that URI.\n"; } + } else { + if (!$quiet) { echo "Couldn't find any existing OStatus profiles.\n"; } + } + exit(0); +} + +while($lop->fetch()) { + if (!$quiet) { echo "Updating OStatus profile '{$lop->uri}' ... "; } + try { + $oprofile = pullOstatusProfile($lop->uri); + + if (!empty($oprofile)) { + $orig = clone($lop); + $lop->avatar = $oprofile->avatar; + $lop->update($orig); + $lop->updateAvatar($oprofile->avatar); + if (!$quiet) { print "Done.\n"; } + } + } catch (Exception $e) { + if (!$quiet) { print $e->getMessage() . "\n"; } + common_log(LOG_WARN, $e->getMessage(), __FILE__); + // continue on error + } +} + +if (!$quiet) { echo "OK.\n"; }