Repository navigation
Fix unreachable target-properties guard in LAPSio.set() (Fixes #8) - #10
Merged
Merged
Conversation
The filtering loop rebound results, the name it was iterating, to the last response dict, so len(results) == 0 tested a dict's key count and the guard could never fire; a target computer that was not found also crashed in paged_search() with a None search base before reaching it. Collect the searchResEntry responses into a separate list and return early when the target computer does not exist.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked Issue
Closes #8Root Cause
Two independent defects made the same guard dead code.
The filtering loop at L269–L272 wrote its result back into
results, the very name it was iterating. Python evaluates the iterable once and iterates the original list object, so the rebind did not disturb the loop — but it did destroy the value the guard was written to test. After the loopresultswas the lastsearchResEntryresponse dict, andlen(results)was that dict's key count (5:dn,raw_dn,attributes,raw_attributes,type), never 0. In the one case the guard was actually meant to catch — a properties search that returns no entries, onlysearchResRefreferrals — the loopcontinued every iteration,resultsstayed bound to the non-empty response list, andlen(results) == 0was false again. The guard could not fire either way.Separately, the not-found branch at L261–L262 printed
[!] Target computer does not exist!and fell through.dnwas stillNone, so L265 calledpaged_search()withNoneas its search base and raisedTypeError: cannot convert 'NoneType' object to bytes— an unhandled crash before the guard was even reached, in exactly the scenario the guard exists for.Fix Description
Collect the
searchResEntryresponses into a separate list,entries, with a comprehension, and testlen(entries) == 0, so the guard reads the value it is meant to read and fires when the properties search yields no entries. Add the missingreturnto the not-found branch, and narrow its condition fromdn is None and sid is Nonetodn is None, sincednis what the code downstream requires and is whatmodify()is called with.sidis left in place. It was already unused before this change apart from that compound condition, and theformat_sid(...)call it wraps can raise on an entry withoutobjectSid; removing it would be a behaviour change beyond this fix.Rejected alternative: keeping the loop and adding a separate flag or
break. The comprehension expresses "the entries among the responses" directly and removes the rebinding that caused the defect, rather than working around it.How Verified
Runtime, ldap3 2.9.1 / Python 3.12, driving
LAPSio.set()against an ldap3MOCK_SYNCdirectory across the three reachable paths. The third stubspaged_search()to return a singlesearchResRefand no entries.Before (on
main):After:
The missing-computer path now exits cleanly instead of raising, the referral-only path reaches the guard and returns instead of issuing the
modify(), and the working path is unchanged — the password is still written.Static.
len(entries)at L272 reads the comprehension built at L271, which nothing reassigns, so the guard's operand is the entry list on every path.resultsis not read after L271.CI:
python3 -m py_compile pyLAPS.pyandpython3 pyLAPS.py --helpboth pass.Test Coverage
None. The repository has no test suite and CI runs
python3 pyLAPS.py --helponly. Covering this would mean introducing a test harness plus an ldap3 mock-directory fixture and a referral stub, which is out of proportion to the fix; the driver used above is available if a suite is added.Scope of Change
pyLAPS.py(LAPSio.set(), 3 insertions / 5 deletions)--action setpath is byte-identical in output; the two changed paths are the ones that previously crashed or wrongly continued.Risk and Rollout
Confined to
LAPSio.set(). The change can only stop execution earlier than before, on paths that previously crashed withTypeErroror proceeded on data they had failed to retrieve, so no working--action setinvocation is affected. Safe to merge directly.Notes
Independent of #5 / PR #6 and of #7 / PR #9, though all three touch
LAPSio. Verified that this branch merges cleanly intomainalongside both of the others in any order.The
paged_search()call at L265 still resolves the module-levelldap_sessionglobal rather thanself.ldap_session; that is #7, fixed in PR #9, and is deliberately left alone here.