Kapitola 09 / 09
ENSK

SmartExchange · .NET 10 · Chapter 09

InterestedParty read and Project Scope

Kompletný migračný príklad: InterestedPartyRead pridelené na Project sa zmení na InterestedParty/read so scope daného Project. Normalizácia a validácia sú v kóde; zodpovedajúca read policy a derived role sú nasadené.

Shared code · Intaker + Web APIReviewed · 2026-10-27

Oba export príklady: celý postup

KrokPerson exportInterestedParty export
Rola / assignmentCompanyReader → Company-AProject View / InterestedPartyRead → Project
Normalizovaný grantCompany/read/ResourceInterestedParty/read/Project
Výber principalaPerson + CompanyInterestedParty + Project + Investor
Derived roleperson_with_readable_companyinterested_party_in_project_scope
Celá demo policyPerson / presentation-demo / exportInterestedParty / presentation-demo / export
Plan / SQLRequest · Expected AST · SQL EXISTS CompanyRequest · Expected AST · SQL EXISTS Project

Obe kompletné derived roles/policies sú vedľa seba v kapitole 05; requests, očakávané plans a SQL sú spárované v kapitole 06. Oba export príklady sú dokumentačné návrhy. Nasledujúca migračná referencia zachováva samostatne overené InterestedParty/read správanie, skutočný zachytený AST a históriu nasadenia.

1. Assignment → štandardný grant

Uložený DEV snapshot (2026-09-17) obsahuje rolu Project View, permission InterestedPartyRead a assignment a7f53aab-6f40-4204-b1d3-02ea54f1713e na Project b48e464a-88cf-4743-bbce-98d6ba89e399 pre usera 5a3e5b0f-fb6d-4abf-ba4f-8b581365a795.

LegacySqlAuthorizationGrantAdapter.Normalize() · isolated normalized fact
{
  "assignmentId": "a7f53aab-6f40-4204-b1d3-02ea54f1713e",
  "resourceId": "b48e464a-88cf-4743-bbce-98d6ba89e399",
  "resource": "InterestedParty",
  "action": "read",
  "scope": "Project",
  "isPublic": false
}

Permission určí resource=InterestedParty a action=read. Miesto assignmentu sa zmení na scope=Project; resourceId je ID tohto Project, nie InterestedParty. CRM permission metadáta a deklarovaný vzťah InterestedParty→Project umožnia generickú konverziu.

SmartExchangeAuthorizationScopeValidator.Validate() overí dvojicu aj ID. Nepridávame atribút pre výnimku. Názov roly neurčuje resource a bežné Project/read neudeľuje InterestedParty/read.

2. Resolver → planning request

SmartExchangeAuthorizationContextResolver.ResolveAsync() vyberie InterestedParty, Project a Investor grant typy. Izolovaný príklad odošle iba grant vyššie, roles: ["user"] a attr.product. Reálny context môže obsahovať ďalšie vybrané grants; nejde o kompletný aktuálny prístup daného usera.

Celý PlanResources request · izolovaný príklad
HTTP JSON representation; the shared .NET client uses gRPC
{
  "requestId": "interested-party-project-scope-plan",
  "principal": {
    "id": "5a3e5b0f-fb6d-4abf-ba4f-8b581365a795",
    "roles": [
      "user"
    ],
    "attr": {
      "product": "API_Dev1",
      "grants": [
        {
          "assignmentId": "a7f53aab-6f40-4204-b1d3-02ea54f1713e",
          "resourceId": "b48e464a-88cf-4743-bbce-98d6ba89e399",
          "resource": "InterestedParty",
          "action": "read",
          "scope": "Project",
          "isPublic": false
        }
      ]
    }
  },
  "resource": {
    "kind": "InterestedParty",
    "policyVersion": "default",
    "attr": {}
  },
  "actions": [
    "read"
  ],
  "includeMeta": true
}

AuthorizationPlanRequest používa resource InterestedParty a action read. Pri plánovaní listu zostáva projectIds konkrétneho záznamu neznáme; request nevymenúva InterestedParty ID.

3. Nasadená derived role + resource policy

Live derived-role-interested-party-project-scope · checked 2026-10-27
apiVersion: api.cerbos.dev/v1
description: InterestedParty read restricted by an explicit Project scope.
derivedRoles:
  name: interested_party_project_scope
  variables:
    local:
      projectScopeIds: >-
        has(P.attr.grants) ? P.attr.grants.filter(g,
          g.resource == "InterestedParty" &&
          g.action == "read" &&
          g.scope == "Project" &&
          g.resourceId != "00000000-0000-0000-0000-000000000000"
        ).map(g, g.resourceId) : []
  definitions:
    - name: interested_party_in_project_scope
      parentRoles: [user]
      condition:
        match:
          expr: >-
            R.kind == "InterestedParty" &&
            size(V.projectScopeIds) > 0 &&
            R.attr.projectIds.exists(projectId,
              projectId in V.projectScopeIds)
