Skip to content

Commit

Permalink
fix(GS): consolidate and extend CSP management
Browse files Browse the repository at this point in the history
based on richdocument`s 398d165a5d8ff63fcc43d2e6965b1f4b4c24cada

Signed-off-by: Arthur Schiwon <[email protected]>
  • Loading branch information
blizzz committed May 16, 2024
1 parent ce45099 commit 361c58d
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 109 deletions.
84 changes: 82 additions & 2 deletions lib/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
namespace OCA\Officeonline;

use OCA\Officeonline\AppInfo\Application;
use \OCP\IConfig;
use OCA\Officeonline\Service\FederationService;
use OCP\App\IAppManager;
use OCP\GlobalScale\IConfig as GlobalScaleConfig;
use OCP\IConfig;

class AppConfig {
private $defaults = [
Expand All @@ -38,12 +41,22 @@ class AppConfig {
];

public const APP_SETTING_TYPES = [];
public const SYSTEM_GS_TRUSTED_HOSTS = 'gs.trustedHosts';
public const FEDERATION_USE_TRUSTED_DOMAINS = 'federation_use_trusted_domains';

/** @var IConfig */
private $config;
private IAppManager $appManager;
private GlobalScaleConfig $globalScaleConfig;

public function __construct(IConfig $config) {
public function __construct(
IConfig $config,
IAppManager $appManager,
GlobalScaleConfig $globalScaleConfig,
) {
$this->config = $config;
$this->appManager = $appManager;
$this->globalScaleConfig = $globalScaleConfig;
}

public function getAppNamespace($key): string {
Expand Down Expand Up @@ -80,4 +93,71 @@ public function getAppSettings(): array {
}
return $result;
}

public function getDomainList(): array {
$urls = array_merge(
[ $this->domainOnly($this->getCollaboraUrlPublic()) ],
$this->getFederationDomains(),
$this->getGSDomains()
);

return array_map(fn ($url) => idn_to_ascii($url), array_filter($urls));
}

public function domainOnly(string $url): string {
$parsedUrl = parse_url($url);
$scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'] . '://' : '';
$host = $parsedUrl['host'] ?? '';
$port = isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';
return "$scheme$host$port";
}

public function getCollaboraUrlPublic(): string {
return $this->config->getAppValue(Application::APP_ID, 'public_wopi_url', $this->getCollaboraUrlInternal());
}

public function getCollaboraUrlInternal(): string {
return $this->config->getAppValue(Application::APP_ID, 'wopi_url', '');
}

private function getFederationDomains(): array {
if (!$this->appManager->isEnabledForUser('federation')) {
return [];
}

/** @var FederationService $federationService */
$federationService = \OCP\Server::get(FederationService::class);
$trustedNextcloudDomains = array_filter(array_map(function ($server) use ($federationService) {
return $federationService->isTrustedRemote($server) ? $server : null;
}, $federationService->getTrustedServers()));

$trustedCollaboraDomains = array_filter(array_map(function ($server) use ($federationService) {
try {
return $federationService->getRemoteCollaboraURL($server);
} catch (\Exception $e) {
// If there is no remote collabora server we can just skip that
return null;
}
}, $trustedNextcloudDomains));

return array_map(function ($url) {
return $this->domainOnly($url);
}, array_merge($trustedNextcloudDomains, $trustedCollaboraDomains));
}

private function getGSDomains(): array {
if (!$this->globalScaleConfig->isGlobalScaleEnabled()) {
return [];
}

return $this->getGlobalScaleTrustedHosts();
}

public function getGlobalScaleTrustedHosts(): array {
return $this->config->getSystemValue(self::SYSTEM_GS_TRUSTED_HOSTS, []);
}

public function isTrustedDomainAllowedForFederation(): bool {
return $this->config->getAppValue(Application::APP_ID, self::FEDERATION_USE_TRUSTED_DOMAINS, 'no') === 'yes';
}
}
66 changes: 6 additions & 60 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,30 @@
namespace OCA\Officeonline\AppInfo;

use OC\Files\Type\Detection;
use OC\Security\CSP\ContentSecurityPolicy;
use OCA\Federation\TrustedServers;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Officeonline\Capabilities;
use OCA\Officeonline\Hooks\WopiLockHooks;
use OCA\Officeonline\Listener\SharingLoadAdditionalScriptsListener;
use OCA\Officeonline\Listener\AddContentSecurityPolicyListener;
use OCA\Officeonline\Listener\LoadViewerListener;
use OCA\Officeonline\Listener\SharingLoadAdditionalScriptsListener;
use OCA\Officeonline\Middleware\WOPIMiddleware;
use OCA\Officeonline\PermissionManager;
use OCA\Officeonline\Preview\MSExcel;
use OCA\Officeonline\Preview\MSWord;
use OCA\Officeonline\Preview\OOXML;
use OCA\Officeonline\Preview\OpenDocument;
use OCA\Officeonline\Preview\Pdf;
use OCA\Officeonline\Service\FederationService;
use OCA\Viewer\Event\LoadViewer;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Files\Template\ITemplateManager;
use OCP\Files\Template\TemplateFileCreator;
use OCP\IPreview;
use Psr\Log\LoggerInterface;
use OCP\IL10N;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
use OCP\Security\CSP\AddContentSecurityPolicyEvent;

class Application extends App implements IBootstrap {
public const APP_ID = 'officeonline';
Expand All @@ -62,6 +60,7 @@ public function __construct(array $urlParams = []) {
public function register(IRegistrationContext $context): void {
$context->registerCapability(Capabilities::class);
$context->registerMiddleWare(WOPIMiddleware::class);
$context->registerEventListener(AddContentSecurityPolicyEvent::class, AddContentSecurityPolicyListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, SharingLoadAdditionalScriptsListener::class);
$context->registerEventListener(LoadViewer::class, LoadViewerListener::class);
}
Expand All @@ -72,7 +71,6 @@ public function boot(IBootContext $context): void {
}

$this->registerProvider();
$this->updateCSP();
$this->registerNewFileCreators($context);
}

Expand Down Expand Up @@ -126,58 +124,6 @@ public function registerProvider() {
$container->query(WopiLockHooks::class)->register();
}

public function updateCSP() {
if (\OC::$CLI) {
return;
}

$container = $this->getContainer();

$publicWopiUrl = \OC::$server->getConfig()->getAppValue('officeonline', 'wopi_url');
$publicWopiUrl = $publicWopiUrl === '' ? \OC::$server->getConfig()->getAppValue('officeonline', 'wopi_url') : $publicWopiUrl;
$cspManager = $container->getServer()->getContentSecurityPolicyManager();
$policy = new ContentSecurityPolicy();
if ($publicWopiUrl !== '') {
$policy->addAllowedFrameDomain('\'self\'');
$policy->addAllowedFrameDomain($this->domainOnly($publicWopiUrl));
if (method_exists($policy, 'addAllowedFormActionDomain')) {
$policy->addAllowedFormActionDomain($this->domainOnly($publicWopiUrl));
}
if (method_exists($policy, 'allowInlineScript')) {
$policy->allowInlineScript(true);
}
}

/**
* Dynamically add CSP for federated editing
*/
$path = '';
try {
$path = $container->getServer()->getRequest()->getPathInfo();

if (strpos($path, '/apps/files') === 0 && $container->getServer()->getAppManager()->isEnabledForUser('federation')) {
/** @var TrustedServers $trustedServers */
$trustedServers = $container->query(TrustedServers::class);
/** @var FederationService $federationService */
$federationService = $container->query(FederationService::class);
$remoteAccess = $container->getServer()->getRequest()->getParam('officeonline_remote_access');

if ($remoteAccess && $trustedServers->isTrustedServer($remoteAccess)) {
$remoteCollabora = $federationService->getRemoteCollaboraURL($remoteAccess);
$policy->addAllowedFrameDomain($remoteAccess);
$policy->addAllowedFrameDomain($remoteCollabora);
}
}
} catch (\Throwable $e) {
\OC::$server->query(LoggerInterface::class)->warning('Failed to gather federation hosts for CSP', [
'exception' => $e,
'app' => 'officeonline'
]);
}

$cspManager->addDefaultPolicy($policy);
}

/**
* Strips the path and query parameters from the URL.
*
Expand Down
60 changes: 14 additions & 46 deletions lib/Controller/DocumentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@

namespace OCA\Officeonline\Controller;

use OC\Files\Type\TemplateManager;
use OCA\Officeonline\AppConfig;
use OCA\Officeonline\Helper;
use OCA\Officeonline\Service\FederationService;
use OCA\Officeonline\TokenManager;
use \OCP\AppFramework\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Constants;
use OCP\Files\File;
use OCP\Files\Folder;
Expand All @@ -25,18 +29,13 @@
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use \OCP\IRequest;
use \OCP\IConfig;
use \OCP\IL10N;
use \OCP\ILogger;
use \OCP\AppFramework\Http\ContentSecurityPolicy;
use \OCP\AppFramework\Http\TemplateResponse;
use \OCA\Officeonline\AppConfig;
use \OCA\Officeonline\Helper;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ISession;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OC\Files\Type\TemplateManager;

class DocumentController extends Controller {
/** @var string */
Expand Down Expand Up @@ -238,7 +237,7 @@ public function index($fileId, $path = null) {
return $response;
}

[$urlSrc, $token, $wopi] = $this->tokenManager->getToken($item->getId());
[$urlSrc, $token] = $this->tokenManager->getToken($item->getId());
$params = [
'permissions' => $item->getPermissions(),
'title' => $item->getName(),
Expand All @@ -262,13 +261,6 @@ public function index($fileId, $path = null) {
}

$response = new TemplateResponse('officeonline', 'documents', $params, 'base');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
$policy->addAllowedScriptDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
if (method_exists($policy, 'allowInlineScript')) {
$policy->allowInlineScript(true);
}
$response->setContentSecurityPolicy($policy);
$response->addHeader('Cache-Control', 'no-cache, no-store');
$response->addHeader('Expires', '-1');
$response->addHeader('Pragma', 'no-cache');
Expand Down Expand Up @@ -330,15 +322,7 @@ public function createFromTemplate($templateId, $fileName, $dir) {
'userId' => $this->uid
];

$response = new TemplateResponse('officeonline', 'documents', $params, 'base');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
$policy->addAllowedScriptDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
if (method_exists($policy, 'allowInlineScript')) {
$policy->allowInlineScript(true);
}
$response->setContentSecurityPolicy($policy);
return $response;
return new TemplateResponse('officeonline', 'documents', $params, 'base');
}

/**
Expand Down Expand Up @@ -385,15 +369,7 @@ public function publicPage($shareToken, $fileName, $fileId) {
$params['urlsrc'] = $urlSrc;
}

$response = new TemplateResponse('officeonline', 'documents', $params, 'base');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
$policy->addAllowedScriptDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
if (method_exists($policy, 'allowInlineScript')) {
$policy->allowInlineScript(true);
}
$response->setContentSecurityPolicy($policy);
return $response;
return new TemplateResponse('officeonline', 'documents', $params, 'base');
}
} catch (\Exception $e) {
$this->logger->logException($e, ['app' => 'officeonline']);
Expand Down Expand Up @@ -453,14 +429,6 @@ public function remote($shareToken, $remoteServer, $remoteServerToken, $filePath
];

$response = new TemplateResponse('officeonline', 'documents', $params, 'base');
$policy = new ContentSecurityPolicy();
$policy->addAllowedFrameDomain($this->domainOnly($this->appConfig->getAppValue('wopi_url')));
$policy->addAllowedScriptDomain($this->domainOnly($this->appConfig->getAppValue('public_wopi_url')));
$policy->addAllowedFrameAncestorDomain('https://*');
if (method_exists($policy, 'allowInlineScript')) {
$policy->allowInlineScript(true);
}
$response->setContentSecurityPolicy($policy);
$response->addHeader('X-Frame-Options', 'ALLOW');
return $response;
}
Expand All @@ -485,8 +453,8 @@ public function remote($shareToken, $remoteServer, $remoteServerToken, $filePath
* @throws GenericFileException
*/
public function create($mimetype,
$filename,
$dir = '/') {
$filename,
$dir = '/') {
$root = $this->rootFolder->getUserFolder($this->uid);
try {
/** @var Folder $folder */
Expand Down
Loading

0 comments on commit 361c58d

Please sign in to comment.