8313a3fcb3
While BREACH requires the following three factors to be effectively exploitable we should add another mitigation: 1. Application must support HTTP compression 2. Response most reflect user-controlled input 3. Response should contain sensitive data Especially part 2 is with ownCloud not really given since user-input is usually only echoed if a CSRF token has been passed. To reduce the risk even further it is however sensible to encrypt the CSRF token with a shared secret. Since this will change on every request an attack such as BREACH is not feasible anymore against the CSRF token at least.
1341 lines
32 KiB
PHP
1341 lines
32 KiB
PHP
<?php
|
|
/**
|
|
* @copyright 2013 Thomas Tanghus (thomas@tanghus.net)
|
|
* @copyright 2015 Lukas Reschke lukas@owncloud.com
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
* later.
|
|
* See the COPYING-README file.
|
|
*/
|
|
|
|
namespace OC\AppFramework\Http;
|
|
|
|
use OC\Security\Crypto;
|
|
use OCP\Security\ISecureRandom;
|
|
use OCP\IConfig;
|
|
|
|
/**
|
|
* Class RequestTest
|
|
*
|
|
* @package OC\AppFramework\Http
|
|
*/
|
|
class RequestTest extends \Test\TestCase {
|
|
/** @var string */
|
|
protected $stream = 'fakeinput://data';
|
|
/** @var ISecureRandom */
|
|
protected $secureRandom;
|
|
/** @var IConfig */
|
|
protected $config;
|
|
|
|
protected function setUp() {
|
|
parent::setUp();
|
|
|
|
require_once __DIR__ . '/requeststream.php';
|
|
if (in_array('fakeinput', stream_get_wrappers())) {
|
|
stream_wrapper_unregister('fakeinput');
|
|
}
|
|
stream_wrapper_register('fakeinput', 'RequestStream');
|
|
|
|
$this->secureRandom = $this->getMockBuilder('\OCP\Security\ISecureRandom')->getMock();
|
|
$this->config = $this->getMockBuilder('\OCP\IConfig')->getMock();
|
|
}
|
|
|
|
protected function tearDown() {
|
|
stream_wrapper_unregister('fakeinput');
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function testRequestAccessors() {
|
|
$vars = array(
|
|
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
|
|
'method' => 'GET',
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
// Countable
|
|
$this->assertEquals(2, count($request));
|
|
// Array access
|
|
$this->assertEquals('Joey', $request['nickname']);
|
|
// "Magic" accessors
|
|
$this->assertEquals('Joey', $request->{'nickname'});
|
|
$this->assertTrue(isset($request['nickname']));
|
|
$this->assertTrue(isset($request->{'nickname'}));
|
|
$this->assertEquals(false, isset($request->{'flickname'}));
|
|
// Only testing 'get', but same approach for post, files etc.
|
|
$this->assertEquals('Joey', $request->get['nickname']);
|
|
// Always returns null if variable not set.
|
|
$this->assertEquals(null, $request->{'flickname'});
|
|
|
|
}
|
|
|
|
// urlParams has precedence over POST which has precedence over GET
|
|
public function testPrecedence() {
|
|
$vars = array(
|
|
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
|
|
'post' => array('name' => 'Jane Doe', 'nickname' => 'Janey'),
|
|
'urlParams' => array('user' => 'jw', 'name' => 'Johnny Weissmüller'),
|
|
'method' => 'GET'
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals(3, count($request));
|
|
$this->assertEquals('Janey', $request->{'nickname'});
|
|
$this->assertEquals('Johnny Weissmüller', $request->{'name'});
|
|
}
|
|
|
|
|
|
/**
|
|
* @expectedException \RuntimeException
|
|
*/
|
|
public function testImmutableArrayAccess() {
|
|
$vars = array(
|
|
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
|
|
'method' => 'GET'
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$request['nickname'] = 'Janey';
|
|
}
|
|
|
|
/**
|
|
* @expectedException \RuntimeException
|
|
*/
|
|
public function testImmutableMagicAccess() {
|
|
$vars = array(
|
|
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
|
|
'method' => 'GET'
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$request->{'nickname'} = 'Janey';
|
|
}
|
|
|
|
/**
|
|
* @expectedException \LogicException
|
|
*/
|
|
public function testGetTheMethodRight() {
|
|
$vars = array(
|
|
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
|
|
'method' => 'GET',
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$request->post;
|
|
}
|
|
|
|
public function testTheMethodIsRight() {
|
|
$vars = array(
|
|
'get' => array('name' => 'John Q. Public', 'nickname' => 'Joey'),
|
|
'method' => 'GET',
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('GET', $request->method);
|
|
$result = $request->get;
|
|
$this->assertEquals('John Q. Public', $result['name']);
|
|
$this->assertEquals('Joey', $result['nickname']);
|
|
}
|
|
|
|
public function testJsonPost() {
|
|
global $data;
|
|
$data = '{"name": "John Q. Public", "nickname": "Joey"}';
|
|
$vars = array(
|
|
'method' => 'POST',
|
|
'server' => array('CONTENT_TYPE' => 'application/json; utf-8')
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('POST', $request->method);
|
|
$result = $request->post;
|
|
$this->assertEquals('John Q. Public', $result['name']);
|
|
$this->assertEquals('Joey', $result['nickname']);
|
|
$this->assertEquals('Joey', $request->params['nickname']);
|
|
$this->assertEquals('Joey', $request['nickname']);
|
|
}
|
|
|
|
public function testPatch() {
|
|
global $data;
|
|
$data = http_build_query(array('name' => 'John Q. Public', 'nickname' => 'Joey'), '', '&');
|
|
|
|
$vars = array(
|
|
'method' => 'PATCH',
|
|
'server' => array('CONTENT_TYPE' => 'application/x-www-form-urlencoded'),
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('PATCH', $request->method);
|
|
$result = $request->patch;
|
|
|
|
$this->assertEquals('John Q. Public', $result['name']);
|
|
$this->assertEquals('Joey', $result['nickname']);
|
|
}
|
|
|
|
public function testJsonPatchAndPut() {
|
|
global $data;
|
|
|
|
// PUT content
|
|
$data = '{"name": "John Q. Public", "nickname": "Joey"}';
|
|
$vars = array(
|
|
'method' => 'PUT',
|
|
'server' => array('CONTENT_TYPE' => 'application/json; utf-8'),
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('PUT', $request->method);
|
|
$result = $request->put;
|
|
|
|
$this->assertEquals('John Q. Public', $result['name']);
|
|
$this->assertEquals('Joey', $result['nickname']);
|
|
|
|
// PATCH content
|
|
$data = '{"name": "John Q. Public", "nickname": null}';
|
|
$vars = array(
|
|
'method' => 'PATCH',
|
|
'server' => array('CONTENT_TYPE' => 'application/json; utf-8'),
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('PATCH', $request->method);
|
|
$result = $request->patch;
|
|
|
|
$this->assertEquals('John Q. Public', $result['name']);
|
|
$this->assertEquals(null, $result['nickname']);
|
|
}
|
|
|
|
public function testPutStream() {
|
|
global $data;
|
|
$data = file_get_contents(__DIR__ . '/../../../data/testimage.png');
|
|
|
|
$vars = array(
|
|
'put' => $data,
|
|
'method' => 'PUT',
|
|
'server' => array('CONTENT_TYPE' => 'image/png'),
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('PUT', $request->method);
|
|
$resource = $request->put;
|
|
$contents = stream_get_contents($resource);
|
|
$this->assertEquals($data, $contents);
|
|
|
|
try {
|
|
$resource = $request->put;
|
|
} catch(\LogicException $e) {
|
|
return;
|
|
}
|
|
$this->fail('Expected LogicException.');
|
|
|
|
}
|
|
|
|
|
|
public function testSetUrlParameters() {
|
|
$vars = array(
|
|
'post' => array(),
|
|
'method' => 'POST',
|
|
'urlParams' => array('id' => '2'),
|
|
);
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$newParams = array('id' => '3', 'test' => 'test2');
|
|
$request->setUrlParameters($newParams);
|
|
$this->assertEquals('test2', $request->getParam('test'));
|
|
$this->assertEquals('3', $request->getParam('id'));
|
|
$this->assertEquals('3', $request->getParams()['id']);
|
|
}
|
|
|
|
public function testGetIdWithModUnique() {
|
|
$vars = [
|
|
'server' => [
|
|
'UNIQUE_ID' => 'GeneratedUniqueIdByModUnique'
|
|
],
|
|
];
|
|
|
|
$request = new Request(
|
|
$vars,
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('GeneratedUniqueIdByModUnique', $request->getId());
|
|
}
|
|
|
|
public function testGetIdWithoutModUnique() {
|
|
$lowRandomSource = $this->getMockBuilder('\OCP\Security\ISecureRandom')
|
|
->disableOriginalConstructor()->getMock();
|
|
$lowRandomSource->expects($this->once())
|
|
->method('generate')
|
|
->with('20')
|
|
->will($this->returnValue('GeneratedByOwnCloudItself'));
|
|
|
|
$this->secureRandom
|
|
->expects($this->once())
|
|
->method('getLowStrengthGenerator')
|
|
->will($this->returnValue($lowRandomSource));
|
|
|
|
$request = new Request(
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('GeneratedByOwnCloudItself', $request->getId());
|
|
}
|
|
|
|
public function testGetIdWithoutModUniqueStable() {
|
|
$request = new Request(
|
|
[],
|
|
\OC::$server->getSecureRandom(),
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
$firstId = $request->getId();
|
|
$secondId = $request->getId();
|
|
$this->assertSame($firstId, $secondId);
|
|
}
|
|
|
|
public function testGetRemoteAddressWithoutTrustedRemote() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('trusted_proxies')
|
|
->will($this->returnValue([]));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REMOTE_ADDR' => '10.0.0.2',
|
|
'HTTP_X_FORWARDED' => '10.4.0.5, 10.4.0.4',
|
|
'HTTP_X_FORWARDED_FOR' => '192.168.0.233'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('10.0.0.2', $request->getRemoteAddress());
|
|
}
|
|
|
|
public function testGetRemoteAddressWithNoTrustedHeader() {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('trusted_proxies')
|
|
->will($this->returnValue(['10.0.0.2']));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('forwarded_for_headers')
|
|
->will($this->returnValue([]));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REMOTE_ADDR' => '10.0.0.2',
|
|
'HTTP_X_FORWARDED' => '10.4.0.5, 10.4.0.4',
|
|
'HTTP_X_FORWARDED_FOR' => '192.168.0.233'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('10.0.0.2', $request->getRemoteAddress());
|
|
}
|
|
|
|
public function testGetRemoteAddressWithSingleTrustedRemote() {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('trusted_proxies')
|
|
->will($this->returnValue(['10.0.0.2']));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('forwarded_for_headers')
|
|
->will($this->returnValue(['HTTP_X_FORWARDED']));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REMOTE_ADDR' => '10.0.0.2',
|
|
'HTTP_X_FORWARDED' => '10.4.0.5, 10.4.0.4',
|
|
'HTTP_X_FORWARDED_FOR' => '192.168.0.233'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('10.4.0.5', $request->getRemoteAddress());
|
|
}
|
|
|
|
public function testGetRemoteAddressVerifyPriorityHeader() {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('trusted_proxies')
|
|
->will($this->returnValue(['10.0.0.2']));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('forwarded_for_headers')
|
|
->will($this->returnValue([
|
|
'HTTP_CLIENT_IP',
|
|
'HTTP_X_FORWARDED_FOR',
|
|
'HTTP_X_FORWARDED'
|
|
]));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REMOTE_ADDR' => '10.0.0.2',
|
|
'HTTP_X_FORWARDED' => '10.4.0.5, 10.4.0.4',
|
|
'HTTP_X_FORWARDED_FOR' => '192.168.0.233'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('192.168.0.233', $request->getRemoteAddress());
|
|
}
|
|
|
|
public function testGetServerProtocolWithOverride() {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue('customProtocol'));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('overwritecondaddr')
|
|
->will($this->returnValue(''));
|
|
$this->config
|
|
->expects($this->at(2))
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue('customProtocol'));
|
|
|
|
$request = new Request(
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('customProtocol', $request->getServerProtocol());
|
|
}
|
|
|
|
public function testGetServerProtocolWithProtoValid() {
|
|
$this->config
|
|
->expects($this->exactly(2))
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue(''));
|
|
|
|
$requestHttps = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_X_FORWARDED_PROTO' => 'HtTpS'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
$requestHttp = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_X_FORWARDED_PROTO' => 'HTTp'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
|
|
$this->assertSame('https', $requestHttps->getServerProtocol());
|
|
$this->assertSame('http', $requestHttp->getServerProtocol());
|
|
}
|
|
|
|
public function testGetServerProtocolWithHttpsServerValueOn() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue(''));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTPS' => 'on'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
$this->assertSame('https', $request->getServerProtocol());
|
|
}
|
|
|
|
public function testGetServerProtocolWithHttpsServerValueOff() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue(''));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTPS' => 'off'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
$this->assertSame('http', $request->getServerProtocol());
|
|
}
|
|
|
|
public function testGetServerProtocolDefault() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue(''));
|
|
|
|
$request = new Request(
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
$this->assertSame('http', $request->getServerProtocol());
|
|
}
|
|
|
|
public function testGetServerProtocolBehindLoadBalancers() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('overwriteprotocol')
|
|
->will($this->returnValue(''));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_X_FORWARDED_PROTO' => 'https,http,http'
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('https', $request->getServerProtocol());
|
|
}
|
|
|
|
/**
|
|
* @dataProvider userAgentProvider
|
|
* @param string $testAgent
|
|
* @param array $userAgent
|
|
* @param bool $matches
|
|
*/
|
|
public function testUserAgent($testAgent, $userAgent, $matches) {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_USER_AGENT' => $testAgent,
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals($matches, $request->isUserAgent($userAgent));
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
function userAgentProvider() {
|
|
return [
|
|
[
|
|
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
|
|
[
|
|
Request::USER_AGENT_IE
|
|
],
|
|
true,
|
|
],
|
|
[
|
|
'Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20100101 Firefox/24.0',
|
|
[
|
|
Request::USER_AGENT_IE
|
|
],
|
|
false,
|
|
],
|
|
[
|
|
'Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.59 Mobile Safari/537.36',
|
|
[
|
|
Request::USER_AGENT_ANDROID_MOBILE_CHROME
|
|
],
|
|
true,
|
|
],
|
|
[
|
|
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
|
|
[
|
|
Request::USER_AGENT_ANDROID_MOBILE_CHROME
|
|
],
|
|
false,
|
|
],
|
|
[
|
|
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
|
|
[
|
|
Request::USER_AGENT_IE,
|
|
Request::USER_AGENT_ANDROID_MOBILE_CHROME,
|
|
],
|
|
true,
|
|
],
|
|
[
|
|
'Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.59 Mobile Safari/537.36',
|
|
[
|
|
Request::USER_AGENT_IE,
|
|
Request::USER_AGENT_ANDROID_MOBILE_CHROME,
|
|
],
|
|
true,
|
|
],
|
|
[
|
|
'Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20100101 Firefox/24.0',
|
|
[
|
|
Request::USER_AGENT_FREEBOX
|
|
],
|
|
false,
|
|
],
|
|
[
|
|
'Mozilla/5.0',
|
|
[
|
|
Request::USER_AGENT_FREEBOX
|
|
],
|
|
true,
|
|
],
|
|
[
|
|
'Fake Mozilla/5.0',
|
|
[
|
|
Request::USER_AGENT_FREEBOX
|
|
],
|
|
false,
|
|
],
|
|
];
|
|
}
|
|
|
|
public function testInsecureServerHostServerNameHeader() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'SERVER_NAME' => 'from.server.name:8080',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('from.server.name:8080', $request->getInsecureServerHost());
|
|
}
|
|
|
|
public function testInsecureServerHostHttpHostHeader() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'SERVER_NAME' => 'from.server.name:8080',
|
|
'HTTP_HOST' => 'from.host.header:8080',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('from.host.header:8080', $request->getInsecureServerHost());
|
|
}
|
|
|
|
public function testInsecureServerHostHttpFromForwardedHeaderSingle() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'SERVER_NAME' => 'from.server.name:8080',
|
|
'HTTP_HOST' => 'from.host.header:8080',
|
|
'HTTP_X_FORWARDED_HOST' => 'from.forwarded.host:8080',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('from.forwarded.host:8080', $request->getInsecureServerHost());
|
|
}
|
|
|
|
public function testInsecureServerHostHttpFromForwardedHeaderStacked() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'SERVER_NAME' => 'from.server.name:8080',
|
|
'HTTP_HOST' => 'from.host.header:8080',
|
|
'HTTP_X_FORWARDED_HOST' => 'from.forwarded.host2:8080,another.one:9000',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('from.forwarded.host2:8080', $request->getInsecureServerHost());
|
|
}
|
|
|
|
public function testGetServerHostWithOverwriteHost() {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('overwritehost')
|
|
->will($this->returnValue('my.overwritten.host'));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('overwritecondaddr')
|
|
->will($this->returnValue(''));
|
|
$this->config
|
|
->expects($this->at(2))
|
|
->method('getSystemValue')
|
|
->with('overwritehost')
|
|
->will($this->returnValue('my.overwritten.host'));
|
|
|
|
$request = new Request(
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('my.overwritten.host', $request->getServerHost());
|
|
}
|
|
|
|
public function testGetServerHostWithTrustedDomain() {
|
|
$this->config
|
|
->expects($this->at(3))
|
|
->method('getSystemValue')
|
|
->with('trusted_domains')
|
|
->will($this->returnValue(['my.trusted.host']));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_X_FORWARDED_HOST' => 'my.trusted.host',
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('my.trusted.host', $request->getServerHost());
|
|
}
|
|
|
|
public function testGetServerHostWithUntrustedDomain() {
|
|
$this->config
|
|
->expects($this->at(3))
|
|
->method('getSystemValue')
|
|
->with('trusted_domains')
|
|
->will($this->returnValue(['my.trusted.host']));
|
|
$this->config
|
|
->expects($this->at(4))
|
|
->method('getSystemValue')
|
|
->with('trusted_domains')
|
|
->will($this->returnValue(['my.trusted.host']));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_X_FORWARDED_HOST' => 'my.untrusted.host',
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('my.trusted.host', $request->getServerHost());
|
|
}
|
|
|
|
public function testGetServerHostWithNoTrustedDomain() {
|
|
$this->config
|
|
->expects($this->at(3))
|
|
->method('getSystemValue')
|
|
->with('trusted_domains')
|
|
->will($this->returnValue([]));
|
|
$this->config
|
|
->expects($this->at(4))
|
|
->method('getSystemValue')
|
|
->with('trusted_domains')
|
|
->will($this->returnValue([]));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'HTTP_X_FORWARDED_HOST' => 'my.untrusted.host',
|
|
],
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('', $request->getServerHost());
|
|
}
|
|
|
|
public function testGetOverwriteHostDefaultNull() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('overwritehost')
|
|
->will($this->returnValue(''));
|
|
$request = new Request(
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertNull(self::invokePrivate($request, 'getOverwriteHost'));
|
|
}
|
|
|
|
public function testGetOverwriteHostWithOverwrite() {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('overwritehost')
|
|
->will($this->returnValue('www.owncloud.org'));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('overwritecondaddr')
|
|
->will($this->returnValue(''));
|
|
$this->config
|
|
->expects($this->at(2))
|
|
->method('getSystemValue')
|
|
->with('overwritehost')
|
|
->will($this->returnValue('www.owncloud.org'));
|
|
|
|
$request = new Request(
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('www.owncloud.org', self::invokePrivate($request, 'getOverwriteHost'));
|
|
}
|
|
|
|
public function testGetPathInfoWithSetEnv() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'PATH_INFO' => 'apps/files/',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals('apps/files/', $request->getPathInfo());
|
|
}
|
|
|
|
/**
|
|
* @expectedException \Exception
|
|
* @expectedExceptionMessage The requested uri(/foo.php) cannot be processed by the script '/var/www/index.php')
|
|
*/
|
|
public function testGetPathInfoNotProcessible() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => '/foo.php',
|
|
'SCRIPT_NAME' => '/var/www/index.php',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$request->getPathInfo();
|
|
}
|
|
|
|
/**
|
|
* @expectedException \Exception
|
|
* @expectedExceptionMessage The requested uri(/foo.php) cannot be processed by the script '/var/www/index.php')
|
|
*/
|
|
public function testGetRawPathInfoNotProcessible() {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => '/foo.php',
|
|
'SCRIPT_NAME' => '/var/www/index.php',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$request->getRawPathInfo();
|
|
}
|
|
|
|
/**
|
|
* @dataProvider genericPathInfoProvider
|
|
* @param string $requestUri
|
|
* @param string $scriptName
|
|
* @param string $expected
|
|
*/
|
|
public function testGetPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $expected) {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => $requestUri,
|
|
'SCRIPT_NAME' => $scriptName,
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals($expected, $request->getPathInfo());
|
|
}
|
|
|
|
/**
|
|
* @dataProvider genericPathInfoProvider
|
|
* @param string $requestUri
|
|
* @param string $scriptName
|
|
* @param string $expected
|
|
*/
|
|
public function testGetRawPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $expected) {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => $requestUri,
|
|
'SCRIPT_NAME' => $scriptName,
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals($expected, $request->getRawPathInfo());
|
|
}
|
|
|
|
/**
|
|
* @dataProvider rawPathInfoProvider
|
|
* @param string $requestUri
|
|
* @param string $scriptName
|
|
* @param string $expected
|
|
*/
|
|
public function testGetRawPathInfoWithoutSetEnv($requestUri, $scriptName, $expected) {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => $requestUri,
|
|
'SCRIPT_NAME' => $scriptName,
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals($expected, $request->getRawPathInfo());
|
|
}
|
|
|
|
/**
|
|
* @dataProvider pathInfoProvider
|
|
* @param string $requestUri
|
|
* @param string $scriptName
|
|
* @param string $expected
|
|
*/
|
|
public function testGetPathInfoWithoutSetEnv($requestUri, $scriptName, $expected) {
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => $requestUri,
|
|
'SCRIPT_NAME' => $scriptName,
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertEquals($expected, $request->getPathInfo());
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function genericPathInfoProvider() {
|
|
return [
|
|
['/index.php/apps/files/', 'index.php', '/apps/files/'],
|
|
['/index.php/apps/files/../&/&?someQueryParameter=QueryParam', 'index.php', '/apps/files/../&/&'],
|
|
['/remote.php/漢字編碼方法 / 汉字编码方法', 'remote.php', '/漢字編碼方法 / 汉字编码方法'],
|
|
['///removeTrailin//gSlashes///', 'remote.php', '/removeTrailin/gSlashes/'],
|
|
['/', '/', ''],
|
|
['', '', ''],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function rawPathInfoProvider() {
|
|
return [
|
|
['/foo%2Fbar/subfolder', '', 'foo%2Fbar/subfolder'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function pathInfoProvider() {
|
|
return [
|
|
['/foo%2Fbar/subfolder', '', 'foo/bar/subfolder'],
|
|
];
|
|
}
|
|
|
|
public function testGetRequestUriWithoutOverwrite() {
|
|
$this->config
|
|
->expects($this->once())
|
|
->method('getSystemValue')
|
|
->with('overwritewebroot')
|
|
->will($this->returnValue(''));
|
|
|
|
$request = new Request(
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => '/test.php'
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
);
|
|
|
|
$this->assertSame('/test.php', $request->getRequestUri());
|
|
}
|
|
|
|
public function providesGetRequestUriWithOverwriteData() {
|
|
return [
|
|
['/scriptname.php/some/PathInfo', '/owncloud/', ''],
|
|
['/scriptname.php/some/PathInfo', '/owncloud/', '123'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider providesGetRequestUriWithOverwriteData
|
|
*/
|
|
public function testGetRequestUriWithOverwrite($expectedUri, $overwriteWebRoot, $overwriteCondAddr) {
|
|
$this->config
|
|
->expects($this->at(0))
|
|
->method('getSystemValue')
|
|
->with('overwritewebroot')
|
|
->will($this->returnValue($overwriteWebRoot));
|
|
$this->config
|
|
->expects($this->at(1))
|
|
->method('getSystemValue')
|
|
->with('overwritecondaddr')
|
|
->will($this->returnValue($overwriteCondAddr));
|
|
|
|
$request = $this->getMockBuilder('\OC\AppFramework\Http\Request')
|
|
->setMethods(['getScriptName'])
|
|
->setConstructorArgs([
|
|
[
|
|
'server' => [
|
|
'REQUEST_URI' => '/test.php/some/PathInfo',
|
|
'SCRIPT_NAME' => '/test.php',
|
|
]
|
|
],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
])
|
|
->getMock();
|
|
$request
|
|
->expects($this->once())
|
|
->method('getScriptName')
|
|
->will($this->returnValue('/scriptname.php'));
|
|
|
|
$this->assertSame($expectedUri, $request->getRequestUri());
|
|
}
|
|
|
|
public function testPassesCSRFCheckWithGet() {
|
|
$crypto = $this->getMock('\OCP\Security\ICrypto');
|
|
$crypto
|
|
->expects($this->once())
|
|
->method('decrypt')
|
|
->with('1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4', 'secret')
|
|
->will($this->returnValue('MyStoredRequestToken'));
|
|
|
|
/** @var Request $request */
|
|
$request = $this->getMockBuilder('\OC\AppFramework\Http\Request')
|
|
->setMethods(['getScriptName'])
|
|
->setConstructorArgs([
|
|
[
|
|
'get' => [
|
|
'requesttoken' => '1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4:secret',
|
|
],
|
|
'requesttoken' => 'MyStoredRequestToken',
|
|
],
|
|
$this->secureRandom,
|
|
$crypto,
|
|
$this->config,
|
|
$this->stream
|
|
])
|
|
->getMock();
|
|
|
|
$this->assertTrue($request->passesCSRFCheck());
|
|
}
|
|
|
|
public function testPassesCSRFCheckWithPost() {
|
|
$crypto = $this->getMock('\OCP\Security\ICrypto');
|
|
$crypto
|
|
->expects($this->once())
|
|
->method('decrypt')
|
|
->with('1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4', 'secret')
|
|
->will($this->returnValue('MyStoredRequestToken'));
|
|
|
|
/** @var Request $request */
|
|
$request = $this->getMockBuilder('\OC\AppFramework\Http\Request')
|
|
->setMethods(['getScriptName'])
|
|
->setConstructorArgs([
|
|
[
|
|
'post' => [
|
|
'requesttoken' => '1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4:secret',
|
|
],
|
|
'requesttoken' => 'MyStoredRequestToken',
|
|
],
|
|
$this->secureRandom,
|
|
$crypto,
|
|
$this->config,
|
|
$this->stream
|
|
])
|
|
->getMock();
|
|
|
|
$this->assertTrue($request->passesCSRFCheck());
|
|
}
|
|
|
|
public function testPassesCSRFCheckWithHeader() {
|
|
$crypto = $this->getMock('\OCP\Security\ICrypto');
|
|
$crypto
|
|
->expects($this->once())
|
|
->method('decrypt')
|
|
->with('1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4', 'secret')
|
|
->will($this->returnValue('MyStoredRequestToken'));
|
|
/** @var Request $request */
|
|
$request = $this->getMockBuilder('\OC\AppFramework\Http\Request')
|
|
->setMethods(['getScriptName'])
|
|
->setConstructorArgs([
|
|
[
|
|
'server' => [
|
|
'HTTP_REQUESTTOKEN' => '1c637c4147e40a8a8f09428ec2059cebea3480c27b402b4e793c69710a731513|wlXxNUaFqHuQnZr5|e6ab49c9e0e20c8d3607e02f1d8e6ec17ad6020ae10b7d64ab4b0a6318c0875940943a6aa303dc090fea0b4cd5b9fb8bcbecac4308a2bd15d9f369cdc22121a4:secret',
|
|
],
|
|
'requesttoken' => 'MyStoredRequestToken',
|
|
],
|
|
$this->secureRandom,
|
|
$crypto,
|
|
$this->config,
|
|
$this->stream
|
|
])
|
|
->getMock();
|
|
|
|
$this->assertTrue($request->passesCSRFCheck());
|
|
}
|
|
|
|
public function invalidTokenDataProvider() {
|
|
return [
|
|
['InvalidSentToken'],
|
|
['InvalidSentToken:InvalidSecret'],
|
|
[null],
|
|
[''],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider invalidTokenDataProvider
|
|
* @param string $invalidToken
|
|
*/
|
|
public function testPassesCSRFCheckWithInvalidToken($invalidToken) {
|
|
$crypto = new Crypto($this->config, $this->secureRandom);
|
|
|
|
/** @var Request $request */
|
|
$request = $this->getMockBuilder('\OC\AppFramework\Http\Request')
|
|
->setMethods(['getScriptName'])
|
|
->setConstructorArgs([
|
|
[
|
|
'server' => [
|
|
'HTTP_REQUESTTOKEN' => $invalidToken,
|
|
],
|
|
'requesttoken' => 'MyStoredRequestToken',
|
|
],
|
|
$this->secureRandom,
|
|
$crypto,
|
|
$this->config,
|
|
$this->stream
|
|
])
|
|
->getMock();
|
|
|
|
$this->assertFalse($request->passesCSRFCheck());
|
|
}
|
|
|
|
public function testPassesCSRFCheckWithoutTokenFail() {
|
|
/** @var Request $request */
|
|
$request = $this->getMockBuilder('\OC\AppFramework\Http\Request')
|
|
->setMethods(['getScriptName'])
|
|
->setConstructorArgs([
|
|
[],
|
|
$this->secureRandom,
|
|
$this->getMock('\OCP\Security\ICrypto'),
|
|
$this->config,
|
|
$this->stream
|
|
])
|
|
->getMock();
|
|
|
|
$this->assertFalse($request->passesCSRFCheck());
|
|
}
|
|
|
|
}
|