@fleetbase/solid-engine 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/ACL_SOLUTION.md +72 -0
  2. package/CSS_SCOPE_ISSUE.md +140 -0
  3. package/HOTFIX_SYNTAX_ERROR.md +100 -0
  4. package/MANUAL_ACL_SETUP.md +135 -0
  5. package/REFACTORING_SUMMARY.md +330 -0
  6. package/VERIFICATION_CHECKLIST.md +82 -0
  7. package/addon/components/modals/create-solid-folder.hbs +29 -0
  8. package/addon/components/modals/import-solid-resources.hbs +85 -0
  9. package/addon/controllers/data/content.js +17 -0
  10. package/addon/controllers/data/index.js +219 -0
  11. package/addon/controllers/home.js +84 -0
  12. package/addon/engine.js +1 -24
  13. package/addon/extension.js +26 -0
  14. package/addon/routes/data/content.js +11 -0
  15. package/addon/routes/data/index.js +17 -0
  16. package/addon/routes.js +2 -7
  17. package/addon/styles/solid-engine.css +1 -2
  18. package/addon/templates/account.hbs +3 -3
  19. package/addon/templates/application.hbs +2 -12
  20. package/addon/templates/data/content.hbs +48 -0
  21. package/addon/templates/{pods/explorer.hbs → data/index.hbs} +6 -5
  22. package/addon/templates/home.hbs +139 -10
  23. package/app/components/modals/{backup-pod.js → create-solid-folder.js} +1 -1
  24. package/app/components/modals/{resync-pod.js → import-solid-resources.js} +1 -1
  25. package/app/components/modals/{create-pod.js → setup-css-credentials.js} +1 -1
  26. package/composer.json +4 -10
  27. package/extension.json +4 -2
  28. package/index.js +0 -11
  29. package/package.json +9 -8
  30. package/server/migrations/2024_12_21_add_css_credentials_to_solid_identities_table.php +32 -0
  31. package/server/src/Client/OpenIDConnectClient.php +686 -15
  32. package/server/src/Client/SolidClient.php +104 -8
  33. package/server/src/Http/Controllers/DataController.php +318 -0
  34. package/server/src/Http/Controllers/OIDCController.php +42 -8
  35. package/server/src/Http/Controllers/SolidController.php +179 -85
  36. package/server/src/Models/SolidIdentity.php +13 -3
  37. package/server/src/Services/AclService.php +405 -0
  38. package/server/src/Services/PodService.php +910 -0
  39. package/server/src/Services/ResourceSyncService.php +344 -0
  40. package/server/src/Services/VehicleSyncService.php +289 -0
  41. package/server/src/Support/Utils.php +10 -0
  42. package/server/src/routes.php +25 -1
  43. package/addon/components/modals/backup-pod.hbs +0 -3
  44. package/addon/components/modals/create-pod.hbs +0 -5
  45. package/addon/components/modals/resync-pod.hbs +0 -3
  46. package/addon/controllers/pods/explorer/content.js +0 -12
  47. package/addon/controllers/pods/explorer.js +0 -149
  48. package/addon/controllers/pods/index/pod.js +0 -12
  49. package/addon/controllers/pods/index.js +0 -137
  50. package/addon/routes/pods/explorer/content.js +0 -10
  51. package/addon/routes/pods/explorer.js +0 -44
  52. package/addon/routes/pods/index/pod.js +0 -3
  53. package/addon/routes/pods/index.js +0 -21
  54. package/addon/templates/pods/explorer/content.hbs +0 -19
  55. package/addon/templates/pods/index/pod.hbs +0 -11
  56. package/addon/templates/pods/index.hbs +0 -19
  57. package/server/src/LegacyClient/Identity/IdentityProvider.php +0 -174
  58. package/server/src/LegacyClient/Identity/Profile.php +0 -18
  59. package/server/src/LegacyClient/OIDCClient.php +0 -350
  60. package/server/src/LegacyClient/Profile/WebID.php +0 -26
  61. package/server/src/LegacyClient/SolidClient.php +0 -271
