Skip to content

fix AR creation bug with incomplete naming schema - #5140

Open
tpurschke wants to merge 23 commits into
CactuseSecurity:developfrom
tpurschke:fix/modell-fixed-part-length
Open

fix AR creation bug with incomplete naming schema#5140
tpurschke wants to merge 23 commits into
CactuseSecurity:developfrom
tpurschke:fix/modell-fixed-part-length

Conversation

@tpurschke

@tpurschke tpurschke commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
  • Prevent App Role creation failures when the naming convention has an incomplete fixed-part configuration.
  • Preserve area-specific identifier content when converting Network Area names to App Role names.
  • Validate that the fixed-part length is at least as long as the Network Area pattern before saving modelling settings.
  • Add localized validation/help text and unit coverage for null, clamped, normal, and rejected configurations.

@tpurschke tpurschke self-assigned this Aug 17, 2026
@tpurschke
tpurschke requested a review from Imat00 August 17, 2026 16:02

@tpurschke tpurschke left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

F1 — medium. convLength is computed above the areaIdString.Length >= FixedPartLength guard

so NetworkAreaPattern is now dereferenced unconditionally on a path that previously never touched it. A stored "networkAreaPattern": null (System.Text.Json happily assigns null to the non-nullable string) converts a safe return areaIdString into a NullReferenceException that tears down the Blazor circuit. Fix: move the convLength line inside the if.

F2 — medium. With fixedPartLength: 3 and networkAreaPattern: "NA12"

areas NA120100 and NA129900 both reduce to fixed part "AR". ProposeFreeAppRoleNumber then queries "AR%" for every area, so app roles from different areas share one number range and the ID carries no area information. Better handled by validating FixedPartLength >= NetworkAreaPattern.Length in SettingsModelling.razor instead of only clamping downstream.

F3 — low. The new test uses FixedPartLength = 0

where Remove(0, convLength) is a no-op under any clamping formula — it proves the crash is gone but not that min() is the right length. The case the ternary exists for (0 < FixedPartLength < NetworkAreaPattern.Length, e.g. FixedPartLength = 1, "NA" → "AR") is untested, as is the unchanged normal path.

Nothing blocking

the fix is a strict crash removal — convLength differs from NetworkAreaPattern.Length only in the case where the old code always threw, so no working configuration changes behaviour.

@tpurschke

Copy link
Copy Markdown
Contributor Author

F1 — medium. convLength is computed above the areaIdString.Length >= FixedPartLength guard

so NetworkAreaPattern is now dereferenced unconditionally on a path that previously never touched it. A stored "networkAreaPattern": null (System.Text.Json happily assigns null to the non-nullable string) converts a safe return areaIdString into a NullReferenceException that tears down the Blazor circuit. Fix: move the convLength line inside the if.

F2 — medium. With fixedPartLength: 3 and networkAreaPattern: "NA12"

areas NA120100 and NA129900 both reduce to fixed part "AR". ProposeFreeAppRoleNumber then queries "AR%" for every area, so app roles from different areas share one number range and the ID carries no area information. Better handled by validating FixedPartLength >= NetworkAreaPattern.Length in SettingsModelling.razor instead of only clamping downstream.

F3 — low. The new test uses FixedPartLength = 0

where Remove(0, convLength) is a no-op under any clamping formula — it proves the crash is gone but not that min() is the right length. The case the ternary exists for (0 < FixedPartLength < NetworkAreaPattern.Length, e.g. FixedPartLength = 1, "NA" → "AR") is untested, as is the unchanged normal path.

Nothing blocking

the fix is a strict crash removal — convLength differs from NetworkAreaPattern.Length only in the case where the old code always threw, so no working configuration changes behaviour.

fixed

@tpurschke
tpurschke marked this pull request as ready for review August 17, 2026 16:31
@tpurschke
tpurschke requested a review from abarz722 August 17, 2026 16:31
@Imat00

Imat00 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

F1 — medium — still partially open. Stored null naming patterns are only handled in one conversion path

ConvertAreaToAppRole now tolerates NetworkAreaPattern == null, but the same deserialized naming convention can still crash elsewhere. ConvertAreaToAppRole still passes namingConvention.AppRolePattern directly into Insert(...), ConvertAppRoleToArea still dereferences namingConvention.AppRolePattern.Length and inserts NetworkAreaPattern, and variance analysis calls StartsWith(namingConvention.NetworkAreaPattern). Since SettingsModelling.IsNamingConventionValid treats a null network area pattern as valid and PrepareConfigData serializes the object unchanged, an existing stored JSON value with null pattern fields can survive the Settings page and later break editing existing App Roles or running variance analysis. Please normalize the naming convention string properties after deserialization, or reject/repair nulls consistently before saving/using the config.

