瀏覽代碼

Let users configure security headers in their Webserver

Doing this in the PHP code is not the right approach for multiple reasons:

1. A bug in the PHP code prevents them from being added to the response.
2. They are only added when something is served via PHP and not in other cases (that makes for example the newest IE UXSS which is not yet patched by Microsoft exploitable on ownCloud)
3. Some headers such as the Strict-Transport-Security might require custom modifications by administrators. This was not possible before and lead to buggy situations.

This pull request moves those headers out of the PHP code and adds a security check to the admin settings performed via JS.
tags/v8.1.0alpha1
Lukas Reschke 9 年之前
父節點
當前提交
bbd5f28415

+ 4
- 0
.htaccess 查看文件

@@ -45,6 +45,10 @@ Options -Indexes
ModPagespeed Off
</IfModule>
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Header set X-Robots-Tag "none"
Header set X-Frame-Options "SAMEORIGIN"
<FilesMatch "\.(css|js)$">
Header set Cache-Control "max-age=7200, public"
</FilesMatch>

+ 0
- 19
config/config.sample.php 查看文件

@@ -744,18 +744,6 @@ $CONFIG = array(
* SSL
*/

/**
* Change this to ``true`` to require HTTPS for all connections, and to reject
* HTTP requests.
*/
'forcessl' => false,

/**
* Change this to ``true`` to require HTTPS connections also for all subdomains.
* Works only together when `forcessl` is set to true.
*/
'forceSSLforSubdomains' => false,

/**
* Extra SSL options to be used for configuration.
*/
@@ -786,13 +774,6 @@ $CONFIG = array(
*/
'theme' => '',

/**
* X-Frame-Restriction is a header which prevents browsers from showing the site
* inside an iframe. This is be used to prevent clickjacking. It is risky to
* disable this, so leave it set at ``true``.
*/
'xframe_restriction' => true,

/**
* The default cipher for encrypting files. Currently AES-128-CFB and
* AES-256-CFB are supported.

+ 1
- 0
core/js/core.json 查看文件

@@ -24,6 +24,7 @@
"config.js",
"multiselect.js",
"oc-requesttoken.js",
"setupchecks.js",
"../search/js/search.js"
]
}

+ 8
- 0
core/js/js.js 查看文件

@@ -210,6 +210,14 @@ var OC={
window.location = targetURL;
},

/**
* Protocol that is used to access this ownCloud instance
* @return {string} Used protocol
*/
getProtocol: function() {
return window.location.protocol.split(':')[0];
},

/**
* get the absolute path to an image file
* if no extension is given for the image, it will automatically decide

+ 95
- 1
core/js/setupchecks.js 查看文件

@@ -70,7 +70,101 @@
url: OC.generateUrl('settings/ajax/checksetup')
}).then(afterCall, afterCall);
return deferred.promise();
},

/**
* Runs generic checks on the server side, the difference to dedicated
* methods is that we use the same XHR object for all checks to save
* requests.
*
* @return $.Deferred object resolved with an array of error messages
*/
checkGeneric: function() {
var self = this;
var deferred = $.Deferred();
var afterCall = function(data, statusText, xhr) {
var messages = [];
messages = messages.concat(self._checkSecurityHeaders(xhr));
messages = messages.concat(self._checkSSL(xhr));
deferred.resolve(messages);
};

$.ajax({
type: 'GET',
url: OC.generateUrl('heartbeat')
}).then(afterCall, afterCall);

return deferred.promise();
},

/**
* Runs check for some generic security headers on the server side
*
* @param {Object} xhr
* @return {Array} Array with error messages
*/
_checkSecurityHeaders: function(xhr) {
var messages = [];

if (xhr.status === 200) {
var securityHeaders = {
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
};

for (var header in securityHeaders) {
if(xhr.getResponseHeader(header) !== securityHeaders[header]) {
messages.push(
t('core', 'The "{header}" HTTP header is not configured to equal to "{expected}". This is a potential security risk and we recommend adjusting this setting.', {header: header, expected: securityHeaders[header]})
);
}
}
} else {
messages.push(t('core', 'Error occurred while checking server setup'));
}

return messages;
},

/**
* Runs check for some SSL configuration issues on the server side
*
* @param {Object} xhr
* @return {Array} Array with error messages
*/
_checkSSL: function(xhr) {
var messages = [];

if (xhr.status === 200) {
if(OC.getProtocol() === 'https') {
// Extract the value of 'Strict-Transport-Security'
var transportSecurityValidity = xhr.getResponseHeader('Strict-Transport-Security');
if(transportSecurityValidity !== null && transportSecurityValidity.length > 8) {
var firstComma = transportSecurityValidity.indexOf(";");
if(firstComma !== -1) {
transportSecurityValidity = transportSecurityValidity.substring(0, firstComma);
} else {
transportSecurityValidity = transportSecurityValidity.substring(8);
}
}

if(isNaN(transportSecurityValidity) || transportSecurityValidity <= 2678399) {
messages.push(
t('core', 'The "Strict-Transport-Security" HTTP header is not configured to least "2,678,400" seconds. This is a potential security risk and we recommend adjusting this setting.')
);
}
} else {
messages.push(
t('core', 'You are accessing this site via HTTP. We strongly suggest you configure your server to require using HTTPS instead.')
);
}
} else {
messages.push(t('core', 'Error occurred while checking server setup'));
}

return messages;
}
};
})();


