@fleetbase/solid-engine 0.0.3 → 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 (63) 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/LICENSE.md +651 -21
  5. package/MANUAL_ACL_SETUP.md +135 -0
  6. package/README.md +74 -27
  7. package/REFACTORING_SUMMARY.md +330 -0
  8. package/VERIFICATION_CHECKLIST.md +82 -0
  9. package/addon/components/modals/create-solid-folder.hbs +29 -0
  10. package/addon/components/modals/import-solid-resources.hbs +85 -0
  11. package/addon/controllers/data/content.js +17 -0
  12. package/addon/controllers/data/index.js +219 -0
  13. package/addon/controllers/home.js +84 -0
  14. package/addon/engine.js +1 -24
  15. package/addon/extension.js +26 -0
  16. package/addon/routes/data/content.js +11 -0
  17. package/addon/routes/data/index.js +17 -0
  18. package/addon/routes.js +2 -7
  19. package/addon/styles/solid-engine.css +1 -2
  20. package/addon/templates/account.hbs +3 -3
  21. package/addon/templates/application.hbs +2 -12
  22. package/addon/templates/data/content.hbs +48 -0
  23. package/addon/templates/{pods/explorer.hbs → data/index.hbs} +6 -5
  24. package/addon/templates/home.hbs +168 -10
  25. package/app/components/modals/{backup-pod.js → create-solid-folder.js} +1 -1
  26. package/app/components/modals/{resync-pod.js → import-solid-resources.js} +1 -1
  27. package/app/components/modals/{create-pod.js → setup-css-credentials.js} +1 -1
  28. package/composer.json +5 -11
  29. package/extension.json +2 -2
  30. package/index.js +0 -11
  31. package/package.json +9 -9
  32. package/server/migrations/2024_12_21_add_css_credentials_to_solid_identities_table.php +32 -0
  33. package/server/src/Client/OpenIDConnectClient.php +686 -15
  34. package/server/src/Client/SolidClient.php +104 -8
  35. package/server/src/Http/Controllers/DataController.php +261 -0
  36. package/server/src/Http/Controllers/OIDCController.php +42 -8
  37. package/server/src/Http/Controllers/SolidController.php +179 -85
  38. package/server/src/Models/SolidIdentity.php +13 -3
  39. package/server/src/Services/AclService.php +146 -0
  40. package/server/src/Services/PodService.php +863 -0
  41. package/server/src/Services/ResourceSyncService.php +336 -0
  42. package/server/src/Services/VehicleSyncService.php +289 -0
  43. package/server/src/Support/Utils.php +10 -0
  44. package/server/src/routes.php +25 -1
  45. package/addon/components/modals/backup-pod.hbs +0 -3
  46. package/addon/components/modals/create-pod.hbs +0 -5
  47. package/addon/components/modals/resync-pod.hbs +0 -3
  48. package/addon/controllers/pods/explorer/content.js +0 -12
  49. package/addon/controllers/pods/explorer.js +0 -149
  50. package/addon/controllers/pods/index/pod.js +0 -12
  51. package/addon/controllers/pods/index.js +0 -137
  52. package/addon/routes/pods/explorer/content.js +0 -10
  53. package/addon/routes/pods/explorer.js +0 -44
  54. package/addon/routes/pods/index/pod.js +0 -3
  55. package/addon/routes/pods/index.js +0 -21
  56. package/addon/templates/pods/explorer/content.hbs +0 -19
  57. package/addon/templates/pods/index/pod.hbs +0 -11
  58. package/addon/templates/pods/index.hbs +0 -19
  59. package/server/src/LegacyClient/Identity/IdentityProvider.php +0 -174
  60. package/server/src/LegacyClient/Identity/Profile.php +0 -18
  61. package/server/src/LegacyClient/OIDCClient.php +0 -350
  62. package/server/src/LegacyClient/Profile/WebID.php +0 -26
  63. package/server/src/LegacyClient/SolidClient.php +0 -271
@@ -5,6 +5,7 @@ namespace Fleetbase\Solid\Client;
5
5
  use Fleetbase\Solid\Models\SolidIdentity;
6
6
  use Illuminate\Http\Client\Response;
7
7
  use Illuminate\Support\Facades\Http;
8
+ use Illuminate\Support\Facades\Log;
8
9
  use Illuminate\Support\Str;
9
10
 
10
11
  class SolidClient
@@ -12,8 +13,8 @@ class SolidClient
12
13
  private string $host = 'localhost';
13
14
  private int $port = 3000;
14
15
  private bool $secure = true;
15
- public ?SolidIdentity $solidIdentity;
16
- public ?OpenIDConnectClient $oidc;
16
+ public SolidIdentity $identity;
17
+ public OpenIDConnectClient $oidc;
17
18
  private const DEFAULT_MIME_TYPE = 'text/turtle';
18
19
  private const LDP_BASIC_CONTAINER = 'http://www.w3.org/ns/ldp#BasicContainer';
19
20
  private const LDP_RESOURCE = 'http://www.w3.org/ns/ldp#Resource';
@@ -95,6 +96,23 @@ class SolidClient
95
96
  return $this;
96
97
  }
97
98
 
99
+ /**
100
+ * Make a request with a specific identity.
101
+ */
102
+ public function requestWithIdentity(SolidIdentity $identity, string $method, string $uri, string|array $data = [], array $options = []): Response
103
+ {
104
+ $this->identity = $identity;
105
+
106
+ // Check if we should skip authentication
107
+ $withoutAuth = data_get($options, 'withoutAuth', false);
108
+
109
+ if ($withoutAuth) {
110
+ return $this->request($method, $uri, $data, $options);
111
+ }
112
+
113
+ return $this->authenticatedRequest($method, $uri, $data, $options);
114
+ }
115
+
98
116
  /**
99
117
  * Make a HTTP request to the Solid server.
100
118
  *
@@ -102,17 +120,27 @@ class SolidClient
102
120
  * @param string $uri The URI to send the request to
103
121
  * @param array $options Options for the request
104
122
  */
105
- protected function request(string $method, string $uri, array $data = [], array $options = []): Response
123
+ protected function request(string $method, string $uri, string|array $data = [], array $options = []): Response
106
124
  {
107
125
  $url = $this->createRequestUrl($uri);
108
126
 
109
- return Http::withOptions($options)->{$method}($url, $data);
127
+ // For development: disable SSL verification when using HTTPS
128
+ if ($this->secure && app()->environment('local', 'development')) {
129
+ $options['verify'] = false;
130
+ }
131
+
132
+ // Handle different data types
133
+ if (is_string($data)) {
134
+ return Http::withOptions($options)->withBody($data, $options['headers']['Content-Type'] ?? 'text/plain')->send($method, $url);
135
+ } else {
136
+ return Http::withOptions($options)->{$method}($url, $data);
137
+ }
110
138
  }
111
139
 
112
140
  /**
113
141
  * Send an authenticated request with the current identity.
114
142
  */
