Skip to content

Commit bf753f3

Browse files
committed
Update cookie handling. Not all requests / controls will provide a new cookie. Add a handler so that library users can manage the cookie as it is changed / updated.
1 parent c75f591 commit bf753f3

File tree

3 files changed

+99
-50
lines changed

3 files changed

+99
-50
lines changed

src/FreeDSx/Ldap/Operation/Request/SyncRequest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ class SyncRequest extends SearchRequest
1313
{
1414
private ?Closure $syncIdSetHandler = null;
1515

16+
private ?Closure $cookieUpdateHandler = null;
17+
1618
public function __construct(
1719
?FilterInterface $filter = null,
1820
string|Attribute ...$attributes
@@ -34,4 +36,16 @@ public function getIdSetHandler(): ?Closure
3436
{
3537
return $this->syncIdSetHandler;
3638
}
39+
40+
public function useCookieUpdateHandler(?Closure $cookieUpdateHandler): self
41+
{
42+
$this->cookieUpdateHandler = $cookieUpdateHandler;
43+
44+
return $this;
45+
}
46+
47+
public function getCookieUpdateHandler(): ?Closure
48+
{
49+
return $this->cookieUpdateHandler;
50+
}
3751
}

src/FreeDSx/Ldap/Protocol/ClientProtocolHandler/ClientSyncHandler.php

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ClientSyncHandler extends ClientBasicHandler
4040
{
4141
use ClientSearchTrait;
4242

43-
private ?SyncRequest $syncRequest = null;
43+
private SyncRequest $syncRequest;
4444

4545
private SyncRequestControl $syncRequestControl;
4646

@@ -52,6 +52,8 @@ class ClientSyncHandler extends ClientBasicHandler
5252

5353
private ?Closure $syncIdSetHandler = null;
5454

55+
private ?Closure $cookieUpdateHandler = null;
56+
5557
/**
5658
* {@inheritDoc}
5759
*/
@@ -79,13 +81,28 @@ public function handleResponse(
7981

8082
try {
8183
do {
84+
$this->syncRequestControl->setCookie($this->session->getCookie());
8285
$searchDone = self::search(
8386
$messageFrom,
8487
$messageTo,
8588
$queue,
8689
);
90+
// @todo This should be a configurable option or a specific exception...
8791
if ($this->isRefreshRequired($searchDone)) {
88-
$this->syncRequestControl->setCookie(null);
92+
// We need to regenerate a search request / response with a new cookie...
93+
$this->syncRequestControl->setCookie(
94+
$searchDone
95+
->controls()
96+
->getByClass(SyncDoneControl::class)
97+
?->getCookie()
98+
);
99+
$messageTo = new LdapMessageRequest(
100+
$queue->generateId(),
101+
$this->syncRequest,
102+
...$messageFrom->controls()->toArray()
103+
);
104+
$messageFrom = $queue->sendMessage($messageTo)
105+
->getMessage($messageTo->getMessageId());
89106
}
90107
} while (!$this->isSyncComplete($searchDone));
91108

@@ -95,12 +112,14 @@ public function handleResponse(
95112
}
96113
}
97114

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

121-
/** @var SyncRequest $searchRequest */
122-
$searchRequest = $messageTo->getRequest();
123-
$this->syncRequest = $searchRequest;
124-
125140
// We override these with our own, so save them here for now.
126-
$this->syncEntryHandler = $searchRequest->getEntryHandler();
127-
$this->syncReferralHandler = $searchRequest->getReferralHandler();
128-
$this->syncIdSetHandler = $searchRequest->getIdSetHandler();
129-
130-
$searchRequest->useEntryHandler($this->processSyncEntry(...));
131-
$searchRequest->useReferralHandler($this->processSyncReferral(...));
132-
$searchRequest->useIntermediateResponseHandler($this->processIntermediateResponse(...));
141+
$this->syncEntryHandler = $this->syncRequest->getEntryHandler();
142+
$this->syncReferralHandler = $this->syncRequest->getReferralHandler();
143+
$this->syncIdSetHandler = $this->syncRequest->getIdSetHandler();
144+
$this->cookieUpdateHandler = $this->syncRequest->getCookieUpdateHandler();
145+
146+
$this->syncRequest->useEntryHandler($this->processSyncEntry(...));
147+
$this->syncRequest->useReferralHandler($this->processSyncReferral(...));
148+
$this->syncRequest->useIntermediateResponseHandler($this->processIntermediateResponse(...));
133149
}
134150

135151
/**
@@ -138,10 +154,6 @@ private function initializeSync(LdapMessageRequest $messageTo): void
138154
*/
139155
private function resetRequestHandlers(): void
140156
{
141-
if ($this->syncRequest === null) {
142-
return;
143-
}
144-
145157
if ($this->syncEntryHandler !== null) {
146158
$this->syncRequest->useEntryHandler($this->syncEntryHandler);
147159
}
@@ -155,7 +167,7 @@ private function resetRequestHandlers(): void
155167

156168
private function isContentUpdatePoll(): bool
157169
{
158-
return !empty($this->syncRequestControl->getCookie());
170+
return $this->syncRequestControl->getCookie() !== null;
159171
}
160172

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

191203
if ($response instanceof SyncRefreshDelete) {
192-
$this->syncRequestControl->setCookie($response->getCookie());
193-
$this->session
194-
->updatePhase(Session::PHASE_DELETE)
195-
->updateCookie($response->getCookie());
204+
$this->updateCookie($response->getCookie());
205+
$this->session->updatePhase(Session::PHASE_DELETE);
196206
} elseif ($response instanceof SyncRefreshPresent) {
197-
$this->syncRequestControl->setCookie($response->getCookie());
198-
$this->session
199-
->updatePhase(Session::PHASE_PRESENT)
200-
->updateCookie($response->getCookie());
207+
$this->updateCookie($response->getCookie());
208+
$this->session->updatePhase(Session::PHASE_PRESENT);
201209
} elseif ($response instanceof SyncNewCookie) {
202-
$this->session->updateCookie($response->getCookie());
203-
$this->syncRequestControl->setCookie($response->getCookie());
204-
} elseif ($response instanceof SyncIdSet && $this->syncIdSetHandler) {
205-
$this->session->updateCookie($response->getCookie());
206-
$this->syncRequestControl->setCookie($response->getCookie());
207-
call_user_func(
208-
$this->syncIdSetHandler,
209-
new SyncIdSetResult($messageFrom),
210-
$this->session,
211-
);
210+
$this->updateCookie($response->getCookie());
211+
} elseif ($response instanceof SyncIdSet) {
212+
$this->updateCookie($response->getCookie());
213+
214+
if ($this->syncIdSetHandler instanceof Closure) {
215+
call_user_func(
216+
$this->syncIdSetHandler,
217+
new SyncIdSetResult($messageFrom),
218+
$this->session,
219+
);
220+
}
212221
}
213222
}
214223

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

222-
return $syncDone === null
223-
|| $result->getResultCode() === ResultCode::SUCCESS;
231+
if ($syncDone === null) {
232+
return true;
233+
}
234+
$this->updateCookie($syncDone->getCookie());
235+
236+
return $result->getResultCode() === ResultCode::SUCCESS;
224237
}
225238

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

231244
return $result->getResultCode() === ResultCode::SYNCHRONIZATION_REFRESH_REQUIRED;
232245
}
246+
247+
/**
248+
* Update the cookie in all the spots if it was actually returned and different from what we already have saved.
249+
* Some controls / requests will return the cookie, but they are not required to actually provide a value.
250+
*/
251+
private function updateCookie(?string $cookie): void
252+
{
253+
if ($cookie === null || $this->session->getCookie() === $cookie) {
254+
return;
255+
}
256+
257+
$this->syncRequestControl->setCookie($cookie);
258+
$this->session->updateCookie($cookie);
259+
260+
if ($this->cookieUpdateHandler !== null) {
261+
call_user_func(
262+
$this->cookieUpdateHandler,
263+
$cookie
264+
);
265+
}
266+
}
233267
}