Live policy-interested-party · scoped-read fragment
# Extract from live InterestedParty/default.
# Existing export and other read rules are omitted here.
resourcePolicy:
  version: default
  resource: InterestedParty
  importDerivedRoles:
    - interested_party_project_scope
  rules:
    - name: read-in-project-scope
      actions: [read]
      derivedRoles: [interested_party_in_project_scope]
      effect: EFFECT_ALLOW

Rola vyberá ID z InterestedParty/read/Project grants a porovná ich s projectIds záznamu. Nevyberá Project/read grants. Resource policy importuje set a explicitne povoľuje read cez túto rolu. Ďalšie runtime read pravidlá pokrývajú priamy Resource scope a Instance scope.

Celá runtime policy vrátane zachovaných export pravidiel · Runtime derived-role YAML

4. Skutočná Cerbos odpoveď → SQL

Odpoveď nižšie bola zachytená 2026-10-26 pre overovací request verify-project-scope-loaded-20261026. Používa rovnaký Project scope fakt; ide o samostatné volanie, nie o odpoveď na ilustračný request vyššie. AST je kompletný, nezjednodušený.

Skutočná PlanResources odpoveď · celý JSON AST
Captured Cerbos response · KIND_CONDITIONAL
{
  "requestId": "verify-project-scope-loaded-20261026",
  "actions": [
    "read"
  ],
  "resourceKind": "InterestedParty",
  "policyVersion": "default",
  "filter": {
    "kind": "KIND_CONDITIONAL",
    "condition": {
      "expression": {
        "operator": "exists",
        "operands": [
          {
            "variable": "request.resource.attr.projectIds"
          },
          {
            "expression": {
              "operator": "lambda",
              "operands": [
                {
                  "expression": {
                    "operator": "eq",
                    "operands": [
                      {
                        "variable": "projectId"
                      },
                      {
                        "value": "b48e464a-88cf-4743-bbce-98d6ba89e399"
                      }
                    ]
                  }
                },
                {
                  "variable": "projectId"
                }
              ]
            }
          }
        ]
      }
    }
  },
  "meta": {
    "filterDebug": "(exists request.resource.attr.projectIds (lambda (eq projectId \"b48e464a-88cf-4743-bbce-98d6ba89e399\") projectId))",
    "matchedScopes": {
      "read": ""
    }
  },
  "cerbosCallId": "01M5W4E4R3TT90WTC83BBFBQCG"
}

CerbosQueryPlanMapper.Map() zmení exists/lambda/eq na AuthorizationCollectionExists a porovnanie. Registry mapuje resource.attr.projectIds na InterestedParty→Project; SqlKataAuthorizationQueryAdapter.Apply() vie podporovaný tvar preložiť na EXISTS.

Equivalent SQL · target integration shape, not captured runtime SQL
-- Target SQL shape for the isolated InterestedParty/read Project-scope example.
-- Illustration, not captured runtime output. Bind @ProjectId from the approved plan.
-- Every schema/table/column mapping comes from trusted application metadata.
DECLARE @ProjectId uniqueidentifier = 'b48e464a-88cf-4743-bbce-98d6ba89e399';

SELECT ip.Id
FROM dbo.InterestedParty AS ip
WHERE ip.Deleted = 0
  AND EXISTS (
      SELECT 1
      FROM dbo.Project AS acl_project
      WHERE acl_project.Id = ip.ProjectId
        AND acl_project.Deleted = 0
        AND acl_project.Id = @ProjectId
  );
-- Ordinary business filters are ANDed with this predicate.
-- Apply authorization before COUNT, ORDER BY/OFFSET and export materialization.
-- No user-supplied ProjectId filter is required to activate this authorization.

Spoločné prekladové komponenty už existujú. Reálne presadenie v query Web API a adapter→Cerbos→databáza test tohto prípadu ešte chýbajú.

5. Overený výsledok a zostávajúca integrácia

KrokStav
Generická normalizácia + validácia scopeImplementované v kóde; pri tejto dokumentačnej úprave neprebehol C# build/test.
Policy a derived role v non-prodNasadené 2026-10-26; pripojené YAML znovu skompilované 2026-10-27.
Správanie policy27 API scenárov / 51 record rozhodnutí overených 2026-10-26. Zhodný project ALLOW; iný/prázdny project DENY; bežné Project/read DENY.
Web API a CheckResources integráciaChýba spoločná klientská metóda/resource-attribute provider a zapojenie endpointov.

Pri overení konkrétneho záznamu musí aplikácia načítať dôveryhodné projectIds. Pri list plánovaní dodá vzťah SQL adapter cez query. IQ500, portal aj PmTask pravidlá musia používať rovnaký resource/action/scope model; špeciálne správanie PmTask ešte vyžaduje analýzu kódu.

Plan request · Zachytená odpoveď · CheckResources request · SQL