Skip to content

Commit

Permalink
part2: proxying JS files from ExApp
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Piskun <[email protected]>
  • Loading branch information
bigcat88 committed Nov 29, 2023
1 parent 46d095f commit 935fa0d
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 115 deletions.
19 changes: 8 additions & 11 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
['name' => 'MenuEntry#ExAppIcon', 'url' => '/embedded/{appId}/{name}/icon', 'verb' => 'GET' , 'root' => '/embedded'],

// Proxy
// ['name' => 'MenuEntry#ExAppProxySubLinksGet',
// 'url' => '/proxying/{appId}/{other}', 'verb' => 'GET' , 'root' => '/proxying',
// 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')],
// ['name' => 'MenuEntry#ExAppProxySubLinksPost',
// 'url' => '/proxying/{appId}/{other}', 'verb' => 'POST' , 'root' => '/proxying',
// 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')],
// ['name' => 'MenuEntry#ExAppProxySubLinksPut',
// 'url' => '/proxying/{appId}/{other}', 'verb' => 'PUT' , 'root' => '/proxying',
// 'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')],
['name' => 'MenuEntry#ExAppProxySubLinksGet',
'url' => '/proxy/css/{appId}/{other}', 'verb' => 'GET' , 'root' => '',
['name' => 'MenuEntry#ExAppProxyGet',
'url' => '/proxy/{appId}/{other}', 'verb' => 'GET' , 'root' => '/proxy',
'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')],
['name' => 'MenuEntry#ExAppProxyPost',
'url' => '/proxy/{appId}/{other}', 'verb' => 'POST' , 'root' => '/proxying',
'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')],
['name' => 'MenuEntry#ExAppProxyPut',
'url' => '/proxy/{appId}/{other}', 'verb' => 'PUT' , 'root' => '/proxying',
'requirements' => array('other' => '.+'), 'defaults' => array('other' => '')],

// ExApps actions
Expand Down
89 changes: 9 additions & 80 deletions lib/Controller/MenuEntryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@

namespace OCA\AppAPI\Controller;

use DOMDocument;
use DOMException;
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Attribute\AppAPIAuth;
use OCA\AppAPI\ProxyResponse;
use OCA\AppAPI\Service\AppAPIService;
use OCA\AppAPI\Service\ExAppMenuEntryService;
use OCA\AppAPI\Service\ExAppInitialStateService;
use OCA\AppAPI\Service\ExAppMenuEntryService;
use OCA\AppAPI\Service\ExAppScriptsService;
use OCA\AppAPI\Service\ExAppStylesService;
use OCP\AppFramework\Controller;
Expand All @@ -31,10 +29,12 @@
use OCP\Http\Client\IResponse;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\Util;

class MenuEntryController extends Controller {

public bool $postprocess = false;
public array $jsProxyMap = [];

public function __construct(
IRequest $request,
private IInitialState $initialState,
Expand Down Expand Up @@ -74,64 +74,14 @@ public function viewExAppPage(string $appId, string $name): TemplateResponse {
foreach ($initialStates as $key => $value) {
$this->initialState->provideInitialState($key, $value);
}
$this->scriptsService->applyExAppScripts($appId, 'top_menu');
$this->jsProxyMap = $this->scriptsService->applyExAppScripts($appId, 'top_menu');
$this->stylesService->applyExAppStyles($appId, 'top_menu');

$this->postprocess = true;
$response = new TemplateResponse(Application::APP_ID, 'main');
return $response;
}

/**
* @NoCSRFRequired
* @NoAdminRequired
* @throws DOMException
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function ExAppIframeProxy(string $appId, string $name): Response {
$exApp = $this->service->getExApp($appId);
if ($exApp === null) {
return new NotFoundResponse();
}
if (!$exApp->getEnabled()) {
return new NotFoundResponse();
}
$menuEntry = $this->menuEntryService->getExAppMenuEntry($appId, $name);
if ($menuEntry === null) {
return new NotFoundResponse();
}
$response = $this->service->aeRequestToExApp(
$exApp, $menuEntry->getRoute(), $this->userId, 'GET', request: $this->request
);
if (is_array($response)) {
$error_response = new Response();
return $error_response->setStatus(500);
}
$reHeaders = [];
foreach ($response->getHeaders() as $k => $values) {
$reHeaders[$k] = count($values) === 1 ? $values[0] : $values;
}
$reHeaders['content-security-policy'] = 'frame-ancestors *;';

$dom = new DOMDocument();
@$dom->loadHTML($response->getBody(), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$base = $dom->createElement('base');
$base->setAttribute(
'href',
$this->url->getAbsoluteURL('index.php/apps/app_api/proxying/'. $exApp->getAppid() . '/')
);
$base->setAttribute('target', '_parent');
$head = $dom->getElementsByTagName('head')->item(0);
if ($head) {
if ($head->hasChildNodes()) {
$head->insertBefore($base, $head->firstChild);
} else {
$head->appendChild($base);
}
}
return new DataDisplayResponse($dom->saveHTML(), $response->getStatusCode(), $reHeaders);
}

private function createProxyResponse(string $path, IResponse $response, $cache = true): ProxyResponse {
$content = $response->getBody();
$isHTML = pathinfo($path, PATHINFO_EXTENSION) === 'html';
Expand Down Expand Up @@ -181,28 +131,7 @@ private function createProxyResponse(string $path, IResponse $response, $cache =

#[NoAdminRequired]
#[NoCSRFRequired]
public function ExAppProxyCss(string $appId, string $other): Response {
$exApp = $this->service->getExApp($appId);
if ($exApp === null) {
return new NotFoundResponse();
}
if (!$exApp->getEnabled()) {
return new NotFoundResponse();
}

$response = $this->service->aeRequestToExApp(
$exApp, '/' . $other, $this->userId, 'GET', request: $this->request
);
if (is_array($response)) {
$error_response = new Response();
return $error_response->setStatus(500);
}
return $this->createProxyResponse($other, $response);
}

#[NoAdminRequired]
#[NoCSRFRequired]
public function ExAppProxySubLinksGet(string $appId, string $other): Response {
public function ExAppProxyGet(string $appId, string $other): Response {
$exApp = $this->service->getExApp($appId);
if ($exApp === null) {
return new NotFoundResponse();
Expand All @@ -223,7 +152,7 @@ public function ExAppProxySubLinksGet(string $appId, string $other): Response {

#[NoAdminRequired]
#[NoCSRFRequired]
public function ExAppIframeProxySubLinksPost(string $appId, string $other): Response {
public function ExAppProxyPost(string $appId, string $other): Response {
$exApp = $this->service->getExApp($appId);
if ($exApp === null) {
return new NotFoundResponse();
Expand All @@ -246,7 +175,7 @@ public function ExAppIframeProxySubLinksPost(string $appId, string $other): Resp

#[NoAdminRequired]
#[NoCSRFRequired]
public function ExAppIframeProxySubLinksPut(string $appId, string $other): Response {
public function ExAppProxyPut(string $appId, string $other): Response {
$exApp = $this->service->getExApp($appId);
if ($exApp === null) {
return new NotFoundResponse();
Expand Down
3 changes: 2 additions & 1 deletion lib/Db/UI/InitialStateMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public function findByAppIdType(string $appId, string $type): array {
->where(
$qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR)),
$qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_STR))
)->executeQuery();;
)->executeQuery();
;
return $result->fetchAll();
}
}
3 changes: 2 additions & 1 deletion lib/Db/UI/ScriptMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public function findByAppIdType(string $appId, string $type): array {
->where(
$qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR)),
$qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_STR))
)->executeQuery();;
)->executeQuery();
;
return $result->fetchAll();
}
}
3 changes: 2 additions & 1 deletion lib/Db/UI/StyleMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public function findByAppIdType(string $appId, string $type): array {
->where(
$qb->expr()->eq('appid', $qb->createNamedParameter($appId, IQueryBuilder::PARAM_STR)),
$qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_STR))
)->executeQuery();;
)->executeQuery();
;
return $result->fetchAll();
}
}
22 changes: 11 additions & 11 deletions lib/Middleware/ExAppUiMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@

namespace OCA\AppAPI\Middleware;

use Exception;
use OCA\AppAPI\Attribute\AppAPIAuth;
use OCA\AppAPI\Controller\MenuEntryController;
use OCA\AppAPI\Exceptions\AppAPIAuthNotValidException;
use OCA\AppAPI\Service\AppAPIService;

use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\IL10N;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
use ReflectionMethod;

class ExAppUiMiddleware extends Middleware {

Expand All @@ -31,12 +24,19 @@ public function __construct(
}

public function beforeOutput(Controller $controller, string $methodName, string $output) {
if ($controller instanceof MenuEntryController) {
$adjustedCss = preg_replace(
'/(href=")(\/.*?)(\/app_api\/css\/)(proxy\/css\/.*css")/',
if (($controller instanceof MenuEntryController) && ($controller->postprocess)) {
$correctedOutput = preg_replace(
'/(href=")(\/.*?)(\/app_api\/css\/)(proxy\/.*css")/',
'$1/index.php/apps/app_api/$4',
$output);
return $adjustedCss;
foreach ($controller->jsProxyMap as $key => $value) {
$correctedOutput = preg_replace(
'/(src=")(\/.*?)(\/app_api\/js\/)(proxy_js\/' . $key . '.js")/',
'$1/index.php/apps/app_api/proxy/' . $value . '.js"',
$correctedOutput,
limit: 1);
}
return $correctedOutput;
}
return $output;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/ProxyResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class ProxyResponse extends Response implements ICallbackResponse {
private mixed $data;

public function __construct(int $status = HttpAlias::STATUS_OK,
array $headers = [], mixed $data = null, int $length = 0,
string $mimeType = '', int $lastModified = 0) {
array $headers = [], mixed $data = null, int $length = 0,
string $mimeType = '', int $lastModified = 0) {
parent::__construct();
$this->data = $data;
$this->setStatus($status);
Expand Down
24 changes: 20 additions & 4 deletions lib/Service/ExAppScriptsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace OCA\AppAPI\Service;

use LengthException;
use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Db\UI\ScriptMapper;
use OCP\DB\Exception;
Expand All @@ -12,6 +13,8 @@

class ExAppScriptsService {

public const MAX_JS_FILES = 20 + 1; //should be equal to number of files in "proxy_js" folder.

public function __construct(
private ScriptMapper $mapper,
private LoggerInterface $logger,
Expand All @@ -29,16 +32,29 @@ public function clearExAppScripts(string $appId, string $type) {
/**
* @throws Exception
*/
public function applyExAppScripts(string $appId, string $type): void {
public function applyExAppScripts(string $appId, string $type): array {
// TODO: Add caching
$mapResult = [];
$scripts = $this->mapper->findByAppIdType($appId, $type);
if (count($scripts) > self::MAX_JS_FILES) {
throw new LengthException('More than' . self::MAX_JS_FILES . 'JS files on one page are not supported.');
}

$i = 0;
foreach ($scripts as $value) {
$fakeJsPath = 'proxy_js/' . $i;
if (is_null($value['after_app_id'])) {
Util::addScript(Application::APP_ID, $value['path']);
Util::addScript(Application::APP_ID, $fakeJsPath);
} else {
Util::addScript(Application::APP_ID, $fakeJsPath, $value['after_app_id']);
}
else {
Util::addScript(Application::APP_ID, $value['path'], $value['after_app_id']);
if (str_starts_with($value['path'], '/')) {
$mapResult[$i] = $appId . $value['path'];
} else {
$mapResult[$i] = $appId . '/' . $value['path'];
}
$i++;
}
return $mapResult;
}
}
7 changes: 3 additions & 4 deletions lib/Service/ExAppStylesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ public function applyExAppStyles(string $appId, string $type): void {
foreach ($styles as $value) {
if (str_starts_with($value['path'], '/')) {
// in the future we should allow offload of styles to the NC instance if they start with '/'
$path = 'proxy/css/'. $appId . $value['path'];
}
else {
$path = 'proxy/css/'. $appId . '/' . $value['path'];
$path = 'proxy/'. $appId . $value['path'];
} else {
$path = 'proxy/'. $appId . '/' . $value['path'];
}
Util::addStyle(Application::APP_ID, $path);
}
Expand Down
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<referencedClass name="OCA\DAV\Events\SabrePluginAuthInitEvent" />
<referencedClass name="GuzzleHttp\Client" />
<referencedClass name="GuzzleHttp\Exception\GuzzleException" />
<referencedClass name="OC\Security\CSP\ContentSecurityPolicyNonceManager" />
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
Expand Down

0 comments on commit 935fa0d

Please sign in to comment.