Skip to content

Conversation

MikeX777
Copy link
Contributor

@MikeX777 MikeX777 commented Aug 20, 2025

Description

Filter GPOs that are disabled or, computer configuration disabled while doing local group processing. Adding the GPO status as one of the collected properties.

Motivation and Context

Resolves BED-6027

How Has This Been Tested?

This has been tested by running collection in an environment with and without the GPO enabled and monitoring the results of the collection to ensure that the results are affected.

Screenshots (if appropriate):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Documentation updates are needed, and have been made accordingly.
  • I have added and/or updated tests to cover my changes.
  • All new and existing tests passed.
  • My changes include a database migration.

Summary by CodeRabbit

  • New Features

    • GPO properties now include a status field (gpostatus) derived from LDAP Flags.
  • Bug Fixes

    • Local group processing now skips GPOs that are disabled or have computer configuration disabled, preventing unintended application of settings and improving result accuracy.
  • Tests

    • Added a unit test validating that enabled GPOs are applied while disabled or computer-disabled GPOs are skipped, increasing coverage.

@MikeX777 MikeX777 self-assigned this Aug 20, 2025
@MikeX777 MikeX777 added bug Something isn't working enhancement New feature or request labels Aug 20, 2025
Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

The LDAP query in GPOLocalGroupProcessor now requests GPCFileSysPath and Flags. Processing skips GPOs when Flags indicate disabled/computer-disabled (2 or 3). LdapPropertyProcessor reads Flags into gpostatus. A unit test validates enabled GPOs are applied and disabled ones are skipped.

Changes

Cohort / File(s) Summary
Processors: LDAP flags handling
src/CommonLib/Processors/GPOLocalGroupProcessor.cs, src/CommonLib/Processors/LdapPropertyProcessor.cs
GPOLocalGroupProcessor: extend LDAP attributes (add Flags), guard to skip GPOs with Flags 2/3; preserve caching when skipped. LdapPropertyProcessor: read Flags and expose as gpostatus in returned properties.
Unit tests
test/unit/GPOLocalGroupProcessorTest.cs
Add test verifying ReadGPOLocalGroups applies enabled GPOs (Flags 0/1) and skips disabled/computer-disabled (Flags 2/3); uses temp files and mocked LDAP responses.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant GPOLocalGroupProcessor
  participant LDAP
  participant FileSystem

  Caller->>GPOLocalGroupProcessor: ReadGPOLocalGroups(gplink)
  GPOLocalGroupProcessor->>LDAP: Query GPCFileSysPath, Flags
  LDAP-->>GPOLocalGroupProcessor: path, flags
  alt Missing path OR flags in {2,3}
    GPOLocalGroupProcessor->>GPOLocalGroupProcessor: Cache current actions for link
    GPOLocalGroupProcessor-->>Caller: Result (skipped)
  else Enabled (flags in {0,1})
    GPOLocalGroupProcessor->>FileSystem: Load Groups.xml / GptTmpl.inf
    FileSystem-->>GPOLocalGroupProcessor: Data
    GPOLocalGroupProcessor->>GPOLocalGroupProcessor: Process and cache actions
    GPOLocalGroupProcessor-->>Caller: Result (applied)
  end
  note over GPOLocalGroupProcessor: New guard based on Flags
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant LdapPropertyProcessor
  participant LDAP

  Caller->>LdapPropertyProcessor: ReadGPOProperties(dn)
  LdapPropertyProcessor->>LDAP: Query properties incl. Flags
  LDAP-->>LdapPropertyProcessor: attributes
  LdapPropertyProcessor-->>Caller: dict{..., gpostatus=Flags}
  note over LdapPropertyProcessor: Adds gpostatus without changing signature
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • definitelynotagoblin

Poem

