Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
448 changes: 448 additions & 0 deletions README.md

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions src/SDK/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,57 @@ public function doPost(string $uri, array $body, ?bool $useManagementKey = false
}
}

/**
* Sends a PATCH request to the specified URI with a JSON body and an optional auth token.
*
* @param string $uri URI endpoint.
* @param array $body Request body.
* @param bool $useManagementKey Whether to use the management key for authentication.
* @return array JWT response array.
* @throws AuthException|RateLimitException|GuzzleException|\JsonException If the request fails.
*/
public function doPatch(string $uri, array $body, ?bool $useManagementKey = false, ?string $refreshToken = null): array
{
$authToken = "";

if ($refreshToken) {
$authToken = $this->getAuthToken(false, $refreshToken);
} else {
$authToken = $this->getAuthToken($useManagementKey, '');
}

$this->assertCredentialHost($uri, $authToken);

$body = $this->transformEmptyArraysToObjects($body);
$jsonBody = empty($body) ? '{}' : json_encode($body);
try {
$headers = $this->getHeaders($authToken);
$response = $this->executeWithRetry(function () use ($uri, $jsonBody, $headers) {
return $this->httpClient->patch($uri, ['headers' => $headers, 'body' => $jsonBody]);
});

// Ensure the response is an object with getBody method
if (!is_object($response) || !method_exists($response, 'getBody') || !method_exists($response, 'getHeader')) {
throw new AuthException(500, 'internal error', 'Invalid response from API');
}

// Read Body
$body = $response->getBody();
$body->rewind();
$contents = $body->getContents() ?? [];

return json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
} catch (RequestException $e) {
if ($this->debug) {
$statusCode = $e->getResponse() ? $e->getResponse()->getStatusCode() : 'N/A';
$responseBody = $e->getResponse() ? $e->getResponse()->getBody()->getContents() : 'No response body';
error_log("Descope SDK [PATCH] RequestException: " . $e->getMessage());
error_log("Descope SDK [PATCH] Error: HTTP Status Code: $statusCode, Response: $responseBody");
}
throw $this->createExceptionFromRequestException($e);
}
}

/**
* Sends a GET request to the specified URI with an optional auth token.
*
Expand Down
73 changes: 73 additions & 0 deletions src/SDK/Management/AccessKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ public function deactivate(string $id): void
$this->api->doPost(MgmtV1::$ACCESS_KEY_DEACTIVATE_PATH, $body, true);
}

/**
* Deactivate multiple access keys in a single batch request.
*
* Deactivated access keys cannot be used to authenticate but can be
* reactivated later using the activate or activateBatch methods.
*
* @param array $ids A list of access key IDs to deactivate.
* @return void
* @throws AuthException If the request fails.
*/
public function deactivateBatch(array $ids): void
{
$body = [
'ids' => $ids,
];

$this->api->doPost(MgmtV1::$ACCESS_KEY_DEACTIVATE_BATCH_PATH, $body, true);
}

/**
* Activate an access key.
*
Expand All @@ -177,6 +196,22 @@ public function activate(string $id): void
$this->api->doPost(MgmtV1::$ACCESS_KEY_ACTIVATE_PATH, $body, true);
}

/**
* Activate multiple access keys in a single batch request.
*
* @param array $ids A list of access key IDs to activate.
* @return void
* @throws AuthException If the request fails.
*/
public function activateBatch(array $ids): void
{
$body = [
'ids' => $ids,
];

$this->api->doPost(MgmtV1::$ACCESS_KEY_ACTIVATE_BATCH_PATH, $body, true);
}

/**
* Delete an access key.
*
Expand All @@ -195,4 +230,42 @@ public function delete(string $id): void

$this->api->doPost(MgmtV1::$ACCESS_KEY_DELETE_PATH, $body, true);
}

/**
* Delete multiple access keys in a single batch request.
*
* IMPORTANT: This action is irreversible. Once an access key is deleted
* it cannot be recovered.
*
* @param array $ids A list of access key IDs to delete.
* @return void
* @throws AuthException If the request fails.
*/
public function deleteBatch(array $ids): void
{
$body = [
'ids' => $ids,
];

$this->api->doPost(MgmtV1::$ACCESS_KEY_DELETE_BATCH_PATH, $body, true);
}

/**
* Rotate an access key, generating a new cleartext value for it.
*
* The old cleartext value is invalidated and the returned response
* contains the new cleartext value along with the updated key info.
*
* @param string $id The ID of the access key to rotate.
* @return array The rotate response, containing 'key' and 'cleartext'.
* @throws AuthException If the request fails.
*/
public function rotate(string $id): array
{
$body = [
'id' => $id,
];

return $this->api->doPost(MgmtV1::$ACCESS_KEY_ROTATE_PATH, $body, true);
}
}
105 changes: 105 additions & 0 deletions src/SDK/Management/Audit.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,86 @@ public function search(
];
}

/**
* Search the audit logs with various filters, returning the total count.
*
* Uses the same audit/search
* endpoint as search() but additionally supports paging (size/page) and
* returns the total number of matching records alongside the audits.
*
* @param array|null $options Optional associative array of filters. Supported keys:
* 'userIds' (array), 'actions' (array),
* 'excludedActions' (array), 'devices' (array),
* 'methods' (array), 'geos' (array),
* 'remoteAddresses' (array), 'loginIds' (array),
* 'tenants' (array), 'noTenants' (bool),
* 'text' (string), 'from' (DateTime), 'to' (DateTime),
* 'limit' (int), 'page' (int).
*
* @return array The response containing 'audits' (list of records) and 'total' (int).
*
* @throws AuthException If the search operation fails.
*/
public function searchAll(?array $options = null): array
{
$options = $options ?? [];

$body = ['noTenants' => $options['noTenants'] ?? false];
if (isset($options['userIds'])) {
$body['userIds'] = $options['userIds'];
}
if (isset($options['actions'])) {
$body['actions'] = $options['actions'];
}
if (isset($options['excludedActions'])) {
$body['excludedActions'] = $options['excludedActions'];
}
if (isset($options['devices'])) {
$body['devices'] = $options['devices'];
}
if (isset($options['methods'])) {
$body['methods'] = $options['methods'];
}
if (isset($options['geos'])) {
$body['geos'] = $options['geos'];
}
if (isset($options['remoteAddresses'])) {
$body['remoteAddresses'] = $options['remoteAddresses'];
}
if (isset($options['loginIds'])) {
$body['externalIds'] = $options['loginIds'];
}
if (isset($options['tenants'])) {
$body['tenants'] = $options['tenants'];
}
if (isset($options['text'])) {
$body['text'] = $options['text'];
}
if (isset($options['from']) && $options['from'] instanceof DateTime) {
$body['from'] = $options['from']->getTimestamp() * 1000;
}
if (isset($options['to']) && $options['to'] instanceof DateTime) {
$body['to'] = $options['to']->getTimestamp() * 1000;
}
if (isset($options['limit'])) {
$body['size'] = $options['limit'];
}
if (isset($options['page'])) {
$body['page'] = $options['page'];
}

$response = $this->api->doPost(
MgmtV1::$AUDIT_SEARCH,
$body,
true
);

return [
'audits' => array_map([$this, 'convertAuditRecord'], $response['audits'] ?? []),
'total' => $response['total'] ?? 0,
];
}

/**
* Create an audit event.
*
Expand Down Expand Up @@ -146,6 +226,31 @@ public function createEvent(
);
}

/**
* Create an audit webhook connector.
*
* This configures an HTTP webhook that receives audit events matching the
* provided filters.
*
* @param array $options Associative array describing the webhook. Supported keys:
* 'name' (string, required), 'description' (string),
* 'url' (string), 'authentication' (array),
* 'hmacSecret' (string), 'headers' (array),
* 'insecure' (bool), 'filters' (array).
*
* @return void
*
* @throws AuthException If the webhook creation operation fails.
*/
public function createAuditWebhook(array $options): void
{
$this->api->doPost(
MgmtV1::$AUDIT_WEBHOOK_CREATE_PATH,
$options,
true
);
}

/**
* Convert an audit record from the API response to a structured array.
*
Expand Down
102 changes: 102 additions & 0 deletions src/SDK/Management/Flow.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,108 @@ public function listFlows(): array
);
}

/**
* Run a management flow synchronously and wait for its output.
*
* This method executes the specified flow and returns its output once the
* flow completes.
*
* @param string $flowId The ID of the flow to run.
* @param array|null $options Optional flow options. Supported keys:
* 'input' (array) input values passed to the flow,
* 'preview' (bool) whether to run in preview mode,
* 'tenant' (string) the tenant to run the flow for.
*
* @return array The response containing the flow output.
*
* @throws AuthException If the run operation fails.
*/
public function runManagementFlow(string $flowId, ?array $options = null): array
{
$body = [
'flowId' => $flowId,
'options' => $options,
];

return $this->api->doPost(
MgmtV1::$FLOW_RUN_PATH,
$body,
true
);
}

/**
* Run a management flow asynchronously.
*
* This method starts the specified flow and returns immediately with the
* execution ID that can be used to poll for the result.
*
* @param string $flowId The ID of the flow to run.
* @param array|null $options Optional flow options. Supported keys:
* 'input' (array) input values passed to the flow,
* 'preview' (bool) whether to run in preview mode,
* 'tenant' (string) the tenant to run the flow for.
*
* @return array The response containing the 'executionId' of the started flow.
*
* @throws AuthException If the run operation fails.
*/
public function runManagementFlowAsync(string $flowId, ?array $options = null): array
{
$body = [
'flowId' => $flowId,
'options' => $options,
];

return $this->api->doPost(
MgmtV1::$FLOW_RUN_ASYNC_PATH,
$body,
true
);
}

/**
* Get the result of an asynchronously executed management flow.
*
* This method retrieves the output of a flow that was started with
* runManagementFlowAsync, using its execution ID.
*
* @param string $executionId The execution ID returned by runManagementFlowAsync.
*
* @return array The response containing the flow output.
*
* @throws AuthException If the result retrieval fails.
*/
public function getManagementFlowAsyncResult(string $executionId): array
{
$body = [
'executionId' => $executionId,
];

return $this->api->doPost(
MgmtV1::$FLOW_ASYNC_RESULT_PATH,
$body,
true
);
}

/**
* Delete flows by their IDs.
*
* This method removes all flows identified by the provided list of flow IDs.
*
* @param array $flowIds The list of flow IDs to delete.
*
* @return void
*
* @throws AuthException If the delete operation fails.
*/
public function deleteFlows(array $flowIds): void
Comment thread
dorsha marked this conversation as resolved.
{
// Alias for delete().
$this->delete($flowIds);
}

/**
* Delete flows by their IDs.
*
Expand Down
Loading