diff --git a/src/Http/Controllers/BulkController.php b/src/Http/Controllers/BulkController.php new file mode 100644 index 0000000..e7ecede --- /dev/null +++ b/src/Http/Controllers/BulkController.php @@ -0,0 +1,114 @@ +getContent(), '8bit'); + + if($contentSize > static::MAX_PAYLOAD_SIZE){ + throw (new SCIMException('Payload too large!'))->setCode(413)->setScimType('tooLarge'); + } + + $validator = Validator::make($request->input(), [ + 'schemas' => 'required|array', + 'schemas.*' => 'required|string|in:urn:ietf:params:scim:api:messages:2.0:BulkRequest', + // TODO: implement failOnErrors + 'failOnErrors' => 'nullable|int', + 'Operations' => 'required|array', + 'Operations.*.method' => 'required|string|in:POST,PUT,PATCH,DELETE', + 'Operations.*.path' => 'required|string|in:/Users,/Groups', + 'Operations.*.bulkId' => 'nullable|string', + 'Operations.*.data' => 'nullable|array', + ]); + + if ($validator->fails()) { + $e = $validator->errors(); + + throw (new SCIMException('Invalid data!'))->setCode(400)->setScimType('invalidSyntax')->setErrors($e); + } + + $operations = $request->input('Operations'); + + if(count($operations) > static::MAX_OPERATIONS){ + throw (new SCIMException('Too many operations!'))->setCode(413)->setScimType('tooLarge'); + } + + $bulkIdMapping = []; + $responses = []; + + // Remove everything till the last occurence of Bulk, e.g. /scim/v2/Bulk should become /scim/v2/ + $prefix = substr($request->path(), 0, strrpos($request->path(), '/Bulk')); + + foreach ($operations as $operation) { + + $method = $operation['method']; + $bulkId = $operation['bulkId'] ?? null; + + // Call internal Laravel route based on method, path and data + $encoded = json_encode($operation['data'] ?? []); + $encoded = str_replace(array_keys($bulkIdMapping), array_values($bulkIdMapping), $encoded); + + $request = Request::create( + $prefix . $operation['path'], + $operation['method'], + server: [ + 'HTTP_Authorization' => $request->header('Authorization'), + 'CONTENT_TYPE' => 'application/scim+json', + ], + content: $encoded + ); + + // run request and get response + /** @var \Illuminate\Http\Response */ + $response = app()->handle($request); + // Get the JSON content of the response + $jsonContent = $response->getContent(); + // Decode the JSON content + $responseData = json_decode($jsonContent, false); + + // Store the id attribute + $id = $responseData?->id ?? null; + + // Store the id attribute in the bulkIdMapping array + if ($bulkId !== null && $id !== null) { + $bulkIdMapping['bulkId:' . $bulkId] = $id; + } + + $responses[] = array_filter([ + "location" => $responseData?->meta?->location ?? null, + "method" => $method, + "bulkId" => $bulkId, + "version" => $responseData?->meta?->version ?? null, + "status" => $response->getStatusCode(), + "response" => $response->getStatusCode() >= 400 ? $responseData : null, + ]); + } + + // Return a response indicating the successful processing of the SCIM BULK request + return response()->json( + [ + 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:BulkResponse'], + 'Operations' => + $responses])->setStatusCode(200) + ->withHeaders(['Content-Type' => 'application/scim+json']); + } +} diff --git a/src/Http/Controllers/ServiceProviderController.php b/src/Http/Controllers/ServiceProviderController.php index eb2cea0..d97d8b3 100644 --- a/src/Http/Controllers/ServiceProviderController.php +++ b/src/Http/Controllers/ServiceProviderController.php @@ -14,7 +14,9 @@ public function index() "supported" => true, ], "bulk" => [ - "supported" => false, + "supported" => true, + "maxPayloadSize" => BulkController::MAX_PAYLOAD_SIZE, + "maxOperations" => BulkController::MAX_OPERATIONS ], "filter" => [ "supported" => true, diff --git a/src/RouteProvider.php b/src/RouteProvider.php index 74f5110..82bb15e 100644 --- a/src/RouteProvider.php +++ b/src/RouteProvider.php @@ -76,6 +76,8 @@ private static function allRoutes(array $options = []) { Route::post('.search', '\ArieTimmerman\Laravel\SCIMServer\Http\Controllers\ResourceController@notImplemented'); + Route::post("/Bulk", '\ArieTimmerman\Laravel\SCIMServer\Http\Controllers\BulkController@processBulkRequest'); + // TODO: Use the attributes parameters ?attributes=userName, excludedAttributes=asdg,asdg (respect "returned" settings "always") Route::get('/{resourceType}/{resourceObject}', '\ArieTimmerman\Laravel\SCIMServer\Http\Controllers\ResourceController@show')->name('scim.resource'); Route::get("/{resourceType}", '\ArieTimmerman\Laravel\SCIMServer\Http\Controllers\ResourceController@index')->name('scim.resources');