Refs: roles/lib/files/FWO.Data/Modelling/ModellingManagedIdString.cs:174, roles/lib/files/FWO.Data/Modelling/ModellingManagedIdString.cs:181, roles/lib/files/FWO.Data/Modelling/ModellingManagedIdString.cs:184, roles/lib/files/FWO.Services/Modelling/ModellingVarianceAnalysisRules.cs:434, roles/ui/files/FWO.UI/Pages/Settings/SettingsModelling.razor:634

F2 — medium — still open. Existing invalid fixed-part configurations still collapse area-specific App Role prefixes

The new Settings validation prevents future saves where FixedPartLength < NetworkAreaPattern.Length, but the App Role creation path still consumes the already persisted userConfig.ModNamingConvention directly. For an upgraded installation that already has an incomplete config, InitAppRole still calls ConvertAreaToAppRoleFixedPart, and the new clamp makes e.g. fixedPartLength = 1, networkAreaPattern = "NA" convert every matching area to the same fixed part AR. ProposeFreeAppRoleNumber then searches under that shared prefix, so App Roles from different areas no longer carry area-specific fixed-part content. Please validate/repair the loaded convention before proposing an App Role ID, or migrate/clamp the stored config during upgrade/startup.

Refs: roles/lib/files/FWO.Services/Modelling/ModellingAppRoleHandler.cs:57, roles/lib/files/FWO.Data/Modelling/ModellingManagedIdString.cs:173, roles/lib/files/FWO.Services/Modelling/ModellingAppRoleHandler.cs:163, roles/ui/files/FWO.UI/Pages/Settings/SettingsModelling.razor:598

Recommendation summary

Please fix F1/F2 before merging. Both are about existing stored configurations, which is exactly where this bug fix needs to be robust.

abarz722
abarz722 previously approved these changes Aug 18, 2026

@abarz722 abarz722 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks ok from my side

@Imat00

Imat00 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

F4 — medium — editing existing App Roles is still effectively broken for installations that already stored an invalid area naming convention

When AreaConversionValid is false, ApplyNamingConvention intentionally leaves ActAppRole.Area unset in edit mode. EditAppRole.OnParametersSetAsync then calls OnSelectedAreaChanged with that null area, so no area is selected and AppServersInArea never gets populated. The dialog therefore opens with an empty area label and an empty left-side app-server library for those existing App Roles, which means upgraded installations with an old invalid convention still cannot meaningfully edit area-bound App Roles even though the crash and ID-collision paths are gone. Either block the dialog explicitly for this case, or recover the area from persisted relations instead of dropping it to null.

Refs: roles/lib/files/FWO.Services/Modelling/ModellingAppRoleHandler.cs:54, roles/lib/files/FWO.Services/Modelling/ModellingAppRoleHandler.cs:58, roles/ui/files/FWO.UI/Pages/NetworkModelling/EditAppRole.razor:214, roles/ui/files/FWO.UI/Pages/NetworkModelling/EditAppRole.razor:232, roles/tests-unit/files/FWO.Test/ModellingHandlerTest.cs:388

@tpurschke

tpurschke commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Review findings

Finding Criticality Status Subject
F1 medium fixed Null naming-pattern handling across consumers
F2 medium fixed Invalid stored conventions collapsing area-specific App Role prefixes
F3 low fixed Conversion-path test coverage
F4 medium fixed Invalid convention opening an unusable existing-App-Role dialog

No findings are currently open. F1-F4 were verified against current PR head 28771c4.

The changes normalize persisted naming conventions, reject unsafe settings saves, prevent unsafe App Role ID proposals, and suppress an existing-App-Role dialog when its area cannot be derived. The security pass found no new authentication, authorization, tenant-isolation, injection, secret-handling, or schema/upgrade issue. The UI changes have matching localized text, and the changed production paths have corresponding unit tests.

Recommendation

No further fix is required for this review round. The remaining validation risk is that the full unit suite did not return a final summary within the environment runner window; the Debug solution build and the targeted modelling-handler tests passed.

Review metadata

Standard-depth review using the fwo-review-pr workflow. A reduced-tier sub-agent collected the diff, history, and mechanical guideline evidence; correctness, security, finding reconciliation, and ratings were verified on the primary agent. Usage quota was not observable, so the review used the configured proxy limits. GitHub review-thread retrieval returned no threads.