I hop through forests of policy trees,
Sniff flags and paths on a gentle breeze.
Enabled? I nibble the crunchy bytes;
Disabled? I tuck my whiskers tight.
With tidy paws I cache and go — rabbit auditor on the go. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "BED-6027 - Filter GPOs with Computer Configuration Setting Disabled" is specific and directly related to the main change in the pull request. It clearly communicates the primary purpose: filtering out Group Policy Objects (GPOs) that are disabled or have computer configuration disabled. The title accurately reflects the changes made across the three modified files, including the new guard conditions in GPOLocalGroupProcessor and the addition of Flags attribute reading in LdapPropertyProcessor. The title is concise, avoids vague terminology, and would help teammates understand the change when scanning commit history.
Description Check ✅ Passed The PR description follows the required template structure and includes all major sections: a clear description of the changes, motivation and context with a link to the tracked issue (BED-6027), testing information, types of changes marked (Chore, Bug fix, and New feature), and a completed checklist indicating documentation was updated, tests were added, and all tests passed. While the "How Has This Been Tested?" section could be more detailed in describing the testing methodology, the description is substantially complete and provides sufficient information about the change, its rationale, and verification approach.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mcuomo/BED-6027

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
src/CommonLib/Processors/LdapPropertyProcessor.cs (1)

168-169: Prefer numeric flags with derived boolean for clarity and future-proofing

Storing Flags as a raw string works, but using a numeric type and exposing a derived boolean makes downstream logic simpler and less error-prone (e.g., bit tests). The GPO status field is bitwise: bit 1 indicates "computer configuration disabled," covering values 2 and 3.

Consider this change:

-            entry.TryGetProperty(LDAPProperties.Flags, out var flags);
-            props.Add("gpostatus", flags);
+            if (entry.TryGetLongProperty(LDAPProperties.Flags, out var gpoFlags)) {
+                props.Add("gpostatus", gpoFlags);
+                // true when Computer Configuration is disabled (flags 2 or 3)
+                props.Add("gpocomputerconfigdisabled", (gpoFlags & 0x2) != 0);
+            }
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (1)

143-146: Use bitwise flag check and normalize cache key to avoid duplicate entries

Two improvements:

  • Flags check: interpret Flags numerically and test bit 1 to cover both "Computer Configuration Disabled" (2) and "All Settings Disabled" (3), instead of string comparisons.
  • Cache key: you read from GpoActionCache using linkDn.ToLower(), but in this early-return branch you add with the original casing. This can create duplicate entries and unnecessary LDAP hits. Normalize the key consistently.

Apply:

-                    if (!result.Value.TryGetProperty(LDAPProperties.GPCFileSYSPath, out var filePath) || 
-                        // Filter out GPOs that are disabled or the computer configuration is disabled
-                        (result.Value.TryGetProperty(LDAPProperties.Flags, out var flags) && flags is "2" or "3")) {
-                        GpoActionCache.TryAdd(linkDn, actions);
-                        continue;
-                    }
+                    if (!result.Value.TryGetProperty(LDAPProperties.GPCFileSYSPath, out var filePath)) {
+                        GpoActionCache.TryAdd(linkDn.ToLower(), actions);
+                        continue;
+                    }
+                    // Filter out GPOs whose computer configuration is disabled: bit 1 set (2 or 3)
+                    if (result.Value.TryGetLongProperty(LDAPProperties.Flags, out var gpoFlags) && (gpoFlags & 0x2) != 0) {
+                        GpoActionCache.TryAdd(linkDn.ToLower(), actions);
+                        continue;
+                    }
test/unit/GPOLocalGroupProcessorTest.cs (3)

162-171: Use a unique temp subdirectory to avoid cross-test interference

Using Path.GetTempPath() as the root can cause collisions across parallel runs. Prefer a unique subdir per test.

-            var gpcFileSysPath = Path.GetTempPath();
+            var gpcFileSysPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
+            Directory.CreateDirectory(gpcFileSysPath);

Optionally wrap the test body in try/finally and delete the directory in finally to keep the temp clean.


172-174: Remove unused variable

