Skip to content

Commit

Permalink
Support for Bulk Operations (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
arietimmerman committed Nov 18, 2023
1 parent f9f48c8 commit 397ee69
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 1 deletion.
114 changes: 114 additions & 0 deletions src/Http/Controllers/BulkController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace ArieTimmerman\Laravel\SCIMServer\Http\Controllers;

use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class BulkController extends Controller
{

const MAX_PAYLOAD_SIZE = 1048576;
const MAX_OPERATIONS = 10;

/**
* Process SCIM BULK requests.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function processBulkRequest(Request $request)
{

// get the content size in bytes from raw content (not entirely accurate, but good enough for now)
$contentSize = mb_strlen($request->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']);
}
}
4 changes: 3 additions & 1 deletion src/Http/Controllers/ServiceProviderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/RouteProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit 397ee69

Please sign in to comment.