2011-01-25 20:57:47 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2012-01-12 01:07:36 +01:00
|
|
|
* Copyright 2012 Facebook, Inc.
|
2011-01-25 20:57:47 +01:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2012-01-12 01:07:36 +01:00
|
|
|
/**
|
|
|
|
* @task uri URI Validation
|
|
|
|
*/
|
2011-01-31 20:55:26 +01:00
|
|
|
final class PhabricatorEnv {
|
|
|
|
private static $env;
|
|
|
|
|
2012-03-15 20:15:23 +01:00
|
|
|
private static $requiredClasses = array(
|
|
|
|
'metamta.differential.reply-handler' => 'PhabricatorMailReplyHandler',
|
|
|
|
);
|
|
|
|
|
2011-01-31 20:55:26 +01:00
|
|
|
public static function setEnvConfig(array $config) {
|
|
|
|
self::$env = $config;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getEnvConfig($key, $default = null) {
|
|
|
|
return idx(self::$env, $key, $default);
|
2011-01-25 20:57:47 +01:00
|
|
|
}
|
2011-01-31 20:55:26 +01:00
|
|
|
|
2012-03-15 20:15:23 +01:00
|
|
|
public static function newObjectFromConfig($key, $args = array()) {
|
|
|
|
$class = self::getEnvConfig($key);
|
|
|
|
$object = newv($class, $args);
|
|
|
|
$instanceof = idx(self::$requiredClasses, $key);
|
|
|
|
if (!($object instanceof $instanceof)) {
|
|
|
|
throw new Exception("Config setting '$key' must be an instance of ".
|
|
|
|
"'$instanceof', is '".get_class($object)."'.");
|
|
|
|
}
|
|
|
|
return $object;
|
|
|
|
}
|
|
|
|
|
2011-04-30 07:20:52 +02:00
|
|
|
public static function envConfigExists($key) {
|
|
|
|
return array_key_exists($key, self::$env);
|
|
|
|
}
|
|
|
|
|
2011-01-31 20:55:26 +01:00
|
|
|
public static function getURI($path) {
|
|
|
|
return rtrim(self::getEnvConfig('phabricator.base-uri'), '/').$path;
|
|
|
|
}
|
2011-02-27 05:57:21 +01:00
|
|
|
|
2011-04-04 23:22:16 +02:00
|
|
|
public static function getProductionURI($path) {
|
2012-02-16 02:06:36 +01:00
|
|
|
// If we're passed a URI which already has a domain, simply return it
|
|
|
|
// unmodified. In particular, files may have URIs which point to a CDN
|
|
|
|
// domain.
|
|
|
|
$uri = new PhutilURI($path);
|
|
|
|
if ($uri->getDomain()) {
|
|
|
|
return $path;
|
2011-04-04 23:22:16 +02:00
|
|
|
}
|
2012-02-16 02:06:36 +01:00
|
|
|
|
|
|
|
$production_domain = self::getEnvConfig('phabricator.production-uri');
|
|
|
|
if (!$production_domain) {
|
|
|
|
$production_domain = self::getEnvConfig('phabricator.base-uri');
|
|
|
|
}
|
|
|
|
return rtrim($production_domain, '/').$path;
|
2011-04-04 23:22:16 +02:00
|
|
|
}
|
|
|
|
|
Move ALL files to serve from the alternate file domain, not just files without
"Content-Disposition: attachment"
Summary:
We currently serve some files off the primary domain (with "Content-Disposition:
attachment" + a CSRF check) and some files off the alternate domain (without
either).
This is not sufficient, because some UAs (like the iPad) ignore
"Content-Disposition: attachment". So there's an attack that goes like this:
- Alice uploads xss.html
- Alice says to Bob "hey download this file on your iPad"
- Bob clicks "Download" on Phabricator on his iPad, gets XSS'd.
NOTE: This removes the CSRF check for downloading files. The check is nice to
have but only raises the barrier to entry slightly. Between iPad / sniffing /
flash bytecode attacks, single-domain installs are simply insecure. We could
restore the check at some point in conjunction with a derived authentication
cookie (i.e., a mini-session-token which is only useful for downloading files),
but that's a lot of complexity to drop all at once.
(Because files are now authenticated only by knowing the PHID and secret key,
this also fixes the "no profile pictures in public feed while logged out"
issue.)
Test Plan: Viewed, info'd, and downloaded files
Reviewers: btrahan, arice, alok
Reviewed By: arice
CC: aran, epriestley
Maniphest Tasks: T843
Differential Revision: https://secure.phabricator.com/D1608
2012-02-14 23:52:27 +01:00
|
|
|
public static function getCDNURI($path) {
|
|
|
|
$alt = self::getEnvConfig('security.alternate-file-domain');
|
|
|
|
if (!$alt) {
|
|
|
|
$alt = self::getEnvConfig('phabricator.base-uri');
|
|
|
|
}
|
|
|
|
$uri = new PhutilURI($alt);
|
|
|
|
$uri->setPath($path);
|
|
|
|
return (string)$uri;
|
|
|
|
}
|
|
|
|
|
2011-02-12 01:48:43 +01:00
|
|
|
public static function getAllConfigKeys() {
|
|
|
|
return self::$env;
|
|
|
|
}
|
2011-01-31 20:55:26 +01:00
|
|
|
|
2011-05-19 22:40:12 +02:00
|
|
|
public static function getDoclink($resource) {
|
|
|
|
return 'http://phabricator.com/docs/phabricator/'.$resource;
|
|
|
|
}
|
|
|
|
|
2012-01-12 01:07:36 +01:00
|
|
|
|
|
|
|
/* -( URI Validation )----------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detect if a URI satisfies either @{method:isValidLocalWebResource} or
|
|
|
|
* @{method:isValidRemoteWebResource}, i.e. is a page on this server or the
|
|
|
|
* URI of some other resource which has a valid protocol. This rejects
|
|
|
|
* garbage URIs and URIs with protocols which do not appear in the
|
|
|
|
* ##uri.allowed-protocols## configuration, notably 'javascript:' URIs.
|
|
|
|
*
|
|
|
|
* NOTE: This method is generally intended to reject URIs which it may be
|
|
|
|
* unsafe to put in an "href" link attribute.
|
|
|
|
*
|
|
|
|
* @param string URI to test.
|
|
|
|
* @return bool True if the URI identifies a web resource.
|
|
|
|
* @task uri
|
|
|
|
*/
|
|
|
|
public static function isValidWebResource($uri) {
|
|
|
|
return self::isValidLocalWebResource($uri) ||
|
|
|
|
self::isValidRemoteWebResource($uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detect if a URI identifies some page on this server.
|
|
|
|
*
|
|
|
|
* NOTE: This method is generally intended to reject URIs which it may be
|
|
|
|
* unsafe to issue a "Location:" redirect to.
|
|
|
|
*
|
|
|
|
* @param string URI to test.
|
|
|
|
* @return bool True if the URI identifies a local page.
|
|
|
|
* @task uri
|
|
|
|
*/
|
|
|
|
public static function isValidLocalWebResource($uri) {
|
|
|
|
$uri = (string)$uri;
|
|
|
|
|
|
|
|
if (!strlen($uri)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preg_match('/\s/', $uri)) {
|
|
|
|
// PHP hasn't been vulnerable to header injection attacks for a bunch of
|
|
|
|
// years, but we can safely reject these anyway since they're never valid.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Valid URIs must begin with '/', followed by the end of the string or some
|
|
|
|
// other non-'/' character. This rejects protocol-relative URIs like
|
|
|
|
// "//evil.com/evil_stuff/".
|
|
|
|
return (bool)preg_match('@^/([^/]|$)@', $uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detect if a URI identifies some valid remote resource.
|
|
|
|
*
|
|
|
|
* @param string URI to test.
|
|
|
|
* @return bool True if a URI idenfies a remote resource with an allowed
|
|
|
|
* protocol.
|
|
|
|
* @task uri
|
|
|
|
*/
|
|
|
|
public static function isValidRemoteWebResource($uri) {
|
|
|
|
$uri = (string)$uri;
|
|
|
|
|
|
|
|
$proto = id(new PhutilURI($uri))->getProtocol();
|
|
|
|
if (!$proto) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$allowed = self::getEnvConfig('uri.allowed-protocols');
|
|
|
|
if (empty($allowed[$proto])) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-01-25 20:57:47 +01:00
|
|
|
}
|