Skip to content

Commit c48cd28

Browse files
committed
add SolidWebhook controller
1 parent 5739d0d commit c48cd28

1 file changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
3+
namespace OCA\Solid\Controller;
4+
5+
use OCA\Solid\AppInfo\Application;
6+
use OCA\Solid\Service\SolidWebhookService;
7+
use OCA\Solid\ServerConfig;
8+
use OCA\Solid\PlainResponse;
9+
use OCA\Solid\Notifications\SolidNotifications;
10+
11+
use OCP\AppFramework\Controller;
12+
use OCP\AppFramework\Http\DataResponse;
13+
use OCP\IRequest;
14+
use OCP\IUserManager;
15+
use OCP\IURLGenerator;
16+
use OCP\ISession;
17+
use OCP\IConfig;
18+
use OCP\Files\IRootFolder;
19+
use OCP\Files\IHomeStorage;
20+
use OCP\Files\SimpleFS\ISimpleRoot;
21+
use OCP\AppFramework\Http;
22+
use OCP\AppFramework\Http\Response;
23+
use OCP\AppFramework\Http\JSONResponse;
24+
use OCP\AppFramework\Http\ContentSecurityPolicy;
25+
26+
use Pdsinterop\Solid\Resources\Server as ResourceServer;
27+
use Pdsinterop\Solid\Auth\Utils\DPop as DPop;
28+
use Pdsinterop\Solid\Auth\WAC as WAC;
29+
30+
class SolidWebhookController extends Controller {
31+
/* @var IURLGenerator */
32+
private $urlGenerator;
33+
34+
/* @var ISession */
35+
private $session;
36+
37+
/** @var SolidWebhookService */
38+
private $webhookService;
39+
40+
public function __construct($AppName, IRootFolder $rootFolder, IRequest $request, ISession $session, IUserManager $userManager, IURLGenerator $urlGenerator, $userId, IConfig $config, SolidWebhookService $webhookService)
41+
{
42+
parent::__construct($AppName, $request);
43+
require_once(__DIR__.'/../../vendor/autoload.php');
44+
$this->config = new \OCA\Solid\ServerConfig($config, $urlGenerator, $userManager);
45+
$this->rootFolder = $rootFolder;
46+
$this->request = $request;
47+
$this->urlGenerator = $urlGenerator;
48+
$this->session = $session;
49+
$this->webhookService = $webhookService;
50+
51+
$this->DPop = new DPop();
52+
try {
53+
$this->webId = $this->DPop->getWebId($request);
54+
} catch(\Exception $e) {
55+
$response = $this->resourceServer->getResponse()->withStatus(409, "Invalid token");
56+
return $this->respond($response);
57+
}
58+
}
59+
60+
/**
61+
* @PublicPage
62+
* @NoAdminRequired
63+
* @NoCSRFRequired
64+
*/
65+
public function listWebhooks(): DataResponse {
66+
return new DataResponse($this->webhookService->findAll($this->webId));
67+
}
68+
69+
/**
70+
* @PublicPage
71+
* @NoAdminRequired
72+
* @NoCSRFRequired
73+
*/
74+
public function register(string $targetUrl, string $webhookUrl, string $expiry): DataResponse {
75+
// FIXME: Validate WAC read access to the target URL for $this->webId
76+
if ($this->checkReadAccess($targetUrl)) {
77+
return new DataResponse($this->webhookService->create($this->webId, $targetUrl, $webhookUrl, $expiry));
78+
}
79+
}
80+
81+
/**
82+
* @PublicPage
83+
* @NoAdminRequired
84+
* @NoCSRFRequired
85+
*/
86+
public function unregister(string $targetUrl): DataResponse {
87+
return $this->handleNotFound(function () use ($targetUrl) {
88+
return $this->webhookService->delete($this->webId, $targetUrl);
89+
});
90+
}
91+
92+
93+
private function getFileSystem() {
94+
// Create the Nextcloud Adapter
95+
$adapter = new \Pdsinterop\Flysystem\Adapter\Nextcloud($this->solidFolder);
96+
$graph = new \EasyRdf\Graph();
97+
98+
// Create Formats objects
99+
$formats = new \Pdsinterop\Rdf\Formats();
100+
101+
$serverUri = "https://" . $this->rawRequest->getServerParams()["SERVER_NAME"] . $this->rawRequest->getServerParams()["REQUEST_URI"]; // FIXME: doublecheck that this is the correct url;
102+
103+
// Create the RDF Adapter
104+
$rdfAdapter = new \Pdsinterop\Rdf\Flysystem\Adapter\Rdf(
105+
$adapter,
106+
$graph,
107+
$formats,
108+
$serverUri
109+
);
110+
111+
$filesystem = new \League\Flysystem\Filesystem($rdfAdapter);
112+
113+
$filesystem->addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats));
114+
115+
$plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph);
116+
$filesystem->addPlugin($plugin);
117+
118+
return $filesystem;
119+
}
120+
121+
private function getStorageUrl($userId) {
122+
$storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.storage.handleHead", array("userId" => $userId, "path" => "foo")));
123+
$storageUrl = preg_replace('/foo$/', '', $storageUrl);
124+
return $storageUrl;
125+
}
126+
private function getAppBaseUrl($userId) {
127+
$appBaseUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.app.appLauncher"));
128+
return $appBaseUrl;
129+
}
130+
private function initializeStorage($userId) {
131+
$this->userFolder = $this->rootFolder->getUserFolder($userId);
132+
$this->solidFolder = $this->userFolder->get("solid");
133+
$this->filesystem = $this->getFileSystem();
134+
}
135+
136+
private function parseTargetUrl($targetUrl) {
137+
// targetUrl = https://nextcloud.server/solid/@alice/storage/foo/bar
138+
$appBaseUrl = $this->getAppBaseUrl(); // https://nextcloud.server/solid/
139+
$internalUrl = str_replace($appBaseUrl, '', $targetUrl); // @alice/storage/foo/bar
140+
$pathicles = explode($internalUrl, "/");
141+
$userId = $pathicles[0]; // alice
142+
143+
$storageUrl = $this->getStorageUrl($userId); // https://nextcloud.server/solid/@alice/storage/
144+
$storagePath = str_replace($storageUrl, '/', $targetUrl); // /foo/bar
145+
return array("userId" => $userId, "path", $storagePath);
146+
}
147+
148+
private function createGetRequest($targetUrl) {
149+
$serverParams = [];
150+
$fileParams = [];
151+
$method = "GET";
152+
$body = null;
153+
$headers = [];
154+
155+
return new \Laminas\Diactoros\ServerRequest(
156+
$serverParams,
157+
$fileParams,
158+
$targetUrl,
159+
$method,
160+
$body,
161+
$headers
162+
);
163+
}
164+
165+
private function checkReadAccess($targetUrl) {
166+
// split out $targetUrl into $userId and $path https://nextcloud.server/solid/@alice/storage/foo/bar
167+
// - userId in this case is the pod owner (not the one doing the request). (alice)
168+
// - path is the path within the storage pod (/foo/bar)
169+
$target = $this->parseTargetUrl($targetUrl);
170+
$userId = $target["userId"];
171+
$path = $target["path"];
172+
173+
$this->initializeStorage($userId);
174+
$this->WAC = new WAC($this->filesystem);
175+
176+
$baseUrl = $this->getStorageUrl($userId);
177+
$this->WAC->setBaseUrl($baseUrl);
178+
179+
$serverParams = [];
180+
$fileParams = [];
181+
182+
$request = $this->createGetRequest($targetUrl);
183+
if (!$this->WAC->isAllowed($request, $this->webId)) { // Deny if we don't have read grants on the URL;
184+
return false;
185+
}
186+
return true;
187+
}
188+
}

0 commit comments

Comments
 (0)