@fleetbase/solid-engine 0.0.4 → 0.0.5

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 +168 -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 +1 -1
  28. package/index.js +0 -11
  29. package/package.json +8 -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 +261 -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 +146 -0
  38. package/server/src/Services/PodService.php +863 -0
  39. package/server/src/Services/ResourceSyncService.php +336 -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
@@ -7,16 +7,24 @@ use Fleetbase\Http\Requests\AdminRequest;
7
7
  use Fleetbase\Models\Setting;
8
8
  use Fleetbase\Solid\Client\SolidClient;
9
9
  use Fleetbase\Solid\Models\SolidIdentity;
10
+ use Fleetbase\Solid\Services\PodService;
10
11
  use Fleetbase\Solid\Support\Utils;
11
12
  use Illuminate\Http\Request;
12
- use Illuminate\Support\Str;
13
+ use Illuminate\Support\Facades\Log;
13
14
 
14
15
  class SolidController extends BaseController
15
16
  {
17
+ protected PodService $podService;
18
+
19
+ public function __construct(PodService $podService)
20
+ {
21
+ $this->podService = $podService;
22
+ }
23
+
16
24
  /**
17
25
  * Welcome message only.
18
26
  */
19
- public function hello()
27
+ public function hello(Request $request)
20
28
  {
21
29
  return response()->json(
22
30
  [
@@ -30,7 +38,7 @@ class SolidController extends BaseController
30
38
  {
31
39
  $defaultConfig = config('solid.server');
32
40
  $savedConfig = Setting::system('solid.server');
33
- $config = array_merge($defaultConfig, $savedConfig);
41
+ $config = array_merge($defaultConfig, $savedConfig ?? []);
34
42
 
35
43
  return response()->json($config);
36
44
  }
@@ -55,116 +63,202 @@ class SolidController extends BaseController
55
63
 
56
64
  public function authenticate(string $identifier)
57
65
  {
58
- $identity = SolidIdentity::initialize();
59
- $oidc = SolidClient::create(['identity' => $identity])->oidc->register(['saveCredentials' => true]);
66
+ try {
67
+ Log::info('[SOLID AUTH START]', ['identifier' => $identifier]);
60
68
 
61
- return $oidc->authenticate();
62
- }
69
+ $identity = SolidIdentity::where('identifier', $identifier)->first();
63
70
 
64
- public function getAccountIndex()
65
- {
66
- $solidIdentity = SolidIdentity::current();
67
- $accountResponse = $solidIdentity->request('get', '.account');
68
- dd($accountResponse->json());
71
+ if (!$identity) {
72
+ throw new \Exception('Identity not found for identifier: ' . $identifier);
73
+ }
74
+
75
+ Log::info('[SOLID IDENTITY FOUND]', [
76
+ 'identity_id' => $identity->id,
77
+ 'redirect_uri' => $identity->getRedirectUri(),
78
+ ]);
79
+
80
+ $oidc = SolidClient::create(['identity' => $identity])->oidc->register(['saveCredentials' => true]);
81
+
82
+ Log::info('[SOLID OIDC REGISTERED]', ['client_id' => $oidc->getClientID()]);
83
+
84
+ // This will redirect to the authorization server
85
+ return $oidc->authenticate();
86
+ } catch (\Throwable $e) {
87
+ Log::error('[SOLID AUTH ERROR]', [
88
+ 'error' => $e->getMessage(),
89
+ 'trace' => $e->getTraceAsString(),
90
+ ]);
91
+
92
+ // Redirect back to frontend with error
93
+ return redirect(Utils::consoleUrl('solid-protocol', ['error' => $e->getMessage()]));
94
+ }
69
95
  }
70
96
 
71
- public function play(Request $request)
97
+ /**
98
+ * Get authentication status and account details.
99
+ */
100
+ public function getAuthenticationStatus(Request $request)
72
101
  {
73
- $action = $request->input('action');
74
- $identity = SolidIdentity::first();
75
- $solid = new SolidClient(['identity' => $identity]);
102
+ try {
103
+ $identity = SolidIdentity::current();
76
104
 
77
- if ($action === 'register_client') {
78
- $registeredClient = $solid->oidc->register();
79
- }
105
+ if (!$identity || !$identity->getAccessToken()) {
106
+ return response()->json([
107
+ 'authenticated' => false,
108
+ 'identity' => null,
109
+ 'profile' => null,
110
+ ]);
111
+ }
80
112
 
81
- if ($action === 'restore') {
82
- $solid->identity->restoreClientCredentials();
83
- }
113
+ // Get WebID and profile information
114
+ $profile = $this->podService->getProfileData($identity);
84
115
 
85
- if ($action === 'login') {
86
- $loginResponse = $solid->post(
87
- '.account/login/password',
88
- [
89
- 'email' => 'ron@fleetbase.io',
90
- 'password' => 'Zerina30662!',
91
- 'remember' => true,
116
+ return response()->json([
117
+ 'authenticated' => true,
118
+ 'identity' => [
119
+ 'id' => $identity->id,
120
+ 'identifier' => $identity->identifier,
121
+ 'created_at' => $identity->created_at,
122
+ 'has_access_token' => (bool) $identity->getAccessToken(),
92
123
  ],
93
- [
94
- 'withoutAuth' => true,
95
- 'headers' => [
96
- 'Cookie' => '_interaction=TDQMh2DWuC8wZvkEB2n_G; _interaction.sig=3HHA_FUVo7Cw9up2keCJ7IaQJws; _session.legacy=jdxTxnTGmvWx2ECaiwYeP; _session.legacy.sig=EUGYX6DAKtBNQqZN5PGcbIJ-5ac',
97
- ],
98
- ]
99
- );
100
- dump($loginResponse->json());
124
+ 'profile' => $profile,
125
+ ]);
126
+ } catch (\Throwable $e) {
127
+ Log::error('[SOLID AUTH STATUS ERROR]', [
128
+ 'error' => $e->getMessage(),
129
+ 'trace' => $e->getTraceAsString(),
130
+ ]);
131
+
132
+ return response()->json([
133
+ 'authenticated' => false,
134
+ 'identity' => null,
135
+ 'profile' => null,
136
+ 'error' => $e->getMessage(),
137
+ ]);
101
138
  }
139
+ }
140
+
141
+ /**
142
+ * Logout user by clearing identity tokens.
143
+ */
144
+ public function logout(Request $request)
145
+ {
146
+ try {
147
+ $identity = SolidIdentity::current();
102
148
 
103
- if ($action === 'test') {
104
- // $solidClient->identity->restoreTokens();
105
- dump('AccessToken: ' . $solid->identity->getAccessToken());
106
- dump('IdToken: ' . $solid->identity->getIdToken());
149
+ if ($identity) {
150
+ // Clear token response
151
+ $identity->update(['token_response' => null]);
107
152
 
108
- $indexResponse = $solid->get('.account', [], ['withoutAuth' => true]);
109
- dump($indexResponse->json());
153
+ // Clear any cached client credentials
154
+ $solid = SolidClient::create(['identity' => $identity]);
155
+ $solid->oidc->clearClientCredentials();
156
+ }
110
157
 
111
- // $createPodResponse = $solidClient->post('rondon/blog');
112
- // dump($createPodResponse->json());
158
+ return response()->json([
159
+ 'success' => true,
160
+ 'message' => 'Logged out successfully',
161
+ ]);
162
+ } catch (\Throwable $e) {
163
+ Log::error('[SOLID LOGOUT ERROR]', [
164
+ 'error' => $e->getMessage(),
165
+ ]);
113
166
 
114
- // $createFileResponse = $solidClient->put('test.txt', ['test'], ['content-type' => 'text/plain']);
115
- // dump($createFileResponse->json());
167
+ return response()->json([
168
+ 'success' => false,
169
+ 'error' => $e->getMessage(),
170
+ ], 500);
116
171
  }
117
172
  }
118
173
 
174
+ /**
175
+ * Get real pods from Solid server.
176
+ */
119
177
  public function getPods(Request $request)
120
178
  {
121
- // Retrieve search and sort parameters from the request
122
- $query = $request->searchQuery();
123
- $sort = $request->input('sort', '-created_at');
124
- $id = $request->input('id');
125
- $slug = $request->input('slug');
179
+ try {
180
+ $identity = SolidIdentity::current();
126
181
 
127
- // Collection of pods data
128
- $pods = json_decode(file_get_contents(base_path('vendor/fleetbase/solid-api/server/data/pods.json')));
129
-
130
- // Get single content from pod via slug
131
- if ($slug) {
132
- $result = Utils::searchPods($pods, 'slug', $slug);
182
+ if (!$identity || !$identity->getAccessToken()) {
183
+ return response()->json([
184
+ 'error' => 'Not authenticated',
185
+ ], 401);
186
+ }
133
187
 
134
- return response()->json($result);
135
- }
188
+ $profile = $this->podService->getProfileData($identity);
189
+ $storageLocations = data_get($profile, 'parsed_profile.storage_locations', []);
136
190
 
137
- // Get a single item via ID
138
- if ($id && is_array($pods)) {
139
- $result = Utils::searchPods($pods, 'id', $id);
140
- if ($result && $query) {
141
- $result->contents = array_values(
142
- array_filter(
143
- data_get($result, 'contents', []),
144
- function ($content) use ($query) {
145
- return Str::contains(strtolower(data_get($content, 'name')), strtolower($query));
146
- }
147
- )
148
- );
191
+ $pods = [];
192
+ foreach ($storageLocations as $storageUrl) {
193
+ try {
194
+ $podResponse = $identity->request('get', $storageUrl);
195
+ if ($podResponse->successful()) {
196
+ $pods[] = [
197
+ 'url' => $storageUrl,
198
+ 'name' => $this->extractPodName($storageUrl),
199
+ 'content' => $podResponse->body(),
200
+ 'status' => $podResponse->status(),
201
+ 'containers' => $this->parseContainers($podResponse->body()),
202
+ ];
203
+ }
204
+ } catch (\Throwable $e) {
205
+ Log::warning('[SOLID POD FETCH ERROR]', [
206
+ 'storage_url' => $storageUrl,
207
+ 'error' => $e->getMessage(),
208
+ ]);
209
+ }
149
210
  }
150
211
 
151
- return response()->json($result);
152
- }
212
+ return response()->json([
213
+ 'pods' => $pods,
214
+ 'storage_locations' => $storageLocations,
215
+ ]);
216
+ } catch (\Throwable $e) {
217
+ Log::error('[SOLID PODS ERROR]', [
218
+ 'error' => $e->getMessage(),
219
+ ]);
153
220
 
154
- // Filtering by search query
155
- if ($query) {
156
- $pods = array_filter($pods, function ($pod) use ($query) {
157
- return Str::contains(strtolower(data_get($pod, 'name')), strtolower($query));
158
- });
221
+ return response()->json([
222
+ 'error' => $e->getMessage(),
223
+ ], 500);
159
224
  }
225
+ }
160
226
 
161
- // Determine sorting direction and key
162
- $sortDesc = substr($sort, 0, 1) === '-';
163
- $sortKey = ltrim($sort, '-');
227
+ /**
228
+ * Extract pod name from URL.
229
+ */
230
+ private function extractPodName(string $url): string
231
+ {
232
+ $parts = explode('/', rtrim($url, '/'));
164
233
 
165
- // Sorting by specified field
166
- $pods = collect($pods)->sortBy($sortKey, SORT_REGULAR, $sortDesc);
234
+ return end($parts) ?: 'Root Pod';
235
+ }
167
236
 
168
- return response()->json($pods->values());
237
+ /**
238
+ * Parse containers from pod content.
239
+ */
240
+ private function parseContainers(string $content): array
241
+ {
242
+ $containers = [];
243
+
244
+ // Parse LDP containers from RDF
245
+ if (preg_match_all('/<([^>]+)>\s+a\s+ldp:Container/', $content, $matches)) {
246
+ foreach ($matches[1] as $containerUrl) {
247
+ $containers[] = [
248
+ 'url' => $containerUrl,
249
+ 'name' => $this->extractPodName($containerUrl),
250
+ 'type' => 'container',
251
+ ];
252
+ }
253
+ }
254
+
255
+ return $containers;
256
+ }
257
+
258
+ public function getAccountIndex()
259
+ {
260
+ $solidIdentity = SolidIdentity::current();
261
+ $accountResponse = $solidIdentity->request('get', '.account');
262
+ dd($accountResponse->json());
169
263
  }
170
264
  }
@@ -56,7 +56,17 @@ class SolidIdentity extends Model
56
56
 
57
57
  public function getAccessToken(): ?string
58
58
  {
59
- return data_get($this, 'token_response.access_token');
59
+ // Use OIDC token only (proper Solid protocol)
60
+ $oidcToken = data_get($this, 'token_response.access_token');
61
+
62
+ if ($oidcToken) {
63
+ \Illuminate\Support\Facades\Log::info('[USING OIDC TOKEN]', ['has_token' => true]);
64
+ return $oidcToken;
65
+ }
66
+
67
+ // No token available
68
+ \Illuminate\Support\Facades\Log::warning('[NO ACCESS TOKEN AVAILABLE]');
69
+ return null;
60
70
  }
61
71
 
62
72
  public function getRedirectUri(array $query = [], int $port = 8000): string
@@ -122,9 +132,9 @@ class SolidIdentity extends Model
122
132
  return $solidIdentity;
123
133
  }
124
134
 
125
- public function request(string $method, string $uri, array $data = [], array $options = [])
135
+ public function request(string $method, string $uri, string|array $data = [], array $options = [])
126
136
  {
127
- $solidClient = new SolidClient();
137
+ $solidClient = new SolidClient(['identity' => $this]);
128
138
 
129
139
  return $solidClient->requestWithIdentity($this, $method, $uri, $data, $options);
130
140
  }
@@ -0,0 +1,146 @@
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
+ }