src/FreeDSx/Ldap/Sync/SyncRepl.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ public function __construct(
5151
$this->controls = new ControlBag();
5252
}
5353

54+
/**
55+
* Define a closure that handles cookie updates.
56+
*
57+
* **Note**: The cookie might not actually be updated if nothing changes between syncs.
58+
*/
59+
public function useCookieUpdateHandler(Closure $handler): self
60+
{
61+
$this->syncRequest->useCookieUpdateHandler($handler);
62+
63+
return $this;
64+
}
65+
5466
/**
5567
* Define a closure that handles normal entries received during the sync process.
5668
*
@@ -134,15 +146,6 @@ public function request(): SyncRequest
134146
return $this->syncRequest;
135147
}
136148

137-
/**
138-
* The cookie related to this sync session. It is updated as the sync is preformed. It can be saved / re-used in
139-
* future sync operations.
140-
*/
141-
public function getCookie(): ?string
142-
{
143-
return $this->cookie;
144-
}
145-
146149
/**
147150
* In a listen based sync, the server sends updates of entries that are changed after the initial refresh content is
148151
* determined. The sync continues indefinitely until the connection is terminated or the sync is canceled.
@@ -205,7 +208,5 @@ private function getResponseAndUpdateCookie(Control ...$controls): void
205208
SyncDoneControl::class,
206209
));
207210
}
208-
209-
$this->cookie = $syncDone->getCookie();
210211
}
211212
}

0 commit comments

Comments
 (0)