115
- public function authenticatedRequest(string $method, string $uri, array $data = [], array $options = []): Response
143
+ public function authenticatedRequest(string $method, string $uri, string|array $data = [], array $options = []): Response
116
144
  {
117
145
  if (!$this->identity) {
118
146
  throw new \Exception('Solid Identity required to make an authenticated request.');
@@ -120,13 +148,81 @@ class SolidClient
120
148
 
121
149
  $url = $this->createRequestUrl($uri);
122
150
  $accessToken = $this->identity->getAccessToken();
151
+
152
+ // Debug: Log access token details
123
153
  if ($accessToken) {
124
- $options['headers'] = is_array($options['headers']) ? $options['headers'] : [];
154
+ try {
155
+ $tokenParts = explode('.', $accessToken);
156
+ if (count($tokenParts) === 3) {
157
+ $payload = json_decode(base64_decode(strtr($tokenParts[1], '-_', '+/')), true);
158
+ Log::debug('[ACCESS TOKEN PAYLOAD]', [
159
+ 'webid' => $payload['webid'] ?? null,
160
+ 'sub' => $payload['sub'] ?? null,
161
+ 'client_id' => $payload['client_id'] ?? null,
162
+ 'scope' => $payload['scope'] ?? null,
163
+ 'iat' => $payload['iat'] ?? null,
164
+ 'exp' => $payload['exp'] ?? null,
165
+ 'cnf_jkt' => $payload['cnf']['jkt'] ?? null,
166
+ ]);
167
+ }
168
+ } catch (\Throwable $e) {
169
+ Log::warning('[ACCESS TOKEN DECODE FAILED]', ['error' => $e->getMessage()]);
170
+ }
171
+
172
+ $options['headers'] = isset($options['headers']) && is_array($options['headers']) ? $options['headers'] : [];
125
173
  $options['headers']['Authorization'] = 'DPoP ' . $accessToken;
126
- $options['headers']['DPoP'] = OpenIDConnectClient::createDPoP($method, $url, $accessToken);
174
+ $options['headers']['DPoP'] = $this->oidc->createDPoP($method, $url, $accessToken);
175
+ }
176
+
177
+ // For development: disable SSL verification when using HTTPS
178
+ if ($this->secure && app()->environment('local', 'development')) {
179
+ $options['verify'] = false;
127
180
  }
128
181
 
129
- return Http::withOptions($options)->{$method}($url, $data);
182
+ Log::info('[SOLID REQUEST HEADERS]', ['headers' => $options['headers']]);
183
+
184
+ // Handle different data types
185
+ if (is_string($data)) {
186
+ // For string data, send as raw body
187
+ $contentType = $options['headers']['Content-Type'] ?? 'text/plain';
188
+
189
+ Log::info('[SENDING STRING BODY]', [
190
+ 'method' => $method,
191
+ 'url' => $url,
192
+ 'content_type' => $contentType,
193
+ 'body_length' => strlen($data),
194
+ 'body_preview' => substr($data, 0, 200),
195
+ ]);
196
+
197
+ $response = Http::withOptions($options)->withBody($data, $contentType)->send($method, $url);
198
+
199
+ // Debug: Log response details
200
+ Log::debug('[SOLID RESPONSE]', [
201
+ 'status' => $response->status(),
202
+ 'headers' => $response->headers(),
203
+ 'body' => $response->body(),
204
+ ]);
205
+
206
+ return $response;
207
+ } else {
208
+ // For array data, use the original method
209
+ Log::info('[SENDING ARRAY DATA]', [
210
+ 'method' => $method,
211
+ 'url' => $url,
212
+ 'data' => $data,
213
+ ]);
214
+
215
+ $response = Http::withOptions($options)->{$method}($url, $data);
216
+
217
+ // Debug: Log response details
218
+ Log::debug('[SOLID RESPONSE]', [
219
+ 'status' => $response->status(),
220
+ 'headers' => $response->headers(),
221
+ 'body' => $response->body(),
222
+ ]);
223
+
224
+ return $response;
225
+ }
130
226
  }
131
227
 
132
228
  /**
@@ -0,0 +1,261 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Solid\Http\Controllers;
4
+
5
+ use Fleetbase\Http\Controllers\Controller as BaseController;
6
+ use Fleetbase\Solid\Models\SolidIdentity;
7
+ use Fleetbase\Solid\Services\PodService;
8
+ use Fleetbase\Solid\Services\ResourceSyncService;
9
+ use Illuminate\Http\Request;
10
+ use Illuminate\Support\Facades\Log;
11
+
12
+ class DataController extends BaseController
13
+ {
14
+ protected PodService $podService;
15
+ protected ResourceSyncService $resourceSyncService;
16
+
17
+ public function __construct(PodService $podService, ResourceSyncService $resourceSyncService)
18
+ {
19
+ $this->podService = $podService;
20
+ $this->resourceSyncService = $resourceSyncService;
21
+ }
22
+
23
+ /**
24
+ * Get the user's pod data (root level folders and files).
25
+ */
26
+ public function index(Request $request)
27
+ {
28
+ try {
29
+ $identity = SolidIdentity::current();
30
+
31
+ if (!$identity || !$identity->getAccessToken()) {
32
+ return response()->json(['error' => 'Not authenticated'], 401);
33
+ }
34
+
35
+ // Get the user's primary pod URL from their WebID
36
+ $profile = $this->podService->getProfileData($identity);
37
+ $webId = $profile['webid'];
38
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
39
+
40
+ Log::info('[DATA INDEX]', [
41
+ 'webid' => $webId,
42
+ 'pod_url' => $podUrl,
43
+ ]);
44
+
45
+ // Get root level contents of the pod
46
+ $contents = $this->podService->getPodContents($identity, $podUrl);
47
+
48
+ return response()->json([
49
+ 'pod_url' => $podUrl,
50
+ 'contents' => $contents,
51
+ ]);
52
+ } catch (\Throwable $e) {
53
+ Log::error('[DATA INDEX ERROR]', [
54
+ 'error' => $e->getMessage(),
55
+ 'trace' => $e->getTraceAsString(),
56
+ ]);
57
+
58
+ return response()->json([
59
+ 'error' => $e->getMessage(),
60
+ ], 500);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get folder contents by slug.
66
+ */
67
+ public function showFolder(Request $request, string $slug)
68
+ {
69
+ try {
70
+ $identity = SolidIdentity::current();
71
+
72
+ if (!$identity || !$identity->getAccessToken()) {
73
+ return response()->json(['error' => 'Not authenticated'], 401);
74
+ }
75
+
76
+ $profile = $this->podService->getProfileData($identity);
77
+ $webId = $profile['webid'];
78
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
79
+ $folderUrl = rtrim($podUrl, '/') . '/' . ltrim($slug, '/');
80
+
81
+ Log::info('[FOLDER SHOW]', [
82
+ 'slug' => $slug,
83
+ 'folder_url' => $folderUrl,
84
+ ]);
85
+
86
+ $contents = $this->podService->getPodContents($identity, $folderUrl);
87
+
88
+ return response()->json([
89
+ 'folder_url' => $folderUrl,
90
+ 'slug' => $slug,
91
+ 'contents' => $contents,
92
+ ]);
93
+ } catch (\Throwable $e) {
94
+ Log::error('[FOLDER SHOW ERROR]', [
95
+ 'slug' => $slug,
96
+ 'error' => $e->getMessage(),
97
+ ]);
98
+
99
+ return response()->json([
100
+ 'error' => $e->getMessage(),
101
+ ], 500);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create a new folder in the pod.
107
+ */
108
+ public function createFolder(Request $request)
109
+ {
110
+ try {
111
+ $identity = SolidIdentity::current();
112
+
113
+ if (!$identity || !$identity->getAccessToken()) {
114
+ return response()->json(['error' => 'Not authenticated'], 401);
115
+ }
116
+
117
+ $request->validate([
118
+ 'name' => 'required|string|max:255',
119
+ 'path' => 'nullable|string',
120
+ ]);
121
+
122
+ $folderName = $request->input('name');
123
+ $path = $request->input('path', '');
124
+
125
+ $profile = $this->podService->getProfileData($identity);
126
+ $webId = $profile['webid'];
127
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
128
+
129
+ // Build parent URL (where to create the folder)
130
+ $parentUrl = rtrim($podUrl, '/') . '/';
131
+ if (!empty($path)) {
132
+ $parentUrl .= trim($path, '/') . '/';
133
+ }
134
+
135
+ Log::info('[FOLDER CREATE]', [
136
+ 'name' => $folderName,
137
+ 'path' => $path,
138
+ 'parent_url' => $parentUrl,
139
+ ]);
140
+
141
+ // Use POST with Slug header (Solid Protocol standard)
142
+ $result = $this->podService->createFolder($identity, $parentUrl, $folderName);
143
+
144
+ return response()->json([
145
+ 'success' => $result,
146
+ 'folder_url' => $parentUrl . $folderName . '/',
147
+ 'message' => "Folder '{$folderName}' created successfully",
148
+ ]);
149
+ } catch (\Throwable $e) {
150
+ Log::error('[FOLDER CREATE ERROR]', [
151
+ 'error' => $e->getMessage(),
152
+ 'trace' => $e->getTraceAsString(),
153
+ ]);
154
+
155
+ return response()->json([
156
+ 'success' => false,
157
+ 'error' => $e->getMessage(),
158
+ ], 500);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Delete a folder or file.
164
+ */
165
+ public function deleteItem(Request $request, string $type, string $slug)
166
+ {
167
+ try {
168
+ $identity = SolidIdentity::current();
169
+
170
+ if (!$identity || !$identity->getAccessToken()) {
171
+ return response()->json(['error' => 'Not authenticated'], 401);
172
+ }
173
+
174
+ $profile = $this->podService->getProfileData($identity);
175
+ $webId = $profile['webid'];
176
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
177
+ $itemUrl = rtrim($podUrl, '/') . '/' . ltrim($slug, '/');
178
+
179
+ // Add trailing slash for folders
180
+ if ($type === 'folder') {
181
+ $itemUrl = rtrim($itemUrl, '/') . '/';
182
+ }
183
+
184
+ Log::info('[ITEM DELETE]', [
185
+ 'type' => $type,
186
+ 'slug' => $slug,
187
+ 'item_url' => $itemUrl,
188
+ ]);
189
+
190
+ $result = $this->podService->deleteResource($identity, $itemUrl);
191
+
192
+ return response()->json([
193
+ 'success' => $result,
194
+ 'message' => $result ? ucfirst($type) . ' deleted successfully' : 'Failed to delete ' . $type,
195
+ ]);
196
+ } catch (\Throwable $e) {
197
+ Log::error('[ITEM DELETE ERROR]', [
198
+ 'type' => $type,
199
+ 'slug' => $slug,
200
+ 'error' => $e->getMessage(),
201
+ ]);
202
+
203
+ return response()->json([
204
+ 'success' => false,
205
+ 'error' => $e->getMessage(),
206
+ ], 500);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Import Fleetops resources into the user's pod.
212
+ */
213
+ public function importResources(Request $request)
214
+ {
215
+ try {
216
+ $identity = SolidIdentity::current();
217
+
218
+ if (!$identity || !$identity->getAccessToken()) {
219
+ return response()->json(['error' => 'Not authenticated'], 401);
220
+ }
221
+
222
+ $request->validate([
223
+ 'resource_types' => 'required|array',
224
+ 'resource_types.*' => 'in:vehicles,drivers,contacts,orders',
225
+ ]);
226
+
227
+ $resourceTypes = $request->input('resource_types');
228
+
229
+ // Use the authenticated user's pod (from their WebID)
230
+ $profile = $this->podService->getProfileData($identity);
231
+ $webId = $profile['webid'];
232
+ $podUrl = $this->podService->getPodUrlFromWebId($webId);
233
+
234
+ Log::info('[IMPORTING RESOURCES]', [
235
+ 'pod_url' => $podUrl,
236
+ 'webid' => $webId,
237
+ 'resource_types' => $resourceTypes,
238
+ ]);
239
+
240
+ $result = $this->resourceSyncService->importResources($identity, $podUrl, $resourceTypes);
241
+
242
+ return response()->json([
243
+ 'success' => true,
244
+ 'imported' => $result['imported'],
245
+ 'imported_count' => $result['total_count'],
246
+ 'errors' => $result['errors'],
247
+ 'message' => "Successfully imported {$result['total_count']} resources",
248
+ ]);
249
+ } catch (\Throwable $e) {
250
+ Log::error('[IMPORT RESOURCES ERROR]', [
251
+ 'error' => $e->getMessage(),
252
+ 'trace' => $e->getTraceAsString(),
253
+ ]);
254
+
255
+ return response()->json([
256
+ 'success' => false,
257
+ 'error' => $e->getMessage(),
258
+ ], 500);
259
+ }
260
+ }
261
+ }
@@ -7,26 +7,60 @@ use Fleetbase\Solid\Client\SolidClient;
7
7
  use Fleetbase\Solid\Models\SolidIdentity;
8
8
  use Fleetbase\Support\Utils;
9
9
  use Illuminate\Http\Request;
10
+ use Illuminate\Support\Facades\Log;
10
11
 
11
12
  class OIDCController extends BaseController
12
13
  {
13
14
  public function completeRegistration(string $identifier, Request $request)
14
15
  {
15
- if ($request->has('code') && $identifier) {
16
+ Log::info('[OIDC COMPLETE REGISTRATION]', [
17
+ 'identifier' => $identifier,
18
+ 'request' => $request->all(),
19
+ 'has_code' => $request->filled('code'),
20
+ 'has_state' => $request->filled('state'),
21
+ ]);
22
+
23
+ if ($request->filled('code') && $identifier) {
16
24
  try {
17
25
  $identity = SolidIdentity::where('identifier', $identifier)->first();
18
- if ($identity) {
19
- $solid = SolidClient::create(['identity' => $identity, 'restore' => true]);
20
- $solid->oidc->authenticate();
21
- $identity->update(['token_response' => $solid->oidc->getTokenResponse()]);
26
+
27
+ if (!$identity) {
28
+ throw new \Exception('Identity not found for identifier: ' . $identifier);
29
+ }
30
+
31
+ Log::info('[OIDC IDENTITY FOUND]', ['identity_id' => $identity->id]);
32
+
33
+ $solid = SolidClient::create(['identity' => $identity, 'restore' => true]);
34
+
35
+ // Get the authorization code
36
+ $code = $request->input('code');
37
+
38
+ // CRITICAL: Use requestTokens() instead of authenticate()
39
+ $tokenResponse = $solid->oidc->exchangeCodeForTokens($code);
40
+
41
+ if (isset($tokenResponse->error)) {
42
+ throw new \Exception($tokenResponse->error_description ?? $tokenResponse->error);
22
43
  }
44
+
45
+ Log::info('[OIDC TOKEN EXCHANGE SUCCESS]', ['has_access_token' => isset($tokenResponse->access_token)]);
46
+
47
+ // Save the token response
48
+ $identity->update(['token_response' => (array) $tokenResponse]);
49
+
50
+ // Redirect to success page
51
+ return redirect(Utils::consoleUrl('solid-protocol', ['success' => 'authenticated']));
23
52
  } catch (\Throwable $e) {
53
+ Log::error('[OIDC ERROR]', [
54
+ 'error' => $e->getMessage(),
55
+ 'trace' => $e->getTraceAsString(),
56
+ ]);
57
+
24
58
  return redirect(Utils::consoleUrl('solid-protocol', ['error' => $e->getMessage()]));
25
59
  }
26
-
27
- return redirect(Utils::consoleUrl('solid-protocol', $request->all()));
28
60
  }
29
61
 
30
- return redirect(Utils::consoleUrl('solid-protocol', ['error' => 'Unable to authenticate with provider']));
62
+ Log::warning('[OIDC MISSING PARAMS]', ['has_code' => $request->filled('code'), 'identifier' => $identifier]);
63
+
64
+ return redirect(Utils::consoleUrl('solid-protocol', ['error' => 'Missing authorization code or identifier']));
31
65
  }
32
66
  }