+ 326
- 0
core/js/tests/specs/setupchecksSpec.js 查看文件

@@ -0,0 +1,326 @@
/**
* Copyright (c) 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.
*
*/

describe('OC.SetupChecks tests', function() {
var suite = this;

beforeEach( function(){
suite.server = sinon.fakeServer.create();
});

afterEach( function(){
suite.server.restore();
});

describe('checkWebDAV', function() {
it('should fail with another response status code than 201 or 207', function(done) {
var async = OC.SetupChecks.checkWebDAV();

suite.server.requests[0].respond(200);

async.done(function( data, s, x ){
expect(data).toEqual(['Your web server is not yet set up properly to allow file synchronization because the WebDAV interface seems to be broken.']);
});
});

it('should return no error with a response status code of 207', function(done) {
var async = OC.SetupChecks.checkWebDAV();

suite.server.requests[0].respond(207);

async.done(function( data, s, x ){
expect(data).toEqual([]);
});
});

it('should return no error with a response status code of 401', function(done) {
var async = OC.SetupChecks.checkWebDAV();

suite.server.requests[0].respond(401);

async.done(function( data, s, x ){
expect(data).toEqual([]);
});
});
});

describe('checkSetup', function() {
it('should return an error if server has no internet connection', function(done) {
var async = OC.SetupChecks.checkSetup();

suite.server.requests[0].respond(
200,
{
'Content-Type': 'application/json'
},
JSON.stringify({data: {serverHasInternetConnection: false}})
);

async.done(function( data, s, x ){
expect(data).toEqual(['This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features.', 'Your data directory and your files are probably accessible from the Internet. The .htaccess file is not working. We strongly suggest that you configure your web server in a way that the data directory is no longer accessible or you move the data directory outside the web server document root.']);
});
});

it('should return an error if server has no internet connection and data directory is not protected', function(done) {
var async = OC.SetupChecks.checkSetup();

suite.server.requests[0].respond(
200,
{
'Content-Type': 'application/json'
},
JSON.stringify({data: {serverHasInternetConnection: false, dataDirectoryProtected: false}})
);

async.done(function( data, s, x ){
expect(data).toEqual(['This server has no working Internet connection. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. We suggest to enable Internet connection for this server if you want to have all features.', 'Your data directory and your files are probably accessible from the Internet. The .htaccess file is not working. We strongly suggest that you configure your web server in a way that the data directory is no longer accessible or you move the data directory outside the web server document root.']);
});
});

it('should return an error if the response has no statuscode 200', function(done) {
var async = OC.SetupChecks.checkSetup();

suite.server.requests[0].respond(
500,
{
'Content-Type': 'application/json'
},
JSON.stringify({data: {serverHasInternetConnection: false, dataDirectoryProtected: false}})
);

async.done(function( data, s, x ){
expect(data).toEqual(['Error occurred while checking server setup']);
});
});
});

describe('checkGeneric', function() {
it('should return an error if the response has no statuscode 200', function(done) {
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(
500,
{
'Content-Type': 'application/json'
}
);

async.done(function( data, s, x ){
expect(data).toEqual(['Error occurred while checking server setup', 'Error occurred while checking server setup']);
});
});

it('should return all errors if all headers are missing', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(
200,
{
'Content-Type': 'application/json',
'Strict-Transport-Security': '2678400'
}
);

async.done(function( data, s, x ){
expect(data).toEqual(['The "X-XSS-Protection" HTTP header is not configured to equal to "1; mode=block". This is a potential security risk and we recommend adjusting this setting.', 'The "X-Content-Type-Options" HTTP header is not configured to equal to "nosniff". This is a potential security risk and we recommend adjusting this setting.', 'The "X-Robots-Tag" HTTP header is not configured to equal to "none". This is a potential security risk and we recommend adjusting this setting.', 'The "X-Frame-Options" HTTP header is not configured to equal to "SAMEORIGIN". This is a potential security risk and we recommend adjusting this setting.']);
});
protocolStub.restore();
});

it('should return only some errors if just some headers are missing', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(
200,
{
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN',
'Strict-Transport-Security': '2678400'

}
);

async.done(function( data, s, x ){
expect(data).toEqual(['The "X-XSS-Protection" HTTP header is not configured to equal to "1; mode=block". This is a potential security risk and we recommend adjusting this setting.', 'The "X-Content-Type-Options" HTTP header is not configured to equal to "nosniff". This is a potential security risk and we recommend adjusting this setting.']);
});
protocolStub.restore();
});

it('should return none errors if all headers are there', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(
200,
{
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN',
'Strict-Transport-Security': '2678400'
}
);

async.done(function( data, s, x ){
expect(data).toEqual([]);
});
protocolStub.restore();
});
});

