@fleetbase/solid-engine 0.0.2

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 (42) hide show
  1. package/.php-cs-fixer.php +29 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +222 -0
  4. package/addon/components/admin/solid-server-config.hbs +19 -0
  5. package/addon/components/admin/solid-server-config.js +52 -0
  6. package/addon/components/solid-brand-icon.hbs +9 -0
  7. package/addon/components/solid-brand-icon.js +13 -0
  8. package/addon/controllers/application.js +27 -0
  9. package/addon/engine.js +40 -0
  10. package/addon/routes/application.js +14 -0
  11. package/addon/routes.js +3 -0
  12. package/addon/styles/solid-engine.css +29 -0
  13. package/addon/templates/application.hbs +15 -0
  14. package/app/components/admin/solid-server-config.js +1 -0
  15. package/app/components/solid-brand-icon.js +1 -0
  16. package/app/controllers/application.js +1 -0
  17. package/app/routes/application.js +1 -0
  18. package/composer.json +96 -0
  19. package/config/environment.js +11 -0
  20. package/extension.json +8 -0
  21. package/index.js +26 -0
  22. package/package.json +134 -0
  23. package/phpstan.neon.dist +8 -0
  24. package/phpunit.xml.dist +16 -0
  25. package/server/config/solid.php +21 -0
  26. package/server/migrations/2024_04_09_064616_create_solid_identities_table.php +33 -0
  27. package/server/src/Client/OpenIDConnectClient.php +217 -0
  28. package/server/src/Client/SolidClient.php +186 -0
  29. package/server/src/Http/Controllers/OIDCController.php +32 -0
  30. package/server/src/Http/Controllers/SolidController.php +117 -0
  31. package/server/src/LegacyClient/Identity/IdentityProvider.php +174 -0
  32. package/server/src/LegacyClient/Identity/Profile.php +18 -0
  33. package/server/src/LegacyClient/OIDCClient.php +350 -0
  34. package/server/src/LegacyClient/Profile/WebID.php +26 -0
  35. package/server/src/LegacyClient/SolidClient.php +270 -0
  36. package/server/src/Models/SolidIdentity.php +131 -0
  37. package/server/src/Providers/SolidServiceProvider.php +62 -0
  38. package/server/src/Support/Utils.php +9 -0
  39. package/server/src/routes.php +47 -0
  40. package/server/tests/Feature.php +5 -0
  41. package/translations/en-us.yaml +2 -0
  42. package/tsconfig.declarations.json +10 -0
