# Authorization, group inheritance and separation of duties

Explain who can perform which action on a resource in the local access desk. Make nested groups, expiry and explicit denies visible.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## Signing in is not permission to act

Authentication concerns the signed-in identity; authorization separately decides an action on a particular resource. A Harbor user may read an invoice but lack permission to change a supplier account. Servers must assess the object and operation at relevant request boundaries. Hiding a button supplies no security boundary. Tenant and ownership context also matter in real systems. The local exercise supports only exact synthetic resources and read/write/admin actions. It is not a production authorization library and deliberately omits provider-specific policy semantics and broader business conditions.

## Direct and indirect access

Ada may have no direct role yet receive access because her team belongs to a broader group. Show the membership chain, assignment ID and role actions together. A group name is not evidence of membership. The local desk actually expands nested memberships and rejects cycles rather than producing a misleading result. Where several paths exist, the shown chain is one witness, not an enumeration of every membership route. Real directories differ in nested-group support and role inheritance; this model claims no equivalence to any provider’s authorization behavior.

## Expiry and explicit-deny precedence

In this teaching profile, candidates require an active subject, exact resource/action match, expiry later than the explicit request time and a review reference. A matching explicit deny overrides candidate grants. This chosen educational rule is not a universal IAM standard. A review reference is a supplied assertion, not verified approval. Time comparisons use the fixed UTC request time, not the computer’s present clock, so the same file yields repeatable results in the downloadable CLI and browser. An admin action does not implicitly include read or write; actions must be listed explicitly.

## Separation of duties and exceptions

Allowing one person to request and approve the same transaction can create a conflict. A technical role list does not automatically explain every business-process conflict. Process owners define incompatible actions and who can review exceptions. Harbor workpapers request independent approval, compensation, scope and expiry. The local desk does not compute separation-of-duties conflicts; that remains a separate business assessment. An allow result reflects narrow rules, not a judgment that a person or organization is trustworthy. Learners should explain the result through specific assignment and deny IDs.

## Working sequence

1. Fix subject, resource and action.
2. Inspect the nested-group chain.
3. Compare grant expiry and review references.
4. Run explicit-deny precedence in both examples.
5. Explain the outcome using rule IDs and model limits.

## 1. Signed in: allow the object?

Successful sign-in establishes no resource/action permission. Assess subject, object and operation scope on the server rather than relying on button visibility as a security control.

This code is a local policy model; it does not replace actual authentication, token validation or live permission tests.

### Weak implementation
```js
s => s.signedIn === true
```

### Corrected implementation
```js
s => s.signedIn === true &&
  s.subjectScoped === true &&
  s.resourceScoped === true &&
  s.actionChecked === true
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "documented-case",
      "input": {
        "signedIn": true,
        "subjectScoped": true,
        "resourceScoped": true,
        "actionChecked": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "missing-resourceScoped",
      "input": {
        "signedIn": true,
        "subjectScoped": true,
        "resourceScoped": false,
        "actionChecked": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "missing-actionChecked",
      "input": {
        "signedIn": true,
        "subjectScoped": true,
        "resourceScoped": true,
        "actionChecked": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "documented-case",
      "input": {
        "signedIn": true,
        "subjectScoped": true,
        "resourceScoped": true,
        "actionChecked": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "missing-resourceScoped",
      "input": {
        "signedIn": true,
        "subjectScoped": true,
        "resourceScoped": false,
        "actionChecked": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "missing-actionChecked",
      "input": {
        "signedIn": true,
        "subjectScoped": true,
        "resourceScoped": true,
        "actionChecked": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. Group chain and expiry

A role name alone cannot explain effective access. Evaluate nested groups, explicit denies and expiry together; a local result does not validate live provider permissions.

This code is a local policy model; it does not replace actual authentication, token validation or live permission tests.

### Weak implementation
```js
s => s.roleNamed === true
```

### Corrected implementation
```js
s => s.roleNamed === true &&
  s.nestedGroupsExpanded === true &&
  s.denyReviewed === true &&
  s.expiryChecked === true
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "documented-case",
      "input": {
        "roleNamed": true,
        "nestedGroupsExpanded": true,
        "denyReviewed": true,
        "expiryChecked": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "missing-nestedGroupsExpanded",
      "input": {
        "roleNamed": true,
        "nestedGroupsExpanded": false,
        "denyReviewed": true,
        "expiryChecked": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "missing-expiryChecked",
      "input": {
        "roleNamed": true,
        "nestedGroupsExpanded": true,
        "denyReviewed": true,
        "expiryChecked": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "documented-case",
      "input": {
        "roleNamed": true,
        "nestedGroupsExpanded": true,
        "denyReviewed": true,
        "expiryChecked": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "missing-nestedGroupsExpanded",
      "input": {
        "roleNamed": true,
        "nestedGroupsExpanded": false,
        "denyReviewed": true,
        "expiryChecked": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "missing-expiryChecked",
      "input": {
        "roleNamed": true,
        "nestedGroupsExpanded": true,
        "denyReviewed": true,
        "expiryChecked": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 3. Approving your own request

An assigned approver is not proof of independence. Review self-approval, incompatible duties and exception ownership separately; the local policy desk does not calculate these business conflicts.

This code is a local policy model; it does not replace actual authentication, token validation or live permission tests.

### Weak implementation
```js
s => s.approverAssigned === true
```

### Corrected implementation
```js
s => s.approverAssigned === true &&
  s.noSelfApproval === true &&
  s.conflictsMapped === true &&
  s.exceptionOwned === true
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "documented-case",
      "input": {
        "approverAssigned": true,
        "noSelfApproval": true,
        "conflictsMapped": true,
        "exceptionOwned": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "missing-noSelfApproval",
      "input": {
        "approverAssigned": true,
        "noSelfApproval": false,
        "conflictsMapped": true,
        "exceptionOwned": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "missing-conflictsMapped",
      "input": {
        "approverAssigned": true,
        "noSelfApproval": true,
        "conflictsMapped": false,
        "exceptionOwned": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "documented-case",
      "input": {
        "approverAssigned": true,
        "noSelfApproval": true,
        "conflictsMapped": true,
        "exceptionOwned": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "missing-noSelfApproval",
      "input": {
        "approverAssigned": true,
        "noSelfApproval": false,
        "conflictsMapped": true,
        "exceptionOwned": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "missing-conflictsMapped",
      "input": {
        "approverAssigned": true,
        "noSelfApproval": true,
        "conflictsMapped": false,
        "exceptionOwned": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Subject/group/role/resource chain and time-bound grant record.
- Decision, explicit deny, excluded-grant and duties-conflict review record.

## References

- [NIST SP 800-207 · Zero Trust Architecture · Final August 2020](https://csrc.nist.gov/pubs/sp/800/207/final)
- [Microsoft Entra · Plan access reviews](https://learn.microsoft.com/en-us/entra/id-governance/deploy-access-reviews)