@tpurschke
tpurschke requested a review from abarz722 August 24, 2026 06:29
@tpurschke

Copy link
Copy Markdown
Contributor Author

Review findings

Reviewed at head b10a6e287c4b1077d1a2288ceec4a1bf9571d703.

Finding Criticality Status Subject
F1 medium fixed Null naming-pattern handling across consumers
F2 medium fixed Invalid stored conventions collapsing area-specific App Role prefixes
F3 low fixed Conversion-path test coverage
F4 medium still open Invalid convention still opens an unusable App Role dialog on the read-only path
F5 medium new An App Role pattern shorter than the Network Area pattern passes the new validation but still breaks the round trip
F6 medium new 275 lines of Python git-helper tests deleted, unrelated to this fix

F4 — medium — still open (re-opened; the previous round reported it fixed)

HandleAppRole guards only EditAppRoleMode, but DisplayAppRole sets DisplayAppRoleMode = true before calling it:

public void DisplayAppRole(ModellingAppRole? appRole)
{
    DisplayAppRoleMode = true;      // NwObjHandling.cs:529
    AddAppRoleMode = false;
    HandleAppRole(appRole);
}

private void HandleAppRole(ModellingAppRole appRole)
{
    EditAppRoleMode = false;
    AppRoleHandler = new ModellingAppRoleHandler(...);
    if (!AddAppRoleMode && !AppRoleHandler.AreaConversionValid)
    {
        return;                     // NwObjHandling.cs:545 — DisplayAppRoleMode is already true
    }
    EditAppRoleMode = true;
}

EditConn.razor:453 binds the read-only dialog to ConnHandler.DisplayAppRoleMode, so the early return does not suppress it. Viewing an existing App Role (the buttons at EditConn.razor:160 and EditConn.razor:314) with an invalid stored convention still opens the dialog with ActAppRole.Area left null, and EditAppRole.OnParametersSetAsync then calls OnSelectedAreaChanged(null) — empty area label, empty App Server library. That is exactly the symptom F4 described; only the edit path was fixed.

The new test EditAppRole_OnlyOpensWhenAreaCanBeDerived exercises EditAppRole(...) only, which is why the gap survived.

Suggested fix: clear DisplayAppRoleMode in the same early return, or move the guard ahead of both flag assignments.

Refs: roles/lib/files/FWO.Services/Modelling/ModellingConnectionHandlerNwObjHandling.cs:525, :539, :545, roles/ui/files/FWO.UI/Pages/NetworkModelling/EditConn.razor:453, roles/ui/files/FWO.UI/Pages/NetworkModelling/EditAppRole.razor:232

F5 — medium — new

IsAppRolePatternLengthValid accepts AppRolePattern.Length <= NetworkAreaPattern.Length, and ModellingNamingConventionTest pins the shorter case as valid ([TestCase("NA", "A", true)]). But a shorter App Role pattern is lossy in the same way the longer one is: the converted fixed part comes out shorter than FixedPartLength, so the ModellingManagedIdString.FixedPart setter pads it with the ? filler (FillFixedIfNecessary(valueToInsert, "?"), ModellingManagedIdString.cs:212).

Verified against the built assembly at this head, with networkAreaPattern: "NET", appRolePattern: "AR", fixedPartLength: 5 — a configuration the new validation reports as valid:

IsAreaConversionValid (NET/AR, fixed=5): True
proposed app role fixed part: 'AR12?'   whole: 'AR12?'
app role id 'AR12?-00001' -> derived area 'NET12?'  (the real area is 'NET12')
round trip matches: False

So the proposed App Role ID carries a literal ?, and on the next edit the derived area NET12? matches no real area — landing back in the empty-library dialog F4 is about, this time through a convention the PR explicitly declares valid. NET / AR is not an exotic setting.

Suggested fix: when network areas are used, require equal pattern lengths (AppRolePattern.Length == NetworkAreaPattern.Length), or make ConvertAreaToAppRole preserve the fixed-part length instead of leaving the ? padding to the setter.

Refs: roles/lib/files/FWO.Data/Modelling/ModellingNamingConvention.cs:82, roles/lib/files/FWO.Data/Modelling/ModellingManagedIdString.cs:175, roles/lib/files/FWO.Data/Modelling/ModellingManagedIdString.cs:212, roles/tests-unit/files/FWO.Test/ModellingNamingConventionTest.cs

F6 — medium — new