it('should return a SSL warning if HTTPS is not used', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('http');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200,
{
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
}
);

async.done(function( data, s, x ){
expect(data).toEqual(['You are accessing this site via HTTP. We strongly suggest you configure your server to require using HTTPS instead.']);
});
protocolStub.restore();
});

it('should return an error if the response has no statuscode 200', function(done) {
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(
500,
{
'Content-Type': 'application/json'
},
JSON.stringify({data: {serverHasInternetConnection: false, dataDirectoryProtected: false}})
);
async.done(function( data, s, x ){
expect(data).toEqual(['Error occurred while checking server setup', 'Error occurred while checking server setup']);
});
});

it('should return a SSL warning if SSL used without Strict-Transport-Security-Header', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200,
{
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
}
);

async.done(function( data, s, x ){
expect(data).toEqual(['The "Strict-Transport-Security" HTTP header is not configured to least "2,678,400" seconds. This is a potential security risk and we recommend adjusting this setting.']);
});
protocolStub.restore();
});

it('should return a SSL warning if SSL used with to small Strict-Transport-Security-Header', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200,
{
'Strict-Transport-Security': '2678399',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
}
);

async.done(function( data, s, x ){
expect(data).toEqual(['The "Strict-Transport-Security" HTTP header is not configured to least "2,678,400" seconds. This is a potential security risk and we recommend adjusting this setting.']);
});
protocolStub.restore();
});

it('should return a SSL warning if SSL used with to a bogus Strict-Transport-Security-Header', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200,
{
'Strict-Transport-Security': 'iAmABogusHeader342',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
}
);

async.done(function( data, s, x ){
expect(data).toEqual(['The "Strict-Transport-Security" HTTP header is not configured to least "2,678,400" seconds. This is a potential security risk and we recommend adjusting this setting.']);
});
protocolStub.restore();
});

it('should return no SSL warning if SSL used with to exact the minimum Strict-Transport-Security-Header', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200, {
'Strict-Transport-Security': '2678400',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
});

async.done(function( data, s, x ){
expect(data).toEqual([]);
});
protocolStub.restore();
});

it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200, {
'Strict-Transport-Security': '12678400',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
});

async.done(function( data, s, x ){
expect(data).toEqual([]);
});
protocolStub.restore();
});

it('should return no SSL warning if SSL used with to more than the minimum Strict-Transport-Security-Header and includeSubDomains parameter', function(done) {
var protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
var async = OC.SetupChecks.checkGeneric();

suite.server.requests[0].respond(200, {
'Strict-Transport-Security': '12678400; includeSubDomains',
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Robots-Tag': 'none',
'X-Frame-Options': 'SAMEORIGIN'
});

async.done(function( data, s, x ){
expect(data).toEqual([]);
});
protocolStub.restore();
});
});

+ 4
- 29
lib/base.php 查看文件

@@ -247,34 +247,6 @@ class OC {
}
}

public static function checkSSL() {
$request = \OC::$server->getRequest();

// redirect to https site if configured
if (\OC::$server->getSystemConfig()->getValue('forcessl', false)) {
// Default HSTS policy
$header = 'Strict-Transport-Security: max-age=31536000';

// If SSL for subdomains is enabled add "; includeSubDomains" to the header
if(\OC::$server->getSystemConfig()->getValue('forceSSLforSubdomains', false)) {
$header .= '; includeSubDomains';
}
header($header);
ini_set('session.cookie_secure', true);

if ($request->getServerProtocol() <> 'https' && !OC::$CLI) {
$url = 'https://' . $request->getServerHost() . $request->getRequestUri();
header("Location: $url");
exit();
}
} else {
// Invalidate HSTS headers
if ($request->getServerProtocol() === 'https') {
header('Strict-Transport-Security: max-age=0');
}
}
}

public static function checkMaintenanceMode() {
// Allow ajax update script to execute without being stopped
if (\OC::$server->getSystemConfig()->getValue('maintenance', false) && OC::$SUBURI != '/core/ajax/update.php') {
@@ -569,8 +541,11 @@ class OC {
self::initTemplateEngine();
self::checkConfig();
self::checkInstalled();
self::checkSSL();
OC_Response::addSecurityHeaders();
if(self::$server->getRequest()->getServerProtocol() === 'https') {
ini_set('session.cookie_secure', true);
}

$errors = OC_Util::checkServer(\OC::$server->getConfig());
if (count($errors) > 0) {

+ 1
- 1
lib/private/appframework/app.php 查看文件

@@ -123,7 +123,7 @@ class App {
$expireDate,
$container->getServer()->getWebRoot(),
null,
$container->getServer()->getConfig()->getSystemValue('forcessl', false),
$container->getServer()->getRequest()->getServerProtocol() === 'https',
true
);
}

+ 1
- 1
lib/private/appframework/http/request.php 查看文件

@@ -475,7 +475,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
private function isOverwriteCondition($type = '') {
$regex = '/' . $this->config->getSystemValue('overwritecondaddr', '') . '/';
return $regex === '//' || preg_match($regex, $this->server['REMOTE_ADDR']) === 1
|| ($type !== 'protocol' && $this->config->getSystemValue('forcessl', false));
|| $type !== 'protocol';
}

/**

+ 0
- 12
lib/private/response.php 查看文件

@@ -195,15 +195,6 @@ class OC_Response {
* components (e.g. SabreDAV) also benefit from this headers.
*/
public static function addSecurityHeaders() {
header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters
header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE

// iFrame Restriction Policy
$xFramePolicy = OC_Config::getValue('xframe_restriction', true);
if ($xFramePolicy) {
header('X-Frame-Options: Sameorigin'); // Disallow iFraming from other domains
}

/**
* FIXME: Content Security Policy for legacy ownCloud components. This
* can be removed once \OCP\AppFramework\Http\Response from the AppFramework
@@ -219,9 +210,6 @@ class OC_Response {
. 'media-src *; '
. 'connect-src *';
header('Content-Security-Policy:' . $policy);

// https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
header('X-Robots-Tag: none');
}

}

+ 1
- 1
lib/private/user/session.php 查看文件

@@ -265,7 +265,7 @@ class Session implements IUserSession, Emitter {
* @param string $token
*/
public function setMagicInCookie($username, $token) {
$secureCookie = \OC_Config::getValue("forcessl", false); //TODO: DI for cookies and OC_Config
$secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https';
$expires = time() + \OC_Config::getValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
setcookie("oc_username", $username, $expires, \OC::$WEBROOT, '', $secureCookie, true);
setcookie("oc_token", $token, $expires, \OC::$WEBROOT, '', $secureCookie, true);

+ 0
- 5
settings/admin.php 查看文件

@@ -58,11 +58,6 @@ $excludedGroupsList = $appConfig->getValue('core', 'shareapi_exclude_groups_list
$excludedGroupsList = explode(',', $excludedGroupsList); // FIXME: this should be JSON!
$template->assign('shareExcludedGroupsList', implode('|', $excludedGroupsList));

// Check if connected using HTTPS
$template->assign('isConnectedViaHTTPS', $request->getServerProtocol() === 'https');
$template->assign('enforceHTTPSEnabled', $config->getSystemValue('forcessl', false));
$template->assign('forceSSLforSubdomainsEnabled', $config->getSystemValue('forceSSLforSubdomains', false));

// If the current web root is non-empty but the web root from the config is,
// and system cron is used, the URL generator fails to build valid URLs.
$shouldSuggestOverwriteCliUrl = $config->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'cron' &&

+ 0
- 37
settings/controller/securitysettingscontroller.php 查看文件

@@ -42,43 +42,6 @@ class SecuritySettingsController extends Controller {
);
}

/**
* @return array
*/
protected function returnError() {
return array(
'status' => 'error'
);
}

/**
* Enforce or disable the enforcement of SSL
* @param boolean $enforceHTTPS Whether SSL should be enforced
* @return array
*/
public function enforceSSL($enforceHTTPS = false) {
if(!is_bool($enforceHTTPS)) {
return $this->returnError();
}
$this->config->setSystemValue('forcessl', $enforceHTTPS);

return $this->returnSuccess();
}

/**
* Enforce or disable the enforcement for SSL on subdomains
* @param bool $forceSSLforSubdomains Whether SSL on subdomains should be enforced
* @return array
*/
public function enforceSSLForSubdomains($forceSSLforSubdomains = false) {
if(!is_bool($forceSSLforSubdomains)) {
return $this->returnError();
}
$this->config->setSystemValue('forceSSLforSubdomains', $forceSSLforSubdomains);

return $this->returnSuccess();
}

/**
* Add a new trusted domain
* @param string $newTrustedDomain The newly to add trusted domain

+ 4
- 29
settings/js/admin.js 查看文件

@@ -75,32 +75,6 @@ $(document).ready(function(){
$('#setDefaultExpireDate').toggleClass('hidden', !(this.checked && $('#shareapiDefaultExpireDate')[0].checked));
});

$('#forcessl').change(function(){
$(this).val(($(this).val() !== 'true'));
var forceSSLForSubdomain = $('#forceSSLforSubdomainsSpan');

$.post(OC.generateUrl('settings/admin/security/ssl'), {
enforceHTTPS: $(this).val()
},function(){} );

if($(this).val() === 'true') {
forceSSLForSubdomain.prop('disabled', false);
forceSSLForSubdomain.removeClass('hidden');
} else {
forceSSLForSubdomain.prop('disabled', true);
forceSSLForSubdomain.addClass('hidden');
}
});

$('#forceSSLforSubdomains').change(function(){
$(this).val(($(this).val() !== 'true'));

$.post(OC.generateUrl('settings/admin/security/ssl/subdomains'), {
forceSSLforSubdomains: $(this).val()
},function(){} );
});


$('#mail_smtpauth').change(function() {
if (!this.checked) {
$('#mail_credentials').addClass('hidden');
@@ -158,9 +132,10 @@ $(document).ready(function(){
// run setup checks then gather error messages
$.when(
OC.SetupChecks.checkWebDAV(),
OC.SetupChecks.checkSetup()
).then(function(check1, check2) {
var errors = [].concat(check1, check2);
OC.SetupChecks.checkSetup(),
OC.SetupChecks.checkGeneric()
).then(function(check1, check2, check3) {
var errors = [].concat(check1, check2, check3);
var $el = $('#postsetupchecks');
var $errorsEl;
$el.find('.loading').addClass('hidden');

+ 0
- 2
settings/routes.php 查看文件

@@ -20,8 +20,6 @@ $application->registerRoutes($this, array(
array('name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'),
array('name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'),
array('name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'),
array('name' => 'SecuritySettings#enforceSSL', 'url' => '/settings/admin/security/ssl', 'verb' => 'POST'),
array('name' => 'SecuritySettings#enforceSSLForSubdomains', 'url' => '/settings/admin/security/ssl/subdomains', 'verb' => 'POST'),
array('name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'),
array('name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'),
array('name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'),

+ 0
- 59
settings/templates/admin.php 查看文件

@@ -66,20 +66,6 @@ if ($_['mail_smtpmode'] == 'qmail') {
<div id="security-warning">
<?php

// is ssl working ?
if (!$_['isConnectedViaHTTPS']) {
?>
<div class="section">
<h2><?php p($l->t('Security Warning'));?></h2>

<span class="securitywarning">
<?php p($l->t('You are accessing %s via HTTP. We strongly suggest you configure your server to require using HTTPS instead.', $theme->getTitle())); ?>
</span>

</div>
<?php
}

// is read only config enabled
if ($_['readOnlyConfigEnabled']) {
?>
@@ -370,51 +356,6 @@ if ($_['cronErrors']) {
</p>
</div>

<div class="section" id="security">
<h2><?php p($l->t('Security'));?></h2>
<p>
<input type="checkbox" name="forcessl" id="forcessl"
<?php if ($_['enforceHTTPSEnabled']) {
print_unescaped('checked="checked" ');
print_unescaped('value="true"');
} else {
print_unescaped('value="false"');
}
?>
<?php if (!$_['isConnectedViaHTTPS']) p('disabled'); ?> />
<label for="forcessl"><?php p($l->t('Enforce HTTPS'));?></label><br/>
<em><?php p($l->t(
'Forces the clients to connect to %s via an encrypted connection.',
$theme->getName()
)); ?></em><br/>
<span id="forceSSLforSubdomainsSpan" <?php if(!$_['enforceHTTPSEnabled']) { print_unescaped('class="hidden"'); } ?>>
<input type="checkbox" name="forceSSLforSubdomains" id="forceSSLforSubdomains"
<?php if ($_['forceSSLforSubdomainsEnabled']) {
print_unescaped('checked="checked" ');
print_unescaped('value="true"');
} else {
print_unescaped('value="false"');
}
?>
<?php if (!$_['isConnectedViaHTTPS']) { p('disabled'); } ?> />
<label for="forceSSLforSubdomains"><?php p($l->t('Enforce HTTPS for subdomains'));?></label><br/>
<em><?php p($l->t(
'Forces the clients to connect to %s and subdomains via an encrypted connection.',
$theme->getName()
)); ?></em>
</span>
<?php if (!$_['isConnectedViaHTTPS']) {
print_unescaped("<br/><em>");
p($l->t(
'Please connect to your %s via HTTPS to enable or disable the SSL enforcement.',
$theme->getName()
));
print_unescaped("</em>");
}
?>
</p>
</div>

<div class="section">
<form id="mail_general_settings" class="mail_settings">
<h2><?php p($l->t('Email Server'));?></h2>

+ 0
- 71
tests/settings/controller/securitysettingscontrollertest.php 查看文件

@@ -31,77 +31,6 @@ class SecuritySettingsControllerTest extends \PHPUnit_Framework_TestCase {
$this->securitySettingsController = $this->container['SecuritySettingsController'];
}


public function testEnforceSSLEmpty() {
$this->container['Config']
->expects($this->once())
->method('setSystemValue')
->with('forcessl', false);

$response = $this->securitySettingsController->enforceSSL();
$expectedResponse = array('status' => 'success');

$this->assertSame($expectedResponse, $response);
}

public function testEnforceSSL() {
$this->container['Config']
->expects($this->once())
->method('setSystemValue')
->with('forcessl', true);

$response = $this->securitySettingsController->enforceSSL(true);
$expectedResponse = array('status' => 'success');

$this->assertSame($expectedResponse, $response);
}

public function testEnforceSSLInvalid() {
$this->container['Config']
->expects($this->exactly(0))
->method('setSystemValue');

$response = $this->securitySettingsController->enforceSSL('blah');
$expectedResponse = array('status' => 'error');

$this->assertSame($expectedResponse, $response);
}

public function testEnforceSSLForSubdomainsEmpty() {
$this->container['Config']
->expects($this->once())
->method('setSystemValue')
->with('forceSSLforSubdomains', false);

$response = $this->securitySettingsController->enforceSSLForSubdomains();
$expectedResponse = array('status' => 'success');

$this->assertSame($expectedResponse, $response);
}

public function testEnforceSSLForSubdomains() {
$this->container['Config']
->expects($this->once())
->method('setSystemValue')
->with('forceSSLforSubdomains', true);

$response = $this->securitySettingsController->enforceSSLForSubdomains(true);
$expectedResponse = array('status' => 'success');

$this->assertSame($expectedResponse, $response);
}

public function testEnforceSSLForSubdomainsInvalid() {
$this->container['Config']
->expects($this->exactly(0))
->method('setSystemValue');

$response = $this->securitySettingsController->enforceSSLForSubdomains('blah');
$expectedResponse = array('status' => 'error');

$this->assertSame($expectedResponse, $response);
}

public function testTrustedDomainsWithExistingValues() {
$this->container['Config']
->expects($this->once())

Loading…
取消
儲存