build / build (push) Failing after 1m56s
bawnet repos are forward facing: a bit of code we made and use, available. References to the private ops/features tracker and ops/psn-docker README become plain words, a named operator quote is gone, the bugs link points at this repo's own issues. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
94 lines
3.2 KiB
PHP
94 lines
3.2 KiB
PHP
<?php
|
|
/**
|
|
* Nextcloud - GoToSocial
|
|
*
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
* later. See the COPYING file.
|
|
*/
|
|
|
|
namespace OCA\GoToSocial\Controller;
|
|
|
|
use OCA\GoToSocial\AppInfo\Application;
|
|
use OCP\AppFramework\Controller;
|
|
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
|
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
|
use OCP\AppFramework\Http\TemplateResponse;
|
|
use OCP\AppFramework\Services\IAppConfig;
|
|
use OCP\IConfig;
|
|
use OCP\IRequest;
|
|
|
|
/**
|
|
* The Social page: Pinnacle inside the Nextcloud frame.
|
|
*
|
|
* SINCE 1.1.0 (the fixed-instance build): Pinnacle is no
|
|
* longer bundled here. It ships INSIDE the node GoToSocial image and GTS
|
|
* serves it at <instance>/pinnacle/ (patch/gotosocial 0004). This app keeps
|
|
* only what Nextcloud is for: the dashboard widgets, and this one framed page
|
|
* so a widget click still lands on that exact status/account/notification
|
|
* inside Nextcloud (?path=/statuses/<id>).
|
|
*
|
|
* Removed with the bundle: the /client/* file server, its CSP carve-out, the
|
|
* NC-theming colour injection (Pinnacle now carries social.bawnet.io's amber),
|
|
* and the auth bridge (NC could inject its token only into pages NC served —
|
|
* Pinnacle now does its own login once, top-level, and the browser keeps it).
|
|
*
|
|
* Framing works because Pinnacle's own CSP allows it: frame-ancestors 'self'
|
|
* https://cloud.bawnet.io (bawnet/pinnacle build-gts, pinnacle-headers.json).
|
|
*/
|
|
class ClientController extends Controller {
|
|
|
|
public function __construct(
|
|
string $appName,
|
|
IRequest $request,
|
|
private IAppConfig $appConfig,
|
|
private IConfig $config,
|
|
private ?string $userId,
|
|
) {
|
|
parent::__construct($appName, $request);
|
|
}
|
|
|
|
/**
|
|
* The GoToSocial instance this user is connected to (their own setting,
|
|
* else the admin default) — the same resolution the widgets use.
|
|
*/
|
|
private function gtsUrl(): string {
|
|
$adminUrl = $this->appConfig->getAppValueString('oauth_instance_url', lazy: true);
|
|
if ($this->userId === null) {
|
|
return $adminUrl;
|
|
}
|
|
return $this->config->getUserValue($this->userId, Application::APP_ID, 'url', $adminUrl) ?: $adminUrl;
|
|
}
|
|
|
|
#[NoAdminRequired]
|
|
#[NoCSRFRequired]
|
|
public function page(string $path = ''): TemplateResponse {
|
|
$scheme = parse_url($this->gtsUrl(), PHP_URL_SCHEME) ?: 'https';
|
|
$host = parse_url($this->gtsUrl(), PHP_URL_HOST);
|
|
$port = parse_url($this->gtsUrl(), PHP_URL_PORT);
|
|
$origin = $host ? $scheme . '://' . $host . ($port ? ':' . $port : '') : '';
|
|
$clientUrl = $origin . '/pinnacle/';
|
|
|
|
// deep link from the dashboard widgets: ?path=/statuses/<id> lands the
|
|
// framed client on that route. Strict allowlist: absolute client-route
|
|
// paths only, no traversal, no scheme, no query — anything else falls
|
|
// back to the client root.
|
|
if ($path !== ''
|
|
&& preg_match('#^/[A-Za-z0-9_@.~/-]*$#', $path)
|
|
&& !str_contains($path, '..')
|
|
&& !str_contains($path, '//')) {
|
|
$clientUrl .= ltrim($path, '/');
|
|
}
|
|
|
|
$response = new TemplateResponse(Application::APP_ID, 'clientPage', [
|
|
'clientUrl' => $clientUrl,
|
|
]);
|
|
$csp = new ContentSecurityPolicy();
|
|
if ($origin !== '') {
|
|
$csp->addAllowedFrameDomain($origin);
|
|
}
|
|
$response->setContentSecurityPolicy($csp);
|
|
return $response;
|
|
}
|
|
}
|