Commit 9cbf25e ("remove some tests temporarily") deletes 275 lines from scripts/customizing/fwo_custom_lib/test_git_helpers.py — 520 lines on upstream/develop, 245 on this head. It removes every test for commit_and_push_deletions, ensure_committer_identity and rebase_onto_remote, all of which remain in production git_helpers.py (lines 245, 203, 132). No replacement coverage was added: on this head no test file references commit_and_push_deletions at all, whereas develop has one.

That is unrelated to the App Role naming fix, and merging it drops regression protection for an unattended git push/rebase path that runs in the middleware — including the shallow-clone, moved-remote, missing-committer-identity and conflict-keeps-the-file cases.

Please restore the tests, or split the removal into its own PR with a stated reason, before merging.

Refs: scripts/customizing/fwo_custom_lib/test_git_helpers.py, scripts/customizing/fwo_custom_lib/git_helpers.py:132, :203, :245

Verification performed

  • dotnet build --configuration Debug roles/FWO.sln at this head: 0 errors, 0 warnings.
  • dotnet test roles/tests-unit/files/FWO.Test/FWO.Test.csproj: 5085 passed, 0 failed, 17 skipped. (The previous round could not obtain a final summary; this run completed.)
  • F1 verified fixed: all ten non-test ModellingNamingConvention construction sites now go through FromJsonNormalize, including ModellingVarianceAnalysis, AppDataImport, ExternalRequestHandler, FileUploadService, EditAppServer.razor and SettingsModelling.razor.
  • F2 verified fixed: InitAppRole gates both ConvertAreaToAppRoleFixedPart and ProposeFreeAppRoleNumber on AreaConversionValid.
  • F3 verified fixed: TestConvertAreaToAppRole now covers the clamped (fixedPartLength = 1) and the normal (fixedPartLength = 4) case.
  • Security pass — no findings. No authentication, JWT, role/tenant or Hasura permission surface is touched; no SQL, GraphQL, LDAP, shell or path-traversal input path; the new Log.WriteWarning emits only admin-configured pattern strings, no secrets; deserialization stays on System.Text.Json over admin-controlled config with no custom converters; no TLS or SSRF surface; tenant isolation unaffected.
  • Localization: E5601 and E5602 are genuinely new keys (absent on develop, no duplicate INSERT) and carry both languages this file uses — German and English, 2964 entries each. H5607 and H5610 were updated in both languages, and Pages/Help/HelpSettingsModelling.cshtml renders those keys directly, so no separate help-page edit is required.
  • whats_new_facts deliberately not raised: this is a bug fix, not a new feature, so the entry does not need to change.
  • Guideline scan of the changed files: no inline array arguments, no method over 100 lines, no magic numbers, no dead or commented-out code. AppDataImport.cs (1332 lines) and the 11-parameter ModellingAppRoleHandler constructor do exceed the guideline limits, but both are unchanged by this PR and already present on develop, so they are not raised against it.
  • Schema/upgrade: only the idempotent texts file changes, so no migration or upgrade risk for existing installations.

Recommendation

Fix before merging:

  • F6 — restore the deleted Python tests (or move the removal to its own PR). This is scope loss unrelated to the fix and is the easiest one to lose track of.
  • F4 — one line: also clear DisplayAppRoleMode on the early return, and extend the new test to cover DisplayAppRole(...).
  • F5 — the <= rule leaves the validation this PR introduces defeatable by a plausible NET / AR configuration. If you would rather handle it separately, please say so explicitly instead of leaving the rule as-is.

Nice to have: a test pinning the round trip area -> app role id -> area for the conventions the validation accepts, which would have caught F5 directly.

Review metadata

Standard-depth review following the fwo-review-pr workflow, run directly against that skill's checklists (no separate review sub-capability was used, and no billed deep-review mode was launched). One reduced-tier sub-agent collected mechanical evidence only — inline-array, file-length, method-length and parameter-count scans, localization and help-content presence, whats_new_facts state, and test-file presence. Every finding, criticality rating, the reconciliation against F1–F4 and the whole security pass were decided on the primary model; one sub-agent data point (a origin/develop line count for the deleted Python test file) was contradicted by a direct check against upstream/develop and discarded. GitHub review-thread retrieval returned no threads, so reconciliation used the top-level comments and submitted reviews. Usage quota is not observable in this environment, so the configured proxy limits were applied instead of a measured percentage: one sub-agent dispatch, standard depth, no whole-tree scans, no re-run passes — the review stayed within them. The before-posting checklist was walked rule by rule against this comment.

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants