Skip to content

Commit

Permalink
Update cookie handling. Not all requests / controls will provide a ne…
Browse files Browse the repository at this point in the history
…w cookie. Add a handler so that library users can manage the cookie as it is changed / updated.
  • Loading branch information
ChadSikorra committed Jun 22, 2023
1 parent c75f591 commit bf753f3
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 50 deletions.
14 changes: 14 additions & 0 deletions src/FreeDSx/Ldap/Operation/Request/SyncRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class SyncRequest extends SearchRequest
{
private ?Closure $syncIdSetHandler = null;

private ?Closure $cookieUpdateHandler = null;

public function __construct(
?FilterInterface $filter = null,
string|Attribute ...$attributes
Expand All @@ -34,4 +36,16 @@ public function getIdSetHandler(): ?Closure
{
return $this->syncIdSetHandler;
}

public function useCookieUpdateHandler(?Closure $cookieUpdateHandler): self
{
$this->cookieUpdateHandler = $cookieUpdateHandler;

return $this;
}

public function getCookieUpdateHandler(): ?Closure
{
return $this->cookieUpdateHandler;
}
}
112 changes: 73 additions & 39 deletions src/FreeDSx/Ldap/Protocol/ClientProtocolHandler/ClientSyncHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class ClientSyncHandler extends ClientBasicHandler
{
use ClientSearchTrait;

private ?SyncRequest $syncRequest = null;
private SyncRequest $syncRequest;

private SyncRequestControl $syncRequestControl;

Expand All @@ -52,6 +52,8 @@ class ClientSyncHandler extends ClientBasicHandler

private ?Closure $syncIdSetHandler = null;

private ?Closure $cookieUpdateHandler = null;

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -79,13 +81,28 @@ public function handleResponse(

try {
do {
$this->syncRequestControl->setCookie($this->session->getCookie());
$searchDone = self::search(
$messageFrom,
$messageTo,
$queue,
);
// @todo This should be a configurable option or a specific exception...
if ($this->isRefreshRequired($searchDone)) {
$this->syncRequestControl->setCookie(null);
// We need to regenerate a search request / response with a new cookie...
$this->syncRequestControl->setCookie(
$searchDone
->controls()
->getByClass(SyncDoneControl::class)
?->getCookie()
);
$messageTo = new LdapMessageRequest(
$queue->generateId(),
$this->syncRequest,
...$messageFrom->controls()->toArray()
);
$messageFrom = $queue->sendMessage($messageTo)
->getMessage($messageTo->getMessageId());
}
} while (!$this->isSyncComplete($searchDone));

Expand All @@ -95,12 +112,14 @@ public function handleResponse(
}
}


/**
* We need to set up / verify the initial sync session and message handlers before starting the overall sync process.
*/
private function initializeSync(LdapMessageRequest $messageTo): void
{
/** @var SyncRequest $searchRequest */
$searchRequest = $messageTo->getRequest();
$this->syncRequest = $searchRequest;
$this->syncRequestControl = $messageTo->controls()
->getByClass(SyncRequestControl::class) ?? throw new RuntimeException(sprintf(
'Expected a "%s", but there is none.',
Expand All @@ -118,18 +137,15 @@ private function initializeSync(LdapMessageRequest $messageTo): void
cookie: $this->syncRequestControl->getCookie(),
);

/** @var SyncRequest $searchRequest */
$searchRequest = $messageTo->getRequest();
$this->syncRequest = $searchRequest;

// We override these with our own, so save them here for now.
$this->syncEntryHandler = $searchRequest->getEntryHandler();
$this->syncReferralHandler = $searchRequest->getReferralHandler();
$this->syncIdSetHandler = $searchRequest->getIdSetHandler();

$searchRequest->useEntryHandler($this->processSyncEntry(...));
$searchRequest->useReferralHandler($this->processSyncReferral(...));
$searchRequest->useIntermediateResponseHandler($this->processIntermediateResponse(...));
$this->syncEntryHandler = $this->syncRequest->getEntryHandler();
$this->syncReferralHandler = $this->syncRequest->getReferralHandler();
$this->syncIdSetHandler = $this->syncRequest->getIdSetHandler();
$this->cookieUpdateHandler = $this->syncRequest->getCookieUpdateHandler();

$this->syncRequest->useEntryHandler($this->processSyncEntry(...));
$this->syncRequest->useReferralHandler($this->processSyncReferral(...));
$this->syncRequest->useIntermediateResponseHandler($this->processIntermediateResponse(...));
}

/**
Expand All @@ -138,10 +154,6 @@ private function initializeSync(LdapMessageRequest $messageTo): void
*/
private function resetRequestHandlers(): void
{
if ($this->syncRequest === null) {
return;
}

if ($this->syncEntryHandler !== null) {
$this->syncRequest->useEntryHandler($this->syncEntryHandler);
}
Expand All @@ -155,7 +167,7 @@ private function resetRequestHandlers(): void

private function isContentUpdatePoll(): bool
{
return !empty($this->syncRequestControl->getCookie());
return $this->syncRequestControl->getCookie() !== null;
}

private function processSyncEntry(EntryResult $entryResult): void
Expand Down Expand Up @@ -189,26 +201,23 @@ private function processIntermediateResponse(LdapMessageResponse $messageFrom):
$response = $messageFrom->getResponse();

if ($response instanceof SyncRefreshDelete) {
$this->syncRequestControl->setCookie($response->getCookie());
$this->session
->updatePhase(Session::PHASE_DELETE)
->updateCookie($response->getCookie());
$this->updateCookie($response->getCookie());
$this->session->updatePhase(Session::PHASE_DELETE);
} elseif ($response instanceof SyncRefreshPresent) {
$this->syncRequestControl->setCookie($response->getCookie());
$this->session
->updatePhase(Session::PHASE_PRESENT)
->updateCookie($response->getCookie());
$this->updateCookie($response->getCookie());
$this->session->updatePhase(Session::PHASE_PRESENT);
} elseif ($response instanceof SyncNewCookie) {
$this->session->updateCookie($response->getCookie());
$this->syncRequestControl->setCookie($response->getCookie());
} elseif ($response instanceof SyncIdSet && $this->syncIdSetHandler) {
$this->session->updateCookie($response->getCookie());
$this->syncRequestControl->setCookie($response->getCookie());
call_user_func(
$this->syncIdSetHandler,
new SyncIdSetResult($messageFrom),
$this->session,
);
$this->updateCookie($response->getCookie());
} elseif ($response instanceof SyncIdSet) {
$this->updateCookie($response->getCookie());

if ($this->syncIdSetHandler instanceof Closure) {
call_user_func(
$this->syncIdSetHandler,
new SyncIdSetResult($messageFrom),
$this->session,
);
}
}
}

Expand All @@ -219,8 +228,12 @@ private function isSyncComplete(LdapMessageResponse $response): bool
$syncDone = $response->controls()
->getByClass(SyncDoneControl::class);

return $syncDone === null
|| $result->getResultCode() === ResultCode::SUCCESS;
if ($syncDone === null) {
return true;
}
$this->updateCookie($syncDone->getCookie());

return $result->getResultCode() === ResultCode::SUCCESS;
}

private function isRefreshRequired(LdapMessageResponse $response): bool
Expand All @@ -230,4 +243,25 @@ private function isRefreshRequired(LdapMessageResponse $response): bool

return $result->getResultCode() === ResultCode::SYNCHRONIZATION_REFRESH_REQUIRED;
}

/**
* Update the cookie in all the spots if it was actually returned and different from what we already have saved.
* Some controls / requests will return the cookie, but they are not required to actually provide a value.
*/
private function updateCookie(?string $cookie): void
{
if ($cookie === null || $this->session->getCookie() === $cookie) {
return;
}

$this->syncRequestControl->setCookie($cookie);
$this->session->updateCookie($cookie);

if ($this->cookieUpdateHandler !== null) {
call_user_func(
$this->cookieUpdateHandler,
$cookie
);
}
}
}
23 changes: 12 additions & 11 deletions src/FreeDSx/Ldap/Sync/SyncRepl.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ public function __construct(
$this->controls = new ControlBag();
}

/**
* Define a closure that handles cookie updates.
*
* **Note**: The cookie might not actually be updated if nothing changes between syncs.
*/
public function useCookieUpdateHandler(Closure $handler): self
{
$this->syncRequest->useCookieUpdateHandler($handler);

return $this;
}

/**
* Define a closure that handles normal entries received during the sync process.
*
Expand Down Expand Up @@ -134,15 +146,6 @@ public function request(): SyncRequest
return $this->syncRequest;
}

/**
* The cookie related to this sync session. It is updated as the sync is preformed. It can be saved / re-used in
* future sync operations.
*/
public function getCookie(): ?string
{
return $this->cookie;
}

/**
* In a listen based sync, the server sends updates of entries that are changed after the initial refresh content is
* determined. The sync continues indefinitely until the connection is terminated or the sync is canceled.
Expand Down Expand Up @@ -205,7 +208,5 @@ private function getResponseAndUpdateCookie(Control ...$controls): void
SyncDoneControl::class,
));
}

$this->cookie = $syncDone->getCookie();
}
}

0 comments on commit bf753f3

Please sign in to comment.