@@ -0,0 +1,405 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Solid\Services;
4
+
5
+ use Fleetbase\Solid\Models\SolidIdentity;
6
+ use Illuminate\Support\Facades\Log;
7
+
8
+ class AclService
9
+ {
10
+ protected PodService $podService;
11
+
12
+ public function __construct(PodService $podService)
13
+ {
14
+ $this->podService = $podService;
15
+ }
16
+
17
+ /**
18
+ * Check if the pod root has write/append permissions
19
+ */
20
+ public function hasWritePermissions(SolidIdentity $identity, string $podUrl): bool
21
+ {
22
+ try {
23
+ $response = $identity->request('get', $podUrl);
24
+
25
+ if (!$response->successful()) {
26
+ Log::warning('[ACL CHECK FAILED]', [
27
+ 'pod_url' => $podUrl,
28
+ 'status' => $response->status(),
29
+ ]);
30
+ return false;
31
+ }
32
+
33
+ $wacAllow = $response->header('WAC-Allow');
34
+ Log::info('[ACL CHECK]', [
35
+ 'pod_url' => $podUrl,
36
+ 'wac_allow' => $wacAllow,
37
+ ]);
38
+
39
+ // Check if WAC-Allow header includes write or append
40
+ if ($wacAllow) {
41
+ return str_contains($wacAllow, 'write') || str_contains($wacAllow, 'append');
42
+ }
43
+
44
+ return false;
45
+ } catch (\Exception $e) {
46
+ Log::error('[ACL CHECK ERROR]', [
47
+ 'pod_url' => $podUrl,
48
+ 'error' => $e->getMessage(),
49
+ ]);
50
+ return false;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Update the pod root ACL to grant write permissions
56
+ */
57
+ public function grantWritePermissions(SolidIdentity $identity, string $podUrl, string $webId): bool
58
+ {
59
+ try {
60
+ $aclUrl = rtrim($podUrl, '/') . '/.acl';
61
+
62
+ // Generate ACL Turtle document
63
+ $aclTurtle = $this->generateAclDocument($podUrl, $webId);
64
+
65
+ Log::info('[ACL UPDATE]', [
66
+ 'acl_url' => $aclUrl,
67
+ 'webid' => $webId,
68
+ ]);
69
+
70
+ // PUT the ACL document
71
+ $response = $identity->request('put', $aclUrl, $aclTurtle, [
72
+ 'headers' => [
73
+ 'Content-Type' => 'text/turtle',
74
+ ],
75
+ ]);
76
+
77
+ if ($response->successful()) {
78
+ Log::info('[ACL UPDATED]', [
79
+ 'acl_url' => $aclUrl,
80
+ 'status' => $response->status(),
81
+ ]);
82
+ return true;
83
+ }
84
+
85
+ Log::error('[ACL UPDATE FAILED]', [
86
+ 'acl_url' => $aclUrl,
87
+ 'status' => $response->status(),
88
+ 'body' => $response->body(),
89
+ ]);
90
+ return false;
91
+
92
+ } catch (\Exception $e) {
93
+ Log::error('[ACL UPDATE ERROR]', [
94
+ 'pod_url' => $podUrl,
95
+ 'error' => $e->getMessage(),
96
+ 'trace' => $e->getTraceAsString(),
97
+ ]);
98
+ return false;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Generate ACL Turtle document
104
+ */
105
+ protected function generateAclDocument(string $podUrl, string $webId): string
106
+ {
107
+ $podUrl = rtrim($podUrl, '/') . '/';
108
+
109
+ return <<<TURTLE
110
+ @prefix acl: <http://www.w3.org/ns/auth/acl#>.
111
+
112
+ # Full rights for the pod owner
113
+ <#owner>
114
+ a acl:Authorization;
115
+ acl:agent <{$webId}>;
116
+ acl:accessTo <{$podUrl}>;
117
+ acl:default <{$podUrl}>;
118
+ acl:mode acl:Read, acl:Write, acl:Control.
119
+
120
+ # Append and read rights for Fleetbase integration
121
+ <#fleetbase>
122
+ a acl:Authorization;
123
+ acl:agent <{$webId}>;
124
+ acl:accessTo <{$podUrl}>;
125
+ acl:default <{$podUrl}>;
126
+ acl:mode acl:Append, acl:Read.
127
+
128
+ TURTLE;
129
+ }
130
+
131
+ /**
132
+ * Ensure pod has write permissions, update ACL if needed
133
+ */
134
+ public function ensureWritePermissions(SolidIdentity $identity, string $podUrl, string $webId): bool
135
+ {
136
+ // Check if already has write permissions
137
+ if ($this->hasWritePermissions($identity, $podUrl)) {
138
+ Log::info('[ACL OK]', ['pod_url' => $podUrl]);
139
+ return true;
140
+ }
141
+
142
+ // Need to update ACL
143
+ Log::info('[ACL NEEDS UPDATE]', ['pod_url' => $podUrl]);
144
+ return $this->grantWritePermissions($identity, $podUrl, $webId);
145
+ }
146
+
147
+ /**
148
+ * Ensure a folder has proper ACL permissions after creation.
149
+ *
150
+ * @param SolidIdentity $identity
151
+ * @param string $folderUrl The folder URL (must end with /)
152
+ * @param string $webId The WebID to grant permissions to
153
+ * @return bool
154
+ */
155
+ public function ensureFolderPermissions(SolidIdentity $identity, string $folderUrl, string $webId): bool
156
+ {
157
+ try {
158
+ // Ensure folder URL ends with /
159
+ $folderUrl = rtrim($folderUrl, '/') . '/';
160
+ $aclUrl = $folderUrl . '.acl';
161
+
162
+ Log::info('[ACL] Ensuring folder permissions', [
163
+ 'folder_url' => $folderUrl,
164
+ 'acl_url' => $aclUrl,
165
+ 'webid' => $webId,
166
+ ]);
167
+
168
+ // Check if ACL already exists and has write permissions
169
+ if ($this->hasFolderWritePermissions($identity, $folderUrl, $webId)) {
170
+ Log::info('[ACL] Folder already has write permissions', ['folder_url' => $folderUrl]);
171
+ return true;
172
+ }
173
+
174
+ // Create ACL with full permissions for the owner
175
+ $aclContent = $this->generateFolderAcl($folderUrl, $webId);
176
+
177
+ return $this->createFolderAcl($identity, $aclUrl, $aclContent);
178
+ } catch (\Throwable $e) {
179
+ Log::error('[ACL] Failed to ensure folder permissions', [
180
+ 'folder_url' => $folderUrl,
181
+ 'error' => $e->getMessage(),
182
+ ]);
183
+ return false;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Check if a folder has write permissions.
189
+ *
190
+ * @param SolidIdentity $identity
191
+ * @param string $folderUrl
192
+ * @param string $webId
193
+ * @return bool
194
+ */
195
+ protected function hasFolderWritePermissions(SolidIdentity $identity, string $folderUrl, string $webId): bool
196
+ {
197
+ try {
198
+ $response = $identity->request('head', $folderUrl);
199
+
200
+ if (!$response->successful()) {
201
+ return false;
202
+ }
203
+
204
+ // Check WAC-Allow header
205
+ $wacAllow = $response->header('WAC-Allow');
206
+
207
+ if ($wacAllow) {
208
+ Log::debug('[ACL] WAC-Allow header', [
209
+ 'folder_url' => $folderUrl,
210
+ 'wac_allow' => $wacAllow,
211
+ ]);
212
+
213
+ // Parse WAC-Allow header: user="read write", public="read"
214
+ if (preg_match('/user="([^"]*)"/i', $wacAllow, $matches)) {
215
+ $userModes = strtolower($matches[1]);
216
+ return str_contains($userModes, 'write') || str_contains($userModes, 'append');
217
+ }
218
+ }
219
+
220
+ return false;
221
+ } catch (\Throwable $e) {
222
+ Log::debug('[ACL] Error checking folder permissions', [
223
+ 'folder_url' => $folderUrl,
224
+ 'error' => $e->getMessage(),
225
+ ]);
226
+ return false;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Create an ACL file for a folder.
232
+ *
233
+ * @param SolidIdentity $identity
234
+ * @param string $aclUrl
235
+ * @param string $aclContent
236
+ * @return bool
237
+ */
238
+ protected function createFolderAcl(SolidIdentity $identity, string $aclUrl, string $aclContent): bool
239
+ {
240
+ try {
241
+ $response = $identity->request('put', $aclUrl, $aclContent, [
242
+ 'headers' => [
243
+ 'Content-Type' => 'text/turtle',
244
+ ],
245
+ ]);
246
+
247
+ if ($response->successful()) {
248
+ Log::info('[ACL] Folder ACL created successfully', [
249
+ 'acl_url' => $aclUrl,
250
+ 'status' => $response->status(),
251
+ ]);
252
+ return true;
253
+ }
254
+
255
+ Log::error('[ACL] Failed to create folder ACL', [
256
+ 'acl_url' => $aclUrl,
257
+ 'status' => $response->status(),
258
+ 'body' => $response->body(),
259
+ ]);
260
+
261
+ return false;
262
+ } catch (\Throwable $e) {
263
+ Log::error('[ACL] Error creating folder ACL', [
264
+ 'acl_url' => $aclUrl,
265
+ 'error' => $e->getMessage(),
266
+ ]);
267
+ return false;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Generate ACL content for a folder with full owner permissions.
273
+ *
274
+ * @param string $folderUrl The folder URL
275
+ * @param string $webId The WebID to grant permissions to
276
+ * @return string
277
+ */
278
+ protected function generateFolderAcl(string $folderUrl, string $webId): string
279
+ {
280
+ return <<<TURTLE
281
+ @prefix acl: <http://www.w3.org/ns/auth/acl#>.
282
+
283
+ <#owner>
284
+ a acl:Authorization;
285
+ acl:agent <{$webId}>;
286
+ acl:accessTo <./>;
287
+ acl:default <./>;
288
+ acl:mode acl:Read, acl:Write, acl:Control.
289
+ TURTLE;
290
+ }
291
+
292
+ /**
293
+ * Check if a specific URL is writable (has write or append permissions).
294
+ *
295
+ * @param SolidIdentity $identity
296
+ * @param string $url
297
+ * @return bool
298
+ */
299
+ public function isWritable(SolidIdentity $identity, string $url): bool
300
+ {
301
+ try {
302
+ $response = $identity->request('head', $url);
303
+
304
+ if (!$response->successful()) {
305
+ return false;
306
+ }
307
+
308
+ $wacAllow = $response->header('WAC-Allow');
309
+
310
+ if ($wacAllow && preg_match('/user="([^"]*)"/i', $wacAllow, $matches)) {
311
+ $modes = strtolower($matches[1]);
312
+ $isWritable = str_contains($modes, 'write') || str_contains($modes, 'append');
313
+
314
+ Log::debug('[ACL] Writable check', [
315
+ 'url' => $url,
316
+ 'wac_allow' => $wacAllow,
317
+ 'is_writable' => $isWritable,
318
+ ]);
319
+
320
+ return $isWritable;
321
+ }
322
+
323
+ return false;
324
+ } catch (\Throwable $e) {
325
+ Log::debug('[ACL] Writable check failed', [
326
+ 'url' => $url,
327
+ 'error' => $e->getMessage(),
328
+ ]);
329
+ return false;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Find writable storage locations from user profile.
335
+ *
336
+ * @param SolidIdentity $identity
337
+ * @param array $profile
338
+ * @return array
339
+ */
340
+ public function findWritableLocations(SolidIdentity $identity, array $profile): array
341
+ {
342
+ $writableLocations = [];
343
+ $webId = $profile['webid'] ?? null;
344
+
345
+ if (!$webId) {
346
+ return $writableLocations;
347
+ }
348
+
349
+ // Get pod URL from WebID
350
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
351
+
352
+ // Check common storage locations
353
+ $commonLocations = [
354
+ 'public' => rtrim($podUrl, '/') . '/public/',
355
+ 'private' => rtrim($podUrl, '/') . '/private/',
356
+ 'inbox' => rtrim($podUrl, '/') . '/inbox/',
357
+ ];
358
+
359
+ foreach ($commonLocations as $name => $url) {
360
+ if ($this->isWritable($identity, $url)) {
361
+ $writableLocations[$name] = $url;
362
+ }
363
+ }
364
+
365
+ // Check storage locations from profile
366
+ foreach ($profile['storage_locations'] ?? [] as $storage) {
367
+ $storageUrl = $this->resolveStorageUrl($storage, $webId, $podUrl);
368
+ if ($storageUrl && $this->isWritable($identity, $storageUrl)) {
369
+ $writableLocations['storage_' . count($writableLocations)] = $storageUrl;
370
+ }
371
+ }
372
+
373
+ Log::info('[ACL] Found writable locations', [
374
+ 'count' => count($writableLocations),
375
+ 'locations' => $writableLocations,
376
+ ]);
377
+
378
+ return $writableLocations;
379
+ }
380
+
381
+ /**
382
+ * Resolve a storage URL from profile data.
383
+ *
384
+ * @param string $storage
385
+ * @param string $webId
386
+ * @param string $podUrl
387
+ * @return string|null
388
+ */
389
+ protected function resolveStorageUrl(string $storage, string $webId, string $podUrl): ?string
390
+ {
391
+ // Handle relative URLs
392
+ if ($storage === '../' || $storage === './') {
393
+ return $podUrl;
394
+ }
395
+
396
+ // Handle absolute URLs
397
+ if (str_starts_with($storage, 'http://') || str_starts_with($storage, 'https://')) {
398
+ return $storage;
399
+ }
400
+
401
+ // Handle relative paths
402
+ $webIdBase = dirname($webId);
403
+ return rtrim($webIdBase, '/') . '/' . ltrim($storage, '/');
404
+ }
405
+ }