@@ -0,0 +1,270 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Solid\LegacyClient;
4
+
5
+ use EasyRdf\Graph;
6
+ use Fleetbase\Solid\Client\Identity\IdentityProvider;
7
+ use Fleetbase\Solid\Models\SolidIdentity;
8
+ use Illuminate\Http\Client\Response;
9
+ use Illuminate\Support\Facades\Http;
10
+ use Illuminate\Support\Str;
11
+
12
+ class SolidClient
13
+ {
14
+ /**
15
+ * The host of the Solid server.
16
+ */
17
+ private string $host = 'localhost';
18
+
19
+ /**
20
+ * The port on which the Solid server is running.
21
+ */
22
+ private int $port = 3000;
23
+
24
+ /**
25
+ * Indicates whether the connection to the Solid server should be secure (HTTPS).
26
+ */
27
+ private bool $secure = true;
28
+
29
+ /**
30
+ * The identity provider for authentication with the Solid server.
31
+ */
32
+ public IdentityProvider $identity;
33
+
34
+ public bool $identityProviderInitialized = false;
35
+
36
+ private const DEFAULT_MIME_TYPE = 'text/turtle';
37
+ private const LDP_BASIC_CONTAINER = 'http://www.w3.org/ns/ldp#BasicContainer';
38
+ private const LDP_RESOURCE = 'http://www.w3.org/ns/ldp#Resource';
39
+ private const OIDC_ISSUER = 'http://www.w3.org/ns/solid/terms#oidcIssuer';
40
+
41
+ /**
42
+ * Constructor for the SolidClient.
43
+ *
44
+ * Initializes the client with the provided options or defaults.
45
+ *
46
+ * @param array $options configuration options for the client
47
+ */
48
+ public function __construct(array $options = [])
49
+ {
50
+ $this->host = config('solid.server.host', data_get($options, 'host'));
51
+ $this->port = (int) config('solid.server.port', data_get($options, 'port'));
52
+ $this->secure = (bool) config('solid.server.secure', data_get($options, 'secure'));
53
+ $this->initializeIdentityProvider($options);
54
+ }
55
+
56
+ private function initializeIdentityProvider(array $options = []): IdentityProvider
57
+ {
58
+ $this->identity = new IdentityProvider($this);
59
+ if (isset($options['restore']) && is_string($options['restore'])) {
60
+ $this->identity->restoreClientCredentials($options['restore']);
61
+ }
62
+
63
+ return $this->identity;
64
+ }
65
+
66
+ /**
67
+ * Factory method to create a new instance of the SolidClient.
68
+ *
69
+ * @param array $options configuration options for the client
70
+ *
71
+ * @return static a new instance of SolidClient
72
+ */
73
+ public static function create(array $options = []): self
74
+ {
75
+ return new static($options);
76
+ }
77
+
78
+ /**
79
+ * Constructs the URL to the Solid server based on the configured host, port, and security.
80
+ *
81
+ * @return string the fully constructed URL
82
+ */
83
+ public function getServerUrl(): string
84
+ {
85
+ $protocol = $this->secure ? 'https' : 'http';
86
+ $host = preg_replace('#^.*://#', '', $this->host);
87
+
88
+ return "{$protocol}://{$host}:{$this->port}";
89
+ }
90
+
91
+ /**
92
+ * Creates a full request URL based on the server URL and the provided URI.
93
+ *
94
+ * This function constructs a complete URL by appending the given URI to the base server URL.
95
+ * It ensures that there is exactly one slash between the base URL and the URI.
96
+ *
97
+ * @param string|null $uri The URI to append to the server URL. If null, only the server URL is returned.
98
+ *
99
+ * @return string the fully constructed URL
100
+ */
101
+ private function createRequestUrl(string $uri = null): string
102
+ {
103
+ $url = $this->getServerUrl();
104
+
105
+ if (is_string($uri)) {
106
+ $uri = '/' . ltrim($uri, '/');
107
+ $url .= $uri;
108
+ }
109
+
110
+ return $url;
111
+ }
112
+
113
+ /**
114
+ * Sets the necessary authentication headers for the request.
115
+ *
116
+ * This function adds authentication headers to the provided options array.
117
+ * It includes an Authorization header with a DPoP token if an access token is available.
118
+ * It also generates a DPoP header based on the request method and URL.
119
+ *
120
+ * @param array &$options The array of options for the HTTP request, passed by reference
121
+ * @param string $method The HTTP method of the request (e.g., 'GET', 'POST').
122
+ * @param string $url the full URL of the request
123
+ *
124
+ * @return array the modified options array with added authentication headers
125
+ */
126
+ private function setAuthenticationHeaders(array &$options, string $method, string $url)
127
+ {
128
+ $withoutAuth = data_get($options, 'withoutAuth', false);
129
+ if ($withoutAuth) {
130
+ return $options;
131
+ }
132
+
133
+ $useCssAuth = data_get($options, 'useCssAuth', false);
134
+ $useDpopAuth = data_get($options, 'useDpopAuth', true);
135
+ $headers = data_get($options, 'headers', []);
136
+ $accessToken = isset($this->identity) ? $this->identity->getAccessToken() : null;
137
+ if ($accessToken) {
138
+ if ($useDpopAuth) {
139
+ $headers['Authorization'] = 'DPoP ' . $accessToken;
140
+ $headers['DPoP'] = $this->identity->createDPoP($method, $url, true);
141
+ }
142
+
143
+ if ($useCssAuth) {
144
+ $headers['Authorization'] = 'CSS-Account-Token ' . $accessToken;
145
+ }
146
+ }
147
+
148
+ $options['headers'] = $headers;
149
+
150
+ return $options;
151
+ }
152
+
153
+ /**
154
+ * Make a HTTP request to the Solid server.
155
+ *
156
+ * @param string $method The HTTP method (GET, POST, etc.)
157
+ * @param string $uri The URI to send the request to
158
+ * @param array $options Options for the request
159
+ */
160
+ protected function request(string $method, string $uri, array $data = [], array $options = []): Response
161
+ {
162
+ if (Str::startsWith($uri, 'http')) {
163
+ $url = $uri;
164
+ } else {
165
+ $url = $this->createRequestUrl($uri);
166
+ }
167
+ $this->setAuthenticationHeaders($options, $method, $url);
168
+ return Http::withOptions($options)->{$method}($url, $data);
169
+ }
170
+
171
+ public function requestWithIdentity(SolidIdentity $solidIdentity, string $method, string $uri, array $data = [], array $options = [])
172
+ {
173
+ if (Str::startsWith($uri, 'http')) {
174
+ $url = $uri;
175
+ } else {
176
+ $url = $this->createRequestUrl($uri);
177
+ }
178
+
179
+ // prepare headers
180
+ if (!isset($options['headers']) || !is_array($options['headers'])) {
181
+ $options['headers'] = [];
182
+ }
183
+
184
+ $accessToken = $solidIdentity->getAccessToken();
185
+ if ($accessToken) {
186
+ $options['headers']['Authorization'] = 'DPoP ' . $accessToken;
187
+ $options['headers']['DPoP'] = $this->identity->createDPoP($method, $url, true, $accessToken);
188
+ }
189
+
190
+ // dump($options);
191
+ return Http::withOptions($options)->{$method}($url, $data);
192
+ }
193
+
194
+ /**
195
+ * Send a GET request to the Solid server.
196
+ *
197
+ * @param string $uri The URI to send the request to
198
+ * @param array $options Options for the request
199
+ */
200
+ public function get(string $uri, array $data = [], array $options = []): Response
201
+ {
202
+ return $this->request('get', $uri, $data, $options);
203
+ }
204
+
205
+ /**
206
+ * Send a POST request to the Solid server.
207
+ *
208
+ * @param string $uri The URI to send the request to
209
+ * @param array $data Data to be sent in the request body
210
+ */
211
+ public function post(string $uri, array $data = [], array $options = []): Response
212
+ {
213
+ return $this->request('post', $uri, $data, $options);
214
+ }
215
+
216
+ /**
217
+ * Send a PUT request to the Solid server.
218
+ *
219
+ * @param string $uri The URI to send the request to
220
+ * @param array $data Data to be sent in the request body
221
+ */
222
+ public function put(string $uri, array $data = [], array $options = []): Response
223
+ {
224
+ return $this->request('put', $uri, $data, $options);
225
+ }
226
+
227
+ /**
228
+ * Send a PATCH request to the Solid server.
229
+ *
230
+ * @param string $uri The URI to send the request to
231
+ * @param array $data Data to be sent in the request body
232
+ */
233
+ public function patch(string $uri, array $data = [], array $options = []): Response
234
+ {
235
+ return $this->request('patch', $uri, $data, $options);
236
+ }
237
+
238
+ /**
239
+ * Send a DELETE request to the Solid server.
240
+ *
241
+ * @param string $uri The URI to send the request to
242
+ * @param array $options Options for the request
243
+ */
244
+ public function delete(string $uri, array $data = [], array $options = []): Response
245
+ {
246
+ return $this->request('delete', $uri, $data, $options);
247
+ }
248
+
249
+ public function getProfile(string $webId, array $options = []): Graph
250
+ {
251
+ $response = $this->get($webId, $options);
252
+ if (null !== $format = $response->getHeaders()['content-type'][0] ?? null) {
253
+ // strip parameters (such as charset) if any
254
+ $format = explode(';', $format, 2)[0];
255
+ }
256
+
257
+ return new Graph($webId, $response->getContent(), $format);
258
+ }
259
+
260
+ public function getOpenIdConfiguration()
261
+ {
262
+ $response = $this->get('.well-known/openid-configuration', [], ['withoutAuth' => true]);
263
+
264
+ if ($response->successful()) {
265
+ return $response->json();
266
+ }
267
+
268
+ throw $response->toException();
269
+ }
270
+ }
@@ -0,0 +1,131 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Solid\Models;
4
+
5
+ use Fleetbase\Casts\Json;
6
+ use Fleetbase\Models\Company;
7
+ use Fleetbase\Models\Model;
8
+ use Fleetbase\Models\User;
9
+ use Fleetbase\Solid\Client\SolidClient;
10
+ use Fleetbase\Support\Utils;
11
+ use Fleetbase\Traits\HasUuid;
12
+ use Illuminate\Support\Str;
13
+
14
+ class SolidIdentity extends Model
15
+ {
16
+ use HasUuid;
17
+
18
+ /**
19
+ * The database table used by the model.
20
+ *
21
+ * @var string
22
+ */
23
+ protected $table = 'solid_identities';
24
+
25
+ /**
26
+ * The attributes that are mass assignable.
27
+ *
28
+ * @var array
29
+ */
30
+ protected $fillable = ['company_uuid', 'user_uuid', 'token_response', 'identifier'];
31
+
32
+ /**
33
+ * The attributes that should be cast to native types.
34
+ *
35
+ * @var array
36
+ */
37
+ protected $casts = [
38
+ 'token_response' => Json::class,
39
+ ];
40
+
41
+ /**
42
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
43
+ */
44
+ public function user()
45
+ {
46
+ return $this->belongsTo(User::class, 'user_uuid', 'uuid');
47
+ }
48
+
49
+ /**
50
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
51
+ */
52
+ public function company()
53
+ {
54
+ return $this->belongsTo(Company::class, 'company_uuid', 'uuid');
55
+ }
56
+
57
+ public function getAccessToken(): ?string
58
+ {
59
+ return data_get($this, 'token_response.access_token');
60
+ }
61
+
62
+ public function getRedirectUri(array $query = [], int $port = 8000): string
63
+ {
64
+ return Utils::apiUrl('solid/int/v1/oidc/complete-registration/' . $this->identifier, $query, $port);
65
+ }
66
+
67
+ public function generateRequestCode(): SolidIdentity
68
+ {
69
+ $requestCode = static::generateUniqueRequestCode();
70
+ $this->update(['identifier' => $requestCode]);
71
+
72
+ return $this;
73
+ }
74
+
75
+ /**
76
+ * Generate a unique request code.
77
+ */
78
+ public static function generateUniqueRequestCode(): string
79
+ {
80
+ do {
81
+ // Generate a random string.
82
+ $requestCode = Str::random(16);
83
+
84
+ // Check if it's unique in the identifier column
85
+ $exists = static::where('identifier', $requestCode)->exists();
86
+ } while ($exists);
87
+
88
+ return $requestCode;
89
+ }
90
+
91
+ /**
92
+ * Initializes a SolidIdentity instance for the current session.
93
+ *
94
+ * This method retrieves an existing SolidIdentity based on the user's and company's UUIDs
95
+ * from the current session. If no identity is found, a new one is created.
96
+ * In both cases, a new unique request code is generated and updated for the SolidIdentity.
97
+ * The updated SolidIdentity instance is then returned.
98
+ *
99
+ * @return SolidIdentity the SolidIdentity instance with updated request code
100
+ */
101
+ public static function initialize(): SolidIdentity
102
+ {
103
+ $requestCode = static::generateUniqueRequestCode();
104
+ $solidIdentity = static::firstOrCreate(['user_uuid' => session('user'), 'company_uuid' => session('company')]);
105
+ $solidIdentity->update(['identifier' => $requestCode]);
106
+
107
+ return $solidIdentity;
108
+ }
109
+
110
+ public static function current(): SolidIdentity
111
+ {
112
+ $solidIdentity = static::where(['user_uuid' => session('user'), 'company_uuid' => session('company')])->first();
113
+ if (!$solidIdentity) {
114
+ return static::initialize();
115
+ }
116
+
117
+ // If no request code
118
+ if (empty($solidIdentity->identifier)) {
119
+ $solidIdentity->generateRequestCode();
120
+ }
121
+
122
+ return $solidIdentity;
123
+ }
124
+
125
+ public function request(string $method, string $uri, array $data = [], array $options = [])
126
+ {
127
+ $solidClient = new SolidClient();
128
+
129
+ return $solidClient->requestWithIdentity($this, $method, $uri, $data, $options);
130
+ }
131
+ }
@@ -0,0 +1,62 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Solid\Providers;
4
+
5
+ use Fleetbase\FleetOps\Providers\FleetOpsServiceProvider;
6
+ use Fleetbase\Providers\CoreServiceProvider;
7
+
8
+ if (!class_exists(CoreServiceProvider::class)) {
9
+ throw new \Exception('Solid Extension cannot be loaded without `fleetbase/core-api` installed!');
10
+ }
11
+
12
+ if (!class_exists(FleetOpsServiceProvider::class)) {
13
+ throw new \Exception('Solid Extension cannot be loaded without `fleetbase/fleetops-api` installed!');
14
+ }
15
+
16
+ /**
17
+ * Solid Protocol Extension Service Provider.
18
+ */
19
+ class SolidServiceProvider extends CoreServiceProvider
20
+ {
21
+ /**
22
+ * The observers registered with the service provider.
23
+ *
24
+ * @var array
25
+ */
26
+ public $observers = [];
27
+
28
+ /**
29
+ * Register any application services.
30
+ *
31
+ * Within the register method, you should only bind things into the
32
+ * service container. You should never attempt to register any event
33
+ * listeners, routes, or any other piece of functionality within the
34
+ * register method.
35
+ *
36
+ * More information on this can be found in the Laravel documentation:
37
+ * https://laravel.com/docs/8.x/providers
38
+ *
39
+ * @return void
40
+ */
41
+ public function register()
42
+ {
43
+ $this->app->register(CoreServiceProvider::class);
44
+ $this->app->register(FleetOpsServiceProvider::class);
45
+ }
46
+
47
+ /**
48
+ * Bootstrap any package services.
49
+ *
50
+ * @return void
51
+ *
52
+ * @throws \Exception if the `fleetbase/core-api` package is not installed
53
+ */
54
+ public function boot()
55
+ {
56
+ $this->registerObservers();
57
+ $this->registerExpansionsFrom(__DIR__ . '/../Expansions');
58
+ $this->loadRoutesFrom(__DIR__ . '/../routes.php');
59
+ $this->loadMigrationsFrom(__DIR__ . '/../../migrations');
60
+ $this->mergeConfigFrom(__DIR__ . '/../../config/solid.php', 'solid');
61
+ }
62
+ }
@@ -0,0 +1,9 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\Solid\Support;
4
+
5
+ use Fleetbase\Support\Utils as FleetbaseUtils;
6
+
7
+ class Utils extends FleetbaseUtils
8
+ {
9
+ }
@@ -0,0 +1,47 @@
1
+ <?php
2
+
3
+ use Illuminate\Support\Facades\Route;
4
+
5
+ /*
6
+ |--------------------------------------------------------------------------
7
+ | API Routes
8
+ |--------------------------------------------------------------------------
9
+ |
10
+ | Here is where you can register API routes for your application. These
11
+ | routes are loaded by the RouteServiceProvider within a group which
12
+ | is assigned the "api" middleware group. Enjoy building your API!
13
+ |
14
+ */
15
+
16
+ Route::prefix(config('solid.api.routing.prefix', 'solid'))->namespace('Fleetbase\Solid\Http\Controllers')->group(
17
+ function ($router) {
18
+ /*
19
+ |--------------------------------------------------------------------------
20
+ | Solid Extension API Routes
21
+ |--------------------------------------------------------------------------
22
+ |
23
+ | Primary internal routes for console.
24
+ */
25
+ $router->prefix(config('solid.api.routing.internal_prefix', 'int'))->group(
26
+ function ($router) {
27
+ $router->get('test', 'SolidController@play');
28
+ $router->group(
29
+ ['prefix' => 'v1'],
30
+ function ($router) {
31
+ $router->get('authenticate/{identifier}', 'SolidController@authenticate');
32
+ $router->group(['middleware' => ['fleetbase.protected']], function ($router) {
33
+ $router->get('account', 'SolidController@getAccountIndex');
34
+ $router->get('request-authentication', 'SolidController@requestAuthentication');
35
+ $router->get('server-config', 'SolidController@getServerConfig');
36
+ $router->post('server-config', 'SolidController@saveServerConfig');
37
+ });
38
+
39
+ $router->group(['prefix' => 'oidc'], function ($router) {
40
+ $router->any('complete-registration/{identifier}', 'OIDCController@completeRegistration');
41
+ });
42
+ }
43
+ );
44
+ }
45
+ );
46
+ }
47
+ );
@@ -0,0 +1,5 @@
1
+ <?php
2
+
3
+ test('example', function () {
4
+ expect(true)->toBeTrue();
5
+ });
@@ -0,0 +1,2 @@
1
+ solid:
2
+ extension-name: Solid
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "declarationDir": "declarations",
5
+ "emitDeclarationOnly": true,
6
+ "noEmit": false,
7
+ "rootDir": "."
8
+ },
9
+ "include": ["addon", "addon-test-support"]
10
+ }