mockComputerEntry is declared but never used.

-            var mockComputerEntry = new Mock<IDirectoryObject>();
-            var mockSearchResultEntry = new Mock<IDirectoryObject>();
+            var mockSearchResultEntry = new Mock<IDirectoryObject>();

292-296: Assertions match intended behavior; consider asserting that actions are empty for skipped GPOs

The LocalAdmins asserts are good. For completeness, you could also assert that other groups (RemoteDesktopUsers, DcomUsers, PSRemoteUsers) are empty for flags 2/3 to guard against regressions in parsing.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ac60f8f and d919aa6.

📒 Files selected for processing (3)
  • src/CommonLib/Processors/GPOLocalGroupProcessor.cs (2 hunks)
  • src/CommonLib/Processors/LdapPropertyProcessor.cs (1 hunks)
  • test/unit/GPOLocalGroupProcessorTest.cs (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/CommonLib/Processors/LdapPropertyProcessor.cs (5)
src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs (1)
  • TryGetProperty (35-50)
src/CommonLib/DirectoryObjects/IDirectoryObject.cs (1)
  • TryGetProperty (8-8)
src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs (1)
  • TryGetProperty (22-38)
test/unit/Facades/MockDirectoryObject.cs (1)
  • TryGetProperty (30-49)
src/CommonLib/Enums/LDAPProperties.cs (1)
  • LDAPProperties (3-99)
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (5)
src/CommonLib/Enums/LDAPProperties.cs (1)
  • LDAPProperties (3-99)
src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs (1)
  • TryGetProperty (35-50)
src/CommonLib/DirectoryObjects/IDirectoryObject.cs (1)
  • TryGetProperty (8-8)
src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs (1)
  • TryGetProperty (22-38)
test/unit/Facades/MockDirectoryObject.cs (1)
  • TryGetProperty (30-49)
test/unit/GPOLocalGroupProcessorTest.cs (4)
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (6)
  • Task (52-58)
  • Task (60-249)
  • Task (346-374)
  • TypedPrincipal (514-519)
  • GPOLocalGroupProcessor (16-578)
  • GPOLocalGroupProcessor (47-50)
test/unit/Facades/MockDirectoryObject.cs (3)
  • TryGetSecurityIdentifier (137-140)
  • MockDirectoryObject (12-180)
  • MockDirectoryObject (18-23)
src/CommonLib/LdapQueries/LdapFilter.cs (17)
  • LdapFilter (8-276)
  • LdapFilter (47-51)
  • LdapFilter (58-62)
  • LdapFilter (69-75)
  • LdapFilter (82-86)
  • LdapFilter (93-97)
  • LdapFilter (104-108)
  • LdapFilter (115-119)
  • LdapFilter (126-130)
  • LdapFilter (137-141)
  • LdapFilter (150-153)
  • LdapFilter (160-163)
  • LdapFilter (170-174)
  • LdapFilter (181-184)
  • LdapFilter (191-194)
  • LdapFilter (201-204)
  • GetFilter (237-256)
src/CommonLib/Enums/LDAPProperties.cs (1)
  • LDAPProperties (3-99)
🔇 Additional comments (1)
src/CommonLib/Processors/GPOLocalGroupProcessor.cs (1)

134-135: Good: Requesting Flags alongside GPCFileSYSPath

Fetching both GPCFileSYSPath and Flags in the same query reduces roundtrips and enables early filtering. Looks good.

Copy link
Contributor

@definitelynotagoblin definitelynotagoblin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holy moly those tests 👍

@MikeX777 MikeX777 enabled auto-merge (squash) September 24, 2025 19:13
@MikeX777 MikeX777 disabled auto-merge September 24, 2025 19:15
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d919aa6 and da36166.

📒 Files selected for processing (1)
  • src/CommonLib/Processors/LdapPropertyProcessor.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/CommonLib/Processors/LdapPropertyProcessor.cs (5)
src/CommonLib/DirectoryObjects/DirectoryEntryWrapper.cs (1)
  • TryGetProperty (35-50)
src/CommonLib/DirectoryObjects/IDirectoryObject.cs (1)
  • TryGetProperty (8-8)
src/CommonLib/DirectoryObjects/SearchResultEntryWrapper.cs (1)
  • TryGetProperty (22-38)
test/unit/Facades/MockDirectoryObject.cs (1)
  • TryGetProperty (30-49)
src/CommonLib/Enums/LDAPProperties.cs (1)
  • LDAPProperties (3-99)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment on lines +168 to +169
entry.TryGetProperty(LDAPProperties.Flags, out var flags);
props.Add("gpostatus", flags);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

LGTM! Consider explicit handling for missing Flags attribute.

The implementation correctly reads the Flags LDAP property and stores it as "gpostatus", following the existing pattern used for GPCFileSYSPath on lines 166-167. When the Flags attribute doesn't exist, TryGetProperty sets flags to an empty string, which won't match "2" or "3" in downstream filtering logic—resulting in the GPO being treated as enabled. This behavior appears correct.

Optional: Make the handling more explicit

While the current implementation works correctly, consider checking the return value of TryGetProperty to make the behavior more explicit:

-entry.TryGetProperty(LDAPProperties.Flags, out var flags);
-props.Add("gpostatus", flags);
+if (entry.TryGetProperty(LDAPProperties.Flags, out var flags)) {
+    props.Add("gpostatus", flags);
+}

Alternatively, add a comment explaining that an empty string represents "enabled" when Flags is absent:

+// Flags attribute may not exist; empty string indicates enabled GPO
 entry.TryGetProperty(LDAPProperties.Flags, out var flags);
 props.Add("gpostatus", flags);

Verification: Confirm downstream handling

Ensure that GPOLocalGroupProcessor correctly handles the empty string case when the Flags attribute is missing from LDAP.


🏁 Script executed:

#!/bin/bash
# Description: Verify how GPOLocalGroupProcessor handles the gpostatus property
# Expected: Code should handle empty string case or explicitly check for "2" or "3"

# Search for where gpostatus is read and used
rg -n -C5 'gpostatus'

# Search for Flags comparison logic in GPOLocalGroupProcessor
rg -n -C10 'class GPOLocalGroupProcessor' -A 200 | rg -C5 '"2"|"3"'

Length of output: 10231



---

🏁 Script executed:

```shell
#!/bin/bash
# Find all usages of ReadGPOProperties to see where the props dictionary is consumed
rg -n 'ReadGPOProperties' -C5

# Also search for where props might be used or returned
rg -n 'props\[' -C3 src/CommonLib/Processors/LdapPropertyProcessor.cs

Length of output: 3265



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full test to see if gpostatus is validated
cat git/test/unit/LdapPropertyTests.cs | sed -n '84,110p'

# Find all production calls to ReadGPOProperties (exclude test files)
rg -n 'ReadGPOProperties' --type cs -g '!test' -C8

Length of output: 1632


Unconsumed gpostatus property
The gpostatus entry added by ReadGPOProperties (src/CommonLib/Processors/LdapPropertyProcessor.cs:168-169) is never consumed by production code nor asserted in tests. Remove this dead code or refactor callers to leverage gpostatus instead of invoking TryGetProperty directly.

🤖 Prompt for AI Agents
In src/CommonLib/Processors/LdapPropertyProcessor.cs around lines 168-169, the
props.Add("gpostatus", flags) call adds a gpostatus entry that is never used;
either remove this dead code or update callers to consume the value from the
props dictionary instead of calling TryGetProperty directly. If you choose
removal: delete the props.Add line and run tests to ensure nothing relied on the
key. If you choose refactor: change callers to read props["gpostatus"] (with
proper null checks/type handling) and then remove the direct TryGetProperty
usage so the processor is the single source of truth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants