diff --git a/README.md b/README.md index af5b93c..bcf5faf 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,44 @@ On success the step outputs: } ``` +## Unified authorization decisions + +`step.authz_check` routes one Workflow gate through the configured provider and +mode. Use it for route guards, workflow action guards, and module-owned admin +actions without coupling YAML to Casbin, Keto, or Permit-specific APIs. + +```yaml +steps: + - type: step.authz_require_capabilities + config: + provider: keto + module: authz + requirements: + - mode: rebac + operations: [check, manage_relations] + + - type: step.authz_check + config: + provider: casbin + module: authz + mode: rbac + subject: "{{.auth_user_id}}" + context: admin + scope: admin:authz.roles:update +``` + +Go modules can call the same service surface through Workflow's module/service +registry; the request shape is provider-neutral: + +```go +decision, err := authzService.InvokeMethod("CheckRelation", map[string]any{ + "subject": userID, + "context": "frontend", + "object": documentID, + "relation": "owner", +}) +``` + ## Build ```sh diff --git a/docs/plans/2026-05-27-authz-modes-admin-design.md b/docs/plans/2026-05-27-authz-modes-admin-design.md new file mode 100644 index 0000000..c48c2e9 --- /dev/null +++ b/docs/plans/2026-05-27-authz-modes-admin-design.md @@ -0,0 +1,230 @@ +# Authz Modes Admin Design + +## Goal + +Build truthful, provider-native authorization management across RBAC, ABAC, and ReBAC: +Workflow YAML can declare/enforce authz, Go modules can expose authz-aware features, the admin UI can manage each supported mode, and SPAs can reflect access without becoming the enforcement boundary. + +## Global Design Guidance + +Source: `/Users/jon/workspace/docs/design-guidance.md` + +| guidance | design response | +|---|---| +| Workflow platform substrate; dogfood plugins/YAML/wfctl | Ship strict plugin contracts, typed steps, scenario YAML/demo proof; no standalone authz tool. | +| Strict-contract invariant | New provider capability, ABAC, ReBAC, and UI runtime contracts use plugin-owned proto messages; free-form attributes cross as typed key/value fields or JSON bytes only where the provider model is intentionally dynamic. | +| Plugin-owned contracts first-class | `workflow-plugin-authz` owns authz provider contracts; `workflow-plugin-authz-ui` consumes them and exposes admin contribution metadata. | +| No mock-first development | Unit tests cover adapters; docker demo exercises real app + admin + Keto; Permit live tests remain opt-in because cloud credentials are external. | +| Secrets never logged | Permit API/PDP keys remain server-only; UI receives capability metadata, catalog entries, and non-sensitive policy shapes only. | +| Multi-component validation | Verify provider package, UI package, docker scenario, Playwright exploratory QA, and workflow YAML checks. | + +## External UI / Engine Evaluation + +| option | fit | decision | +|---|---|---| +| Casdoor / Casbin admin portal | Casbin docs describe Casdoor and third-party admin portals for model/policy management ([Casbin admin portal](https://casbin.org/docs/admin-portal)); strong for Casbin-only identity/admin, not embeddable provider-neutral Workflow UI. | Do not switch wholesale; borrow model/policy editor patterns for Casbin advanced mode. | +| Casbin library/editor | Casbin supports multiple models via PERM config and management APIs ([Casbin](https://casbin.org/)); useful for model-aware capability detection. | Keep provider adapter; expose safe policy/model views only for Casbin. | +| Ory Keto | Open-source Zanzibar/ReBAC server, headless/API-first ([Ory Keto GitHub](https://github.com/ory/keto)). | Keep as ReBAC provider; UI manages schema metadata + tuples, not a separate Keto UI. | +| Permit.io | SDK-backed SaaS provider already added; supports RBAC/ReBAC/ABAC product surface but live verification needs credentials. | Provider implementation must advertise only implemented surfaces; live tests env-gated. | +| Permify | Open-source authz service for RBAC/ReBAC/ABAC DSL ([Permify GitHub](https://github.com/permify/permify), [modeling docs](https://docs.permify.co/getting-started/modeling)). | Candidate future provider, not current replacement. Adding it now expands provider scope beyond user ask. | + +Conclusion: no open-source UI cleanly matches Workflow's embedded, provider-neutral, strict-contract admin need. Continue our own UI, but make it capability-driven, lookup-backed, and mode-native. + +## Architecture + +### Provider Contracts + +`workflow-plugin-authz` exposes one capability discovery contract and three model-native contracts. + +| contract | purpose | examples | +|---|---|---| +| `ProviderCapabilities` | Provider says what is supported and configured now. | `rbac/manage_roles/check`, `abac/manage_policies/check`, `rebac/manage_relations/check`, `explain`, `audit`, `schema_required`. | +| `ScopeRoleProvider` | RBAC + scope role management. | Role grants, subject role assignments, direct scopes, context-aware scope check. | +| `AttributePolicyProvider` | ABAC policy/schema/check management. | Attribute definitions, allowed values/lookups, policy rules, decision checks with subject/resource/environment attrs. | +| `RelationshipProvider` | ReBAC relation/schema/check management. | Relation types, tuples, graph lookup, relation check, tuple delete/list. | +| `AuthorizationDecisionProvider` | Unified check surface for YAML/Go/UI. | `subject`, `resource`, `action`, optional scope, attrs, relation context → allow/deny/explain. | + +Capability discovery is instance-aware. Casbin derives modes from the loaded model; Keto advertises ReBAC + RBAC-as-relations; Permit advertises surfaces implemented by the SDK adapter and configured project. + +### Admin UI + +`workflow-plugin-authz-ui` asks `/api/authz/capabilities` first and renders tabs only for supported modes. + +| tab | source-of-truth inputs | UX rules | +|---|---|---| +| Overview | provider capabilities + health | show configured provider, enabled modes, unsupported modes, last audit events. | +| RBAC | scope catalog, roles, users, groups | no free-text scopes; roles/scopes/users/resources selected from lookups; direct scope assignment explicit and auditable. | +| ABAC | attribute catalog, resource/action catalog, policy templates | attributes selected from declared dictionaries; operators from provider capabilities; free-text only for declared string attrs where `allow_custom_values=true`. | +| ReBAC | relation schema, subjects/resources, relation tuples | graph-like tuple table with subject/object lookup, relation dropdown, resource type filters, add/remove tuple actions. | +| Simulator | unified decision API | persona picker + resource/action picker + attrs/relations preview; explain output shows provider path and matched rule/tuple/scope. | +| Audit | append-only events | filters by actor/mode/resource/action; never logs secrets or raw tokens. | + +Unsupported modes are hidden by default, with a compact disabled state in Overview to avoid implying false functionality. + +### Workflow YAML + +YAML gets mode-native typed steps plus a unified decision step: + +| step | use | +|---|---| +| `step.authz_capabilities` | branch pipeline or fail startup if required mode missing. | +| `step.authz_check` | unified check for handlers/workflows; returns allow/deny/explain. | +| `step.authz_rbac_assign`, `step.authz_rbac_remove`, `step.authz_rbac_check` | role/scope administration and enforcement. | +| `step.authz_abac_policy_upsert`, `step.authz_abac_policy_remove`, `step.authz_abac_check` | attribute policy lifecycle. | +| `step.authz_rebac_tuple_upsert`, `step.authz_rebac_tuple_remove`, `step.authz_rebac_check` | relationship lifecycle. | + +YAML can require capabilities declaratively through authz-owned config or a typed startup step; no engine-level `modules[].requires` field is introduced: + +```yaml +modules: + - name: authz + type: authz.keto + config: { read_url: http://keto:4466, write_url: http://keto:4467 } + required_capabilities: + - mode: rebac + operations: [check, manage_relations] + +steps: + - type: step.authz_require_capabilities + config: + module: authz + requirements: + - mode: rebac + operations: [check, manage_relations] +``` + +### Go / Module Code + +Third-party plugins declare authz facts without choosing the application engine: + +| declaration | owned by plugin | consumed by app/admin | +|---|---|---| +| Scope declarations | feature/action visibility and coarse checks. | RBAC providers, SPA permission projection. | +| Attribute schema | subject/resource/environment attrs with types, lookups, validation. | ABAC UI and decision checks. | +| Resource/action catalog | known resource types, actions, owner plugin/module. | all modes, simulator, policy forms. | +| Relation schema | resource types and relation names. | ReBAC UI and provider schema. | +| UI contribution metadata | required permissions for view/action. | admin shell and SPA rendering. | + +Declarations are engine-agnostic. The app chooses provider; unsupported provider/mode combinations fail with explicit startup/API errors rather than silently degrading. + +### SPA Access Projection + +SPAs receive a non-authoritative permission projection: + +```json +{ + "subject": "alice", + "context": "frontend", + "capabilities": ["rbac", "rebac"], + "scopes": ["frontend:orders:read"], + "ui": { + "views": [{"id": "orders", "visible": true}], + "actions": [{"id": "orders.resolve", "enabled": false, "reason": "missing scope"}] + } +} +``` + +Projection is for UX only. Mutating backend APIs still call the unified decision provider. + +## Personas / Use Cases + +| persona | use case | mode | +|---|---|---| +| Platform admin | assign admin dashboard permissions to support staff. | RBAC | +| Product manager | allow regional managers to edit only region-owned campaigns. | ABAC | +| Tenant admin | share one project/doc with a collaborator. | ReBAC | +| Developer/plugin author | declare resources/actions/attrs/relations once in plugin proto. | all | +| Frontend engineer | hide disabled UI actions without hardcoding roles. | projection | +| Security auditor | inspect who changed a policy or tuple. | audit | + +## Edge Cases + +| edge | required behavior | +|---|---| +| Provider lacks mode requested by app | startup/API validation fails closed with capability error. | +| Provider temporarily down | decision checks fail closed; UI shows degraded provider health. | +| Attribute schema changes while policies exist | migration report lists affected policies; destructive removal blocked unless policies removed first. | +| Relation tuple references missing object | provider may store tuple, but UI flags unresolved lookup; decision still provider-authoritative. | +| User loses admin scope while viewing admin | next API request denies; UI refresh hides actions. | +| SPA projection stale | backend enforcement wins; projection TTL short and revalidated on session refresh. | +| Permit credentials absent | live Permit tests skip; demo can run Casbin/Keto locally; UI marks Permit unavailable. | +| Text-entry temptation | form fields use lookups for scopes/resources/actions/users/relations/operators; free-text only for declared dynamic attributes. | + +## Security Review + +| area | control | +|---|---| +| Authn/authz boundary | Admin UI requires auth plugin session plus backend authz checks; SPA projection is advisory. | +| Least privilege | Each admin tab/action has required scope/mode operation; missing grants hide UI and deny API. | +| Confused deputy | All checks carry `context`, `resource`, `action`, provider, and actor; admin/frontend contexts cannot be inferred from path alone. | +| CSRF | Mutating admin APIs require session + CSRF token or same-site POST token in scenario/admin implementation. | +| Injection | Attribute values and tuple components validate against declared schema; provider DSL/model text editing limited to advanced mode and audited. | +| Secrets | Permit keys/PDP URLs stay server-side; logs redact credentials. | +| Audit | policy/role/tuple mutations append actor, mode, resource, action, before/after IDs. | + +## Infrastructure Impact + +| item | impact | +|---|---| +| Local demo | Docker Compose runs app + Keto; optional Permit requires env secrets; optional Casbin runs in-process. | +| Network | Tailnet serve remains local; no production deploy. | +| Storage | Demo in-memory; plugin contracts allow provider persistence but no migration in this phase unless provider needs it. | +| Secrets | Permit live integration env-gated: `PERMIT_API_KEY`, `PERMIT_PROJECT`, `PERMIT_ENVIRONMENT`, `PERMIT_INTEGRATION=1`. | +| Cost | No live SaaS calls unless env opt-in. | + +## Multi-Component Validation + +| proof | expected | +|---|---| +| `workflow-plugin-authz` unit + contract tests | provider capability, RBAC, ABAC, ReBAC contracts pass. | +| Provider conformance matrix | Casbin/Keto local pass; Permit fake SDK pass; Permit live env-gated pass when creds set. | +| `workflow-plugin-authz-ui` unit/build/lint | capability-driven tabs, lookup-backed forms, no false tabs. | +| Scenario script | app/admin/RBAC/ABAC/ReBAC endpoints and enforcement pass. | +| Docker runtime | app + Keto healthy; provider rotation works for local providers. | +| Playwright exploratory QA | login, tabs, lookup forms, select/deselect, enforcement, denied UI, mobile layout. | + +## Rollback + +| change | rollback | +|---|---| +| Authz contracts/providers | revert provider contract commits; run `GOWORK=off go test ./...`; old scope-role path remains. | +| Authz UI | revert UI commits; `npm test && npm run build && npm run lint`; old RBAC-only UI remains. | +| Scenario | revert scenario commits; `docker compose down && docker compose up --build -d`; previous RBAC/Keto scope demo remains. | +| Demo Tailnet serve | `tailscale serve --http=18080 off`; docker sidecar idle if `TS_AUTHKEY` absent. | + +## Assumptions + +| id | assumption | challenge | fallback | +|---|---|---|---| +| A1 | Provider-neutral admin can be mode-native without abstracting away semantics. | one form might not fit every provider. | Capability-specific tabs and provider-specific advanced pane. | +| A2 | Third-party plugins can declare resources/scopes/attrs/relations without knowing provider choice. | some provider schemas need provider-specific model syntax. | Shared declarations generate baseline provider schema; provider-specific extension block optional. | +| A3 | Casbin ABAC can be detected from model text enough for truthful capabilities. | model analysis can miss custom funcs. | Allow explicit capability override only if model tests pass; show `detected` vs `declared`. | +| A4 | Permit live environment may not be available locally/CI. | no real Permit verification by default. | Fake SDK conformance + env-gated live suite + UI unavailable state. | +| A5 | UI should avoid free text wherever possible. | users need custom string attrs. | Allow custom only for schema-declared dynamic attributes. | + +## Adversarial Design Review + +**Phase:** design +**Status:** PASS after revision + +| Class | Result | Note | +|---|---|---| +| Project-guidance conflicts | Fixed | Initial YAML example implied engine-owned `modules[].requires`; revised to authz-owned config/typed step. | +| Assumptions under attack | Clean | A1-A5 list fallback paths; provider-specific edge cases pushed to capability-specific tabs. | +| Repo-precedent conflicts | Clean | Uses existing plugin-owned proto/contract pattern and admin contribution model. | +| YAGNI | Clean | Does not add Permify provider or wholesale Casdoor adoption. | +| Missing failure modes | Clean | Provider down, unsupported mode, schema changes, stale SPA projection covered. | +| Security/privacy | Clean | Backend enforcement, least privilege, CSRF, validation, secret redaction, audit specified. | +| Infrastructure impact | Clean | Local docker/Keto and env-gated Permit only; no production deploy. | +| Multi-component validation | Clean | Provider, UI, scenario, Docker, Playwright, YAML checks specified. | +| Rollback | Clean | Per-component rollback paths specified. | +| Simpler alternative | Clean | Casdoor/Casbin portal considered and rejected for provider-neutral embedded Workflow use. | +| User-intent drift | Clean | Directly covers RBAC/ABAC/ReBAC, capabilities, UI friendliness, YAML/Go/SPA, demo/QA/security review. | + +## Self-Challenge + +| doubt | response | +|---|---| +| Could this be just Casdoor/Casbin portal? | No: it is Casbin-specific and not Workflow provider-neutral/admin-contribution native. | +| Is unified decision API too much? | Needed to make YAML, Go modules, and SPA projection use one enforcement surface while preserving model-native management APIs. | +| Is ABAC UI overreach? | User explicitly asked ABAC and user-friendly lookup-backed forms; plan scopes ABAC to declared schemas/policies, not arbitrary DSL authoring first. | diff --git a/docs/plans/2026-05-27-authz-modes-admin.md b/docs/plans/2026-05-27-authz-modes-admin.md new file mode 100644 index 0000000..3f4774d --- /dev/null +++ b/docs/plans/2026-05-27-authz-modes-admin.md @@ -0,0 +1,349 @@ +# Authz Modes Admin Implementation Plan + +> **For the implementing agent:** REQUIRED SUB-SKILL: Use autodev:executing-plans to implement this plan task-by-task. + +**Goal:** Implement truthful RBAC/ABAC/ReBAC capability discovery, provider-native management contracts, capability-driven admin UI, YAML/Go/SPA enforcement surfaces, and a refreshed demo with exploratory QA. + +**Architecture:** `workflow-plugin-authz` owns strict provider contracts and enforcement APIs. `workflow-plugin-authz-ui` renders capability-aware management surfaces from lookup-backed catalogs. `workflow-scenarios` proves app/admin/provider behavior with local Casbin/Keto and env-gated Permit live tests. + +**Tech Stack:** Go, protobuf strict contracts, Workflow plugin SDK, React/Vite/TypeScript, Docker Compose, Ory Keto, Permit Go SDK, Casbin, Playwright. + +**Base branch:** `workflow-plugin-authz/main`; related branches in `workflow-plugin-authz-ui` and `workflow-scenarios`. + +--- + +## Scope Manifest + +**PR Count:** 4 +**Tasks:** 12 +**Estimated Lines of Change:** ~4500 + +**Out of scope:** +- Adding Permify as a provider in this implementation. +- Replacing authn provider/session implementation. +- Production deploy or production Permit project mutation without explicit env-backed test approval. +- Engine ABI changes outside plugin-owned contracts. + +**PR Grouping:** + +| PR # | Title | Tasks | Branch | +|------|-------|-------|--------| +| 1 | Authz provider contracts and enforcement | Task 1, Task 2, Task 3, Task 4, Task 5 | feat/authz-modes-core | +| 2 | Capability-driven authz admin UI | Task 6, Task 7, Task 8, Task 9, Task 10 | feat/authz-modes-ui | +| 3 | Scenario demo and provider rotation | Task 11 | feat/authz-modes-demo | +| 4 | Integration, exploratory QA, security review | Task 12 | feat/authz-modes-validation | + +**Status:** Locked 2026-05-27T04:46:45Z + +## Preflight Already Completed + +- Capability bug fixed in `f6ec8f3`: Casbin reports model-aware capabilities; Keto reports RBAC/ReBAC; `step.authz_capabilities` can resolve Keto providers. +- Design committed in `docs/plans/2026-05-27-authz-modes-admin-design.md`. +- Design adversarial review PASS after replacing engine-level `modules[].requires` with authz-owned config/typed step. + +### Task 1: Provider Capability Descriptors + +**Files:** +- Modify: `internal/contracts/authz.proto` +- Modify: `internal/contracts/authz.pb.go` +- Modify: `internal/capabilities.go` +- Modify: `internal/capabilities_test.go` +- Modify: `internal/plugin.go` +- Test: `internal/plugin_contracts_test.go` + +**Steps:** +1. Write failing tests for structured capability output: modes, operations, configured flag, source (`detected|declared|provider`), health, unsupported reasons. +2. Run `GOWORK=off go test ./internal -run 'TestCapabilityDescriptor|TestAuthzCapabilitiesStep'`; expect missing proto/fields. +3. Extend proto with `AuthzMode`, `AuthzOperation`, `CapabilityDescriptor`, `ProviderCapabilitiesInput/Output`, `CapabilityRequirement`. +4. Replace string-only output with descriptor output while preserving legacy `capabilities: []string` for compatibility. +5. Add provider health/degraded state without secrets. +6. Add typed service contracts for `ProviderCapabilities.GetCapabilities` and `ProviderCapabilities.RequireCapabilities`. +7. Regenerate protobufs with repo-local generation command. +8. Run focused tests and contract validation. + +**Verification:** `GOWORK=off go test ./internal -run 'TestCapabilityDescriptor|TestAuthzCapabilitiesStep|TestPluginImplementsStrictContractProviders' && wfctl plugin validate-contract .` → PASS. + +**Rollback:** Revert Task 1 commit; legacy string capabilities remain from preflight fix. + +### Task 2: Authz Declaration Catalog + +**Files:** +- Modify: `internal/contracts/authz.proto` +- Modify: `internal/contracts/authz.pb.go` +- Modify: `internal/module_scope_catalog.go` +- Modify: `internal/scope_catalog_test.go` +- Create: `internal/authz_declarations.go` +- Create: `internal/authz_declarations_test.go` + +**Steps:** +1. Write failing tests for plugin-owned declarations: resources/actions, scopes, attributes, relation types, UI actions. +2. Run focused tests; expect missing types. +3. Add proto messages: `ResourceDeclaration`, `ActionDeclaration`, `AttributeDeclaration`, `AttributeValue`, `RelationDeclaration`, `UIActionDeclaration`, `AuthzDeclarationSet`. +4. Extend scope catalog into an authz declaration catalog while preserving existing scope APIs. +5. Validate names, contexts, owner plugin/module, data types, allowed values, lookup source IDs. +6. Add service methods `RegisterDeclarations`, `ListDeclarations`, `ResolveProjectionInputs`. +7. Add tests that third-party plugin declarations are provider-neutral. + +**Verification:** `GOWORK=off go test ./internal -run 'TestAuthzDeclarations|TestScopeCatalog' && wfctl plugin validate-contract .` → PASS. + +**Rollback:** Revert Task 2 commit; scope catalog APIs remain. + +### Task 3: ABAC Provider Contract + +**Files:** +- Modify: `internal/contracts/authz.proto` +- Modify: `internal/capabilities.go` +- Modify: `internal/step_abac.go` +- Modify: `internal/step_abac_test.go` +- Create: `internal/abac_provider.go` +- Create: `internal/abac_provider_test.go` +- Modify: `internal/provider_permit.go` +- Modify: `internal/module_casbin.go` + +**Steps:** +1. Write failing conformance tests for `AttributePolicyProvider`: declare attributes, upsert/list/remove policy, check with subject/resource/environment attrs. +2. Add proto messages: `AttributePolicy`, `AttributeCondition`, `AttributeCheckInput/Output`, `UpsertAttributePolicy*`, `ListAttributePolicies*`, `RemoveAttributePolicy*`. +3. Implement in-memory conformance store used by Casbin tests and Permit fake SDK tests. +4. Implement Casbin ABAC adapter only when loaded model supports ABAC; otherwise return unsupported. +5. Implement Permit ABAC adapter only for SDK-backed surfaces actually wired; unsupported operations return typed unsupported errors and are not advertised. +6. Add provider capability descriptors that show ABAC operations only when implemented/configured. +7. Add fail-closed checks for malformed attrs, unknown attrs, and provider errors. + +**Verification:** `GOWORK=off go test ./internal -run 'TestABACProvider|TestABAC|TestCapabilityDescriptor'` → PASS with explicit unsupported tests. + +**Rollback:** Revert Task 3 commit; RBAC/ReBAC contracts remain. + +### Task 4: ReBAC Provider Contract + +**Files:** +- Modify: `internal/contracts/authz.proto` +- Modify: `internal/provider_keto.go` +- Modify: `internal/provider_keto_test.go` +- Modify: `internal/provider_permit.go` +- Modify: `internal/step_rebac.go` +- Modify: `internal/step_rebac_test.go` +- Create: `internal/rebac_provider.go` +- Create: `internal/rebac_provider_test.go` + +**Steps:** +1. Write failing conformance tests for relation schema, tuple upsert/delete/list, relationship check, and graph lookup. +2. Add proto messages: `RelationTuple`, `RelationCheckInput/Output`, `UpsertRelationTuple*`, `ListRelationTuples*`, `RemoveRelationTuple*`. +3. Implement Keto relationship provider using official SDK tuple APIs. +4. Implement Casbin ReBAC adapter only for models with `g2`; unsupported otherwise. +5. Implement Permit ReBAC adapter only for SDK surfaces actually wired; no claims for unwired operations. +6. Add tuple validation against relation/resource declarations. +7. Add removal tests proving deselect/delete changes enforcement. + +**Verification:** `GOWORK=off go test ./internal -run 'TestReBACProvider|TestKetoProvider|TestReBAC'` → PASS. + +**Rollback:** Revert Task 4 commit; existing Keto scope-role provider remains. + +### Task 5: Unified Decision API and YAML Steps + +**Files:** +- Modify: `internal/contracts/authz.proto` +- Modify: `internal/plugin.go` +- Modify: `internal/typed.go` +- Create: `internal/decision_provider.go` +- Create: `internal/decision_provider_test.go` +- Create: `internal/step_authz_decision.go` +- Create: `internal/step_authz_decision_test.go` +- Modify: `README.md` + +**Steps:** +1. Write failing tests for `step.authz_check`, `step.authz_require_capabilities`, RBAC/ABAC/ReBAC mode-specific typed steps. +2. Add unified `AuthorizationDecisionInput/Output` with provider, mode preference, subject, context, resource, action, scope, attrs, relation hint, explain. +3. Implement routing to provider-native RBAC/ABAC/ReBAC checks based on capability descriptors and request shape. +4. Implement explicit unsupported and ambiguous-mode errors. +5. Add YAML examples for route gating, workflow action gating, and startup capability requirements. +6. Add Go service examples for module code invoking decision provider. + +**Verification:** `GOWORK=off go test ./internal -run 'TestAuthorizationDecision|TestAuthzDecisionStep|TestTypedProviders' && wfctl plugin validate-contract .` → PASS. + +**Rollback:** Revert Task 5 commit; mode-native contracts remain. + +### Task 6: UI Runtime Capability Client + +**Files:** +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/api.ts` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/types.ts` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/App.tsx` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/test/source-contract.test.mjs` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/internal/contracts/authz_ui.proto` + +**Steps:** +1. Write failing UI source tests for capability fetch, hidden unsupported tabs, and lookup-driven config. +2. Extend runtime config with capabilities endpoint, projection endpoint, and declaration catalog endpoint. +3. Add typed TS models for capability descriptors and declarations. +4. Refactor app shell to render `Overview`, `RBAC`, `ABAC`, `ReBAC`, `Simulator`, `Audit` based on descriptors. +5. Add disabled Overview states for unsupported/unavailable modes. +6. Keep base path configurable. + +**Verification:** `cd /Users/jon/workspace/workflow-plugin-authz-ui/ui && npm test && npm run build && npm run lint` → PASS. + +**Rollback:** Revert Task 6 commit; old RBAC screen remains. + +### Task 7: RBAC UI Lookup UX + +**Files:** +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/components/RoleTable.tsx` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/styles/index.css` +- Test: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/test/source-contract.test.mjs` + +**Steps:** +1. Write failing tests that no RBAC scope/user/resource/action form uses free-text when lookup data exists. +2. Add role scope select/deselect with persisted remove assignment flow. +3. Add user/group lookup, role lookup, scope lookup, context filter, and selected chips. +4. Add explicit enforcement simulator entry from selected role/user. +5. Verify deselect removes scope and subsequent check denies. + +**Verification:** UI tests/build/lint PASS; Playwright later checks checkbox/select/deselect behavior in running demo. + +**Rollback:** Revert Task 7 commit; Task 6 shell still renders. + +### Task 8: ABAC UI + +**Files:** +- Create: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/components/ABACPolicyTable.tsx` +- Create: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/components/AttributeConditionEditor.tsx` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/api.ts` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/styles/index.css` +- Test: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/test/source-contract.test.mjs` + +**Steps:** +1. Write failing source tests for ABAC tab, attribute lookup, operator lookup, no arbitrary text for declared enums. +2. Implement policy list/upsert/delete forms using attribute declarations. +3. Add condition builder with type-aware controls: enum select, boolean toggle, numeric input, string input only if schema permits custom. +4. Add ABAC simulator panel with subject/resource/environment attrs. +5. Hide tab if provider lacks `abac/check`. + +**Verification:** `npm test && npm run build && npm run lint` → PASS; scenario/Playwright later confirms runtime. + +**Rollback:** Revert Task 8 commit; shell hides ABAC if unsupported. + +### Task 9: ReBAC UI + +**Files:** +- Create: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/components/ReBACTupleTable.tsx` +- Create: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/components/RelationPicker.tsx` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/api.ts` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/styles/index.css` +- Test: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/test/source-contract.test.mjs` + +**Steps:** +1. Write failing source tests for relation tuple tab and lookup-backed subject/relation/object selectors. +2. Implement tuple list/upsert/delete. +3. Add resource type filter and relation dropdown sourced from declarations. +4. Add relation check simulator. +5. Add graph-adjacent table showing subject → relation → object without decorative graph complexity. +6. Hide tab if provider lacks `rebac/check`. + +**Verification:** `npm test && npm run build && npm run lint` → PASS; scenario/Playwright later confirms runtime. + +**Rollback:** Revert Task 9 commit; shell hides ReBAC if unsupported. + +### Task 10: SPA Projection and Admin Contribution Authz + +**Files:** +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/api.ts` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/types.ts` +- Create: `/Users/jon/workspace/workflow-plugin-authz-ui/ui/src/components/ProjectionPreview.tsx` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/internal/module.go` +- Modify: `/Users/jon/workspace/workflow-plugin-authz-ui/internal/plugin_test.go` + +**Steps:** +1. Write failing tests for non-authoritative projection fields and admin action permission metadata. +2. Extend `authz.ui` admin contribution to declare tab/action permissions and projection endpoint. +3. Add projection preview in Simulator, clearly marked UX-only. +4. Ensure UI hides disabled actions from projection but API errors still render denial reason. +5. Add tests for admin/frontend context split. + +**Verification:** `GOWORK=off go test ./...` in authz-ui repo and `npm test && npm run build && npm run lint` in UI → PASS. + +**Rollback:** Revert Task 10 commit; admin contribution continues to expose RBAC page only. + +### Task 11: Refreshed Demo and Provider Rotation + +**Files:** +- Modify: `/Users/jon/workspace/workflow-scenarios/scenarios/90-admin-tailnet-demo/app/app.py` +- Modify: `/Users/jon/workspace/workflow-scenarios/scenarios/90-admin-tailnet-demo/docker-compose.yml` +- Modify: `/Users/jon/workspace/workflow-scenarios/scenarios/90-admin-tailnet-demo/keto/namespaces.ts` +- Modify: `/Users/jon/workspace/workflow-scenarios/scenarios/90-admin-tailnet-demo/test/run.sh` +- Modify: `/Users/jon/workspace/workflow-scenarios/scenarios/90-admin-tailnet-demo/README.md` +- Create: `/Users/jon/workspace/workflow-scenarios/scenarios/90-admin-tailnet-demo/test/playwright-qa.mjs` + +**Steps:** +1. Write failing scenario tests for capabilities endpoint, RBAC select/deselect enforcement, ABAC allow/deny, ReBAC tuple add/delete enforcement, SPA projection, and unsupported mode UI. +2. Expand demo personas: platform admin, readonly admin, frontend user, regional manager, project collaborator, auditor. +3. Add provider selector for `casbin`, `keto`, `permit`; local Casbin/Keto run without external secrets; Permit requires env and marks unavailable otherwise. +4. Implement demo APIs for declarations, capabilities, RBAC, ABAC, ReBAC, decision checks, projection, and audit. +5. Wire Keto relation tuples for ReBAC; Casbin in-process for RBAC/ABAC where model supports; Permit env-gated integration path. +6. Add Tailscale sidecar unchanged; refresh host `tailscale serve` after launch. +7. Add README matrix of personas/use cases and credentials. + +**Verification:** `docker compose up --build -d && ./test/run.sh` → all scenario checks PASS; `node test/playwright-qa.mjs` → PASS; `docker compose ps` shows app healthy + Keto running. + +**Rollback:** Revert Task 11 commit; run `docker compose down && docker compose up --build -d`; previous demo remains. + +### Task 12: Integration, Exploratory QA, and Security Review + +**Files:** +- Modify: `docs/plans/2026-05-27-authz-modes-admin.md` +- Create: `docs/security/2026-05-27-authz-modes-review.md` +- Modify only files already owned by Tasks 1-11 when fixing validation findings; any new file or new feature scope requires the scope-lock amendment path. + +**Steps:** +1. Run full authz tests: `GOWORK=off go test ./... && wfctl plugin validate-contract .`. +2. Run authz-ui tests/build/lint/contracts. +3. Run scenario docker script and Playwright exploratory QA. +4. Run provider matrix: + - Casbin local: RBAC/ABAC where configured, ReBAC if `g2`; unsupported modes hidden. + - Keto local: ReBAC + RBAC-as-relations; ABAC hidden. + - Permit fake SDK: RBAC/ABAC/ReBAC conformance for implemented methods. + - Permit live: only when `PERMIT_INTEGRATION=1` and creds set; otherwise record skipped. +5. Security review checklist: fail-closed, confused deputy, CSRF, injection, secret logging, stale projection, audit, tenant/context isolation. +6. Fix all Critical/Important findings. +7. Refresh Tailnet serve and report URL/credentials. + +**Verification:** Security report lists PASS/FAIL for each checklist item; all local commands exit 0; Playwright QA PASS; demo reachable at Tailnet URL. + +**Rollback:** Revert validation fixes if needed; stop Tailnet serve with `tailscale serve --http=18080 off`; stop docker with `docker compose down`. + +## Alignment Matrix + +| requirement | tasks | +|---|---| +| Fix misleading capabilities | Preflight `f6ec8f3`, Task 1 | +| RBAC/ABAC/ReBAC support | Tasks 3, 4, 5 | +| Provider advertises support | Task 1 | +| Proper UI for each mode | Tasks 6-10 | +| Use lookups instead of text entry | Tasks 7-9, 11 | +| No false functionality | Tasks 1, 3, 4, 6, 11, 12 | +| Workflow YAML enforcement | Task 5, Task 11 | +| Go/module extensibility | Task 2, Task 5 | +| SPA access reflection | Task 10, Task 11 | +| Third-party plugin declarations | Task 2 | +| Personas/use cases/edge cases | Task 11 | +| Adversarial security review | Task 12 | +| Demo refreshed and tested | Task 11, Task 12 | + +## Adversarial Plan Review + +**Phase:** plan +**Status:** PASS after revision + +| Class | Result | Note | +|---|---|---| +| Project-guidance conflicts | Clean | Plan keeps changes in plugin-owned contracts and scenario/demo repos; no standalone CLI/tool. | +| Assumptions under attack | Clean | Provider-specific uncertainty handled via unsupported descriptors, env-gated Permit, and local Casbin/Keto proof. | +| Repo-precedent conflicts | Clean | Follows existing strict proto, typed step, admin contribution, and scenario test patterns. | +| YAGNI | Clean | Permify and full Casdoor replacement explicitly out of scope. | +| Missing failure modes | Clean | Unsupported provider modes, provider down, stale projection, attr schema drift, tuple delete/deselect are tested. | +| Security/privacy | Clean | Task 12 requires fail-closed, confused deputy, CSRF, injection, secret redaction, projection, audit, and tenant/context checks. | +| Infrastructure impact | Clean | Local Docker/Tailnet only; Permit live path env-gated. | +| Multi-component validation | Clean | Provider packages, UI package, Docker scenario, provider matrix, Playwright QA all required. | +| Rollback | Clean | Each runtime-affecting task includes rollback. | +| Verification-class mismatch | Clean | API/contract/UI/runtime/security tasks have class-appropriate verification. | +| Hidden serial dependencies | Clean | PR grouping serializes provider contracts before UI and demo. | +| Manifest drift | Clean | 4 PRs, 12 tasks, each task listed once. | +| Issue fixed | Fixed | Task 12 originally allowed `Modify as needed`; now restricted to files owned by Tasks 1-11 unless amendment path is used. | diff --git a/docs/plans/2026-05-27-authz-modes-admin.md.scope-lock b/docs/plans/2026-05-27-authz-modes-admin.md.scope-lock new file mode 100644 index 0000000..bbd2022 --- /dev/null +++ b/docs/plans/2026-05-27-authz-modes-admin.md.scope-lock @@ -0,0 +1 @@ +fcbc394534c3c389f8a177982765c79f6bdecfbdf077fdf861326c3e2ec8c3c2 diff --git a/docs/plans/2026-05-27-scope-provider-engines-design.md b/docs/plans/2026-05-27-scope-provider-engines-design.md new file mode 100644 index 0000000..bcfc0a3 --- /dev/null +++ b/docs/plans/2026-05-27-scope-provider-engines-design.md @@ -0,0 +1,189 @@ +# Scope Provider Engines Design + +## User Ask + +Implement scope picker backed by declared scopes; replace unused Permit REST +contract with official Permit.io Go SDK; add Ory Keto as third authz provider +using official SDK; verify role→scope assignment and enforcement rotate across +providers; update demo to use one newly added engine. Ignore prior Hydra +mention. + +## Global Design Guidance + +Source: `/Users/jon/workspace/AGENTS.md`; `workflow-plugin-authz-ui/docs/plans/2026-05-27-admin-authz-ui-design.md`. + +| guidance | design response | +|---|---| +| Dogfood Workflow/plugin ecosystem | Authz provider modules expose strict proto contracts and shared methods; authz-ui consumes normalized API. | +| Repo ownership boundaries | `workflow-plugin-authz` owns provider adapters/enforcement; `workflow-plugin-authz-ui` owns selection UX; `workflow-scenarios` owns demo wiring. | +| Security/quality on platform surfaces | Default-deny checks, declared-scope validation, no arbitrary scope grants, provider conformance tests. | +| Use real integrations | Permit via `github.com/permitio/permit-golang`; Keto via `github.com/ory/keto-client-go/v25`; no handwritten provider REST clients. | +| Multi-component validation | Unit conformance + SDK integration gates + running demo + Playwright/curl authz checks. | + +## Design + +### Canonical Model + +| object | shape | notes | +|---|---|---| +| scope | `context:resource:action` + metadata | Declared by modules through scope catalog; free-text grants rejected by admin UI/API. | +| role | `name`, `context`, `scopes[]` | One role namespace with context dimension; roles can be shared by name only when context matches. | +| assignment | `subject`, `role`, `context`, optional direct `scopes[]` | Direct scopes still validate against catalog; role scopes and direct scopes merge at check time. | +| check | `subject`, `context`, `scope` or `resource/action` | Normalized before provider call; result includes provider, matched grants, denied reason. | + +### Provider Interface + +`workflow-plugin-authz` adds an internal conformance interface: + +```go +type ScopeRoleProvider interface { + Name() string + DeclareScopes(context.Context, []*contracts.ScopeDeclaration) error + UpsertRole(context.Context, RoleScopeGrant) error + AssignRole(context.Context, SubjectRoleAssignment) error + ListAssignments(context.Context, AssignmentFilter) ([]SubjectRoleAssignment, error) + RemoveAssignment(context.Context, SubjectRoleAssignment) error + CheckScope(context.Context, ScopeCheck) (ScopeCheckResult, error) +} +``` + +Strict proto service methods wrap this model so Workflow modules, admin, and +scenarios do not depend on provider-specific SDK types. + +### Providers + +| provider | implementation | persistence/eval | +|---|---|---| +| Casbin | existing `authz.casbin` adapted to scope-role interface | Local memory/file/gorm Casbin policies; good local default. | +| Permit.io | replace unused custom REST client with official SDK module `github.com/permitio/permit-golang@v1.2.8` | SDK management APIs create roles/resources/actions; SDK/PDP checks enforce. Integration tests require Permit env vars. | +| Ory Keto | new `authz.keto` module using `github.com/ory/keto-client-go/v25@v25.4.0` | Roles/scopes represented as Keto relationship tuples; check API evaluates subject relation to context/resource/action object. Local Docker Keto used in scenario/tests. | + +Permit provider contract compatibility is not preserved because user confirmed it is unused. + +### Authz UI Scope Picker + +`workflow-plugin-authz-ui` replaces comma-separated scope entry with a declared +scope picker: + +- load `/scopes` from configurable API base path; +- group by context/category/resource; search by name/resource/description; +- filter to selected role context by default; +- selected scopes render as removable chips; +- submit only catalog scope names; +- no free-text fallback. + +### Demo + +`workflow-scenarios/scenarios/90-admin-tailnet-demo` moves from in-memory +scope checks to a provider-backed check path. Default demo engine: **Ory Keto** +because it can run locally in Docker without SaaS credentials and proves a new +provider in the running app. Provider selector remains configurable so tests can +run `casbin`, `keto`, and Permit when credentials are present. + +## Security Review + +| topic | design | +|---|---| +| Authn | Demo/admin routes still require login/session before authz checks. | +| Authz | UI is advisory; API rejects undeclared scopes and provider `CheckScope` gates routes/actions. | +| Least privilege | Admin role management needs `admin:authz.roles:update`; scope read needs `admin:authz.scopes:read`. | +| Provider trust | SDK clients are isolated behind provider adapters; no provider secrets exposed to UI/runtime config. | +| Abuse cases | Unknown scope grant, wrong context, absent provider, failed network check → deny. | +| Audit | Role assignment/check errors include provider + safe reason, never API keys or tokens. | + +## Infrastructure Impact + +| item | impact | +|---|---| +| Go deps | Add Permit SDK and Keto SDK to `workflow-plugin-authz`. | +| Docker demo | Add Keto service and init wiring; Permit remains env-gated because SaaS credentials are required. | +| Network | Demo exposes same app port/Tailscale serve; Keto internal Docker network only. | +| Secrets | Permit tests use env vars only; no committed keys. | +| Prod approval | None; local/demo only. | + +## Multi-Component Validation + +| proof | command/evidence | +|---|---| +| Provider conformance | `GOWORK=off go test ./internal -run TestScopeRoleProviderConformance` across casbin/keto/permit fake SDK boundary. | +| SDK compile | `GOWORK=off go test ./...` proves official SDK usage compiles. | +| Keto integration | Docker Keto + Go integration test or scenario check exercises real write/check boundary. | +| Permit integration | Env-gated real Permit test runs when `PERMIT_API_KEY`/project/env present; otherwise skipped with explicit message. | +| UI | `cd ui && npm test && npm run build && npm run lint`; Playwright confirms picker has selectable scopes and no text scope entry. | +| Scenario | Docker compose app+keto; curl/Playwright verify login, role assignment, allowed/denied frontend/admin actions. | + +## Rollback + +| change | rollback | +|---|---| +| Provider adapters/proto | Revert authz provider commits; run `GOWORK=off go test ./...`; Casbin legacy module remains rollback baseline. | +| UI picker | Revert authz-ui commit; rebuild UI; older API payload remains compatible if server accepts `scopes[]`. | +| Demo Keto service | `docker compose down -v`; revert scenario files; relaunch prior app; `tailscale serve --http=18080 off` if needed. | +| Dependency pins | Revert `go.mod`/`go.sum`; rerun Go tests. | + +## Assumptions + +| id | assumption | challenge | fallback | +|---|---|---|---| +| A1 | Scope catalog is the source of truth for grantable scopes. | Legacy payloads may include unknown scopes. | Reject new unknown grants; render existing unknown grants as provider drift requiring cleanup. | +| A2 | Permit SDK exposes required role/resource/check surfaces. | SDK lacks some management method ergonomics. | Use SDK-generated OpenAPI clients from the same module, not custom HTTP. | +| A3 | Keto relationship tuples can model role→scope and subject→role checks cleanly. | Tuple schema may need namespace choices. | Use minimal documented namespace layout and conformance tests; no app-specific assumptions. | +| A4 | Keto can run locally in Docker scenario. | Startup/migration order may be brittle. | Add health/readiness wait and fail closed; keep Casbin selector for local fallback tests. | +| A5 | Provider rotation should verify same semantics, not same storage shape. | Engines differ internally. | Conformance asserts external allow/deny/list behavior only. | + +## Self-Challenge + +| doubt | response | +|---|---| +| Could this just be UI picker + Casbin? | No; user explicitly requested Permit SDK, Keto SDK, provider rotation, and demo using a new engine. | +| Is a normalized provider abstraction overbuilt? | Without it, UI/demo would branch per engine and role/scope semantics would drift. | +| What fails first? | External provider unavailability. Design requires default-deny, skipped/gated SaaS tests, and local Keto readiness checks. | +| Does this solve unasked features? | No policy designer/tenant UI/migrations; only scope grants, role assignments, checks, and provider adapters. | +| Repo pattern conflict? | Fits existing module/provider plugin shape; replaces unused Permit contract only with user approval. | + +## Approved Direction + +User approved on 2026-05-27 and clarified: use Ory Keto, ignore Hydra mention, +do not preserve existing Permit provider contract. + +## Adversarial Design Review + +Status: PASS after inline adversarial review. + +Required framing used: find at least three things wrong with this design; +bias toward misconceptions, unstated assumptions, missing failure modes, and +simpler alternatives. + +### Findings + +| sev | class | issue | resolution | +|---|---|---|---| +| Minor | Multi-component validation | Permit real enforcement cannot be proven locally without SaaS credentials. | Keep unit conformance mandatory; make real Permit integration env-gated and report SKIP explicitly when credentials absent. Do not claim real Permit integration passed unless env-gated test ran. | +| Minor | Repo precedent | Scenario is currently Python/in-memory; a direct Python Keto call could bypass Go SDK provider integration. | Plan must keep SDK proof in `workflow-plugin-authz` and make scenario clearly a demo consumer of provider semantics, not the provider implementation. | +| Minor | Failure mode | Keto tuple schema is underspecified and could drift from role/scope model. | Plan must define tuple/object naming in a dedicated task and verify with a real Keto check. | + +### Bug-Class Scan + +| class | result | note | +|---|---|---| +| Project-guidance conflicts | Clean | Follows Workflow/plugin ownership, strict proto, security, and multi-component proof guidance. | +| Assumptions under attack | Finding | A2/A3/A4 are fragile; design lists fallback and plan must verify exact SDK/Keto behavior. | +| Repo-precedent conflicts | Finding | Python demo precedent can bypass SDK; resolved by separating demo consumer from Go SDK provider proof. | +| YAGNI | Clean | No tenant UI, policy designer, migration layer, or generalized identity system. | +| Missing failure modes | Finding | External provider outage and Keto readiness addressed as default-deny/readiness checks. | +| Security/privacy | Clean | Unknown grants rejected, provider errors fail closed, secrets stay server/env only. | +| Infrastructure impact | Clean | Local Docker Keto only; Permit env-gated; no production resources. | +| Multi-component validation | Finding | Permit real boundary depends on credentials; design requires explicit skip vs pass distinction. | +| Rollback story | Clean | Revert deps/provider/UI/demo and stop Docker/Tailscale paths covered. | +| Simpler alternative | Clean | Scope picker + Casbin-only rejected because user requested Permit/Keto/provider rotation. | +| User-intent drift | Clean | Hydra removed; Keto is third provider per user correction. | + +### Options Considered + +1. UI picker + Casbin only: simplest, but fails explicit provider requirements. +2. Permit/Keto as step-only integrations: smaller API surface, but admin cannot + administer roles/scopes uniformly across engines. +3. Full normalized provider interface: more code, but required to rotate engines + without UI/demo branching and to conformance-test semantics. + +Verdict: PASS. Findings are plan-level guardrails, not design blockers. diff --git a/docs/plans/2026-05-27-scope-provider-engines.md b/docs/plans/2026-05-27-scope-provider-engines.md new file mode 100644 index 0000000..f35a507 --- /dev/null +++ b/docs/plans/2026-05-27-scope-provider-engines.md @@ -0,0 +1,303 @@ +# Scope Provider Engines Implementation Plan + +> **For the implementing agent:** REQUIRED SUB-SKILL: Use autodev:executing-plans to implement this plan task-by-task. + +**Goal:** Provide declared-scope role management across Casbin, Permit.io SDK, and Ory Keto SDK, update authz-ui to select known scopes, and run the demo on a new provider. + +**Architecture:** `workflow-plugin-authz` owns a normalized scope-role provider contract and adapters. `workflow-plugin-authz-ui` consumes the normalized scope catalog/role APIs with a catalog-backed picker. `workflow-scenarios` demonstrates the same role/scope semantics with Keto as the default locally runnable provider. + +**Tech Stack:** Go protobuf contracts, Workflow external SDK, Casbin, `github.com/permitio/permit-golang@v1.2.8`, `github.com/ory/keto-client-go/v25@v25.4.0`, React/Vite/TypeScript, Docker Compose, Playwright/curl. + +**Base branch:** `workflow-plugin-authz` `main`; `workflow-plugin-authz-ui` `feat/admin-authz-ui-integration`; `workflow-scenarios` `pr-27`. + +--- + +## Scope Manifest + +**PR Count:** 3 +**Tasks:** 5 +**Estimated Lines of Change:** ~1800 + +**Out of scope:** +- Ory Hydra integration. +- Preserving the unused old Permit REST step/module contract. +- Building a full policy designer, tenant admin UI, or persistent role database beyond provider-backed storage. +- Running real Permit SaaS integration without credentials in the environment. + +**PR Grouping:** + +| PR # | Title | Tasks | Branch | +|------|-------|-------|--------| +| 1 | Scope-backed authz provider engines | Task 1, Task 2, Task 3 | workflow-plugin-authz: feat/scope-provider-engines | +| 2 | Declared-scope authz UI picker | Task 4 | workflow-plugin-authz-ui: feat/scope-picker | +| 3 | Keto-backed admin tailnet demo | Task 5 | workflow-scenarios: feat/keto-admin-demo | + +**Status:** Complete 2026-05-27T03:30:47Z + +### Task 1: Normalized Scope-Role Provider Contract + +**Files:** +- Modify: `workflow-plugin-authz/internal/contracts/authz.proto` +- Modify: `workflow-plugin-authz/internal/contracts/authz.pb.go` +- Modify: `workflow-plugin-authz/internal/plugin.go` +- Modify: `workflow-plugin-authz/internal/typed.go` +- Modify: `workflow-plugin-authz/plugin.contracts.json` +- Create: `workflow-plugin-authz/internal/scope_role_provider.go` +- Create: `workflow-plugin-authz/internal/scope_role_provider_test.go` +- Modify: `workflow-plugin-authz/internal/module_casbin.go` +- Test: `workflow-plugin-authz/internal/scope_catalog_test.go` + +**Step 1: Write failing tests** + +Add tests for: +- unknown scope grants rejected against catalog; +- role assignment stores `subject`, `role`, `context`, `scopes`; +- `CheckScope` allows assigned frontend scope and denies admin scope for same user; +- Casbin adapter satisfies shared conformance. + +Run: `GOWORK=off go test ./internal -run 'TestScopeRoleProvider|TestCasbinScopeRoleConformance'` +Expected: FAIL because provider contract/types do not exist. + +**Step 2: Implement contract** + +Add proto messages: `ScopeRoleProviderConfig`, `RoleScopeGrant`, `SubjectRoleAssignment`, `ScopeCheckInput`, `ScopeCheckOutput`, `ListRoleAssignmentsInput/Output`, and service contracts for assign/list/remove/check. Add Go interface + registry. Adapt Casbin with in-memory normalized scope policies. + +Regenerate: `protoc --go_out=. --go_opt=paths=source_relative internal/contracts/authz.proto` + +**Step 3: Verify** + +Run: `GOWORK=off go test ./internal -run 'TestScopeRoleProvider|TestCasbinScopeRoleConformance|TestScopeCatalog'` +Expected: PASS. + +Rollback: revert Task 1 files and regenerate proto from prior `authz.proto`. + +### Task 2: Permit.io SDK Provider + +**Files:** +- Modify: `workflow-plugin-authz/go.mod` +- Modify: `workflow-plugin-authz/go.sum` +- Replace/remove: `workflow-plugin-authz/internal/permit_client.go` +- Modify: `workflow-plugin-authz/internal/module_permit.go` +- Modify: `workflow-plugin-authz/internal/permit_registry.go` +- Modify/remove as needed: `workflow-plugin-authz/internal/step_permit*.go` +- Create: `workflow-plugin-authz/internal/provider_permit.go` +- Create: `workflow-plugin-authz/internal/provider_permit_test.go` +- Modify: `workflow-plugin-authz/internal/capabilities.go` + +**Step 1: Write failing tests** + +Add tests proving: +- `permit.provider` constructs via official SDK types, not custom HTTP client; +- adapter maps declared scopes to Permit resources/actions; +- adapter maps role assignments to Permit role/permission operations through an SDK-facing client interface; +- `CheckScope` result follows SDK/PDP allow/deny. + +Run: `GOWORK=off go test ./internal -run 'TestPermitSDKProvider'` +Expected: FAIL because SDK adapter is absent. + +**Step 2: Add SDK and implementation** + +Run: `GOWORK=off go get github.com/permitio/permit-golang@v1.2.8` + +Implement Permit adapter with official SDK/openapi clients from that module. Remove handwritten request builder as provider implementation. Keep any generic Permit step only if it delegates through SDK/openapi clients; otherwise remove from contract registry because compatibility is out of scope. + +**Step 3: Verify** + +Run: `GOWORK=off go test ./internal -run 'TestPermitSDKProvider|TestScopeRoleProviderConformance'` +Expected: PASS with mocked SDK boundary. + +Run when env exists: `PERMIT_INTEGRATION=1 GOWORK=off go test ./internal -run TestPermitRealIntegration -count=1` +Expected with credentials: PASS; without required env vars: SKIP message naming missing vars. + +Rollback: revert Task 2 files; `go mod tidy`; Casbin/Keto providers remain unaffected. + +### Task 3: Ory Keto SDK Provider + +**Files:** +- Modify: `workflow-plugin-authz/go.mod` +- Modify: `workflow-plugin-authz/go.sum` +- Create: `workflow-plugin-authz/internal/module_keto.go` +- Create: `workflow-plugin-authz/internal/provider_keto.go` +- Create: `workflow-plugin-authz/internal/provider_keto_test.go` +- Create: `workflow-plugin-authz/internal/provider_keto_integration_test.go` +- Modify: `workflow-plugin-authz/internal/plugin.go` +- Modify: `workflow-plugin-authz/internal/contracts/authz.proto` +- Modify: `workflow-plugin-authz/internal/contracts/authz.pb.go` +- Modify: `workflow-plugin-authz/plugin.contracts.json` + +**Step 1: Write failing tests** + +Add tests for tuple naming: +- subject role tuple: `role::#member@user:`; +- role scope tuple: `scope:::#granted@role::`; +- optional direct scope tuple: `scope:::#granted@user:`; +- `CheckScope` calls Keto check and fails closed on errors. + +Run: `GOWORK=off go test ./internal -run 'TestKetoProvider'` +Expected: FAIL because Keto module/provider do not exist. + +**Step 2: Add SDK and implementation** + +Run: `GOWORK=off go get github.com/ory/keto-client-go/v25@v25.4.0` + +Implement `authz.keto` module config with read/write URLs and namespace prefix. Use official SDK client for tuple writes and checks. Register provider and strict contract. + +**Step 3: Verify** + +Run: `GOWORK=off go test ./internal -run 'TestKetoProvider|TestScopeRoleProviderConformance'` +Expected: PASS. + +Run local integration if Docker available: `KETO_INTEGRATION=1 GOWORK=off go test ./internal -run TestKetoRealIntegration -count=1` +Expected: PASS when test starts/reaches local Keto, otherwise SKIP with explicit Docker/env reason. + +Rollback: revert Task 3 files; remove Keto service/deps; run `go mod tidy`. + +### Task 4: Authz UI Declared-Scope Picker + +**Files:** +- Modify: `workflow-plugin-authz-ui/ui/src/components/RoleTable.tsx` +- Modify: `workflow-plugin-authz-ui/ui/src/styles/index.css` +- Modify: `workflow-plugin-authz-ui/ui/src/types.ts` +- Modify: `workflow-plugin-authz-ui/ui/src/api.ts` +- Modify: `workflow-plugin-authz-ui/ui/test/source-contract.test.mjs` +- Add if needed: `workflow-plugin-authz-ui/ui/test/scope-picker.test.mjs` + +**Step 1: Write failing tests** + +Update source/static tests to assert: +- no `"Direct scopes, comma separated"` input exists; +- scope controls are checkboxes/buttons/selectable chips sourced from `scopes`; +- submitted role payload uses `scopes: string[]`; +- unknown free-text scopes cannot be entered. + +Run: `cd ui && npm test` +Expected: FAIL because current UI has text entry. + +**Step 2: Implement picker** + +Replace text input with grouped/searchable selectable scope list filtered by selected context. Render selected scopes as removable chips. Keep API base path configurable. + +**Step 3: Verify** + +Run: `cd ui && npm test && npm run build && npm run lint` +Expected: PASS. + +Rollback: revert Task 4 files and rebuild prior UI. + +### Task 5: Keto-Backed Demo and Runtime QA + +**Files:** +- Modify: `workflow-scenarios/scenarios/90-admin-tailnet-demo/app/app.py` +- Modify: `workflow-scenarios/scenarios/90-admin-tailnet-demo/docker-compose.yml` +- Modify: `workflow-scenarios/scenarios/90-admin-tailnet-demo/README.md` +- Modify: `workflow-scenarios/scenarios/90-admin-tailnet-demo/test/run.sh` +- Add if needed: `workflow-scenarios/scenarios/90-admin-tailnet-demo/keto/` + +**Step 1: Write failing scenario checks** + +Add checks for: +- `/api/status` reports `authz.provider == "keto"`; +- admin scope picker renders selectable known scopes, not text scope field; +- assigning a declared admin scope affects access; +- assigning an unknown scope returns 400; +- frontend-only user remains denied from admin. + +Run: `./test/run.sh` +Expected: FAIL before demo changes. + +**Step 2: Add local Keto demo wiring** + +Add Keto service/config to compose. Seed relation tuples for demo roles/scopes at app startup or test setup. App uses provider selector `AUTHZ_PROVIDER=keto` by default and fails closed when provider unavailable. Keep Tailscale sidecar unchanged. + +**Step 3: Runtime launch** + +Run: `docker compose up --build -d` +Expected: app and Keto healthy. + +Run: `./test/run.sh` +Expected: `passed` count includes provider-backed role checks; zero failures. + +Run Playwright/curl smoke: +- anonymous `/admin` redirects to login; +- admin login sees Authz page and picker options; +- readonly admin cannot update roles; +- app user cannot access admin; +- `/api/status` reports Keto provider. + +Scrape: `docker compose logs --no-color | rg -i 'panic|traceback|exception|permission denied|address already in use|connection refused'` +Expected: no failure-signature hits. + +Rollback: `docker compose down -v`; revert Task 5 files; optionally `tailscale serve --http=18080 off`. + +## Adversarial Plan Review + +Status: PASS after inline adversarial review. + +Required framing used: find at least three things wrong with this plan; bias +toward hidden dependencies, verification mismatch, and scope drift. + +### Findings + +| sev | class | issue | resolution | +|---|---|---|---| +| Important | PR grouping | Initial manifest collapsed three repos into one PR row. | Fixed manifest: 3 PR rows for authz, authz-ui, scenario. | +| Minor | Verification mismatch | Permit SaaS cannot be forced in local env. | Task 2 explicitly distinguishes mandatory conformance from env-gated real integration and forbids pass claim on skip. | +| Minor | Hidden dependency | Scenario depends on Keto provider semantics from Task 3. | PR grouping keeps scenario after authz core; Task 5 provider selector defaults to Keto after provider exists. | +| Minor | SDK bypass risk | Demo could become a provider implementation if it directly substitutes for Go SDK adapter proof. | Task 5 is runtime demo only; Tasks 2/3 own SDK compile and conformance proof. | + +### Bug-Class Scan + +| class | result | note | +|---|---|---| +| Project-guidance conflicts | Clean | Tasks follow plugin ownership and multi-component validation guidance. | +| Assumptions under attack | Clean | Fragile Permit/Keto assumptions have explicit tests/skips/fallbacks. | +| Repo-precedent conflicts | Finding | Cross-repo PR collapse fixed by 3-row manifest. | +| YAGNI | Clean | No tenant/policy designer/migration UI added. | +| Missing failure modes | Clean | Provider unavailable → fail closed; Keto readiness/log scrape covered. | +| Security/privacy | Clean | Unknown scope reject and least-privilege route checks covered. | +| Infrastructure impact | Clean | Docker Keto local only; rollback listed. | +| Multi-component validation | Clean | Go SDK tests, UI build/lint, Docker app+keto, curl/Playwright paths included. | +| Rollback story | Clean | Every runtime/dependency task has rollback note. | +| Simpler alternative | Clean | Casbin-only rejected by approved design. | +| User-intent drift | Clean | Uses Keto, ignores Hydra, permits breaking old Permit provider contract. | +| Over/under-decomposition | Clean | 5 tasks map to independent components and review boundaries. | +| Verification-class mismatch | Finding | Permit real SaaS integration env-gated; plan requires explicit SKIP vs PASS. | +| Hidden serial dependencies | Finding | Scenario depends on Task 3; ordering is explicit. | +| Missing rollback wiring | Clean | Rollback steps included per task. | +| Missing integration proof | Clean | Keto local integration and runtime scenario provide real boundary proof. | +| Infrastructure verification mismatch | Clean | Compose launch, health, log scrape covered for local infra. | + +Verdict: PASS. Important finding fixed in manifest; remaining findings are implementation guardrails. + +## Alignment Report + +**Status:** PASS + +**Coverage:** + +| Design Requirement | Plan Task(s) | Status | +|---|---|---| +| Declared-scope role model and unknown-scope rejection | Task 1 | Covered | +| Normalized provider interface and strict proto contracts | Task 1 | Covered | +| Casbin adapter conformance | Task 1 | Covered | +| Permit.io official Go SDK provider, old REST compatibility not required | Task 2 | Covered | +| Ory Keto official Go SDK provider | Task 3 | Covered | +| Provider rotation/conformance tests | Task 1, Task 2, Task 3 | Covered | +| Authz UI scope picker with no free-text scope entry | Task 4 | Covered | +| Demo uses a newly added engine | Task 5 | Covered | +| Security: fail closed, least privilege, no secrets in UI | Task 1, Task 2, Task 3, Task 5 | Covered | +| Infrastructure: local Keto compose, Permit env-gated | Task 2, Task 3, Task 5 | Covered | +| Multi-component validation | Task 2, Task 3, Task 4, Task 5 | Covered | +| Rollback | Task 1, Task 2, Task 3, Task 4, Task 5 | Covered | + +**Scope Check:** + +| Plan Task | Design Requirement | Status | +|---|---|---| +| Task 1 | Canonical role/scope model, Casbin conformance, strict contracts | Justified | +| Task 2 | Permit SDK provider | Justified | +| Task 3 | Keto SDK provider | Justified | +| Task 4 | Declared-scope picker | Justified | +| Task 5 | Keto-backed demo and runtime QA | Justified | + +**Drift Items:** none. `plan-scope-check.sh --plan /Users/jon/workspace/workflow-plugin-authz/docs/plans/2026-05-27-scope-provider-engines.md` → PASS. diff --git a/go.mod b/go.mod index ef8cbba..5366bf5 100644 --- a/go.mod +++ b/go.mod @@ -140,6 +140,8 @@ require ( github.com/oklog/run v1.2.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/ory/keto-client-go/v25 v25.4.0 // indirect + github.com/permitio/permit-golang v1.2.8 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.23.2 // indirect diff --git a/go.sum b/go.sum index 2a672b9..384128f 100644 --- a/go.sum +++ b/go.sum @@ -475,8 +475,12 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/ory/keto-client-go/v25 v25.4.0 h1:fasLjAyAcbf4IxdEaw8GxeYepuS3voSKwO6aFqnC/iw= +github.com/ory/keto-client-go/v25 v25.4.0/go.mod h1:OZqK5ILup3XxdHjv5gpe3K1ogu/MjynCw2qC6rv33hc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/permitio/permit-golang v1.2.8 h1:sfApf4qUUbznSYuyCHzrzk/MUj1/QneKHCflZCzsdf0= +github.com/permitio/permit-golang v1.2.8/go.mod h1:U3ytJkUh6mH7dPiBt7cWbVVsRSxAiJtnuL7FFhbDk8s= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= diff --git a/internal/abac_provider.go b/internal/abac_provider.go new file mode 100644 index 0000000..64c9f37 --- /dev/null +++ b/internal/abac_provider.go @@ -0,0 +1,451 @@ +package internal + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + "sync" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +var errUnsupportedABAC = errors.New("abac provider is not supported by this module configuration") + +type AttributePolicyProvider interface { + Name() string + DeclareAttributes(context.Context, []*contracts.AttributeDeclaration) error + UpsertAttributePolicy(context.Context, AttributePolicy) error + ListAttributePolicies(context.Context, AttributePolicyFilter) ([]AttributePolicy, error) + RemoveAttributePolicy(context.Context, AttributePolicyFilter) error + CheckAttributes(context.Context, AttributeCheck) (AttributeCheckResult, error) +} + +type AttributeCondition struct { + Target string + Attribute string + Operator string + Values []string +} + +type AttributePolicy struct { + ID string + Context string + Resource string + Action string + Effect string + Conditions []AttributeCondition + Description string + OwnerPlugin string + OwnerModule string +} + +type AttributePolicyFilter struct { + ID string + Context string + Resource string + Action string + OwnerPlugin string + OwnerModule string +} + +type AttributeCheck struct { + Subject string + Context string + Resource string + Action string + SubjectAttributes map[string]string + ResourceAttributes map[string]string + EnvironmentAttributes map[string]string +} + +type AttributeCheckResult struct { + Allowed bool + Subject string + Context string + Resource string + Action string + MatchedPolicyID string + Reason string +} + +type attributePolicyStore struct { + mu sync.RWMutex + attrs map[string]*contracts.AttributeDeclaration + policies map[string]AttributePolicy + provider string + supported func() bool + attributes []*contracts.AttributeDeclaration +} + +func newAttributePolicyStore(provider string, supported func() bool) *attributePolicyStore { + return &attributePolicyStore{ + attrs: map[string]*contracts.AttributeDeclaration{}, + policies: map[string]AttributePolicy{}, + provider: provider, + supported: supported, + } +} + +func (s *attributePolicyStore) DeclareAttributes(_ context.Context, attrs []*contracts.AttributeDeclaration) error { + if err := s.ensureSupported(); err != nil { + return err + } + set := &contracts.AuthzDeclarationSet{Attributes: cloneAttributeDeclarations(attrs)} + if err := validateDeclarationSet(set); err != nil { + return err + } + s.mu.Lock() + defer s.mu.Unlock() + for _, attr := range set.GetAttributes() { + s.attrs[attributeDeclarationKey(attr.GetContext(), attr.GetTarget(), attr.GetName())] = cloneAttributeDeclaration(attr) + } + return nil +} + +func (s *attributePolicyStore) UpsertAttributePolicy(_ context.Context, policy AttributePolicy) error { + if err := s.ensureSupported(); err != nil { + return err + } + policy = normalizeAttributePolicy(policy) + if err := validateAttributePolicy(policy); err != nil { + return err + } + s.mu.RLock() + for _, condition := range policy.Conditions { + if _, ok := s.attrs[attributeDeclarationKey(policy.Context, condition.Target, condition.Attribute)]; !ok { + s.mu.RUnlock() + return fmt.Errorf("attribute %q for target %q is not declared in context %q", condition.Attribute, condition.Target, policy.Context) + } + } + s.mu.RUnlock() + s.mu.Lock() + defer s.mu.Unlock() + s.policies[attributePolicyKey(policy.Context, policy.ID)] = cloneAttributePolicy(policy) + return nil +} + +func (s *attributePolicyStore) ListAttributePolicies(_ context.Context, filter AttributePolicyFilter) ([]AttributePolicy, error) { + if err := s.ensureSupported(); err != nil { + return nil, err + } + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]AttributePolicy, 0, len(s.policies)) + for _, policy := range s.policies { + if !attributePolicyMatches(policy, filter) { + continue + } + out = append(out, cloneAttributePolicy(policy)) + } + sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) + return out, nil +} + +func (s *attributePolicyStore) RemoveAttributePolicy(_ context.Context, filter AttributePolicyFilter) error { + if err := s.ensureSupported(); err != nil { + return err + } + if strings.TrimSpace(filter.ID) == "" || strings.TrimSpace(filter.Context) == "" { + return fmt.Errorf("id and context are required") + } + s.mu.Lock() + defer s.mu.Unlock() + delete(s.policies, attributePolicyKey(filter.Context, filter.ID)) + return nil +} + +func (s *attributePolicyStore) CheckAttributes(_ context.Context, check AttributeCheck) (AttributeCheckResult, error) { + result := AttributeCheckResult{ + Subject: strings.TrimSpace(check.Subject), + Context: strings.TrimSpace(check.Context), + Resource: strings.TrimSpace(check.Resource), + Action: strings.TrimSpace(check.Action), + } + if err := s.ensureSupported(); err != nil { + return result, err + } + if result.Subject == "" || result.Context == "" || result.Resource == "" || result.Action == "" { + result.Reason = "subject, context, resource, and action are required" + return result, nil + } + s.mu.RLock() + defer s.mu.RUnlock() + for _, policy := range s.policies { + if policy.Context != result.Context || policy.Resource != result.Resource || policy.Action != result.Action { + continue + } + if attributeConditionsMatch(policy.Conditions, check) { + result.MatchedPolicyID = policy.ID + result.Allowed = strings.EqualFold(policy.Effect, "allow") + if !result.Allowed { + result.Reason = "matched non-allow policy" + } + return result, nil + } + } + result.Reason = "no matching attribute policy" + return result, nil +} + +func (s *attributePolicyStore) ensureSupported() error { + if s.supported != nil && !s.supported() { + return errUnsupportedABAC + } + return nil +} + +func attributeConditionsMatch(conditions []AttributeCondition, check AttributeCheck) bool { + for _, condition := range conditions { + actual, ok := attributeCheckBag(condition.Target, check)[condition.Attribute] + if !ok || !attributeConditionMatches(condition, actual) { + return false + } + } + return true +} + +func attributeCheckBag(target string, check AttributeCheck) map[string]string { + switch strings.ToLower(target) { + case "subject": + return check.SubjectAttributes + case "resource": + return check.ResourceAttributes + case "environment": + return check.EnvironmentAttributes + default: + return nil + } +} + +func attributeConditionMatches(condition AttributeCondition, actual string) bool { + switch strings.ToLower(defaultString(condition.Operator, "equals")) { + case "equals": + return len(condition.Values) > 0 && actual == condition.Values[0] + case "in": + return containsString(condition.Values, actual) + default: + return false + } +} + +func normalizeAttributePolicy(policy AttributePolicy) AttributePolicy { + policy.ID = strings.TrimSpace(policy.ID) + policy.Context = strings.TrimSpace(policy.Context) + policy.Resource = strings.TrimSpace(policy.Resource) + policy.Action = strings.TrimSpace(policy.Action) + policy.Effect = defaultString(strings.TrimSpace(policy.Effect), "allow") + for i := range policy.Conditions { + policy.Conditions[i].Target = strings.TrimSpace(policy.Conditions[i].Target) + policy.Conditions[i].Attribute = strings.TrimSpace(policy.Conditions[i].Attribute) + policy.Conditions[i].Operator = defaultString(strings.TrimSpace(policy.Conditions[i].Operator), "equals") + policy.Conditions[i].Values = uniqueStrings(policy.Conditions[i].Values) + } + return policy +} + +func validateAttributePolicy(policy AttributePolicy) error { + if policy.ID == "" || policy.Context == "" || policy.Resource == "" || policy.Action == "" { + return fmt.Errorf("attribute policy requires id, context, resource, and action") + } + if !strings.EqualFold(policy.Effect, "allow") && !strings.EqualFold(policy.Effect, "deny") { + return fmt.Errorf("attribute policy %q has unsupported effect %q", policy.ID, policy.Effect) + } + if len(policy.Conditions) == 0 { + return fmt.Errorf("attribute policy %q requires at least one condition", policy.ID) + } + for _, condition := range policy.Conditions { + if condition.Target == "" || condition.Attribute == "" || len(condition.Values) == 0 { + return fmt.Errorf("attribute policy %q has an incomplete condition", policy.ID) + } + switch strings.ToLower(condition.Target) { + case "subject", "resource", "environment": + default: + return fmt.Errorf("attribute policy %q has unsupported condition target %q", policy.ID, condition.Target) + } + switch strings.ToLower(condition.Operator) { + case "equals", "in": + default: + return fmt.Errorf("attribute policy %q has unsupported condition operator %q", policy.ID, condition.Operator) + } + } + return nil +} + +func attributePolicyMatches(policy AttributePolicy, filter AttributePolicyFilter) bool { + return (filter.ID == "" || policy.ID == filter.ID) && + (filter.Context == "" || policy.Context == filter.Context) && + (filter.Resource == "" || policy.Resource == filter.Resource) && + (filter.Action == "" || policy.Action == filter.Action) && + (filter.OwnerPlugin == "" || policy.OwnerPlugin == filter.OwnerPlugin) && + (filter.OwnerModule == "" || policy.OwnerModule == filter.OwnerModule) +} + +func cloneAttributePolicy(policy AttributePolicy) AttributePolicy { + policy.Conditions = cloneAttributeConditions(policy.Conditions) + return policy +} + +func cloneAttributeConditions(conditions []AttributeCondition) []AttributeCondition { + out := make([]AttributeCondition, len(conditions)) + for i, condition := range conditions { + out[i] = condition + out[i].Values = append([]string(nil), condition.Values...) + } + return out +} + +func attributeDeclarationKey(contextName, target, name string) string { + return contextName + "/" + target + "/" + name +} + +func attributePolicyKey(contextName, id string) string { + return contextName + "/" + id +} + +func declareAttributesInvoke(ctx context.Context, provider AttributePolicyProvider, input map[string]any) (map[string]any, error) { + attrs := attributeDeclarationsFromAny(input["attributes"], stringValue(input["owner_plugin"]), stringValue(input["owner_module"])) + if err := provider.DeclareAttributes(ctx, attrs); err != nil { + return nil, err + } + return map[string]any{"registered": len(attrs), "attributes": attributeDeclarationsToMaps(attrs)}, nil +} + +func upsertAttributePolicyInvoke(ctx context.Context, provider AttributePolicyProvider, input map[string]any) (map[string]any, error) { + policy := attributePolicyFromMap(mapValue(input["policy"])) + if err := provider.UpsertAttributePolicy(ctx, policy); err != nil { + return nil, err + } + return map[string]any{"changed": true, "policy": attributePolicyToMap(policy)}, nil +} + +func listAttributePoliciesInvoke(ctx context.Context, provider AttributePolicyProvider, input map[string]any) (map[string]any, error) { + policies, err := provider.ListAttributePolicies(ctx, attributePolicyFilterFromMap(mapValue(input["filter"]))) + if err != nil { + return nil, err + } + items := make([]map[string]any, 0, len(policies)) + for _, policy := range policies { + items = append(items, attributePolicyToMap(policy)) + } + return map[string]any{"policies": items}, nil +} + +func removeAttributePolicyInvoke(ctx context.Context, provider AttributePolicyProvider, input map[string]any) (map[string]any, error) { + if err := provider.RemoveAttributePolicy(ctx, attributePolicyFilterFromMap(mapValue(input["filter"]))); err != nil { + return nil, err + } + return map[string]any{"changed": true}, nil +} + +func checkAttributesInvoke(ctx context.Context, provider AttributePolicyProvider, input map[string]any) (map[string]any, error) { + result, err := provider.CheckAttributes(ctx, attributeCheckFromMap(input)) + if err != nil { + return attributeCheckResultToMap(result), err + } + return attributeCheckResultToMap(result), nil +} + +func attributePolicyFromMap(values map[string]any) AttributePolicy { + return AttributePolicy{ + ID: stringValue(values["id"]), + Context: stringValue(values["context"]), + Resource: stringValue(values["resource"]), + Action: stringValue(values["action"]), + Effect: stringValue(values["effect"]), + Conditions: attributeConditionsFromAny(values["conditions"]), + Description: stringValue(values["description"]), + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + } +} + +func attributePolicyFilterFromMap(values map[string]any) AttributePolicyFilter { + return AttributePolicyFilter{ + ID: stringValue(values["id"]), + Context: stringValue(values["context"]), + Resource: stringValue(values["resource"]), + Action: stringValue(values["action"]), + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + } +} + +func attributeConditionsFromAny(value any) []AttributeCondition { + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]AttributeCondition, 0, len(items)) + for _, item := range items { + values := mapValue(item) + out = append(out, AttributeCondition{ + Target: stringValue(values["target"]), + Attribute: stringValue(values["attribute"]), + Operator: stringValue(values["operator"]), + Values: stringSliceValue(values["values"]), + }) + } + return out +} + +func attributeCheckFromMap(values map[string]any) AttributeCheck { + return AttributeCheck{ + Subject: stringValue(values["subject"]), + Context: stringValue(values["context"]), + Resource: stringValue(values["resource"]), + Action: stringValue(values["action"]), + SubjectAttributes: stringMapFromAny(values["subject_attributes"]), + ResourceAttributes: stringMapFromAny(values["resource_attributes"]), + EnvironmentAttributes: stringMapFromAny(values["environment_attributes"]), + } +} + +func attributePolicyToMap(policy AttributePolicy) map[string]any { + return compactMap(map[string]any{ + "id": policy.ID, + "context": policy.Context, + "resource": policy.Resource, + "action": policy.Action, + "effect": policy.Effect, + "conditions": attributeConditionsToMaps(policy.Conditions), + "description": policy.Description, + "owner_plugin": policy.OwnerPlugin, + "owner_module": policy.OwnerModule, + }) +} + +func attributeConditionsToMaps(conditions []AttributeCondition) []map[string]any { + out := make([]map[string]any, 0, len(conditions)) + for _, condition := range conditions { + out = append(out, compactMap(map[string]any{ + "target": condition.Target, + "attribute": condition.Attribute, + "operator": condition.Operator, + "values": stringsToAny(condition.Values), + })) + } + return out +} + +func attributeCheckResultToMap(result AttributeCheckResult) map[string]any { + return compactMap(map[string]any{ + "allowed": result.Allowed, + "subject": result.Subject, + "context": result.Context, + "resource": result.Resource, + "action": result.Action, + "matched_policy_id": result.MatchedPolicyID, + "reason": result.Reason, + }) +} + +func stringMapFromAny(value any) map[string]string { + values := mapValue(value) + out := make(map[string]string, len(values)) + for key, item := range values { + out[key] = fmt.Sprint(item) + } + return out +} diff --git a/internal/abac_provider_test.go b/internal/abac_provider_test.go new file mode 100644 index 0000000..a775085 --- /dev/null +++ b/internal/abac_provider_test.go @@ -0,0 +1,125 @@ +package internal + +import ( + "context" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestABACProviderCasbinConformance(t *testing.T) { + ctx := context.Background() + provider, ok := any(abacAttributeTestModule(t)).(AttributePolicyProvider) + if !ok { + t.Fatal("CasbinModule should implement AttributePolicyProvider") + } + err := provider.DeclareAttributes(ctx, []*contracts.AttributeDeclaration{ + {Name: "department", Context: "frontend", Target: "subject", DataType: "string"}, + {Name: "kind", Context: "frontend", Target: "resource", DataType: "string"}, + }) + if err != nil { + t.Fatalf("DeclareAttributes: %v", err) + } + policy := AttributePolicy{ + ID: "support-code-read", + Context: "frontend", + Resource: "document", + Action: "read", + Effect: "allow", + Conditions: []AttributeCondition{ + {Target: "subject", Attribute: "department", Operator: "equals", Values: []string{"support"}}, + {Target: "resource", Attribute: "kind", Operator: "equals", Values: []string{"code"}}, + }, + } + if err := provider.UpsertAttributePolicy(ctx, policy); err != nil { + t.Fatalf("UpsertAttributePolicy: %v", err) + } + + allowed, err := provider.CheckAttributes(ctx, AttributeCheck{ + Subject: "alice", + Context: "frontend", + Resource: "document", + Action: "read", + SubjectAttributes: map[string]string{"department": "support"}, + ResourceAttributes: map[string]string{"kind": "code"}, + }) + if err != nil { + t.Fatalf("CheckAttributes allowed: %v", err) + } + if !allowed.Allowed || allowed.MatchedPolicyID != "support-code-read" { + t.Fatalf("allowed result = %#v", allowed) + } + + denied, err := provider.CheckAttributes(ctx, AttributeCheck{ + Subject: "bob", + Context: "frontend", + Resource: "document", + Action: "read", + SubjectAttributes: map[string]string{"department": "finance"}, + ResourceAttributes: map[string]string{"kind": "code"}, + }) + if err != nil { + t.Fatalf("CheckAttributes denied: %v", err) + } + if denied.Allowed || denied.Reason == "" { + t.Fatalf("denied result = %#v", denied) + } + + policies, err := provider.ListAttributePolicies(ctx, AttributePolicyFilter{Context: "frontend"}) + if err != nil { + t.Fatalf("ListAttributePolicies: %v", err) + } + if len(policies) != 1 || policies[0].ID != "support-code-read" { + t.Fatalf("policies = %#v", policies) + } + if err := provider.RemoveAttributePolicy(ctx, AttributePolicyFilter{ID: "support-code-read", Context: "frontend"}); err != nil { + t.Fatalf("RemoveAttributePolicy: %v", err) + } + afterRemove, err := provider.CheckAttributes(ctx, AttributeCheck{ + Subject: "alice", + Context: "frontend", + Resource: "document", + Action: "read", + SubjectAttributes: map[string]string{"department": "support"}, + ResourceAttributes: map[string]string{"kind": "code"}, + }) + if err != nil { + t.Fatalf("CheckAttributes after remove: %v", err) + } + if afterRemove.Allowed { + t.Fatal("expected removed ABAC policy to stop allowing access") + } +} + +func TestABACProviderFailsClosedForMalformedAndUnsupported(t *testing.T) { + ctx := context.Background() + aclProvider, ok := any(aclTestModule(t, nil)).(AttributePolicyProvider) + if !ok { + t.Fatal("CasbinModule should expose unsupported ABAC errors through AttributePolicyProvider") + } + _, err := aclProvider.CheckAttributes(ctx, AttributeCheck{ + Subject: "alice", + Context: "frontend", + Resource: "document", + Action: "read", + }) + if err == nil { + t.Fatal("expected ACL Casbin model to reject ABAC checks") + } + + permitProvider, ok := any(&PermitModule{name: "permit"}).(AttributePolicyProvider) + if !ok { + t.Fatal("PermitModule should implement AttributePolicyProvider with unsupported errors") + } + if err := permitProvider.UpsertAttributePolicy(ctx, AttributePolicy{ID: "p", Context: "frontend"}); err == nil { + t.Fatal("expected Permit ABAC adapter to return unsupported until SDK surfaces are wired") + } + + casbinProvider := any(abacAttributeTestModule(t)).(AttributePolicyProvider) + err = casbinProvider.DeclareAttributes(ctx, []*contracts.AttributeDeclaration{ + {Name: "risk", Context: "frontend", Target: "subject", DataType: "currency"}, + }) + if err == nil { + t.Fatal("expected invalid attribute declaration to fail closed") + } +} diff --git a/internal/authz_declarations.go b/internal/authz_declarations.go new file mode 100644 index 0000000..a9423ae --- /dev/null +++ b/internal/authz_declarations.go @@ -0,0 +1,771 @@ +package internal + +import ( + "fmt" + "sort" + "strings" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +var allowedAttributeDataTypes = map[string]bool{ + "string": true, + "number": true, + "int": true, + "float": true, + "bool": true, + "boolean": true, + "string_list": true, +} + +func (m *scopeCatalogModule) registerDeclarations(input *contracts.RegisterDeclarationsInput) (*contracts.RegisterDeclarationsOutput, error) { + if input == nil || input.GetDeclarations() == nil { + return &contracts.RegisterDeclarationsOutput{Declarations: &contracts.AuthzDeclarationSet{}}, nil + } + set := cloneDeclarationSet(input.GetDeclarations()) + ownerPlugin := defaultString(input.GetOwnerPlugin(), set.GetOwnerPlugin()) + ownerModule := defaultString(input.GetOwnerModule(), set.GetOwnerModule()) + applyDeclarationOwners(set, ownerPlugin, ownerModule) + if err := validateDeclarationSet(set); err != nil { + return nil, err + } + + registered := int32(0) + if len(set.GetScopes()) > 0 { + scopeOut, err := m.registerScopes(&contracts.RegisterScopesInput{ + Scopes: set.GetScopes(), + OwnerPlugin: ownerPlugin, + OwnerModule: ownerModule, + }) + if err != nil { + return nil, err + } + registered += scopeOut.GetRegistered() + set.Scopes = scopeOut.GetScopes() + } + + m.mu.Lock() + defer m.mu.Unlock() + for _, resource := range set.GetResources() { + key := resourceKey(resource.GetContext(), resource.GetName()) + if _, exists := m.resources[key]; !exists { + registered++ + } + m.resources[key] = cloneResourceDeclaration(resource) + } + for _, action := range set.GetActions() { + key := actionKey(action.GetContext(), action.GetResource(), action.GetName()) + if _, exists := m.actions[key]; !exists { + registered++ + } + m.actions[key] = cloneActionDeclaration(action) + } + for _, attribute := range set.GetAttributes() { + key := attributeKey(attribute.GetContext(), attribute.GetName()) + if _, exists := m.attributes[key]; !exists { + registered++ + } + m.attributes[key] = cloneAttributeDeclaration(attribute) + } + for _, relation := range set.GetRelations() { + key := relationKey(relation.GetContext(), relation.GetObjectType(), relation.GetName()) + if _, exists := m.relations[key]; !exists { + registered++ + } + m.relations[key] = cloneRelationDeclaration(relation) + } + for _, action := range set.GetUiActions() { + key := uiActionKey(action.GetContext(), action.GetId()) + if _, exists := m.uiActions[key]; !exists { + registered++ + } + m.uiActions[key] = cloneUIActionDeclaration(action) + } + return &contracts.RegisterDeclarationsOutput{Registered: registered, Declarations: set}, nil +} + +func (m *scopeCatalogModule) listDeclarations(input *contracts.ListDeclarationsInput) *contracts.AuthzDeclarationSet { + m.mu.RLock() + defer m.mu.RUnlock() + out := &contracts.AuthzDeclarationSet{} + scopeInput := &contracts.ListScopesInput{} + if input != nil { + scopeInput.Context = input.GetContext() + scopeInput.OwnerPlugin = input.GetOwnerPlugin() + scopeInput.OwnerModule = input.GetOwnerModule() + } + out.Scopes = m.listScopes(scopeInput) + for _, resource := range m.resources { + if declarationMatches(input, resource.GetContext(), resource.GetOwnerPlugin(), resource.GetOwnerModule()) { + out.Resources = append(out.Resources, cloneResourceDeclaration(resource)) + } + } + for _, action := range m.actions { + if declarationMatches(input, action.GetContext(), action.GetOwnerPlugin(), action.GetOwnerModule()) { + out.Actions = append(out.Actions, cloneActionDeclaration(action)) + } + } + for _, attribute := range m.attributes { + if declarationMatches(input, attribute.GetContext(), attribute.GetOwnerPlugin(), attribute.GetOwnerModule()) { + out.Attributes = append(out.Attributes, cloneAttributeDeclaration(attribute)) + } + } + for _, relation := range m.relations { + if declarationMatches(input, relation.GetContext(), relation.GetOwnerPlugin(), relation.GetOwnerModule()) { + out.Relations = append(out.Relations, cloneRelationDeclaration(relation)) + } + } + for _, action := range m.uiActions { + if declarationMatches(input, action.GetContext(), action.GetOwnerPlugin(), action.GetOwnerModule()) { + out.UiActions = append(out.UiActions, cloneUIActionDeclaration(action)) + } + } + sortDeclarationSet(out) + return out +} + +func (m *scopeCatalogModule) resolveProjectionInputs(input *contracts.ResolveProjectionInputsInput) *contracts.ProjectionInputs { + set := m.listDeclarations(&contracts.ListDeclarationsInput{ + Context: input.GetContext(), + OwnerPlugin: input.GetOwnerPlugin(), + OwnerModule: input.GetOwnerModule(), + }) + projection := &contracts.ProjectionInputs{} + lookupIDs := map[string]bool{} + for _, scope := range set.GetScopes() { + projection.ScopeNames = append(projection.ScopeNames, scope.GetName()) + } + for _, resource := range set.GetResources() { + projection.ResourceNames = append(projection.ResourceNames, resource.GetName()) + if resource.GetLookupSourceId() != "" { + lookupIDs[resource.GetLookupSourceId()] = true + } + } + for _, action := range set.GetActions() { + projection.ActionNames = append(projection.ActionNames, action.GetResource()+":"+action.GetName()) + } + for _, attribute := range set.GetAttributes() { + projection.AttributeNames = append(projection.AttributeNames, attribute.GetName()) + if attribute.GetLookupSourceId() != "" { + lookupIDs[attribute.GetLookupSourceId()] = true + } + } + for _, relation := range set.GetRelations() { + projection.RelationNames = append(projection.RelationNames, relation.GetObjectType()+":"+relation.GetName()) + } + for _, action := range set.GetUiActions() { + projection.UiActionIds = append(projection.UiActionIds, action.GetId()) + } + for id := range lookupIDs { + projection.LookupSourceIds = append(projection.LookupSourceIds, id) + } + sort.Strings(projection.ScopeNames) + sort.Strings(projection.ResourceNames) + sort.Strings(projection.ActionNames) + sort.Strings(projection.AttributeNames) + sort.Strings(projection.RelationNames) + sort.Strings(projection.UiActionIds) + sort.Strings(projection.LookupSourceIds) + return projection +} + +func validateDeclarationSet(set *contracts.AuthzDeclarationSet) error { + for _, resource := range set.GetResources() { + if resource.GetName() == "" || resource.GetContext() == "" { + return fmt.Errorf("resource declarations require name and context") + } + } + for _, action := range set.GetActions() { + if action.GetName() == "" || action.GetContext() == "" || action.GetResource() == "" { + return fmt.Errorf("action declarations require name, context, and resource") + } + } + for _, attribute := range set.GetAttributes() { + if attribute.GetName() == "" || attribute.GetContext() == "" || attribute.GetTarget() == "" { + return fmt.Errorf("attribute declarations require name, context, and target") + } + dataType := strings.ToLower(attribute.GetDataType()) + if !allowedAttributeDataTypes[dataType] { + return fmt.Errorf("attribute %q has unsupported data_type %q", attribute.GetName(), attribute.GetDataType()) + } + attribute.DataType = dataType + } + for _, relation := range set.GetRelations() { + if relation.GetName() == "" || relation.GetContext() == "" || relation.GetSubjectType() == "" || relation.GetObjectType() == "" { + return fmt.Errorf("relation declarations require name, context, subject_type, and object_type") + } + } + for _, action := range set.GetUiActions() { + if action.GetId() == "" || action.GetContext() == "" || action.GetLabel() == "" { + return fmt.Errorf("ui action declarations require id, context, and label") + } + } + return nil +} + +func registerDeclarationsInputFromMap(values map[string]any) *contracts.RegisterDeclarationsInput { + return &contracts.RegisterDeclarationsInput{ + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + Declarations: declarationSetFromAny(values["declarations"], stringValue(values["owner_plugin"]), stringValue(values["owner_module"])), + } +} + +func listDeclarationsInputFromMap(values map[string]any) *contracts.ListDeclarationsInput { + return &contracts.ListDeclarationsInput{ + Context: stringValue(values["context"]), + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + } +} + +func resolveProjectionInputsInputFromMap(values map[string]any) *contracts.ResolveProjectionInputsInput { + return &contracts.ResolveProjectionInputsInput{ + Context: stringValue(values["context"]), + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + } +} + +func registerDeclarationsOutputToMap(out *contracts.RegisterDeclarationsOutput) map[string]any { + if out == nil { + return map[string]any{"registered": 0, "declarations": declarationSetToMap(nil)} + } + return map[string]any{"registered": int(out.GetRegistered()), "declarations": declarationSetToMap(out.GetDeclarations())} +} + +func declarationSetFromAny(value any, ownerPlugin, ownerModule string) *contracts.AuthzDeclarationSet { + switch v := value.(type) { + case *contracts.AuthzDeclarationSet: + out := cloneDeclarationSet(v) + applyDeclarationOwners(out, defaultString(ownerPlugin, out.GetOwnerPlugin()), defaultString(ownerModule, out.GetOwnerModule())) + return out + case map[string]any: + out := &contracts.AuthzDeclarationSet{ + OwnerPlugin: stringValue(firstNonNil(v["owner_plugin"], ownerPlugin)), + OwnerModule: stringValue(firstNonNil(v["owner_module"], ownerModule)), + } + out.Scopes = scopeDeclarationsFromAny(v["scopes"], out.GetOwnerPlugin(), out.GetOwnerModule()) + out.Resources = resourceDeclarationsFromAny(v["resources"], out.GetOwnerPlugin(), out.GetOwnerModule()) + out.Actions = actionDeclarationsFromAny(v["actions"], out.GetOwnerPlugin(), out.GetOwnerModule()) + out.Attributes = attributeDeclarationsFromAny(v["attributes"], out.GetOwnerPlugin(), out.GetOwnerModule()) + out.Relations = relationDeclarationsFromAny(v["relations"], out.GetOwnerPlugin(), out.GetOwnerModule()) + out.UiActions = uiActionDeclarationsFromAny(v["ui_actions"], out.GetOwnerPlugin(), out.GetOwnerModule()) + return out + default: + return nil + } +} + +func declarationSetToMap(set *contracts.AuthzDeclarationSet) map[string]any { + if set == nil { + set = &contracts.AuthzDeclarationSet{} + } + return compactMap(map[string]any{ + "owner_plugin": set.GetOwnerPlugin(), + "owner_module": set.GetOwnerModule(), + "scopes": scopeDeclarationsToMaps(set.GetScopes()), + "resources": resourceDeclarationsToMaps(set.GetResources()), + "actions": actionDeclarationsToMaps(set.GetActions()), + "attributes": attributeDeclarationsToMaps(set.GetAttributes()), + "relations": relationDeclarationsToMaps(set.GetRelations()), + "ui_actions": uiActionDeclarationsToMaps(set.GetUiActions()), + }) +} + +func projectionInputsToMap(projection *contracts.ProjectionInputs) map[string]any { + if projection == nil { + projection = &contracts.ProjectionInputs{} + } + return map[string]any{ + "scope_names": append([]string(nil), projection.GetScopeNames()...), + "resource_names": append([]string(nil), projection.GetResourceNames()...), + "action_names": append([]string(nil), projection.GetActionNames()...), + "attribute_names": append([]string(nil), projection.GetAttributeNames()...), + "relation_names": append([]string(nil), projection.GetRelationNames()...), + "ui_action_ids": append([]string(nil), projection.GetUiActionIds()...), + "lookup_source_ids": append([]string(nil), projection.GetLookupSourceIds()...), + } +} + +func applyDeclarationOwners(set *contracts.AuthzDeclarationSet, ownerPlugin, ownerModule string) { + if set == nil { + return + } + if set.OwnerPlugin == "" { + set.OwnerPlugin = ownerPlugin + } + if set.OwnerModule == "" { + set.OwnerModule = ownerModule + } + for _, scope := range set.GetScopes() { + if scope.OwnerPlugin == "" { + scope.OwnerPlugin = set.GetOwnerPlugin() + } + if scope.OwnerModule == "" { + scope.OwnerModule = set.GetOwnerModule() + } + } + for _, resource := range set.GetResources() { + if resource.OwnerPlugin == "" { + resource.OwnerPlugin = set.GetOwnerPlugin() + } + if resource.OwnerModule == "" { + resource.OwnerModule = set.GetOwnerModule() + } + } + for _, action := range set.GetActions() { + if action.OwnerPlugin == "" { + action.OwnerPlugin = set.GetOwnerPlugin() + } + if action.OwnerModule == "" { + action.OwnerModule = set.GetOwnerModule() + } + } + for _, attribute := range set.GetAttributes() { + if attribute.OwnerPlugin == "" { + attribute.OwnerPlugin = set.GetOwnerPlugin() + } + if attribute.OwnerModule == "" { + attribute.OwnerModule = set.GetOwnerModule() + } + } + for _, relation := range set.GetRelations() { + if relation.OwnerPlugin == "" { + relation.OwnerPlugin = set.GetOwnerPlugin() + } + if relation.OwnerModule == "" { + relation.OwnerModule = set.GetOwnerModule() + } + } + for _, action := range set.GetUiActions() { + if action.OwnerPlugin == "" { + action.OwnerPlugin = set.GetOwnerPlugin() + } + if action.OwnerModule == "" { + action.OwnerModule = set.GetOwnerModule() + } + } +} + +func declarationMatches(input *contracts.ListDeclarationsInput, contextName, ownerPlugin, ownerModule string) bool { + if input == nil { + return true + } + if input.GetContext() != "" && contextName != input.GetContext() { + return false + } + if input.GetOwnerPlugin() != "" && ownerPlugin != input.GetOwnerPlugin() { + return false + } + if input.GetOwnerModule() != "" && ownerModule != input.GetOwnerModule() { + return false + } + return true +} + +func resourceDeclarationsFromAny(value any, ownerPlugin, ownerModule string) []*contracts.ResourceDeclaration { + switch items := value.(type) { + case []*contracts.ResourceDeclaration: + out := cloneResourceDeclarations(items) + for _, item := range out { + if item.OwnerPlugin == "" { + item.OwnerPlugin = ownerPlugin + } + if item.OwnerModule == "" { + item.OwnerModule = ownerModule + } + } + return out + case []map[string]any: + anyItems := make([]any, len(items)) + for i, item := range items { + anyItems[i] = item + } + return resourceDeclarationsFromAny(anyItems, ownerPlugin, ownerModule) + } + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.ResourceDeclaration, 0, len(items)) + for _, item := range items { + v := mapValue(item) + out = append(out, &contracts.ResourceDeclaration{ + Name: stringValue(v["name"]), + Context: stringValue(v["context"]), + DisplayName: stringValue(v["display_name"]), + Description: stringValue(v["description"]), + OwnerPlugin: defaultString(stringValue(v["owner_plugin"]), ownerPlugin), + OwnerModule: defaultString(stringValue(v["owner_module"]), ownerModule), + Category: stringValue(v["category"]), + LookupSourceId: stringValue(v["lookup_source_id"]), + }) + } + return out +} + +func actionDeclarationsFromAny(value any, ownerPlugin, ownerModule string) []*contracts.ActionDeclaration { + switch items := value.(type) { + case []*contracts.ActionDeclaration: + out := cloneActionDeclarations(items) + for _, item := range out { + if item.OwnerPlugin == "" { + item.OwnerPlugin = ownerPlugin + } + if item.OwnerModule == "" { + item.OwnerModule = ownerModule + } + } + return out + case []map[string]any: + anyItems := make([]any, len(items)) + for i, item := range items { + anyItems[i] = item + } + return actionDeclarationsFromAny(anyItems, ownerPlugin, ownerModule) + } + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.ActionDeclaration, 0, len(items)) + for _, item := range items { + v := mapValue(item) + out = append(out, &contracts.ActionDeclaration{ + Name: stringValue(v["name"]), + Context: stringValue(v["context"]), + Resource: stringValue(v["resource"]), + Description: stringValue(v["description"]), + OwnerPlugin: defaultString(stringValue(v["owner_plugin"]), ownerPlugin), + OwnerModule: defaultString(stringValue(v["owner_module"]), ownerModule), + Category: stringValue(v["category"]), + }) + } + return out +} + +func attributeDeclarationsFromAny(value any, ownerPlugin, ownerModule string) []*contracts.AttributeDeclaration { + switch items := value.(type) { + case []*contracts.AttributeDeclaration: + out := cloneAttributeDeclarations(items) + for _, item := range out { + if item.OwnerPlugin == "" { + item.OwnerPlugin = ownerPlugin + } + if item.OwnerModule == "" { + item.OwnerModule = ownerModule + } + } + return out + case []map[string]any: + anyItems := make([]any, len(items)) + for i, item := range items { + anyItems[i] = item + } + return attributeDeclarationsFromAny(anyItems, ownerPlugin, ownerModule) + } + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.AttributeDeclaration, 0, len(items)) + for _, item := range items { + v := mapValue(item) + out = append(out, &contracts.AttributeDeclaration{ + Name: stringValue(v["name"]), + Context: stringValue(v["context"]), + Target: stringValue(v["target"]), + DataType: stringValue(v["data_type"]), + AllowedValues: attributeValuesFromAny(v["allowed_values"]), + LookupSourceId: stringValue(v["lookup_source_id"]), + Description: stringValue(v["description"]), + OwnerPlugin: defaultString(stringValue(v["owner_plugin"]), ownerPlugin), + OwnerModule: defaultString(stringValue(v["owner_module"]), ownerModule), + Category: stringValue(v["category"]), + }) + } + return out +} + +func relationDeclarationsFromAny(value any, ownerPlugin, ownerModule string) []*contracts.RelationDeclaration { + switch items := value.(type) { + case []*contracts.RelationDeclaration: + out := cloneRelationDeclarations(items) + for _, item := range out { + if item.OwnerPlugin == "" { + item.OwnerPlugin = ownerPlugin + } + if item.OwnerModule == "" { + item.OwnerModule = ownerModule + } + } + return out + case []map[string]any: + anyItems := make([]any, len(items)) + for i, item := range items { + anyItems[i] = item + } + return relationDeclarationsFromAny(anyItems, ownerPlugin, ownerModule) + } + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.RelationDeclaration, 0, len(items)) + for _, item := range items { + v := mapValue(item) + out = append(out, &contracts.RelationDeclaration{ + Name: stringValue(v["name"]), + Context: stringValue(v["context"]), + SubjectType: stringValue(v["subject_type"]), + ObjectType: stringValue(v["object_type"]), + Description: stringValue(v["description"]), + OwnerPlugin: defaultString(stringValue(v["owner_plugin"]), ownerPlugin), + OwnerModule: defaultString(stringValue(v["owner_module"]), ownerModule), + Category: stringValue(v["category"]), + }) + } + return out +} + +func uiActionDeclarationsFromAny(value any, ownerPlugin, ownerModule string) []*contracts.UIActionDeclaration { + switch items := value.(type) { + case []*contracts.UIActionDeclaration: + out := cloneUIActionDeclarations(items) + for _, item := range out { + if item.OwnerPlugin == "" { + item.OwnerPlugin = ownerPlugin + } + if item.OwnerModule == "" { + item.OwnerModule = ownerModule + } + } + return out + case []map[string]any: + anyItems := make([]any, len(items)) + for i, item := range items { + anyItems[i] = item + } + return uiActionDeclarationsFromAny(anyItems, ownerPlugin, ownerModule) + } + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.UIActionDeclaration, 0, len(items)) + for _, item := range items { + v := mapValue(item) + out = append(out, &contracts.UIActionDeclaration{ + Id: stringValue(v["id"]), + Context: stringValue(v["context"]), + Label: stringValue(v["label"]), + Route: stringValue(v["route"]), + RequiredScopes: stringSliceValue(v["required_scopes"]), + Description: stringValue(v["description"]), + OwnerPlugin: defaultString(stringValue(v["owner_plugin"]), ownerPlugin), + OwnerModule: defaultString(stringValue(v["owner_module"]), ownerModule), + Category: stringValue(v["category"]), + }) + } + return out +} + +func attributeValuesFromAny(value any) []*contracts.AttributeValue { + items, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.AttributeValue, 0, len(items)) + for _, item := range items { + v := mapValue(item) + out = append(out, &contracts.AttributeValue{Value: stringValue(v["value"]), Label: stringValue(v["label"])}) + } + return out +} + +func resourceDeclarationsToMaps(items []*contracts.ResourceDeclaration) []map[string]any { + out := make([]map[string]any, 0, len(items)) + for _, item := range items { + out = append(out, compactMap(map[string]any{"name": item.GetName(), "context": item.GetContext(), "display_name": item.GetDisplayName(), "description": item.GetDescription(), "owner_plugin": item.GetOwnerPlugin(), "owner_module": item.GetOwnerModule(), "category": item.GetCategory(), "lookup_source_id": item.GetLookupSourceId()})) + } + return out +} + +func actionDeclarationsToMaps(items []*contracts.ActionDeclaration) []map[string]any { + out := make([]map[string]any, 0, len(items)) + for _, item := range items { + out = append(out, compactMap(map[string]any{"name": item.GetName(), "context": item.GetContext(), "resource": item.GetResource(), "description": item.GetDescription(), "owner_plugin": item.GetOwnerPlugin(), "owner_module": item.GetOwnerModule(), "category": item.GetCategory()})) + } + return out +} + +func attributeDeclarationsToMaps(items []*contracts.AttributeDeclaration) []map[string]any { + out := make([]map[string]any, 0, len(items)) + for _, item := range items { + out = append(out, compactMap(map[string]any{"name": item.GetName(), "context": item.GetContext(), "target": item.GetTarget(), "data_type": item.GetDataType(), "allowed_values": attributeValuesToMaps(item.GetAllowedValues()), "lookup_source_id": item.GetLookupSourceId(), "description": item.GetDescription(), "owner_plugin": item.GetOwnerPlugin(), "owner_module": item.GetOwnerModule(), "category": item.GetCategory()})) + } + return out +} + +func relationDeclarationsToMaps(items []*contracts.RelationDeclaration) []map[string]any { + out := make([]map[string]any, 0, len(items)) + for _, item := range items { + out = append(out, compactMap(map[string]any{"name": item.GetName(), "context": item.GetContext(), "subject_type": item.GetSubjectType(), "object_type": item.GetObjectType(), "description": item.GetDescription(), "owner_plugin": item.GetOwnerPlugin(), "owner_module": item.GetOwnerModule(), "category": item.GetCategory()})) + } + return out +} + +func uiActionDeclarationsToMaps(items []*contracts.UIActionDeclaration) []map[string]any { + out := make([]map[string]any, 0, len(items)) + for _, item := range items { + out = append(out, compactMap(map[string]any{"id": item.GetId(), "context": item.GetContext(), "label": item.GetLabel(), "route": item.GetRoute(), "required_scopes": stringsToAny(item.GetRequiredScopes()), "description": item.GetDescription(), "owner_plugin": item.GetOwnerPlugin(), "owner_module": item.GetOwnerModule(), "category": item.GetCategory()})) + } + return out +} + +func attributeValuesToMaps(items []*contracts.AttributeValue) []map[string]any { + out := make([]map[string]any, 0, len(items)) + for _, item := range items { + out = append(out, compactMap(map[string]any{"value": item.GetValue(), "label": item.GetLabel()})) + } + return out +} + +func cloneDeclarationSet(set *contracts.AuthzDeclarationSet) *contracts.AuthzDeclarationSet { + if set == nil { + return &contracts.AuthzDeclarationSet{} + } + return &contracts.AuthzDeclarationSet{ + Scopes: scopeDeclarationsFromAny(set.GetScopes(), set.GetOwnerPlugin(), set.GetOwnerModule()), + Resources: cloneResourceDeclarations(set.GetResources()), + Actions: cloneActionDeclarations(set.GetActions()), + Attributes: cloneAttributeDeclarations(set.GetAttributes()), + Relations: cloneRelationDeclarations(set.GetRelations()), + UiActions: cloneUIActionDeclarations(set.GetUiActions()), + OwnerPlugin: set.GetOwnerPlugin(), + OwnerModule: set.GetOwnerModule(), + } +} + +func cloneResourceDeclarations(items []*contracts.ResourceDeclaration) []*contracts.ResourceDeclaration { + out := make([]*contracts.ResourceDeclaration, 0, len(items)) + for _, item := range items { + out = append(out, cloneResourceDeclaration(item)) + } + return out +} + +func cloneResourceDeclaration(item *contracts.ResourceDeclaration) *contracts.ResourceDeclaration { + if item == nil { + return nil + } + clone := *item + return &clone +} + +func cloneActionDeclarations(items []*contracts.ActionDeclaration) []*contracts.ActionDeclaration { + out := make([]*contracts.ActionDeclaration, 0, len(items)) + for _, item := range items { + out = append(out, cloneActionDeclaration(item)) + } + return out +} + +func cloneActionDeclaration(item *contracts.ActionDeclaration) *contracts.ActionDeclaration { + if item == nil { + return nil + } + clone := *item + return &clone +} + +func cloneAttributeDeclarations(items []*contracts.AttributeDeclaration) []*contracts.AttributeDeclaration { + out := make([]*contracts.AttributeDeclaration, 0, len(items)) + for _, item := range items { + out = append(out, cloneAttributeDeclaration(item)) + } + return out +} + +func cloneAttributeDeclaration(item *contracts.AttributeDeclaration) *contracts.AttributeDeclaration { + if item == nil { + return nil + } + clone := *item + clone.AllowedValues = cloneAttributeValues(item.GetAllowedValues()) + return &clone +} + +func cloneAttributeValues(items []*contracts.AttributeValue) []*contracts.AttributeValue { + out := make([]*contracts.AttributeValue, 0, len(items)) + for _, item := range items { + if item == nil { + continue + } + clone := *item + out = append(out, &clone) + } + return out +} + +func cloneRelationDeclarations(items []*contracts.RelationDeclaration) []*contracts.RelationDeclaration { + out := make([]*contracts.RelationDeclaration, 0, len(items)) + for _, item := range items { + out = append(out, cloneRelationDeclaration(item)) + } + return out +} + +func cloneRelationDeclaration(item *contracts.RelationDeclaration) *contracts.RelationDeclaration { + if item == nil { + return nil + } + clone := *item + return &clone +} + +func cloneUIActionDeclarations(items []*contracts.UIActionDeclaration) []*contracts.UIActionDeclaration { + out := make([]*contracts.UIActionDeclaration, 0, len(items)) + for _, item := range items { + out = append(out, cloneUIActionDeclaration(item)) + } + return out +} + +func cloneUIActionDeclaration(item *contracts.UIActionDeclaration) *contracts.UIActionDeclaration { + if item == nil { + return nil + } + clone := *item + clone.RequiredScopes = append([]string(nil), item.GetRequiredScopes()...) + clone.RequiredCapabilities = append([]*contracts.CapabilityRequirement(nil), item.GetRequiredCapabilities()...) + return &clone +} + +func sortDeclarationSet(set *contracts.AuthzDeclarationSet) { + sortScopes(set.Scopes) + sort.Slice(set.Resources, func(i, j int) bool { return set.Resources[i].GetName() < set.Resources[j].GetName() }) + sort.Slice(set.Actions, func(i, j int) bool { + return actionKey(set.Actions[i].GetContext(), set.Actions[i].GetResource(), set.Actions[i].GetName()) < actionKey(set.Actions[j].GetContext(), set.Actions[j].GetResource(), set.Actions[j].GetName()) + }) + sort.Slice(set.Attributes, func(i, j int) bool { return set.Attributes[i].GetName() < set.Attributes[j].GetName() }) + sort.Slice(set.Relations, func(i, j int) bool { + return relationKey(set.Relations[i].GetContext(), set.Relations[i].GetObjectType(), set.Relations[i].GetName()) < relationKey(set.Relations[j].GetContext(), set.Relations[j].GetObjectType(), set.Relations[j].GetName()) + }) + sort.Slice(set.UiActions, func(i, j int) bool { return set.UiActions[i].GetId() < set.UiActions[j].GetId() }) +} + +func resourceKey(contextName, name string) string { return contextName + "/" + name } + +func actionKey(contextName, resource, name string) string { + return contextName + "/" + resource + "/" + name +} + +func attributeKey(contextName, name string) string { return contextName + "/" + name } + +func relationKey(contextName, objectType, name string) string { + return contextName + "/" + objectType + "/" + name +} + +func uiActionKey(contextName, id string) string { return contextName + "/" + id } diff --git a/internal/authz_declarations_test.go b/internal/authz_declarations_test.go new file mode 100644 index 0000000..24d3789 --- /dev/null +++ b/internal/authz_declarations_test.go @@ -0,0 +1,153 @@ +package internal + +import ( + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestAuthzDeclarationsRegisterListAndProjection(t *testing.T) { + module := newScopeCatalogModule("catalog", nil) + out, err := module.InvokeMethod("RegisterDeclarations", map[string]any{ + "owner_plugin": "workflow-plugin-orders", + "owner_module": "orders", + "declarations": map[string]any{ + "resources": []any{ + map[string]any{ + "name": "orders.order", + "context": "frontend", + "display_name": "Order", + "description": "Customer order", + "lookup_source_id": "orders", + }, + }, + "actions": []any{ + map[string]any{"name": "read", "context": "frontend", "resource": "orders.order"}, + map[string]any{"name": "refund", "context": "frontend", "resource": "orders.order"}, + }, + "scopes": []any{ + map[string]any{ + "name": "frontend:orders.order:read", + "context": "frontend", + "resource": "orders.order", + "actions": []any{"read"}, + }, + }, + "attributes": []any{ + map[string]any{ + "name": "subject.department", + "context": "frontend", + "target": "subject", + "data_type": "string", + "lookup_source_id": "departments", + "allowed_values": []any{ + map[string]any{"value": "support", "label": "Support"}, + map[string]any{"value": "finance", "label": "Finance"}, + }, + }, + }, + "relations": []any{ + map[string]any{ + "name": "owner", + "context": "frontend", + "subject_type": "user", + "object_type": "orders.order", + }, + }, + "ui_actions": []any{ + map[string]any{ + "id": "orders.refund", + "context": "frontend", + "label": "Refund order", + "route": "/orders/:id", + "required_scopes": []any{"frontend:orders.order:refund"}, + }, + }, + }, + }) + if err != nil { + t.Fatalf("RegisterDeclarations: %v", err) + } + if out["registered"] != 7 { + t.Fatalf("registered = %v, want 7 declarations", out["registered"]) + } + + listed, err := module.InvokeMethod("ListDeclarations", map[string]any{"context": "frontend"}) + if err != nil { + t.Fatalf("ListDeclarations: %v", err) + } + declarations := listed["declarations"].(map[string]any) + resources := declarations["resources"].([]map[string]any) + if len(resources) != 1 || resources[0]["owner_plugin"] != "workflow-plugin-orders" { + t.Fatalf("resources = %#v, want owner metadata", resources) + } + attributes := declarations["attributes"].([]map[string]any) + if attributes[0]["lookup_source_id"] != "departments" { + t.Fatalf("attribute lookup source = %#v, want departments", attributes[0]) + } + + projected, err := module.InvokeMethod("ResolveProjectionInputs", map[string]any{"context": "frontend"}) + if err != nil { + t.Fatalf("ResolveProjectionInputs: %v", err) + } + projection := projected["projection"].(map[string]any) + if got := projection["scope_names"].([]string); len(got) != 1 || got[0] != "frontend:orders.order:read" { + t.Fatalf("scope_names = %#v", got) + } + if got := projection["lookup_source_ids"].([]string); len(got) != 2 || got[0] != "departments" || got[1] != "orders" { + t.Fatalf("lookup_source_ids = %#v, want sorted unique lookup IDs", got) + } +} + +func TestAuthzDeclarationsRejectInvalidDeclarations(t *testing.T) { + module := newScopeCatalogModule("catalog", nil) + _, err := module.InvokeMethod("RegisterDeclarations", map[string]any{ + "declarations": map[string]any{ + "attributes": []any{ + map[string]any{ + "name": "subject.risk", + "context": "admin", + "target": "subject", + "data_type": "currency", + }, + }, + }, + }) + if err == nil { + t.Fatal("expected invalid attribute data type to fail") + } +} + +func TestAuthzDeclarationsAreProviderNeutral(t *testing.T) { + set := &contracts.AuthzDeclarationSet{ + OwnerPlugin: "workflow-plugin-docs", + OwnerModule: "docs", + Resources: []*contracts.ResourceDeclaration{{ + Name: "docs.document", + Context: "frontend", + }}, + Actions: []*contracts.ActionDeclaration{{ + Name: "read", + Context: "frontend", + Resource: "docs.document", + }}, + Relations: []*contracts.RelationDeclaration{{ + Name: "viewer", + Context: "frontend", + SubjectType: "user", + ObjectType: "docs.document", + }}, + } + module := newScopeCatalogModule("catalog", nil) + out, err := module.registerDeclarations(&contracts.RegisterDeclarationsInput{Declarations: set}) + if err != nil { + t.Fatalf("registerDeclarations: %v", err) + } + if out.GetRegistered() != 3 { + t.Fatalf("registered = %d, want 3", out.GetRegistered()) + } + listed := module.listDeclarations(&contracts.ListDeclarationsInput{Context: "frontend"}) + if len(listed.GetResources()) != 1 || listed.GetResources()[0].GetOwnerPlugin() != "workflow-plugin-docs" { + t.Fatalf("listed resources = %#v", listed.GetResources()) + } +} diff --git a/internal/capabilities.go b/internal/capabilities.go index a887921..85209d0 100644 --- a/internal/capabilities.go +++ b/internal/capabilities.go @@ -3,6 +3,7 @@ package internal import ( "context" "fmt" + "strings" sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" ) @@ -17,16 +18,91 @@ const ( CapabilityACL AuthzCapability = "acl" // Access Control Lists ) +// AuthzOperation is a provider-neutral operation that can be performed for an +// authorization mode. Operations are intentionally narrow so providers do not +// advertise management surfaces they have not implemented. +type AuthzOperation string + +const ( + OperationCheck AuthzOperation = "check" + OperationManageRoles AuthzOperation = "manage_roles" + OperationManagePolicies AuthzOperation = "manage_policies" + OperationManageRelations AuthzOperation = "manage_relations" + OperationList AuthzOperation = "list" +) + +// CapabilityRequirement describes the mode and operations a consumer needs. +type CapabilityRequirement struct { + Mode AuthzCapability + Operations []AuthzOperation +} + +// CapabilityDescriptor describes a provider mode, the implemented operations, +// and whether the provider is healthy enough to use that mode. +type CapabilityDescriptor struct { + Mode AuthzCapability + Operations []AuthzOperation + Configured bool + Source string + Health string + UnsupportedReason string +} + // AuthzProvider is implemented by authorization providers to declare their // supported authorization models. type AuthzProvider interface { Capabilities() []AuthzCapability SupportsCapability(AuthzCapability) bool + CapabilityDescriptors() []CapabilityDescriptor + RequireCapabilities([]CapabilityRequirement) error } // Capabilities returns the authorization models supported by Casbin. func (m *CasbinModule) Capabilities() []AuthzCapability { - return []AuthzCapability{CapabilityRBAC, CapabilityABAC, CapabilityACL} + descriptors := m.CapabilityDescriptors() + capabilities := make([]AuthzCapability, 0, len(descriptors)) + for _, descriptor := range descriptors { + if descriptor.Configured && descriptor.UnsupportedReason == "" { + capabilities = append(capabilities, descriptor.Mode) + } + } + return capabilities +} + +// CapabilityDescriptors returns Casbin authorization modes detected from the +// configured model and only includes operations the adapter exposes. +func (m *CasbinModule) CapabilityDescriptors() []CapabilityDescriptor { + modelText := strings.ToLower(m.config.Model) + descriptors := make([]CapabilityDescriptor, 0, 4) + if casbinModelHasRoleDefinition(modelText, "g") { + descriptors = append(descriptors, newCapabilityDescriptor( + CapabilityRBAC, + []AuthzOperation{OperationCheck, OperationManageRoles}, + "detected", + )) + } + if casbinModelHasRoleDefinition(modelText, "g2") || strings.Contains(modelText, "g2(") { + descriptors = append(descriptors, newCapabilityDescriptor( + CapabilityReBAC, + []AuthzOperation{OperationCheck, OperationManageRelations, OperationList}, + "detected", + )) + } + if casbinModelHasAttributeAccess(modelText) { + descriptors = append(descriptors, newCapabilityDescriptor( + CapabilityABAC, + []AuthzOperation{OperationCheck, OperationManagePolicies}, + "detected", + )) + } + if len(descriptors) == 0 { + descriptors = append(descriptors, newCapabilityDescriptor( + CapabilityACL, + []AuthzOperation{OperationCheck, OperationManagePolicies, OperationList}, + "detected", + )) + } + return descriptors } // SupportsCapability reports whether the Casbin module supports the given @@ -40,9 +116,13 @@ func (m *CasbinModule) SupportsCapability(cap AuthzCapability) bool { return false } +func (m *CasbinModule) RequireCapabilities(requirements []CapabilityRequirement) error { + return requireCapabilities("casbin", m.CapabilityDescriptors(), requirements) +} + // Capabilities returns the authorization models supported by Permit.io. func (m *PermitModule) Capabilities() []AuthzCapability { - return []AuthzCapability{CapabilityRBAC, CapabilityABAC, CapabilityReBAC} + return capabilitiesFromDescriptors(m.CapabilityDescriptors()) } // SupportsCapability reports whether the Permit module supports the given @@ -56,6 +136,60 @@ func (m *PermitModule) SupportsCapability(cap AuthzCapability) bool { return false } +// CapabilityDescriptors returns the provider-neutral Permit operations exposed +// by this adapter today. More Permit-native ABAC/ReBAC operations are added by +// later adapter phases; they are not advertised until implemented. +func (m *PermitModule) CapabilityDescriptors() []CapabilityDescriptor { + return []CapabilityDescriptor{ + newCapabilityDescriptor( + CapabilityRBAC, + []AuthzOperation{OperationCheck, OperationManageRoles}, + "provider", + ), + } +} + +func (m *PermitModule) RequireCapabilities(requirements []CapabilityRequirement) error { + return requireCapabilities("permit", m.CapabilityDescriptors(), requirements) +} + +// Capabilities returns authorization models represented by the Keto adapter. +func (m *KetoModule) Capabilities() []AuthzCapability { + return capabilitiesFromDescriptors(m.CapabilityDescriptors()) +} + +// SupportsCapability reports whether the Keto module supports the given +// authorization model. +func (m *KetoModule) SupportsCapability(cap AuthzCapability) bool { + for _, c := range m.Capabilities() { + if c == cap { + return true + } + } + return false +} + +// CapabilityDescriptors returns the provider-neutral operations implemented by +// the Keto adapter. +func (m *KetoModule) CapabilityDescriptors() []CapabilityDescriptor { + return []CapabilityDescriptor{ + newCapabilityDescriptor( + CapabilityRBAC, + []AuthzOperation{OperationCheck, OperationManageRoles}, + "provider", + ), + newCapabilityDescriptor( + CapabilityReBAC, + []AuthzOperation{OperationCheck, OperationManageRelations, OperationList}, + "provider", + ), + } +} + +func (m *KetoModule) RequireCapabilities(requirements []CapabilityRequirement) error { + return requireCapabilities("keto", m.CapabilityDescriptors(), requirements) +} + // authzCapabilitiesStep implements sdk.StepInstance. It returns the capabilities // of a given provider module. // @@ -105,14 +239,28 @@ func (s *authzCapabilitiesStep) Execute( } provider = mod case "permit": - _, ok := GetPermitClient(s.moduleName) - if !ok { + if reg, ok := s.registry.(authzProviderRegistry); ok { + if registered, found := reg.GetAuthzProvider(s.moduleName); found { + provider = registered + break + } + } + if _, ok := GetPermitClient(s.moduleName); !ok { return nil, fmt.Errorf("step.authz_capabilities %q: permit module %q not found", s.name, s.moduleName) } - // We need a PermitModule to call Capabilities; create a minimal one. provider = &PermitModule{name: s.moduleName} + case "keto": + reg, ok := s.registry.(authzProviderRegistry) + if !ok { + return nil, fmt.Errorf("step.authz_capabilities %q: registry cannot look up keto module %q", s.name, s.moduleName) + } + registered, found := reg.GetAuthzProvider(s.moduleName) + if !found { + return nil, fmt.Errorf("step.authz_capabilities %q: keto module %q not found", s.name, s.moduleName) + } + provider = registered default: - return nil, fmt.Errorf("step.authz_capabilities %q: unknown provider %q (expected \"casbin\" or \"permit\")", s.name, s.provider) + return nil, fmt.Errorf("step.authz_capabilities %q: unknown provider %q (expected \"casbin\", \"permit\", or \"keto\")", s.name, s.provider) } caps := provider.Capabilities() @@ -120,12 +268,187 @@ func (s *authzCapabilitiesStep) Execute( for i, c := range caps { capStrings[i] = string(c) } + descriptors := capabilityDescriptorsToMaps(provider.CapabilityDescriptors()) return &sdk.StepResult{ Output: map[string]any{ - "provider": s.provider, - "module": s.moduleName, - "capabilities": capStrings, + "provider": s.provider, + "module": s.moduleName, + "capabilities": capStrings, + "capability_descriptors": descriptors, + "health": providerHealth(provider.CapabilityDescriptors()), }, }, nil } + +func newCapabilityDescriptor(mode AuthzCapability, operations []AuthzOperation, source string) CapabilityDescriptor { + return CapabilityDescriptor{ + Mode: mode, + Operations: copyAuthzOperations(operations), + Configured: true, + Source: source, + Health: "ok", + } +} + +func capabilitiesFromDescriptors(descriptors []CapabilityDescriptor) []AuthzCapability { + out := make([]AuthzCapability, 0, len(descriptors)) + for _, descriptor := range descriptors { + if descriptor.Configured && descriptor.UnsupportedReason == "" { + out = append(out, descriptor.Mode) + } + } + return out +} + +func requireCapabilities(providerName string, descriptors []CapabilityDescriptor, requirements []CapabilityRequirement) error { + missing := missingCapabilityRequirements(descriptors, requirements) + if len(missing) > 0 { + return fmt.Errorf("%s provider missing required capabilities: %s", providerName, strings.Join(missing, ", ")) + } + return nil +} + +func missingCapabilityRequirements(descriptors []CapabilityDescriptor, requirements []CapabilityRequirement) []string { + byMode := make(map[AuthzCapability]CapabilityDescriptor, len(descriptors)) + for _, descriptor := range descriptors { + byMode[descriptor.Mode] = descriptor + } + missing := make([]string, 0) + for _, requirement := range requirements { + descriptor, ok := byMode[requirement.Mode] + if !ok || !descriptor.Configured || descriptor.UnsupportedReason != "" { + missing = append(missing, string(requirement.Mode)) + continue + } + ops := make(map[AuthzOperation]bool, len(descriptor.Operations)) + for _, operation := range descriptor.Operations { + ops[operation] = true + } + for _, operation := range requirement.Operations { + if !ops[operation] { + missing = append(missing, fmt.Sprintf("%s:%s", requirement.Mode, operation)) + } + } + } + return missing +} + +func providerCapabilitiesOutputMap(moduleName, providerName string, provider AuthzProvider, requirements []CapabilityRequirement) map[string]any { + descriptors := provider.CapabilityDescriptors() + caps := capabilitiesFromDescriptors(descriptors) + capStrings := make([]any, 0, len(caps)) + for _, cap := range caps { + capStrings = append(capStrings, string(cap)) + } + missing := missingCapabilityRequirements(descriptors, requirements) + missingStrings := make([]any, 0, len(missing)) + for _, item := range missing { + missingStrings = append(missingStrings, item) + } + return map[string]any{ + "module": moduleName, + "provider": providerName, + "capabilities": capStrings, + "capability_descriptors": capabilityDescriptorsToMaps(descriptors), + "health": providerHealth(descriptors), + "missing_requirements": missingStrings, + } +} + +func providerCapabilitiesInvoke(moduleName, providerName string, provider AuthzProvider, input map[string]any, require bool) (map[string]any, error) { + requirements := capabilityRequirementsFromAny(input["requirements"]) + out := providerCapabilitiesOutputMap(moduleName, providerName, provider, requirements) + if require { + if err := provider.RequireCapabilities(requirements); err != nil { + return out, err + } + } + return out, nil +} + +func capabilityRequirementsFromAny(value any) []CapabilityRequirement { + switch values := value.(type) { + case []CapabilityRequirement: + return append([]CapabilityRequirement(nil), values...) + case []any: + out := make([]CapabilityRequirement, 0, len(values)) + for _, value := range values { + requirement := capabilityRequirementFromMap(mapValue(value)) + if requirement.Mode != "" { + out = append(out, requirement) + } + } + return out + default: + return nil + } +} + +func capabilityRequirementFromMap(values map[string]any) CapabilityRequirement { + return CapabilityRequirement{ + Mode: AuthzCapability(stringValue(values["mode"])), + Operations: authzOperationsFromStrings(stringSliceValue(values["operations"])), + } +} + +func authzOperationsFromStrings(values []string) []AuthzOperation { + out := make([]AuthzOperation, 0, len(values)) + for _, value := range values { + out = append(out, AuthzOperation(value)) + } + return out +} + +func capabilityDescriptorsToMaps(descriptors []CapabilityDescriptor) []any { + out := make([]any, 0, len(descriptors)) + for _, descriptor := range descriptors { + ops := make([]any, 0, len(descriptor.Operations)) + for _, operation := range descriptor.Operations { + ops = append(ops, string(operation)) + } + out = append(out, map[string]any{ + "mode": string(descriptor.Mode), + "operations": ops, + "configured": descriptor.Configured, + "source": descriptor.Source, + "health": descriptor.Health, + "unsupported_reason": descriptor.UnsupportedReason, + }) + } + return out +} + +func providerHealth(descriptors []CapabilityDescriptor) string { + if len(descriptors) == 0 { + return "degraded" + } + for _, descriptor := range descriptors { + if descriptor.Health != "ok" { + return "degraded" + } + } + return "ok" +} + +func copyAuthzOperations(in []AuthzOperation) []AuthzOperation { + out := make([]AuthzOperation, len(in)) + copy(out, in) + return out +} + +func casbinModelHasRoleDefinition(modelText, name string) bool { + for _, line := range strings.Split(modelText, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, name+" ") || strings.HasPrefix(line, name+"=") { + return true + } + } + return false +} + +func casbinModelHasAttributeAccess(modelText string) bool { + return strings.Contains(modelText, "r.sub.") || + strings.Contains(modelText, "r.obj.") || + strings.Contains(modelText, "eval(") +} diff --git a/internal/capabilities_test.go b/internal/capabilities_test.go index aa88578..6bf062f 100644 --- a/internal/capabilities_test.go +++ b/internal/capabilities_test.go @@ -10,9 +10,7 @@ func TestCasbinCapabilities(t *testing.T) { caps := mod.Capabilities() expected := map[AuthzCapability]bool{ - CapabilityRBAC: true, - CapabilityABAC: true, - CapabilityACL: true, + CapabilityACL: true, } if len(caps) != len(expected) { t.Fatalf("expected %d capabilities, got %d: %v", len(expected), len(caps), caps) @@ -31,8 +29,8 @@ func TestCasbinSupportsCapability(t *testing.T) { cap AuthzCapability want bool }{ - {CapabilityRBAC, true}, - {CapabilityABAC, true}, + {CapabilityRBAC, false}, + {CapabilityABAC, false}, {CapabilityACL, true}, {CapabilityReBAC, false}, } { @@ -43,14 +41,53 @@ func TestCasbinSupportsCapability(t *testing.T) { } } +func TestCasbinCapabilitiesAreModelAware(t *testing.T) { + for _, tc := range []struct { + name string + mod *CasbinModule + want map[AuthzCapability]bool + }{ + { + name: "acl", + mod: aclTestModule(t, nil), + want: map[AuthzCapability]bool{CapabilityACL: true}, + }, + { + name: "rbac", + mod: rbacTestModule(t, nil, nil), + want: map[AuthzCapability]bool{CapabilityRBAC: true}, + }, + { + name: "rebac", + mod: rebacTestModule(t), + want: map[AuthzCapability]bool{CapabilityRBAC: true, CapabilityReBAC: true}, + }, + { + name: "abac", + mod: abacAttributeTestModule(t), + want: map[AuthzCapability]bool{CapabilityABAC: true}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + got := capabilitySet(tc.mod.Capabilities()) + if len(got) != len(tc.want) { + t.Fatalf("capabilities = %v, want %v", got, tc.want) + } + for capability := range tc.want { + if !got[capability] { + t.Fatalf("capability %q missing from %v", capability, got) + } + } + }) + } +} + func TestPermitCapabilities(t *testing.T) { pm := &PermitModule{name: "test-permit"} caps := pm.Capabilities() expected := map[AuthzCapability]bool{ - CapabilityRBAC: true, - CapabilityABAC: true, - CapabilityReBAC: true, + CapabilityRBAC: true, } if len(caps) != len(expected) { t.Fatalf("expected %d capabilities, got %d: %v", len(expected), len(caps), caps) @@ -70,8 +107,8 @@ func TestPermitSupportsCapability(t *testing.T) { want bool }{ {CapabilityRBAC, true}, - {CapabilityABAC, true}, - {CapabilityReBAC, true}, + {CapabilityABAC, false}, + {CapabilityReBAC, false}, {CapabilityACL, false}, } { got := pm.SupportsCapability(tc.cap) @@ -81,6 +118,26 @@ func TestPermitSupportsCapability(t *testing.T) { } } +func TestKetoCapabilities(t *testing.T) { + km := &KetoModule{name: "test-keto"} + got := capabilitySet(km.Capabilities()) + want := map[AuthzCapability]bool{ + CapabilityRBAC: true, + CapabilityReBAC: true, + } + if len(got) != len(want) { + t.Fatalf("capabilities = %v, want %v", got, want) + } + for capability := range want { + if !got[capability] { + t.Fatalf("capability %q missing from %v", capability, got) + } + } + if km.SupportsCapability(CapabilityABAC) { + t.Fatal("keto should not claim ABAC support") + } +} + func TestAuthzCapabilitiesStep_Casbin(t *testing.T) { mod := aclTestModule(t, nil) reg := &testRegistry{mod: mod} @@ -106,15 +163,15 @@ func TestAuthzCapabilitiesStep_Casbin(t *testing.T) { if !ok { t.Fatalf("expected capabilities to be []any, got %T", result.Output["capabilities"]) } - if len(caps) != 3 { - t.Errorf("expected 3 capabilities for casbin, got %d", len(caps)) + if len(caps) != 1 { + t.Errorf("expected 1 capability for casbin ACL model, got %d", len(caps)) } capSet := make(map[string]bool) for _, c := range caps { capSet[c.(string)] = true } - for _, expected := range []string{"rbac", "abac", "acl"} { + for _, expected := range []string{"acl"} { if !capSet[expected] { t.Errorf("expected capability %q in output", expected) } @@ -147,19 +204,152 @@ func TestAuthzCapabilitiesStep_Permit(t *testing.T) { if !ok { t.Fatalf("expected capabilities to be []any, got %T", result.Output["capabilities"]) } - if len(caps) != 3 { - t.Errorf("expected 3 capabilities for permit, got %d", len(caps)) + if len(caps) != 1 { + t.Errorf("expected 1 implemented capability for permit, got %d", len(caps)) + } + + capSet := make(map[string]bool) + for _, c := range caps { + capSet[c.(string)] = true + } + if !capSet["rbac"] { + t.Error("expected rbac capability in output") + } + for _, unsupported := range []string{"abac", "rebac"} { + if capSet[unsupported] { + t.Errorf("permit step should not advertise unimplemented capability %q", unsupported) + } + } +} + +func TestAuthzCapabilitiesStep_Keto(t *testing.T) { + reg := &testCapabilityRegistry{providers: map[string]AuthzProvider{ + "test-keto": &KetoModule{name: "test-keto"}, + }} + s, err := newAuthzCapabilitiesStep("caps", map[string]any{ + "module": "test-keto", + "provider": "keto", + }) + if err != nil { + t.Fatalf("newAuthzCapabilitiesStep: %v", err) } + s.registry = reg + result, err := s.Execute(context.Background(), nil, nil, nil, nil, nil) + if err != nil { + t.Fatalf("Execute: %v", err) + } + caps := result.Output["capabilities"].([]any) capSet := make(map[string]bool) for _, c := range caps { capSet[c.(string)] = true } - for _, expected := range []string{"rbac", "abac", "rebac"} { + for _, expected := range []string{"rbac", "rebac"} { if !capSet[expected] { t.Errorf("expected capability %q in output", expected) } } + if capSet["abac"] { + t.Error("keto capabilities should not include abac") + } +} + +func TestAuthzCapabilityDescriptorsAreTruthful(t *testing.T) { + reg := &testCapabilityRegistry{mod: rebacTestModule(t), providers: map[string]AuthzProvider{ + "keto": &KetoModule{name: "keto"}, + }} + for _, tc := range []struct { + name string + module string + provider string + wantModes []string + wantOps map[string][]string + rejectModes []string + }{ + { + name: "casbin rebac model", + module: "authz", + provider: "casbin", + wantModes: []string{"rbac", "rebac"}, + wantOps: map[string][]string{ + "rbac": {"check", "manage_roles"}, + "rebac": {"check", "manage_relations", "list"}, + }, + rejectModes: []string{"abac", "acl"}, + }, + { + name: "keto", + module: "keto", + provider: "keto", + wantModes: []string{"rbac", "rebac"}, + wantOps: map[string][]string{ + "rbac": {"check", "manage_roles"}, + "rebac": {"check", "manage_relations"}, + }, + rejectModes: []string{"abac", "acl"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + step, err := newAuthzCapabilitiesStep("caps", map[string]any{ + "module": tc.module, + "provider": tc.provider, + }) + if err != nil { + t.Fatalf("newAuthzCapabilitiesStep: %v", err) + } + step.registry = reg + result, err := step.Execute(context.Background(), nil, nil, nil, nil, nil) + if err != nil { + t.Fatalf("Execute: %v", err) + } + descriptors := capabilityDescriptorMaps(t, result.Output["capability_descriptors"]) + byMode := make(map[string]map[string]any, len(descriptors)) + for _, descriptor := range descriptors { + mode := descriptor["mode"].(string) + byMode[mode] = descriptor + if descriptor["configured"] != true { + t.Fatalf("%s configured = %v, want true", mode, descriptor["configured"]) + } + if descriptor["source"] == "" { + t.Fatalf("%s missing source", mode) + } + if descriptor["health"] != "ok" { + t.Fatalf("%s health = %v, want ok", mode, descriptor["health"]) + } + if descriptor["unsupported_reason"] != "" { + t.Fatalf("%s unsupported_reason = %v, want empty", mode, descriptor["unsupported_reason"]) + } + } + for _, mode := range tc.wantModes { + descriptor, ok := byMode[mode] + if !ok { + t.Fatalf("missing descriptor for %q in %#v", mode, descriptors) + } + gotOps := stringSet(stringSliceValue(descriptor["operations"])) + for _, op := range tc.wantOps[mode] { + if !gotOps[op] { + t.Fatalf("%s missing operation %q in %v", mode, op, gotOps) + } + } + } + for _, mode := range tc.rejectModes { + if _, ok := byMode[mode]; ok { + t.Fatalf("descriptor should not advertise unsupported mode %q", mode) + } + } + }) + } +} + +func TestAuthzProviderRequireCapabilitiesFailsClosed(t *testing.T) { + var provider AuthzProvider = &KetoModule{name: "keto"} + err := provider.RequireCapabilities([]CapabilityRequirement{{ + Mode: CapabilityABAC, + Operations: []AuthzOperation{OperationCheck}, + }}) + if err == nil { + t.Fatal("expected Keto to reject ABAC requirement") + } } func TestAuthzCapabilitiesStep_UnknownProvider(t *testing.T) { @@ -267,3 +457,79 @@ func TestIntegration_UnsupportedCapability_ClearError(t *testing.T) { t.Error("Permit should not support ACL capability") } } + +func abacAttributeTestModule(t *testing.T) *CasbinModule { + t.Helper() + m, err := newCasbinModule("authz", map[string]any{ + "model": ` +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub_department, obj_kind, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub.department == p.sub_department && r.obj.kind == p.obj_kind && r.act == p.act +`, + }) + if err != nil { + t.Fatalf("newCasbinModule: %v", err) + } + if err := m.Init(); err != nil { + t.Fatalf("Init: %v", err) + } + return m +} + +func capabilitySet(capabilities []AuthzCapability) map[AuthzCapability]bool { + out := make(map[AuthzCapability]bool, len(capabilities)) + for _, capability := range capabilities { + out[capability] = true + } + return out +} + +func capabilityDescriptorMaps(t *testing.T, value any) []map[string]any { + t.Helper() + raw, ok := value.([]any) + if !ok { + t.Fatalf("expected capability_descriptors to be []any, got %T", value) + } + out := make([]map[string]any, 0, len(raw)) + for _, item := range raw { + m, ok := item.(map[string]any) + if !ok { + t.Fatalf("expected descriptor map, got %T", item) + } + out = append(out, m) + } + return out +} + +func stringSet(values []string) map[string]bool { + out := make(map[string]bool, len(values)) + for _, value := range values { + out[value] = true + } + return out +} + +type testCapabilityRegistry struct { + mod *CasbinModule + providers map[string]AuthzProvider +} + +func (r *testCapabilityRegistry) GetEnforcer(name string) (*CasbinModule, bool) { + if r.mod != nil && r.mod.name == name { + return r.mod, true + } + return nil, false +} + +func (r *testCapabilityRegistry) GetAuthzProvider(name string) (AuthzProvider, bool) { + provider, ok := r.providers[name] + return provider, ok +} diff --git a/internal/contracts/authz.pb.go b/internal/contracts/authz.pb.go index 2903d5c..61674b6 100644 --- a/internal/contracts/authz.pb.go +++ b/internal/contracts/authz.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc v7.35.0 // source: internal/contracts/authz.proto package contracts @@ -22,6 +22,119 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type AuthzMode int32 + +const ( + AuthzMode_AUTHZ_MODE_UNSPECIFIED AuthzMode = 0 + AuthzMode_AUTHZ_MODE_RBAC AuthzMode = 1 + AuthzMode_AUTHZ_MODE_ABAC AuthzMode = 2 + AuthzMode_AUTHZ_MODE_REBAC AuthzMode = 3 + AuthzMode_AUTHZ_MODE_ACL AuthzMode = 4 +) + +// Enum value maps for AuthzMode. +var ( + AuthzMode_name = map[int32]string{ + 0: "AUTHZ_MODE_UNSPECIFIED", + 1: "AUTHZ_MODE_RBAC", + 2: "AUTHZ_MODE_ABAC", + 3: "AUTHZ_MODE_REBAC", + 4: "AUTHZ_MODE_ACL", + } + AuthzMode_value = map[string]int32{ + "AUTHZ_MODE_UNSPECIFIED": 0, + "AUTHZ_MODE_RBAC": 1, + "AUTHZ_MODE_ABAC": 2, + "AUTHZ_MODE_REBAC": 3, + "AUTHZ_MODE_ACL": 4, + } +) + +func (x AuthzMode) Enum() *AuthzMode { + p := new(AuthzMode) + *p = x + return p +} + +func (x AuthzMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AuthzMode) Descriptor() protoreflect.EnumDescriptor { + return file_internal_contracts_authz_proto_enumTypes[0].Descriptor() +} + +func (AuthzMode) Type() protoreflect.EnumType { + return &file_internal_contracts_authz_proto_enumTypes[0] +} + +func (x AuthzMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AuthzMode.Descriptor instead. +func (AuthzMode) EnumDescriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{0} +} + +type AuthzOperation int32 + +const ( + AuthzOperation_AUTHZ_OPERATION_UNSPECIFIED AuthzOperation = 0 + AuthzOperation_AUTHZ_OPERATION_CHECK AuthzOperation = 1 + AuthzOperation_AUTHZ_OPERATION_MANAGE_ROLES AuthzOperation = 2 + AuthzOperation_AUTHZ_OPERATION_MANAGE_POLICIES AuthzOperation = 3 + AuthzOperation_AUTHZ_OPERATION_MANAGE_RELATIONS AuthzOperation = 4 + AuthzOperation_AUTHZ_OPERATION_LIST AuthzOperation = 5 +) + +// Enum value maps for AuthzOperation. +var ( + AuthzOperation_name = map[int32]string{ + 0: "AUTHZ_OPERATION_UNSPECIFIED", + 1: "AUTHZ_OPERATION_CHECK", + 2: "AUTHZ_OPERATION_MANAGE_ROLES", + 3: "AUTHZ_OPERATION_MANAGE_POLICIES", + 4: "AUTHZ_OPERATION_MANAGE_RELATIONS", + 5: "AUTHZ_OPERATION_LIST", + } + AuthzOperation_value = map[string]int32{ + "AUTHZ_OPERATION_UNSPECIFIED": 0, + "AUTHZ_OPERATION_CHECK": 1, + "AUTHZ_OPERATION_MANAGE_ROLES": 2, + "AUTHZ_OPERATION_MANAGE_POLICIES": 3, + "AUTHZ_OPERATION_MANAGE_RELATIONS": 4, + "AUTHZ_OPERATION_LIST": 5, + } +) + +func (x AuthzOperation) Enum() *AuthzOperation { + p := new(AuthzOperation) + *p = x + return p +} + +func (x AuthzOperation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AuthzOperation) Descriptor() protoreflect.EnumDescriptor { + return file_internal_contracts_authz_proto_enumTypes[1].Descriptor() +} + +func (AuthzOperation) Type() protoreflect.EnumType { + return &file_internal_contracts_authz_proto_enumTypes[1] +} + +func (x AuthzOperation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AuthzOperation.Descriptor instead. +func (AuthzOperation) EnumDescriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{1} +} + type StringList struct { state protoimpl.MessageState `protogen:"open.v1"` Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` @@ -370,6 +483,58 @@ func (x *PermitModuleConfig) GetEnvironment() string { return "" } +type KetoModuleConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + ReadUrl string `protobuf:"bytes,1,opt,name=read_url,json=readUrl,proto3" json:"read_url,omitempty"` + WriteUrl string `protobuf:"bytes,2,opt,name=write_url,json=writeUrl,proto3" json:"write_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *KetoModuleConfig) Reset() { + *x = KetoModuleConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *KetoModuleConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KetoModuleConfig) ProtoMessage() {} + +func (x *KetoModuleConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KetoModuleConfig.ProtoReflect.Descriptor instead. +func (*KetoModuleConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{5} +} + +func (x *KetoModuleConfig) GetReadUrl() string { + if x != nil { + return x.ReadUrl + } + return "" +} + +func (x *KetoModuleConfig) GetWriteUrl() string { + if x != nil { + return x.WriteUrl + } + return "" +} + type ExtraField struct { state protoimpl.MessageState `protogen:"open.v1"` Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` @@ -380,7 +545,7 @@ type ExtraField struct { func (x *ExtraField) Reset() { *x = ExtraField{} - mi := &file_internal_contracts_authz_proto_msgTypes[5] + mi := &file_internal_contracts_authz_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -392,7 +557,7 @@ func (x *ExtraField) String() string { func (*ExtraField) ProtoMessage() {} func (x *ExtraField) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[5] + mi := &file_internal_contracts_authz_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -405,7 +570,7 @@ func (x *ExtraField) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtraField.ProtoReflect.Descriptor instead. func (*ExtraField) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{5} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{6} } func (x *ExtraField) GetKey() string { @@ -436,7 +601,7 @@ type AuthzCheckConfig struct { func (x *AuthzCheckConfig) Reset() { *x = AuthzCheckConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[6] + mi := &file_internal_contracts_authz_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -448,7 +613,7 @@ func (x *AuthzCheckConfig) String() string { func (*AuthzCheckConfig) ProtoMessage() {} func (x *AuthzCheckConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[6] + mi := &file_internal_contracts_authz_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -461,7 +626,7 @@ func (x *AuthzCheckConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthzCheckConfig.ProtoReflect.Descriptor instead. func (*AuthzCheckConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{6} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{7} } func (x *AuthzCheckConfig) GetModule() string { @@ -520,7 +685,7 @@ type AuthzCheckInput struct { func (x *AuthzCheckInput) Reset() { *x = AuthzCheckInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[7] + mi := &file_internal_contracts_authz_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -532,7 +697,7 @@ func (x *AuthzCheckInput) String() string { func (*AuthzCheckInput) ProtoMessage() {} func (x *AuthzCheckInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[7] + mi := &file_internal_contracts_authz_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -545,7 +710,7 @@ func (x *AuthzCheckInput) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthzCheckInput.ProtoReflect.Descriptor instead. func (*AuthzCheckInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{7} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{8} } func (x *AuthzCheckInput) GetModule() string { @@ -606,7 +771,7 @@ type AuthzCheckOutput struct { func (x *AuthzCheckOutput) Reset() { *x = AuthzCheckOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[8] + mi := &file_internal_contracts_authz_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -618,7 +783,7 @@ func (x *AuthzCheckOutput) String() string { func (*AuthzCheckOutput) ProtoMessage() {} func (x *AuthzCheckOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[8] + mi := &file_internal_contracts_authz_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -631,7 +796,7 @@ func (x *AuthzCheckOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthzCheckOutput.ProtoReflect.Descriptor instead. func (*AuthzCheckOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{8} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{9} } func (x *AuthzCheckOutput) GetSubject() string { @@ -700,7 +865,7 @@ type PolicyRuleConfig struct { func (x *PolicyRuleConfig) Reset() { *x = PolicyRuleConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[9] + mi := &file_internal_contracts_authz_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -712,7 +877,7 @@ func (x *PolicyRuleConfig) String() string { func (*PolicyRuleConfig) ProtoMessage() {} func (x *PolicyRuleConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[9] + mi := &file_internal_contracts_authz_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -725,7 +890,7 @@ func (x *PolicyRuleConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRuleConfig.ProtoReflect.Descriptor instead. func (*PolicyRuleConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{9} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{10} } func (x *PolicyRuleConfig) GetModule() string { @@ -752,7 +917,7 @@ type PolicyRuleInput struct { func (x *PolicyRuleInput) Reset() { *x = PolicyRuleInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[10] + mi := &file_internal_contracts_authz_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -764,7 +929,7 @@ func (x *PolicyRuleInput) String() string { func (*PolicyRuleInput) ProtoMessage() {} func (x *PolicyRuleInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[10] + mi := &file_internal_contracts_authz_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -777,7 +942,7 @@ func (x *PolicyRuleInput) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRuleInput.ProtoReflect.Descriptor instead. func (*PolicyRuleInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{10} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{11} } func (x *PolicyRuleInput) GetModule() string { @@ -805,7 +970,7 @@ type PolicyRuleOutput struct { func (x *PolicyRuleOutput) Reset() { *x = PolicyRuleOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[11] + mi := &file_internal_contracts_authz_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -817,7 +982,7 @@ func (x *PolicyRuleOutput) String() string { func (*PolicyRuleOutput) ProtoMessage() {} func (x *PolicyRuleOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[11] + mi := &file_internal_contracts_authz_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -830,7 +995,7 @@ func (x *PolicyRuleOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRuleOutput.ProtoReflect.Descriptor instead. func (*PolicyRuleOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{11} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{12} } func (x *PolicyRuleOutput) GetChanged() bool { @@ -865,7 +1030,7 @@ type RoleAssignConfig struct { func (x *RoleAssignConfig) Reset() { *x = RoleAssignConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[12] + mi := &file_internal_contracts_authz_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -877,7 +1042,7 @@ func (x *RoleAssignConfig) String() string { func (*RoleAssignConfig) ProtoMessage() {} func (x *RoleAssignConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[12] + mi := &file_internal_contracts_authz_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -890,7 +1055,7 @@ func (x *RoleAssignConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RoleAssignConfig.ProtoReflect.Descriptor instead. func (*RoleAssignConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{12} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{13} } func (x *RoleAssignConfig) GetModule() string { @@ -925,7 +1090,7 @@ type RoleAssignInput struct { func (x *RoleAssignInput) Reset() { *x = RoleAssignInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[13] + mi := &file_internal_contracts_authz_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -937,7 +1102,7 @@ func (x *RoleAssignInput) String() string { func (*RoleAssignInput) ProtoMessage() {} func (x *RoleAssignInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[13] + mi := &file_internal_contracts_authz_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -950,7 +1115,7 @@ func (x *RoleAssignInput) ProtoReflect() protoreflect.Message { // Deprecated: Use RoleAssignInput.ProtoReflect.Descriptor instead. func (*RoleAssignInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{13} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{14} } func (x *RoleAssignInput) GetModule() string { @@ -985,7 +1150,7 @@ type RoleAssignOutput struct { func (x *RoleAssignOutput) Reset() { *x = RoleAssignOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[14] + mi := &file_internal_contracts_authz_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -997,7 +1162,7 @@ func (x *RoleAssignOutput) String() string { func (*RoleAssignOutput) ProtoMessage() {} func (x *RoleAssignOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[14] + mi := &file_internal_contracts_authz_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1010,7 +1175,7 @@ func (x *RoleAssignOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use RoleAssignOutput.ProtoReflect.Descriptor instead. func (*RoleAssignOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{14} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{15} } func (x *RoleAssignOutput) GetAction() string { @@ -1044,7 +1209,7 @@ type CapabilitiesConfig struct { func (x *CapabilitiesConfig) Reset() { *x = CapabilitiesConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[15] + mi := &file_internal_contracts_authz_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1056,7 +1221,7 @@ func (x *CapabilitiesConfig) String() string { func (*CapabilitiesConfig) ProtoMessage() {} func (x *CapabilitiesConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[15] + mi := &file_internal_contracts_authz_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1069,7 +1234,7 @@ func (x *CapabilitiesConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilitiesConfig.ProtoReflect.Descriptor instead. func (*CapabilitiesConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{15} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{16} } func (x *CapabilitiesConfig) GetModule() string { @@ -1096,7 +1261,7 @@ type CapabilitiesInput struct { func (x *CapabilitiesInput) Reset() { *x = CapabilitiesInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[16] + mi := &file_internal_contracts_authz_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1108,7 +1273,7 @@ func (x *CapabilitiesInput) String() string { func (*CapabilitiesInput) ProtoMessage() {} func (x *CapabilitiesInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[16] + mi := &file_internal_contracts_authz_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1121,7 +1286,7 @@ func (x *CapabilitiesInput) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilitiesInput.ProtoReflect.Descriptor instead. func (*CapabilitiesInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{16} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{17} } func (x *CapabilitiesInput) GetModule() string { @@ -1139,18 +1304,21 @@ func (x *CapabilitiesInput) GetProvider() string { } type CapabilitiesOutput struct { - state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` - Capabilities []string `protobuf:"bytes,3,rep,name=capabilities,proto3" json:"capabilities,omitempty"` - Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Capabilities []string `protobuf:"bytes,3,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + Descriptors []*CapabilityDescriptor `protobuf:"bytes,4,rep,name=descriptors,proto3" json:"descriptors,omitempty"` + Health string `protobuf:"bytes,5,opt,name=health,proto3" json:"health,omitempty"` + MissingRequirements []string `protobuf:"bytes,6,rep,name=missing_requirements,json=missingRequirements,proto3" json:"missing_requirements,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CapabilitiesOutput) Reset() { *x = CapabilitiesOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[17] + mi := &file_internal_contracts_authz_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1162,7 +1330,7 @@ func (x *CapabilitiesOutput) String() string { func (*CapabilitiesOutput) ProtoMessage() {} func (x *CapabilitiesOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[17] + mi := &file_internal_contracts_authz_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1175,7 +1343,7 @@ func (x *CapabilitiesOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use CapabilitiesOutput.ProtoReflect.Descriptor instead. func (*CapabilitiesOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{17} + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{18} } func (x *CapabilitiesOutput) GetModule() string { @@ -1199,6 +1367,27 @@ func (x *CapabilitiesOutput) GetCapabilities() []string { return nil } +func (x *CapabilitiesOutput) GetDescriptors() []*CapabilityDescriptor { + if x != nil { + return x.Descriptors + } + return nil +} + +func (x *CapabilitiesOutput) GetHealth() string { + if x != nil { + return x.Health + } + return "" +} + +func (x *CapabilitiesOutput) GetMissingRequirements() []string { + if x != nil { + return x.MissingRequirements + } + return nil +} + func (x *CapabilitiesOutput) GetError() string { if x != nil { return x.Error @@ -1206,31 +1395,33 @@ func (x *CapabilitiesOutput) GetError() string { return "" } -type SubjectObjectActionConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` - Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +type CapabilityDescriptor struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mode AuthzMode `protobuf:"varint,1,opt,name=mode,proto3,enum=workflow.plugins.authz.v1.AuthzMode" json:"mode,omitempty"` + Operations []AuthzOperation `protobuf:"varint,2,rep,packed,name=operations,proto3,enum=workflow.plugins.authz.v1.AuthzOperation" json:"operations,omitempty"` + Configured bool `protobuf:"varint,3,opt,name=configured,proto3" json:"configured,omitempty"` + Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` + Health string `protobuf:"bytes,5,opt,name=health,proto3" json:"health,omitempty"` + UnsupportedReason string `protobuf:"bytes,6,opt,name=unsupported_reason,json=unsupportedReason,proto3" json:"unsupported_reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *SubjectObjectActionConfig) Reset() { - *x = SubjectObjectActionConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[18] +func (x *CapabilityDescriptor) Reset() { + *x = CapabilityDescriptor{} + mi := &file_internal_contracts_authz_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SubjectObjectActionConfig) String() string { +func (x *CapabilityDescriptor) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SubjectObjectActionConfig) ProtoMessage() {} +func (*CapabilityDescriptor) ProtoMessage() {} -func (x *SubjectObjectActionConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[18] +func (x *CapabilityDescriptor) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1241,64 +1432,76 @@ func (x *SubjectObjectActionConfig) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SubjectObjectActionConfig.ProtoReflect.Descriptor instead. -func (*SubjectObjectActionConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{18} +// Deprecated: Use CapabilityDescriptor.ProtoReflect.Descriptor instead. +func (*CapabilityDescriptor) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{19} } -func (x *SubjectObjectActionConfig) GetModule() string { +func (x *CapabilityDescriptor) GetMode() AuthzMode { if x != nil { - return x.Module + return x.Mode } - return "" + return AuthzMode_AUTHZ_MODE_UNSPECIFIED } -func (x *SubjectObjectActionConfig) GetSubject() string { +func (x *CapabilityDescriptor) GetOperations() []AuthzOperation { if x != nil { - return x.Subject + return x.Operations + } + return nil +} + +func (x *CapabilityDescriptor) GetConfigured() bool { + if x != nil { + return x.Configured + } + return false +} + +func (x *CapabilityDescriptor) GetSource() string { + if x != nil { + return x.Source } return "" } -func (x *SubjectObjectActionConfig) GetObject() string { +func (x *CapabilityDescriptor) GetHealth() string { if x != nil { - return x.Object + return x.Health } return "" } -func (x *SubjectObjectActionConfig) GetAction() string { +func (x *CapabilityDescriptor) GetUnsupportedReason() string { if x != nil { - return x.Action + return x.UnsupportedReason } return "" } -type SubjectObjectActionInput struct { +type CapabilityRequirement struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` - Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + Mode AuthzMode `protobuf:"varint,1,opt,name=mode,proto3,enum=workflow.plugins.authz.v1.AuthzMode" json:"mode,omitempty"` + Operations []AuthzOperation `protobuf:"varint,2,rep,packed,name=operations,proto3,enum=workflow.plugins.authz.v1.AuthzOperation" json:"operations,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SubjectObjectActionInput) Reset() { - *x = SubjectObjectActionInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[19] +func (x *CapabilityRequirement) Reset() { + *x = CapabilityRequirement{} + mi := &file_internal_contracts_authz_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SubjectObjectActionInput) String() string { +func (x *CapabilityRequirement) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SubjectObjectActionInput) ProtoMessage() {} +func (*CapabilityRequirement) ProtoMessage() {} -func (x *SubjectObjectActionInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[19] +func (x *CapabilityRequirement) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1309,66 +1512,4477 @@ func (x *SubjectObjectActionInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SubjectObjectActionInput.ProtoReflect.Descriptor instead. -func (*SubjectObjectActionInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{19} +// Deprecated: Use CapabilityRequirement.ProtoReflect.Descriptor instead. +func (*CapabilityRequirement) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{20} } -func (x *SubjectObjectActionInput) GetModule() string { +func (x *CapabilityRequirement) GetMode() AuthzMode { if x != nil { - return x.Module + return x.Mode } - return "" + return AuthzMode_AUTHZ_MODE_UNSPECIFIED } -func (x *SubjectObjectActionInput) GetSubject() string { +func (x *CapabilityRequirement) GetOperations() []AuthzOperation { if x != nil { - return x.Subject + return x.Operations } - return "" + return nil } -func (x *SubjectObjectActionInput) GetObject() string { - if x != nil { - return x.Object - } - return "" +type ProviderCapabilitiesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Requirements []*CapabilityRequirement `protobuf:"bytes,3,rep,name=requirements,proto3" json:"requirements,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *SubjectObjectActionInput) GetAction() string { - if x != nil { - return x.Action - } - return "" +func (x *ProviderCapabilitiesInput) Reset() { + *x = ProviderCapabilitiesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCapabilitiesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCapabilitiesInput) ProtoMessage() {} + +func (x *ProviderCapabilitiesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCapabilitiesInput.ProtoReflect.Descriptor instead. +func (*ProviderCapabilitiesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{21} +} + +func (x *ProviderCapabilitiesInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ProviderCapabilitiesInput) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ProviderCapabilitiesInput) GetRequirements() []*CapabilityRequirement { + if x != nil { + return x.Requirements + } + return nil +} + +type ProviderCapabilitiesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Capabilities []string `protobuf:"bytes,3,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + Descriptors []*CapabilityDescriptor `protobuf:"bytes,4,rep,name=descriptors,proto3" json:"descriptors,omitempty"` + Health string `protobuf:"bytes,5,opt,name=health,proto3" json:"health,omitempty"` + MissingRequirements []string `protobuf:"bytes,6,rep,name=missing_requirements,json=missingRequirements,proto3" json:"missing_requirements,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderCapabilitiesOutput) Reset() { + *x = ProviderCapabilitiesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderCapabilitiesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderCapabilitiesOutput) ProtoMessage() {} + +func (x *ProviderCapabilitiesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderCapabilitiesOutput.ProtoReflect.Descriptor instead. +func (*ProviderCapabilitiesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{22} +} + +func (x *ProviderCapabilitiesOutput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ProviderCapabilitiesOutput) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ProviderCapabilitiesOutput) GetCapabilities() []string { + if x != nil { + return x.Capabilities + } + return nil +} + +func (x *ProviderCapabilitiesOutput) GetDescriptors() []*CapabilityDescriptor { + if x != nil { + return x.Descriptors + } + return nil +} + +func (x *ProviderCapabilitiesOutput) GetHealth() string { + if x != nil { + return x.Health + } + return "" +} + +func (x *ProviderCapabilitiesOutput) GetMissingRequirements() []string { + if x != nil { + return x.MissingRequirements + } + return nil +} + +func (x *ProviderCapabilitiesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type AuthorizationDecisionConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Mode AuthzMode `protobuf:"varint,3,opt,name=mode,proto3,enum=workflow.plugins.authz.v1.AuthzMode" json:"mode,omitempty"` + Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,5,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,6,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,7,opt,name=action,proto3" json:"action,omitempty"` + Scope string `protobuf:"bytes,8,opt,name=scope,proto3" json:"scope,omitempty"` + Relation string `protobuf:"bytes,9,opt,name=relation,proto3" json:"relation,omitempty"` + Explain bool `protobuf:"varint,10,opt,name=explain,proto3" json:"explain,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizationDecisionConfig) Reset() { + *x = AuthorizationDecisionConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizationDecisionConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizationDecisionConfig) ProtoMessage() {} + +func (x *AuthorizationDecisionConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizationDecisionConfig.ProtoReflect.Descriptor instead. +func (*AuthorizationDecisionConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{23} +} + +func (x *AuthorizationDecisionConfig) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetMode() AuthzMode { + if x != nil { + return x.Mode + } + return AuthzMode_AUTHZ_MODE_UNSPECIFIED +} + +func (x *AuthorizationDecisionConfig) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *AuthorizationDecisionConfig) GetExplain() bool { + if x != nil { + return x.Explain + } + return false +} + +type AuthorizationDecisionInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Mode AuthzMode `protobuf:"varint,3,opt,name=mode,proto3,enum=workflow.plugins.authz.v1.AuthzMode" json:"mode,omitempty"` + Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,5,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,6,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,7,opt,name=action,proto3" json:"action,omitempty"` + Scope string `protobuf:"bytes,8,opt,name=scope,proto3" json:"scope,omitempty"` + Relation string `protobuf:"bytes,9,opt,name=relation,proto3" json:"relation,omitempty"` + SubjectAttributes *structpb.Struct `protobuf:"bytes,10,opt,name=subject_attributes,json=subjectAttributes,proto3" json:"subject_attributes,omitempty"` + ResourceAttributes *structpb.Struct `protobuf:"bytes,11,opt,name=resource_attributes,json=resourceAttributes,proto3" json:"resource_attributes,omitempty"` + EnvironmentAttributes *structpb.Struct `protobuf:"bytes,12,opt,name=environment_attributes,json=environmentAttributes,proto3" json:"environment_attributes,omitempty"` + Explain bool `protobuf:"varint,13,opt,name=explain,proto3" json:"explain,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizationDecisionInput) Reset() { + *x = AuthorizationDecisionInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizationDecisionInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizationDecisionInput) ProtoMessage() {} + +func (x *AuthorizationDecisionInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizationDecisionInput.ProtoReflect.Descriptor instead. +func (*AuthorizationDecisionInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{24} +} + +func (x *AuthorizationDecisionInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *AuthorizationDecisionInput) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *AuthorizationDecisionInput) GetMode() AuthzMode { + if x != nil { + return x.Mode + } + return AuthzMode_AUTHZ_MODE_UNSPECIFIED +} + +func (x *AuthorizationDecisionInput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *AuthorizationDecisionInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AuthorizationDecisionInput) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *AuthorizationDecisionInput) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *AuthorizationDecisionInput) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *AuthorizationDecisionInput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *AuthorizationDecisionInput) GetSubjectAttributes() *structpb.Struct { + if x != nil { + return x.SubjectAttributes + } + return nil +} + +func (x *AuthorizationDecisionInput) GetResourceAttributes() *structpb.Struct { + if x != nil { + return x.ResourceAttributes + } + return nil +} + +func (x *AuthorizationDecisionInput) GetEnvironmentAttributes() *structpb.Struct { + if x != nil { + return x.EnvironmentAttributes + } + return nil +} + +func (x *AuthorizationDecisionInput) GetExplain() bool { + if x != nil { + return x.Explain + } + return false +} + +type AuthorizationDecisionOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Mode AuthzMode `protobuf:"varint,2,opt,name=mode,proto3,enum=workflow.plugins.authz.v1.AuthzMode" json:"mode,omitempty"` + Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + Reason string `protobuf:"bytes,5,opt,name=reason,proto3" json:"reason,omitempty"` + Explain string `protobuf:"bytes,6,opt,name=explain,proto3" json:"explain,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthorizationDecisionOutput) Reset() { + *x = AuthorizationDecisionOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthorizationDecisionOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizationDecisionOutput) ProtoMessage() {} + +func (x *AuthorizationDecisionOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizationDecisionOutput.ProtoReflect.Descriptor instead. +func (*AuthorizationDecisionOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{25} +} + +func (x *AuthorizationDecisionOutput) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +func (x *AuthorizationDecisionOutput) GetMode() AuthzMode { + if x != nil { + return x.Mode + } + return AuthzMode_AUTHZ_MODE_UNSPECIFIED +} + +func (x *AuthorizationDecisionOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *AuthorizationDecisionOutput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AuthorizationDecisionOutput) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *AuthorizationDecisionOutput) GetExplain() string { + if x != nil { + return x.Explain + } + return "" +} + +func (x *AuthorizationDecisionOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type RequireCapabilitiesConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Requirements []*CapabilityRequirement `protobuf:"bytes,3,rep,name=requirements,proto3" json:"requirements,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RequireCapabilitiesConfig) Reset() { + *x = RequireCapabilitiesConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequireCapabilitiesConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequireCapabilitiesConfig) ProtoMessage() {} + +func (x *RequireCapabilitiesConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequireCapabilitiesConfig.ProtoReflect.Descriptor instead. +func (*RequireCapabilitiesConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{26} +} + +func (x *RequireCapabilitiesConfig) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *RequireCapabilitiesConfig) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *RequireCapabilitiesConfig) GetRequirements() []*CapabilityRequirement { + if x != nil { + return x.Requirements + } + return nil +} + +type RequireCapabilitiesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Requirements []*CapabilityRequirement `protobuf:"bytes,3,rep,name=requirements,proto3" json:"requirements,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RequireCapabilitiesInput) Reset() { + *x = RequireCapabilitiesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RequireCapabilitiesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequireCapabilitiesInput) ProtoMessage() {} + +func (x *RequireCapabilitiesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequireCapabilitiesInput.ProtoReflect.Descriptor instead. +func (*RequireCapabilitiesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{27} +} + +func (x *RequireCapabilitiesInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *RequireCapabilitiesInput) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *RequireCapabilitiesInput) GetRequirements() []*CapabilityRequirement { + if x != nil { + return x.Requirements + } + return nil +} + +type SubjectObjectActionConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubjectObjectActionConfig) Reset() { + *x = SubjectObjectActionConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubjectObjectActionConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubjectObjectActionConfig) ProtoMessage() {} + +func (x *SubjectObjectActionConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubjectObjectActionConfig.ProtoReflect.Descriptor instead. +func (*SubjectObjectActionConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{28} +} + +func (x *SubjectObjectActionConfig) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *SubjectObjectActionConfig) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *SubjectObjectActionConfig) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *SubjectObjectActionConfig) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +type SubjectObjectActionInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubjectObjectActionInput) Reset() { + *x = SubjectObjectActionInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubjectObjectActionInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubjectObjectActionInput) ProtoMessage() {} + +func (x *SubjectObjectActionInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubjectObjectActionInput.ProtoReflect.Descriptor instead. +func (*SubjectObjectActionInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{29} +} + +func (x *SubjectObjectActionInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *SubjectObjectActionInput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *SubjectObjectActionInput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *SubjectObjectActionInput) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +type SubjectObjectActionOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Changed bool `protobuf:"varint,2,opt,name=changed,proto3" json:"changed,omitempty"` + Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` + Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubjectObjectActionOutput) Reset() { + *x = SubjectObjectActionOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubjectObjectActionOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubjectObjectActionOutput) ProtoMessage() {} + +func (x *SubjectObjectActionOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubjectObjectActionOutput.ProtoReflect.Descriptor instead. +func (*SubjectObjectActionOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{30} +} + +func (x *SubjectObjectActionOutput) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +func (x *SubjectObjectActionOutput) GetChanged() bool { + if x != nil { + return x.Changed + } + return false +} + +func (x *SubjectObjectActionOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *SubjectObjectActionOutput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *SubjectObjectActionOutput) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *SubjectObjectActionOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ListConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Filter string `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` + Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListConfig) Reset() { + *x = ListConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConfig) ProtoMessage() {} + +func (x *ListConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConfig.ProtoReflect.Descriptor instead. +func (*ListConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{31} +} + +func (x *ListConfig) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ListConfig) GetFilter() string { + if x != nil { + return x.Filter + } + return "" +} + +func (x *ListConfig) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type ListInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Filter string `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` + Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListInput) Reset() { + *x = ListInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListInput) ProtoMessage() {} + +func (x *ListInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListInput.ProtoReflect.Descriptor instead. +func (*ListInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{32} +} + +func (x *ListInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ListInput) GetFilter() string { + if x != nil { + return x.Filter + } + return "" +} + +func (x *ListInput) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type GenericStepOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Output *structpb.Struct `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GenericStepOutput) Reset() { + *x = GenericStepOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenericStepOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenericStepOutput) ProtoMessage() {} + +func (x *GenericStepOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenericStepOutput.ProtoReflect.Descriptor instead. +func (*GenericStepOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{33} +} + +func (x *GenericStepOutput) GetOutput() *structpb.Struct { + if x != nil { + return x.Output + } + return nil +} + +func (x *GenericStepOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type RelationConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationConfig) Reset() { + *x = RelationConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationConfig) ProtoMessage() {} + +func (x *RelationConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationConfig.ProtoReflect.Descriptor instead. +func (*RelationConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{34} +} + +func (x *RelationConfig) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *RelationConfig) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationConfig) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationConfig) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +type RelationInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationInput) Reset() { + *x = RelationInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationInput) ProtoMessage() {} + +func (x *RelationInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationInput.ProtoReflect.Descriptor instead. +func (*RelationInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{35} +} + +func (x *RelationInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *RelationInput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationInput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationInput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +type RelationOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationOutput) Reset() { + *x = RelationOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationOutput) ProtoMessage() {} + +func (x *RelationOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationOutput.ProtoReflect.Descriptor instead. +func (*RelationOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{36} +} + +func (x *RelationOutput) GetChanged() bool { + if x != nil { + return x.Changed + } + return false +} + +func (x *RelationOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationOutput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationOutput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *RelationOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type PermitStepConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Values *structpb.Struct `protobuf:"bytes,2,opt,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PermitStepConfig) Reset() { + *x = PermitStepConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PermitStepConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PermitStepConfig) ProtoMessage() {} + +func (x *PermitStepConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PermitStepConfig.ProtoReflect.Descriptor instead. +func (*PermitStepConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{37} +} + +func (x *PermitStepConfig) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *PermitStepConfig) GetValues() *structpb.Struct { + if x != nil { + return x.Values + } + return nil +} + +type PermitStepInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + Values *structpb.Struct `protobuf:"bytes,2,opt,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PermitStepInput) Reset() { + *x = PermitStepInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PermitStepInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PermitStepInput) ProtoMessage() {} + +func (x *PermitStepInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PermitStepInput.ProtoReflect.Descriptor instead. +func (*PermitStepInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{38} +} + +func (x *PermitStepInput) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *PermitStepInput) GetValues() *structpb.Struct { + if x != nil { + return x.Values + } + return nil +} + +type ScopeDeclaration struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Actions []string `protobuf:"bytes,4,rep,name=actions,proto3" json:"actions,omitempty"` + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,6,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,7,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + Category string `protobuf:"bytes,8,opt,name=category,proto3" json:"category,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScopeDeclaration) Reset() { + *x = ScopeDeclaration{} + mi := &file_internal_contracts_authz_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScopeDeclaration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScopeDeclaration) ProtoMessage() {} + +func (x *ScopeDeclaration) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScopeDeclaration.ProtoReflect.Descriptor instead. +func (*ScopeDeclaration) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{39} +} + +func (x *ScopeDeclaration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ScopeDeclaration) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ScopeDeclaration) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *ScopeDeclaration) GetActions() []string { + if x != nil { + return x.Actions + } + return nil +} + +func (x *ScopeDeclaration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ScopeDeclaration) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *ScopeDeclaration) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +func (x *ScopeDeclaration) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type ScopeCatalogConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Scopes []*ScopeDeclaration `protobuf:"bytes,1,rep,name=scopes,proto3" json:"scopes,omitempty"` + AllowRuntimeRegistration bool `protobuf:"varint,2,opt,name=allow_runtime_registration,json=allowRuntimeRegistration,proto3" json:"allow_runtime_registration,omitempty"` + Declarations *AuthzDeclarationSet `protobuf:"bytes,3,opt,name=declarations,proto3" json:"declarations,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScopeCatalogConfig) Reset() { + *x = ScopeCatalogConfig{} + mi := &file_internal_contracts_authz_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScopeCatalogConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScopeCatalogConfig) ProtoMessage() {} + +func (x *ScopeCatalogConfig) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScopeCatalogConfig.ProtoReflect.Descriptor instead. +func (*ScopeCatalogConfig) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{40} +} + +func (x *ScopeCatalogConfig) GetScopes() []*ScopeDeclaration { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *ScopeCatalogConfig) GetAllowRuntimeRegistration() bool { + if x != nil { + return x.AllowRuntimeRegistration + } + return false +} + +func (x *ScopeCatalogConfig) GetDeclarations() *AuthzDeclarationSet { + if x != nil { + return x.Declarations + } + return nil +} + +type RegisterScopesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Scopes []*ScopeDeclaration `protobuf:"bytes,1,rep,name=scopes,proto3" json:"scopes,omitempty"` + OwnerPlugin string `protobuf:"bytes,2,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,3,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterScopesInput) Reset() { + *x = RegisterScopesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterScopesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterScopesInput) ProtoMessage() {} + +func (x *RegisterScopesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterScopesInput.ProtoReflect.Descriptor instead. +func (*RegisterScopesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{41} +} + +func (x *RegisterScopesInput) GetScopes() []*ScopeDeclaration { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *RegisterScopesInput) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *RegisterScopesInput) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type RegisterScopesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Registered int32 `protobuf:"varint,1,opt,name=registered,proto3" json:"registered,omitempty"` + Scopes []*ScopeDeclaration `protobuf:"bytes,2,rep,name=scopes,proto3" json:"scopes,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterScopesOutput) Reset() { + *x = RegisterScopesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterScopesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterScopesOutput) ProtoMessage() {} + +func (x *RegisterScopesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterScopesOutput.ProtoReflect.Descriptor instead. +func (*RegisterScopesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{42} +} + +func (x *RegisterScopesOutput) GetRegistered() int32 { + if x != nil { + return x.Registered + } + return 0 +} + +func (x *RegisterScopesOutput) GetScopes() []*ScopeDeclaration { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *RegisterScopesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ListScopesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Context string `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + OwnerPlugin string `protobuf:"bytes,2,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,3,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListScopesInput) Reset() { + *x = ListScopesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListScopesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListScopesInput) ProtoMessage() {} + +func (x *ListScopesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListScopesInput.ProtoReflect.Descriptor instead. +func (*ListScopesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{43} +} + +func (x *ListScopesInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ListScopesInput) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *ListScopesInput) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type ListScopesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Scopes []*ScopeDeclaration `protobuf:"bytes,1,rep,name=scopes,proto3" json:"scopes,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListScopesOutput) Reset() { + *x = ListScopesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListScopesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListScopesOutput) ProtoMessage() {} + +func (x *ListScopesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListScopesOutput.ProtoReflect.Descriptor instead. +func (*ListScopesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{44} +} + +func (x *ListScopesOutput) GetScopes() []*ScopeDeclaration { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *ListScopesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ResourceDeclaration struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,5,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,6,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + Category string `protobuf:"bytes,7,opt,name=category,proto3" json:"category,omitempty"` + LookupSourceId string `protobuf:"bytes,8,opt,name=lookup_source_id,json=lookupSourceId,proto3" json:"lookup_source_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceDeclaration) Reset() { + *x = ResourceDeclaration{} + mi := &file_internal_contracts_authz_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceDeclaration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceDeclaration) ProtoMessage() {} + +func (x *ResourceDeclaration) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceDeclaration.ProtoReflect.Descriptor instead. +func (*ResourceDeclaration) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{45} +} + +func (x *ResourceDeclaration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ResourceDeclaration) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ResourceDeclaration) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *ResourceDeclaration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ResourceDeclaration) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *ResourceDeclaration) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +func (x *ResourceDeclaration) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *ResourceDeclaration) GetLookupSourceId() string { + if x != nil { + return x.LookupSourceId + } + return "" +} + +type ActionDeclaration struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,5,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,6,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + Category string `protobuf:"bytes,7,opt,name=category,proto3" json:"category,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ActionDeclaration) Reset() { + *x = ActionDeclaration{} + mi := &file_internal_contracts_authz_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ActionDeclaration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActionDeclaration) ProtoMessage() {} + +func (x *ActionDeclaration) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActionDeclaration.ProtoReflect.Descriptor instead. +func (*ActionDeclaration) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{46} +} + +func (x *ActionDeclaration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ActionDeclaration) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ActionDeclaration) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *ActionDeclaration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ActionDeclaration) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *ActionDeclaration) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +func (x *ActionDeclaration) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type AttributeValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributeValue) Reset() { + *x = AttributeValue{} + mi := &file_internal_contracts_authz_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributeValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributeValue) ProtoMessage() {} + +func (x *AttributeValue) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributeValue.ProtoReflect.Descriptor instead. +func (*AttributeValue) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{47} +} + +func (x *AttributeValue) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *AttributeValue) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +type AttributeDeclaration struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` + DataType string `protobuf:"bytes,4,opt,name=data_type,json=dataType,proto3" json:"data_type,omitempty"` + AllowedValues []*AttributeValue `protobuf:"bytes,5,rep,name=allowed_values,json=allowedValues,proto3" json:"allowed_values,omitempty"` + LookupSourceId string `protobuf:"bytes,6,opt,name=lookup_source_id,json=lookupSourceId,proto3" json:"lookup_source_id,omitempty"` + Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,8,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,9,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + Category string `protobuf:"bytes,10,opt,name=category,proto3" json:"category,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributeDeclaration) Reset() { + *x = AttributeDeclaration{} + mi := &file_internal_contracts_authz_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributeDeclaration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributeDeclaration) ProtoMessage() {} + +func (x *AttributeDeclaration) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributeDeclaration.ProtoReflect.Descriptor instead. +func (*AttributeDeclaration) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{48} +} + +func (x *AttributeDeclaration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AttributeDeclaration) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AttributeDeclaration) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *AttributeDeclaration) GetDataType() string { + if x != nil { + return x.DataType + } + return "" +} + +func (x *AttributeDeclaration) GetAllowedValues() []*AttributeValue { + if x != nil { + return x.AllowedValues + } + return nil +} + +func (x *AttributeDeclaration) GetLookupSourceId() string { + if x != nil { + return x.LookupSourceId + } + return "" +} + +func (x *AttributeDeclaration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *AttributeDeclaration) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *AttributeDeclaration) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +func (x *AttributeDeclaration) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type AttributeCondition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` + Attribute string `protobuf:"bytes,2,opt,name=attribute,proto3" json:"attribute,omitempty"` + Operator string `protobuf:"bytes,3,opt,name=operator,proto3" json:"operator,omitempty"` + Values []string `protobuf:"bytes,4,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributeCondition) Reset() { + *x = AttributeCondition{} + mi := &file_internal_contracts_authz_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributeCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributeCondition) ProtoMessage() {} + +func (x *AttributeCondition) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributeCondition.ProtoReflect.Descriptor instead. +func (*AttributeCondition) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{49} +} + +func (x *AttributeCondition) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +func (x *AttributeCondition) GetAttribute() string { + if x != nil { + return x.Attribute + } + return "" +} + +func (x *AttributeCondition) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +func (x *AttributeCondition) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +type AttributePolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + Effect string `protobuf:"bytes,5,opt,name=effect,proto3" json:"effect,omitempty"` + Conditions []*AttributeCondition `protobuf:"bytes,6,rep,name=conditions,proto3" json:"conditions,omitempty"` + Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,8,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,9,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributePolicy) Reset() { + *x = AttributePolicy{} + mi := &file_internal_contracts_authz_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributePolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributePolicy) ProtoMessage() {} + +func (x *AttributePolicy) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributePolicy.ProtoReflect.Descriptor instead. +func (*AttributePolicy) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{50} +} + +func (x *AttributePolicy) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AttributePolicy) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AttributePolicy) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *AttributePolicy) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *AttributePolicy) GetEffect() string { + if x != nil { + return x.Effect + } + return "" +} + +func (x *AttributePolicy) GetConditions() []*AttributeCondition { + if x != nil { + return x.Conditions + } + return nil +} + +func (x *AttributePolicy) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *AttributePolicy) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *AttributePolicy) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type AttributePolicyFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + OwnerPlugin string `protobuf:"bytes,5,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,6,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributePolicyFilter) Reset() { + *x = AttributePolicyFilter{} + mi := &file_internal_contracts_authz_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributePolicyFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributePolicyFilter) ProtoMessage() {} + +func (x *AttributePolicyFilter) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributePolicyFilter.ProtoReflect.Descriptor instead. +func (*AttributePolicyFilter) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{51} +} + +func (x *AttributePolicyFilter) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AttributePolicyFilter) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AttributePolicyFilter) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *AttributePolicyFilter) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *AttributePolicyFilter) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *AttributePolicyFilter) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type AttributeCheckInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,4,opt,name=action,proto3" json:"action,omitempty"` + SubjectAttributes *structpb.Struct `protobuf:"bytes,5,opt,name=subject_attributes,json=subjectAttributes,proto3" json:"subject_attributes,omitempty"` + ResourceAttributes *structpb.Struct `protobuf:"bytes,6,opt,name=resource_attributes,json=resourceAttributes,proto3" json:"resource_attributes,omitempty"` + EnvironmentAttributes *structpb.Struct `protobuf:"bytes,7,opt,name=environment_attributes,json=environmentAttributes,proto3" json:"environment_attributes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributeCheckInput) Reset() { + *x = AttributeCheckInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributeCheckInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributeCheckInput) ProtoMessage() {} + +func (x *AttributeCheckInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributeCheckInput.ProtoReflect.Descriptor instead. +func (*AttributeCheckInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{52} +} + +func (x *AttributeCheckInput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *AttributeCheckInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AttributeCheckInput) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *AttributeCheckInput) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *AttributeCheckInput) GetSubjectAttributes() *structpb.Struct { + if x != nil { + return x.SubjectAttributes + } + return nil +} + +func (x *AttributeCheckInput) GetResourceAttributes() *structpb.Struct { + if x != nil { + return x.ResourceAttributes + } + return nil +} + +func (x *AttributeCheckInput) GetEnvironmentAttributes() *structpb.Struct { + if x != nil { + return x.EnvironmentAttributes + } + return nil +} + +type AttributeCheckOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,3,opt,name=context,proto3" json:"context,omitempty"` + Resource string `protobuf:"bytes,4,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` + MatchedPolicyId string `protobuf:"bytes,6,opt,name=matched_policy_id,json=matchedPolicyId,proto3" json:"matched_policy_id,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AttributeCheckOutput) Reset() { + *x = AttributeCheckOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AttributeCheckOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AttributeCheckOutput) ProtoMessage() {} + +func (x *AttributeCheckOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AttributeCheckOutput.ProtoReflect.Descriptor instead. +func (*AttributeCheckOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{53} +} + +func (x *AttributeCheckOutput) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +func (x *AttributeCheckOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *AttributeCheckOutput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *AttributeCheckOutput) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *AttributeCheckOutput) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *AttributeCheckOutput) GetMatchedPolicyId() string { + if x != nil { + return x.MatchedPolicyId + } + return "" +} + +func (x *AttributeCheckOutput) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *AttributeCheckOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type DeclareAttributesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Attributes []*AttributeDeclaration `protobuf:"bytes,1,rep,name=attributes,proto3" json:"attributes,omitempty"` + OwnerPlugin string `protobuf:"bytes,2,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,3,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeclareAttributesInput) Reset() { + *x = DeclareAttributesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeclareAttributesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeclareAttributesInput) ProtoMessage() {} + +func (x *DeclareAttributesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeclareAttributesInput.ProtoReflect.Descriptor instead. +func (*DeclareAttributesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{54} +} + +func (x *DeclareAttributesInput) GetAttributes() []*AttributeDeclaration { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *DeclareAttributesInput) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *DeclareAttributesInput) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type DeclareAttributesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Registered int32 `protobuf:"varint,1,opt,name=registered,proto3" json:"registered,omitempty"` + Attributes []*AttributeDeclaration `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeclareAttributesOutput) Reset() { + *x = DeclareAttributesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeclareAttributesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeclareAttributesOutput) ProtoMessage() {} + +func (x *DeclareAttributesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[55] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeclareAttributesOutput.ProtoReflect.Descriptor instead. +func (*DeclareAttributesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{55} +} + +func (x *DeclareAttributesOutput) GetRegistered() int32 { + if x != nil { + return x.Registered + } + return 0 +} + +func (x *DeclareAttributesOutput) GetAttributes() []*AttributeDeclaration { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *DeclareAttributesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type UpsertAttributePolicyInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policy *AttributePolicy `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertAttributePolicyInput) Reset() { + *x = UpsertAttributePolicyInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertAttributePolicyInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertAttributePolicyInput) ProtoMessage() {} + +func (x *UpsertAttributePolicyInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertAttributePolicyInput.ProtoReflect.Descriptor instead. +func (*UpsertAttributePolicyInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{56} +} + +func (x *UpsertAttributePolicyInput) GetPolicy() *AttributePolicy { + if x != nil { + return x.Policy + } + return nil +} + +type UpsertAttributePolicyOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Policy *AttributePolicy `protobuf:"bytes,2,opt,name=policy,proto3" json:"policy,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertAttributePolicyOutput) Reset() { + *x = UpsertAttributePolicyOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertAttributePolicyOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertAttributePolicyOutput) ProtoMessage() {} + +func (x *UpsertAttributePolicyOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[57] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertAttributePolicyOutput.ProtoReflect.Descriptor instead. +func (*UpsertAttributePolicyOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{57} +} + +func (x *UpsertAttributePolicyOutput) GetChanged() bool { + if x != nil { + return x.Changed + } + return false +} + +func (x *UpsertAttributePolicyOutput) GetPolicy() *AttributePolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *UpsertAttributePolicyOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ListAttributePoliciesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Filter *AttributePolicyFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAttributePoliciesInput) Reset() { + *x = ListAttributePoliciesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAttributePoliciesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAttributePoliciesInput) ProtoMessage() {} + +func (x *ListAttributePoliciesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[58] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAttributePoliciesInput.ProtoReflect.Descriptor instead. +func (*ListAttributePoliciesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{58} +} + +func (x *ListAttributePoliciesInput) GetFilter() *AttributePolicyFilter { + if x != nil { + return x.Filter + } + return nil +} + +type ListAttributePoliciesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policies []*AttributePolicy `protobuf:"bytes,1,rep,name=policies,proto3" json:"policies,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAttributePoliciesOutput) Reset() { + *x = ListAttributePoliciesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAttributePoliciesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAttributePoliciesOutput) ProtoMessage() {} + +func (x *ListAttributePoliciesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[59] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAttributePoliciesOutput.ProtoReflect.Descriptor instead. +func (*ListAttributePoliciesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{59} +} + +func (x *ListAttributePoliciesOutput) GetPolicies() []*AttributePolicy { + if x != nil { + return x.Policies + } + return nil +} + +func (x *ListAttributePoliciesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type RemoveAttributePolicyInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Filter *AttributePolicyFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveAttributePolicyInput) Reset() { + *x = RemoveAttributePolicyInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveAttributePolicyInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveAttributePolicyInput) ProtoMessage() {} + +func (x *RemoveAttributePolicyInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[60] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveAttributePolicyInput.ProtoReflect.Descriptor instead. +func (*RemoveAttributePolicyInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{60} +} + +func (x *RemoveAttributePolicyInput) GetFilter() *AttributePolicyFilter { + if x != nil { + return x.Filter + } + return nil +} + +type RemoveAttributePolicyOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveAttributePolicyOutput) Reset() { + *x = RemoveAttributePolicyOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveAttributePolicyOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveAttributePolicyOutput) ProtoMessage() {} + +func (x *RemoveAttributePolicyOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveAttributePolicyOutput.ProtoReflect.Descriptor instead. +func (*RemoveAttributePolicyOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{61} +} + +func (x *RemoveAttributePolicyOutput) GetChanged() bool { + if x != nil { + return x.Changed + } + return false +} + +func (x *RemoveAttributePolicyOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type RelationDeclaration struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + SubjectType string `protobuf:"bytes,3,opt,name=subject_type,json=subjectType,proto3" json:"subject_type,omitempty"` + ObjectType string `protobuf:"bytes,4,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"` + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,6,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,7,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + Category string `protobuf:"bytes,8,opt,name=category,proto3" json:"category,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationDeclaration) Reset() { + *x = RelationDeclaration{} + mi := &file_internal_contracts_authz_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationDeclaration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationDeclaration) ProtoMessage() {} + +func (x *RelationDeclaration) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[62] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationDeclaration.ProtoReflect.Descriptor instead. +func (*RelationDeclaration) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{62} +} + +func (x *RelationDeclaration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RelationDeclaration) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *RelationDeclaration) GetSubjectType() string { + if x != nil { + return x.SubjectType + } + return "" +} + +func (x *RelationDeclaration) GetObjectType() string { + if x != nil { + return x.ObjectType + } + return "" +} + +func (x *RelationDeclaration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *RelationDeclaration) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *RelationDeclaration) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +func (x *RelationDeclaration) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type RelationTuple struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,2,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationTuple) Reset() { + *x = RelationTuple{} + mi := &file_internal_contracts_authz_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationTuple) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationTuple) ProtoMessage() {} + +func (x *RelationTuple) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[63] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationTuple.ProtoReflect.Descriptor instead. +func (*RelationTuple) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{63} +} + +func (x *RelationTuple) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationTuple) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationTuple) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *RelationTuple) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type RelationTupleFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,2,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationTupleFilter) Reset() { + *x = RelationTupleFilter{} + mi := &file_internal_contracts_authz_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationTupleFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationTupleFilter) ProtoMessage() {} + +func (x *RelationTupleFilter) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationTupleFilter.ProtoReflect.Descriptor instead. +func (*RelationTupleFilter) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{64} +} + +func (x *RelationTupleFilter) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationTupleFilter) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationTupleFilter) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *RelationTupleFilter) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type RelationCheckInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,2,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationCheckInput) Reset() { + *x = RelationCheckInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationCheckInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationCheckInput) ProtoMessage() {} + +func (x *RelationCheckInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[65] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationCheckInput.ProtoReflect.Descriptor instead. +func (*RelationCheckInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{65} +} + +func (x *RelationCheckInput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationCheckInput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationCheckInput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *RelationCheckInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type RelationCheckOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + Context string `protobuf:"bytes,5,opt,name=context,proto3" json:"context,omitempty"` + Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RelationCheckOutput) Reset() { + *x = RelationCheckOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RelationCheckOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RelationCheckOutput) ProtoMessage() {} + +func (x *RelationCheckOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[66] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RelationCheckOutput.ProtoReflect.Descriptor instead. +func (*RelationCheckOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{66} +} + +func (x *RelationCheckOutput) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +func (x *RelationCheckOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *RelationCheckOutput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *RelationCheckOutput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *RelationCheckOutput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *RelationCheckOutput) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *RelationCheckOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type UpsertRelationTupleInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tuple *RelationTuple `protobuf:"bytes,1,opt,name=tuple,proto3" json:"tuple,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertRelationTupleInput) Reset() { + *x = UpsertRelationTupleInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertRelationTupleInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertRelationTupleInput) ProtoMessage() {} + +func (x *UpsertRelationTupleInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[67] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertRelationTupleInput.ProtoReflect.Descriptor instead. +func (*UpsertRelationTupleInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{67} +} + +func (x *UpsertRelationTupleInput) GetTuple() *RelationTuple { + if x != nil { + return x.Tuple + } + return nil +} + +type UpsertRelationTupleOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Tuple *RelationTuple `protobuf:"bytes,2,opt,name=tuple,proto3" json:"tuple,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpsertRelationTupleOutput) Reset() { + *x = UpsertRelationTupleOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpsertRelationTupleOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpsertRelationTupleOutput) ProtoMessage() {} + +func (x *UpsertRelationTupleOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[68] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpsertRelationTupleOutput.ProtoReflect.Descriptor instead. +func (*UpsertRelationTupleOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{68} +} + +func (x *UpsertRelationTupleOutput) GetChanged() bool { + if x != nil { + return x.Changed + } + return false +} + +func (x *UpsertRelationTupleOutput) GetTuple() *RelationTuple { + if x != nil { + return x.Tuple + } + return nil +} + +func (x *UpsertRelationTupleOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ListRelationTuplesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Filter *RelationTupleFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRelationTuplesInput) Reset() { + *x = ListRelationTuplesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRelationTuplesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRelationTuplesInput) ProtoMessage() {} + +func (x *ListRelationTuplesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRelationTuplesInput.ProtoReflect.Descriptor instead. +func (*ListRelationTuplesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{69} +} + +func (x *ListRelationTuplesInput) GetFilter() *RelationTupleFilter { + if x != nil { + return x.Filter + } + return nil +} + +type ListRelationTuplesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tuples []*RelationTuple `protobuf:"bytes,1,rep,name=tuples,proto3" json:"tuples,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRelationTuplesOutput) Reset() { + *x = ListRelationTuplesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRelationTuplesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRelationTuplesOutput) ProtoMessage() {} + +func (x *ListRelationTuplesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[70] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRelationTuplesOutput.ProtoReflect.Descriptor instead. +func (*ListRelationTuplesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{70} +} + +func (x *ListRelationTuplesOutput) GetTuples() []*RelationTuple { + if x != nil { + return x.Tuples + } + return nil +} + +func (x *ListRelationTuplesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type RemoveRelationTupleInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tuple *RelationTuple `protobuf:"bytes,1,opt,name=tuple,proto3" json:"tuple,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveRelationTupleInput) Reset() { + *x = RemoveRelationTupleInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveRelationTupleInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveRelationTupleInput) ProtoMessage() {} + +func (x *RemoveRelationTupleInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[71] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveRelationTupleInput.ProtoReflect.Descriptor instead. +func (*RemoveRelationTupleInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{71} +} + +func (x *RemoveRelationTupleInput) GetTuple() *RelationTuple { + if x != nil { + return x.Tuple + } + return nil +} + +type RemoveRelationTupleOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveRelationTupleOutput) Reset() { + *x = RemoveRelationTupleOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveRelationTupleOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveRelationTupleOutput) ProtoMessage() {} + +func (x *RemoveRelationTupleOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[72] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveRelationTupleOutput.ProtoReflect.Descriptor instead. +func (*RemoveRelationTupleOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{72} +} + +func (x *RemoveRelationTupleOutput) GetChanged() bool { + if x != nil { + return x.Changed + } + return false +} + +func (x *RemoveRelationTupleOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type UIActionDeclaration struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"` + Route string `protobuf:"bytes,4,opt,name=route,proto3" json:"route,omitempty"` + RequiredScopes []string `protobuf:"bytes,5,rep,name=required_scopes,json=requiredScopes,proto3" json:"required_scopes,omitempty"` + RequiredCapabilities []*CapabilityRequirement `protobuf:"bytes,6,rep,name=required_capabilities,json=requiredCapabilities,proto3" json:"required_capabilities,omitempty"` + Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` + OwnerPlugin string `protobuf:"bytes,8,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,9,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + Category string `protobuf:"bytes,10,opt,name=category,proto3" json:"category,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UIActionDeclaration) Reset() { + *x = UIActionDeclaration{} + mi := &file_internal_contracts_authz_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UIActionDeclaration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UIActionDeclaration) ProtoMessage() {} + +func (x *UIActionDeclaration) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[73] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UIActionDeclaration.ProtoReflect.Descriptor instead. +func (*UIActionDeclaration) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{73} +} + +func (x *UIActionDeclaration) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UIActionDeclaration) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *UIActionDeclaration) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *UIActionDeclaration) GetRoute() string { + if x != nil { + return x.Route + } + return "" +} + +func (x *UIActionDeclaration) GetRequiredScopes() []string { + if x != nil { + return x.RequiredScopes + } + return nil +} + +func (x *UIActionDeclaration) GetRequiredCapabilities() []*CapabilityRequirement { + if x != nil { + return x.RequiredCapabilities + } + return nil +} + +func (x *UIActionDeclaration) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *UIActionDeclaration) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *UIActionDeclaration) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +func (x *UIActionDeclaration) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +type AuthzDeclarationSet struct { + state protoimpl.MessageState `protogen:"open.v1"` + Scopes []*ScopeDeclaration `protobuf:"bytes,1,rep,name=scopes,proto3" json:"scopes,omitempty"` + Resources []*ResourceDeclaration `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"` + Actions []*ActionDeclaration `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"` + Attributes []*AttributeDeclaration `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty"` + Relations []*RelationDeclaration `protobuf:"bytes,5,rep,name=relations,proto3" json:"relations,omitempty"` + UiActions []*UIActionDeclaration `protobuf:"bytes,6,rep,name=ui_actions,json=uiActions,proto3" json:"ui_actions,omitempty"` + OwnerPlugin string `protobuf:"bytes,7,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,8,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuthzDeclarationSet) Reset() { + *x = AuthzDeclarationSet{} + mi := &file_internal_contracts_authz_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthzDeclarationSet) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthzDeclarationSet) ProtoMessage() {} + +func (x *AuthzDeclarationSet) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[74] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthzDeclarationSet.ProtoReflect.Descriptor instead. +func (*AuthzDeclarationSet) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{74} +} + +func (x *AuthzDeclarationSet) GetScopes() []*ScopeDeclaration { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *AuthzDeclarationSet) GetResources() []*ResourceDeclaration { + if x != nil { + return x.Resources + } + return nil +} + +func (x *AuthzDeclarationSet) GetActions() []*ActionDeclaration { + if x != nil { + return x.Actions + } + return nil +} + +func (x *AuthzDeclarationSet) GetAttributes() []*AttributeDeclaration { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *AuthzDeclarationSet) GetRelations() []*RelationDeclaration { + if x != nil { + return x.Relations + } + return nil +} + +func (x *AuthzDeclarationSet) GetUiActions() []*UIActionDeclaration { + if x != nil { + return x.UiActions + } + return nil +} + +func (x *AuthzDeclarationSet) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *AuthzDeclarationSet) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type RegisterDeclarationsInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Declarations *AuthzDeclarationSet `protobuf:"bytes,1,opt,name=declarations,proto3" json:"declarations,omitempty"` + OwnerPlugin string `protobuf:"bytes,2,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,3,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterDeclarationsInput) Reset() { + *x = RegisterDeclarationsInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterDeclarationsInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterDeclarationsInput) ProtoMessage() {} + +func (x *RegisterDeclarationsInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[75] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterDeclarationsInput.ProtoReflect.Descriptor instead. +func (*RegisterDeclarationsInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{75} +} + +func (x *RegisterDeclarationsInput) GetDeclarations() *AuthzDeclarationSet { + if x != nil { + return x.Declarations + } + return nil +} + +func (x *RegisterDeclarationsInput) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *RegisterDeclarationsInput) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type RegisterDeclarationsOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Registered int32 `protobuf:"varint,1,opt,name=registered,proto3" json:"registered,omitempty"` + Declarations *AuthzDeclarationSet `protobuf:"bytes,2,opt,name=declarations,proto3" json:"declarations,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterDeclarationsOutput) Reset() { + *x = RegisterDeclarationsOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterDeclarationsOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterDeclarationsOutput) ProtoMessage() {} + +func (x *RegisterDeclarationsOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[76] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterDeclarationsOutput.ProtoReflect.Descriptor instead. +func (*RegisterDeclarationsOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{76} +} + +func (x *RegisterDeclarationsOutput) GetRegistered() int32 { + if x != nil { + return x.Registered + } + return 0 +} + +func (x *RegisterDeclarationsOutput) GetDeclarations() *AuthzDeclarationSet { + if x != nil { + return x.Declarations + } + return nil +} + +func (x *RegisterDeclarationsOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ListDeclarationsInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Context string `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + OwnerPlugin string `protobuf:"bytes,2,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,3,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListDeclarationsInput) Reset() { + *x = ListDeclarationsInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListDeclarationsInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDeclarationsInput) ProtoMessage() {} + +func (x *ListDeclarationsInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[77] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDeclarationsInput.ProtoReflect.Descriptor instead. +func (*ListDeclarationsInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{77} +} + +func (x *ListDeclarationsInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ListDeclarationsInput) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *ListDeclarationsInput) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type ListDeclarationsOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Declarations *AuthzDeclarationSet `protobuf:"bytes,1,opt,name=declarations,proto3" json:"declarations,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListDeclarationsOutput) Reset() { + *x = ListDeclarationsOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[78] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListDeclarationsOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDeclarationsOutput) ProtoMessage() {} + +func (x *ListDeclarationsOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[78] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDeclarationsOutput.ProtoReflect.Descriptor instead. +func (*ListDeclarationsOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{78} +} + +func (x *ListDeclarationsOutput) GetDeclarations() *AuthzDeclarationSet { + if x != nil { + return x.Declarations + } + return nil +} + +func (x *ListDeclarationsOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ResolveProjectionInputsInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Context string `protobuf:"bytes,1,opt,name=context,proto3" json:"context,omitempty"` + OwnerPlugin string `protobuf:"bytes,2,opt,name=owner_plugin,json=ownerPlugin,proto3" json:"owner_plugin,omitempty"` + OwnerModule string `protobuf:"bytes,3,opt,name=owner_module,json=ownerModule,proto3" json:"owner_module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResolveProjectionInputsInput) Reset() { + *x = ResolveProjectionInputsInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[79] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResolveProjectionInputsInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResolveProjectionInputsInput) ProtoMessage() {} + +func (x *ResolveProjectionInputsInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[79] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResolveProjectionInputsInput.ProtoReflect.Descriptor instead. +func (*ResolveProjectionInputsInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{79} +} + +func (x *ResolveProjectionInputsInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ResolveProjectionInputsInput) GetOwnerPlugin() string { + if x != nil { + return x.OwnerPlugin + } + return "" +} + +func (x *ResolveProjectionInputsInput) GetOwnerModule() string { + if x != nil { + return x.OwnerModule + } + return "" +} + +type ProjectionInputs struct { + state protoimpl.MessageState `protogen:"open.v1"` + ScopeNames []string `protobuf:"bytes,1,rep,name=scope_names,json=scopeNames,proto3" json:"scope_names,omitempty"` + ResourceNames []string `protobuf:"bytes,2,rep,name=resource_names,json=resourceNames,proto3" json:"resource_names,omitempty"` + ActionNames []string `protobuf:"bytes,3,rep,name=action_names,json=actionNames,proto3" json:"action_names,omitempty"` + AttributeNames []string `protobuf:"bytes,4,rep,name=attribute_names,json=attributeNames,proto3" json:"attribute_names,omitempty"` + RelationNames []string `protobuf:"bytes,5,rep,name=relation_names,json=relationNames,proto3" json:"relation_names,omitempty"` + UiActionIds []string `protobuf:"bytes,6,rep,name=ui_action_ids,json=uiActionIds,proto3" json:"ui_action_ids,omitempty"` + LookupSourceIds []string `protobuf:"bytes,7,rep,name=lookup_source_ids,json=lookupSourceIds,proto3" json:"lookup_source_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProjectionInputs) Reset() { + *x = ProjectionInputs{} + mi := &file_internal_contracts_authz_proto_msgTypes[80] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProjectionInputs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProjectionInputs) ProtoMessage() {} + +func (x *ProjectionInputs) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[80] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProjectionInputs.ProtoReflect.Descriptor instead. +func (*ProjectionInputs) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{80} +} + +func (x *ProjectionInputs) GetScopeNames() []string { + if x != nil { + return x.ScopeNames + } + return nil +} + +func (x *ProjectionInputs) GetResourceNames() []string { + if x != nil { + return x.ResourceNames + } + return nil +} + +func (x *ProjectionInputs) GetActionNames() []string { + if x != nil { + return x.ActionNames + } + return nil +} + +func (x *ProjectionInputs) GetAttributeNames() []string { + if x != nil { + return x.AttributeNames + } + return nil +} + +func (x *ProjectionInputs) GetRelationNames() []string { + if x != nil { + return x.RelationNames + } + return nil +} + +func (x *ProjectionInputs) GetUiActionIds() []string { + if x != nil { + return x.UiActionIds + } + return nil +} + +func (x *ProjectionInputs) GetLookupSourceIds() []string { + if x != nil { + return x.LookupSourceIds + } + return nil +} + +type ResolveProjectionInputsOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Projection *ProjectionInputs `protobuf:"bytes,1,opt,name=projection,proto3" json:"projection,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResolveProjectionInputsOutput) Reset() { + *x = ResolveProjectionInputsOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[81] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResolveProjectionInputsOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResolveProjectionInputsOutput) ProtoMessage() {} + +func (x *ResolveProjectionInputsOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[81] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResolveProjectionInputsOutput.ProtoReflect.Descriptor instead. +func (*ResolveProjectionInputsOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{81} +} + +func (x *ResolveProjectionInputsOutput) GetProjection() *ProjectionInputs { + if x != nil { + return x.Projection + } + return nil +} + +func (x *ResolveProjectionInputsOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ResolveSubjectScopesInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + DirectScopes []string `protobuf:"bytes,2,rep,name=direct_scopes,json=directScopes,proto3" json:"direct_scopes,omitempty"` + RoleScopes []string `protobuf:"bytes,3,rep,name=role_scopes,json=roleScopes,proto3" json:"role_scopes,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResolveSubjectScopesInput) Reset() { + *x = ResolveSubjectScopesInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[82] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResolveSubjectScopesInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResolveSubjectScopesInput) ProtoMessage() {} + +func (x *ResolveSubjectScopesInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[82] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResolveSubjectScopesInput.ProtoReflect.Descriptor instead. +func (*ResolveSubjectScopesInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{82} +} + +func (x *ResolveSubjectScopesInput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *ResolveSubjectScopesInput) GetDirectScopes() []string { + if x != nil { + return x.DirectScopes + } + return nil +} + +func (x *ResolveSubjectScopesInput) GetRoleScopes() []string { + if x != nil { + return x.RoleScopes + } + return nil +} + +func (x *ResolveSubjectScopesInput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type ResolveSubjectScopesOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Scopes []string `protobuf:"bytes,2,rep,name=scopes,proto3" json:"scopes,omitempty"` + DeclaredScopes []*ScopeDeclaration `protobuf:"bytes,3,rep,name=declared_scopes,json=declaredScopes,proto3" json:"declared_scopes,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResolveSubjectScopesOutput) Reset() { + *x = ResolveSubjectScopesOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -type SubjectObjectActionOutput struct { +func (x *ResolveSubjectScopesOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResolveSubjectScopesOutput) ProtoMessage() {} + +func (x *ResolveSubjectScopesOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[83] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResolveSubjectScopesOutput.ProtoReflect.Descriptor instead. +func (*ResolveSubjectScopesOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{83} +} + +func (x *ResolveSubjectScopesOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *ResolveSubjectScopesOutput) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +func (x *ResolveSubjectScopesOutput) GetDeclaredScopes() []*ScopeDeclaration { + if x != nil { + return x.DeclaredScopes + } + return nil +} + +func (x *ResolveSubjectScopesOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type RoleScopeGrant struct { state protoimpl.MessageState `protogen:"open.v1"` - Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` - Changed bool `protobuf:"varint,2,opt,name=changed,proto3" json:"changed,omitempty"` - Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` - Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` - Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` - Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Scopes []string `protobuf:"bytes,3,rep,name=scopes,proto3" json:"scopes,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *SubjectObjectActionOutput) Reset() { - *x = SubjectObjectActionOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[20] +func (x *RoleScopeGrant) Reset() { + *x = RoleScopeGrant{} + mi := &file_internal_contracts_authz_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *SubjectObjectActionOutput) String() string { +func (x *RoleScopeGrant) String() string { return protoimpl.X.MessageStringOf(x) } -func (*SubjectObjectActionOutput) ProtoMessage() {} +func (*RoleScopeGrant) ProtoMessage() {} -func (x *SubjectObjectActionOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[20] +func (x *RoleScopeGrant) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1379,77 +5993,186 @@ func (x *SubjectObjectActionOutput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use SubjectObjectActionOutput.ProtoReflect.Descriptor instead. -func (*SubjectObjectActionOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{20} +// Deprecated: Use RoleScopeGrant.ProtoReflect.Descriptor instead. +func (*RoleScopeGrant) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{84} } -func (x *SubjectObjectActionOutput) GetAllowed() bool { +func (x *RoleScopeGrant) GetRole() string { if x != nil { - return x.Allowed + return x.Role } - return false + return "" } -func (x *SubjectObjectActionOutput) GetChanged() bool { +func (x *RoleScopeGrant) GetContext() string { if x != nil { - return x.Changed + return x.Context } - return false + return "" } -func (x *SubjectObjectActionOutput) GetSubject() string { +func (x *RoleScopeGrant) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +type SubjectRoleAssignment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` + Context string `protobuf:"bytes,3,opt,name=context,proto3" json:"context,omitempty"` + DirectScopes []string `protobuf:"bytes,4,rep,name=direct_scopes,json=directScopes,proto3" json:"direct_scopes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubjectRoleAssignment) Reset() { + *x = SubjectRoleAssignment{} + mi := &file_internal_contracts_authz_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubjectRoleAssignment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubjectRoleAssignment) ProtoMessage() {} + +func (x *SubjectRoleAssignment) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[85] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubjectRoleAssignment.ProtoReflect.Descriptor instead. +func (*SubjectRoleAssignment) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{85} +} + +func (x *SubjectRoleAssignment) GetSubject() string { if x != nil { return x.Subject } return "" } -func (x *SubjectObjectActionOutput) GetObject() string { +func (x *SubjectRoleAssignment) GetRole() string { if x != nil { - return x.Object + return x.Role } return "" } -func (x *SubjectObjectActionOutput) GetAction() string { +func (x *SubjectRoleAssignment) GetContext() string { if x != nil { - return x.Action + return x.Context } return "" } -func (x *SubjectObjectActionOutput) GetError() string { +func (x *SubjectRoleAssignment) GetDirectScopes() []string { if x != nil { - return x.Error + return x.DirectScopes + } + return nil +} + +type AssignmentFilter struct { + state protoimpl.MessageState `protogen:"open.v1"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` + Context string `protobuf:"bytes,3,opt,name=context,proto3" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssignmentFilter) Reset() { + *x = AssignmentFilter{} + mi := &file_internal_contracts_authz_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssignmentFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssignmentFilter) ProtoMessage() {} + +func (x *AssignmentFilter) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[86] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssignmentFilter.ProtoReflect.Descriptor instead. +func (*AssignmentFilter) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{86} +} + +func (x *AssignmentFilter) GetSubject() string { + if x != nil { + return x.Subject } return "" } -type ListConfig struct { +func (x *AssignmentFilter) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *AssignmentFilter) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +type ScopeCheckInput struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Filter string `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` - Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,2,opt,name=context,proto3" json:"context,omitempty"` + Scope string `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"` + Resource string `protobuf:"bytes,4,opt,name=resource,proto3" json:"resource,omitempty"` + Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListConfig) Reset() { - *x = ListConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[21] +func (x *ScopeCheckInput) Reset() { + *x = ScopeCheckInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListConfig) String() string { +func (x *ScopeCheckInput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListConfig) ProtoMessage() {} +func (*ScopeCheckInput) ProtoMessage() {} -func (x *ListConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[21] +func (x *ScopeCheckInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1460,56 +6183,176 @@ func (x *ListConfig) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListConfig.ProtoReflect.Descriptor instead. -func (*ListConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{21} +// Deprecated: Use ScopeCheckInput.ProtoReflect.Descriptor instead. +func (*ScopeCheckInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{87} } -func (x *ListConfig) GetModule() string { +func (x *ScopeCheckInput) GetSubject() string { if x != nil { - return x.Module + return x.Subject } return "" } -func (x *ListConfig) GetFilter() string { +func (x *ScopeCheckInput) GetContext() string { if x != nil { - return x.Filter + return x.Context } return "" } -func (x *ListConfig) GetValue() string { +func (x *ScopeCheckInput) GetScope() string { if x != nil { - return x.Value + return x.Scope } return "" } -type ListInput struct { +func (x *ScopeCheckInput) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *ScopeCheckInput) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +type ScopeCheckOutput struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Filter string `protobuf:"bytes,2,opt,name=filter,proto3" json:"filter,omitempty"` - Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"` + Context string `protobuf:"bytes,4,opt,name=context,proto3" json:"context,omitempty"` + Scope string `protobuf:"bytes,5,opt,name=scope,proto3" json:"scope,omitempty"` + MatchedRole string `protobuf:"bytes,6,opt,name=matched_role,json=matchedRole,proto3" json:"matched_role,omitempty"` + MatchedScopes []string `protobuf:"bytes,7,rep,name=matched_scopes,json=matchedScopes,proto3" json:"matched_scopes,omitempty"` + Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScopeCheckOutput) Reset() { + *x = ScopeCheckOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[88] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScopeCheckOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScopeCheckOutput) ProtoMessage() {} + +func (x *ScopeCheckOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[88] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScopeCheckOutput.ProtoReflect.Descriptor instead. +func (*ScopeCheckOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{88} +} + +func (x *ScopeCheckOutput) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +func (x *ScopeCheckOutput) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ScopeCheckOutput) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + +func (x *ScopeCheckOutput) GetContext() string { + if x != nil { + return x.Context + } + return "" +} + +func (x *ScopeCheckOutput) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +func (x *ScopeCheckOutput) GetMatchedRole() string { + if x != nil { + return x.MatchedRole + } + return "" +} + +func (x *ScopeCheckOutput) GetMatchedScopes() []string { + if x != nil { + return x.MatchedScopes + } + return nil +} + +func (x *ScopeCheckOutput) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *ScopeCheckOutput) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type UpsertRoleInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Grant *RoleScopeGrant `protobuf:"bytes,1,opt,name=grant,proto3" json:"grant,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ListInput) Reset() { - *x = ListInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[22] +func (x *UpsertRoleInput) Reset() { + *x = UpsertRoleInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ListInput) String() string { +func (x *UpsertRoleInput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListInput) ProtoMessage() {} +func (*UpsertRoleInput) ProtoMessage() {} -func (x *ListInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[22] +func (x *UpsertRoleInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1520,55 +6363,42 @@ func (x *ListInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListInput.ProtoReflect.Descriptor instead. -func (*ListInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{22} -} - -func (x *ListInput) GetModule() string { - if x != nil { - return x.Module - } - return "" -} - -func (x *ListInput) GetFilter() string { - if x != nil { - return x.Filter - } - return "" +// Deprecated: Use UpsertRoleInput.ProtoReflect.Descriptor instead. +func (*UpsertRoleInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{89} } -func (x *ListInput) GetValue() string { +func (x *UpsertRoleInput) GetGrant() *RoleScopeGrant { if x != nil { - return x.Value + return x.Grant } - return "" + return nil } -type GenericStepOutput struct { +type UpsertRoleOutput struct { state protoimpl.MessageState `protogen:"open.v1"` - Output *structpb.Struct `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Grant *RoleScopeGrant `protobuf:"bytes,2,opt,name=grant,proto3" json:"grant,omitempty"` Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GenericStepOutput) Reset() { - *x = GenericStepOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[23] +func (x *UpsertRoleOutput) Reset() { + *x = UpsertRoleOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GenericStepOutput) String() string { +func (x *UpsertRoleOutput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GenericStepOutput) ProtoMessage() {} +func (*UpsertRoleOutput) ProtoMessage() {} -func (x *GenericStepOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[23] +func (x *UpsertRoleOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1579,50 +6409,54 @@ func (x *GenericStepOutput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GenericStepOutput.ProtoReflect.Descriptor instead. -func (*GenericStepOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{23} +// Deprecated: Use UpsertRoleOutput.ProtoReflect.Descriptor instead. +func (*UpsertRoleOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{90} } -func (x *GenericStepOutput) GetOutput() *structpb.Struct { +func (x *UpsertRoleOutput) GetChanged() bool { if x != nil { - return x.Output + return x.Changed + } + return false +} + +func (x *UpsertRoleOutput) GetGrant() *RoleScopeGrant { + if x != nil { + return x.Grant } return nil } -func (x *GenericStepOutput) GetError() string { +func (x *UpsertRoleOutput) GetError() string { if x != nil { return x.Error } return "" } -type RelationConfig struct { +type AssignRoleInput struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` - Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + Assignment *SubjectRoleAssignment `protobuf:"bytes,1,opt,name=assignment,proto3" json:"assignment,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RelationConfig) Reset() { - *x = RelationConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[24] +func (x *AssignRoleInput) Reset() { + *x = AssignRoleInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RelationConfig) String() string { +func (x *AssignRoleInput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RelationConfig) ProtoMessage() {} +func (*AssignRoleInput) ProtoMessage() {} -func (x *RelationConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[24] +func (x *AssignRoleInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1633,64 +6467,42 @@ func (x *RelationConfig) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RelationConfig.ProtoReflect.Descriptor instead. -func (*RelationConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{24} -} - -func (x *RelationConfig) GetModule() string { - if x != nil { - return x.Module - } - return "" -} - -func (x *RelationConfig) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *RelationConfig) GetRelation() string { - if x != nil { - return x.Relation - } - return "" +// Deprecated: Use AssignRoleInput.ProtoReflect.Descriptor instead. +func (*AssignRoleInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{91} } -func (x *RelationConfig) GetObject() string { +func (x *AssignRoleInput) GetAssignment() *SubjectRoleAssignment { if x != nil { - return x.Object + return x.Assignment } - return "" + return nil } -type RelationInput struct { +type AssignRoleOutput struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` - Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Assignment *SubjectRoleAssignment `protobuf:"bytes,2,opt,name=assignment,proto3" json:"assignment,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RelationInput) Reset() { - *x = RelationInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[25] +func (x *AssignRoleOutput) Reset() { + *x = AssignRoleOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RelationInput) String() string { +func (x *AssignRoleOutput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RelationInput) ProtoMessage() {} +func (*AssignRoleOutput) ProtoMessage() {} -func (x *RelationInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[25] +func (x *AssignRoleOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1701,65 +6513,54 @@ func (x *RelationInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RelationInput.ProtoReflect.Descriptor instead. -func (*RelationInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{25} -} - -func (x *RelationInput) GetModule() string { - if x != nil { - return x.Module - } - return "" +// Deprecated: Use AssignRoleOutput.ProtoReflect.Descriptor instead. +func (*AssignRoleOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{92} } -func (x *RelationInput) GetSubject() string { +func (x *AssignRoleOutput) GetChanged() bool { if x != nil { - return x.Subject + return x.Changed } - return "" + return false } -func (x *RelationInput) GetRelation() string { +func (x *AssignRoleOutput) GetAssignment() *SubjectRoleAssignment { if x != nil { - return x.Relation + return x.Assignment } - return "" + return nil } -func (x *RelationInput) GetObject() string { +func (x *AssignRoleOutput) GetError() string { if x != nil { - return x.Object + return x.Error } return "" } -type RelationOutput struct { +type ListRoleAssignmentsInput struct { state protoimpl.MessageState `protogen:"open.v1"` - Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Relation string `protobuf:"bytes,3,opt,name=relation,proto3" json:"relation,omitempty"` - Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` - Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + Filter *AssignmentFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *RelationOutput) Reset() { - *x = RelationOutput{} - mi := &file_internal_contracts_authz_proto_msgTypes[26] +func (x *ListRoleAssignmentsInput) Reset() { + *x = ListRoleAssignmentsInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *RelationOutput) String() string { +func (x *ListRoleAssignmentsInput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RelationOutput) ProtoMessage() {} +func (*ListRoleAssignmentsInput) ProtoMessage() {} -func (x *RelationOutput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[26] +func (x *ListRoleAssignmentsInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1770,69 +6571,92 @@ func (x *RelationOutput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RelationOutput.ProtoReflect.Descriptor instead. -func (*RelationOutput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{26} +// Deprecated: Use ListRoleAssignmentsInput.ProtoReflect.Descriptor instead. +func (*ListRoleAssignmentsInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{93} } -func (x *RelationOutput) GetChanged() bool { +func (x *ListRoleAssignmentsInput) GetFilter() *AssignmentFilter { if x != nil { - return x.Changed + return x.Filter } - return false + return nil } -func (x *RelationOutput) GetSubject() string { - if x != nil { - return x.Subject - } - return "" +type ListRoleAssignmentsOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Assignments []*SubjectRoleAssignment `protobuf:"bytes,1,rep,name=assignments,proto3" json:"assignments,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } -func (x *RelationOutput) GetRelation() string { +func (x *ListRoleAssignmentsOutput) Reset() { + *x = ListRoleAssignmentsOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRoleAssignmentsOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleAssignmentsOutput) ProtoMessage() {} + +func (x *ListRoleAssignmentsOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[94] if x != nil { - return x.Relation + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms } - return "" + return mi.MessageOf(x) } -func (x *RelationOutput) GetObject() string { +// Deprecated: Use ListRoleAssignmentsOutput.ProtoReflect.Descriptor instead. +func (*ListRoleAssignmentsOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{94} +} + +func (x *ListRoleAssignmentsOutput) GetAssignments() []*SubjectRoleAssignment { if x != nil { - return x.Object + return x.Assignments } - return "" + return nil } -func (x *RelationOutput) GetError() string { +func (x *ListRoleAssignmentsOutput) GetError() string { if x != nil { return x.Error } return "" } -type PermitStepConfig struct { +type RemoveRoleAssignmentInput struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Values *structpb.Struct `protobuf:"bytes,2,opt,name=values,proto3" json:"values,omitempty"` + Assignment *SubjectRoleAssignment `protobuf:"bytes,1,opt,name=assignment,proto3" json:"assignment,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *PermitStepConfig) Reset() { - *x = PermitStepConfig{} - mi := &file_internal_contracts_authz_proto_msgTypes[27] +func (x *RemoveRoleAssignmentInput) Reset() { + *x = RemoveRoleAssignmentInput{} + mi := &file_internal_contracts_authz_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *PermitStepConfig) String() string { +func (x *RemoveRoleAssignmentInput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PermitStepConfig) ProtoMessage() {} +func (*RemoveRoleAssignmentInput) ProtoMessage() {} -func (x *PermitStepConfig) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[27] +func (x *RemoveRoleAssignmentInput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1843,48 +6667,41 @@ func (x *PermitStepConfig) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PermitStepConfig.ProtoReflect.Descriptor instead. -func (*PermitStepConfig) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{27} -} - -func (x *PermitStepConfig) GetModule() string { - if x != nil { - return x.Module - } - return "" +// Deprecated: Use RemoveRoleAssignmentInput.ProtoReflect.Descriptor instead. +func (*RemoveRoleAssignmentInput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{95} } -func (x *PermitStepConfig) GetValues() *structpb.Struct { +func (x *RemoveRoleAssignmentInput) GetAssignment() *SubjectRoleAssignment { if x != nil { - return x.Values + return x.Assignment } return nil } -type PermitStepInput struct { +type RemoveRoleAssignmentOutput struct { state protoimpl.MessageState `protogen:"open.v1"` - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - Values *structpb.Struct `protobuf:"bytes,2,opt,name=values,proto3" json:"values,omitempty"` + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + Error string `protobuf:"bytes,100,opt,name=error,proto3" json:"error,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *PermitStepInput) Reset() { - *x = PermitStepInput{} - mi := &file_internal_contracts_authz_proto_msgTypes[28] +func (x *RemoveRoleAssignmentOutput) Reset() { + *x = RemoveRoleAssignmentOutput{} + mi := &file_internal_contracts_authz_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *PermitStepInput) String() string { +func (x *RemoveRoleAssignmentOutput) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PermitStepInput) ProtoMessage() {} +func (*RemoveRoleAssignmentOutput) ProtoMessage() {} -func (x *PermitStepInput) ProtoReflect() protoreflect.Message { - mi := &file_internal_contracts_authz_proto_msgTypes[28] +func (x *RemoveRoleAssignmentOutput) ProtoReflect() protoreflect.Message { + mi := &file_internal_contracts_authz_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1895,23 +6712,23 @@ func (x *PermitStepInput) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PermitStepInput.ProtoReflect.Descriptor instead. -func (*PermitStepInput) Descriptor() ([]byte, []int) { - return file_internal_contracts_authz_proto_rawDescGZIP(), []int{28} +// Deprecated: Use RemoveRoleAssignmentOutput.ProtoReflect.Descriptor instead. +func (*RemoveRoleAssignmentOutput) Descriptor() ([]byte, []int) { + return file_internal_contracts_authz_proto_rawDescGZIP(), []int{96} } -func (x *PermitStepInput) GetModule() string { +func (x *RemoveRoleAssignmentOutput) GetChanged() bool { if x != nil { - return x.Module + return x.Changed } - return "" + return false } -func (x *PermitStepInput) GetValues() *structpb.Struct { +func (x *RemoveRoleAssignmentOutput) GetError() string { if x != nil { - return x.Values + return x.Error } - return nil + return "" } var File_internal_contracts_authz_proto protoreflect.FileDescriptor @@ -1946,7 +6763,10 @@ const file_internal_contracts_authz_proto_rawDesc = "" + "\apdp_url\x18\x02 \x01(\tR\x06pdpUrl\x12\x17\n" + "\aapi_url\x18\x03 \x01(\tR\x06apiUrl\x12\x18\n" + "\aproject\x18\x04 \x01(\tR\aproject\x12 \n" + - "\venvironment\x18\x05 \x01(\tR\venvironment\"4\n" + + "\venvironment\x18\x05 \x01(\tR\venvironment\"J\n" + + "\x10KetoModuleConfig\x12\x19\n" + + "\bread_url\x18\x01 \x01(\tR\areadUrl\x12\x1b\n" + + "\twrite_url\x18\x02 \x01(\tR\bwriteUrl\"4\n" + "\n" + "ExtraField\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + @@ -2003,12 +6823,86 @@ const file_internal_contracts_authz_proto_rawDesc = "" + "\bprovider\x18\x02 \x01(\tR\bprovider\"G\n" + "\x11CapabilitiesInput\x12\x16\n" + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + - "\bprovider\x18\x02 \x01(\tR\bprovider\"\x82\x01\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\"\xa0\x02\n" + "\x12CapabilitiesOutput\x12\x16\n" + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + "\bprovider\x18\x02 \x01(\tR\bprovider\x12\"\n" + - "\fcapabilities\x18\x03 \x03(\tR\fcapabilities\x12\x14\n" + - "\x05error\x18d \x01(\tR\x05error\"}\n" + + "\fcapabilities\x18\x03 \x03(\tR\fcapabilities\x12Q\n" + + "\vdescriptors\x18\x04 \x03(\v2/.workflow.plugins.authz.v1.CapabilityDescriptorR\vdescriptors\x12\x16\n" + + "\x06health\x18\x05 \x01(\tR\x06health\x121\n" + + "\x14missing_requirements\x18\x06 \x03(\tR\x13missingRequirements\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\x9a\x02\n" + + "\x14CapabilityDescriptor\x128\n" + + "\x04mode\x18\x01 \x01(\x0e2$.workflow.plugins.authz.v1.AuthzModeR\x04mode\x12I\n" + + "\n" + + "operations\x18\x02 \x03(\x0e2).workflow.plugins.authz.v1.AuthzOperationR\n" + + "operations\x12\x1e\n" + + "\n" + + "configured\x18\x03 \x01(\bR\n" + + "configured\x12\x16\n" + + "\x06source\x18\x04 \x01(\tR\x06source\x12\x16\n" + + "\x06health\x18\x05 \x01(\tR\x06health\x12-\n" + + "\x12unsupported_reason\x18\x06 \x01(\tR\x11unsupportedReason\"\x9c\x01\n" + + "\x15CapabilityRequirement\x128\n" + + "\x04mode\x18\x01 \x01(\x0e2$.workflow.plugins.authz.v1.AuthzModeR\x04mode\x12I\n" + + "\n" + + "operations\x18\x02 \x03(\x0e2).workflow.plugins.authz.v1.AuthzOperationR\n" + + "operations\"\xa5\x01\n" + + "\x19ProviderCapabilitiesInput\x12\x16\n" + + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12T\n" + + "\frequirements\x18\x03 \x03(\v20.workflow.plugins.authz.v1.CapabilityRequirementR\frequirements\"\xa8\x02\n" + + "\x1aProviderCapabilitiesOutput\x12\x16\n" + + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12\"\n" + + "\fcapabilities\x18\x03 \x03(\tR\fcapabilities\x12Q\n" + + "\vdescriptors\x18\x04 \x03(\v2/.workflow.plugins.authz.v1.CapabilityDescriptorR\vdescriptors\x12\x16\n" + + "\x06health\x18\x05 \x01(\tR\x06health\x121\n" + + "\x14missing_requirements\x18\x06 \x03(\tR\x13missingRequirements\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\xbf\x02\n" + + "\x1bAuthorizationDecisionConfig\x12\x16\n" + + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x128\n" + + "\x04mode\x18\x03 \x01(\x0e2$.workflow.plugins.authz.v1.AuthzModeR\x04mode\x12\x18\n" + + "\asubject\x18\x04 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x05 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x06 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\a \x01(\tR\x06action\x12\x14\n" + + "\x05scope\x18\b \x01(\tR\x05scope\x12\x1a\n" + + "\brelation\x18\t \x01(\tR\brelation\x12\x18\n" + + "\aexplain\x18\n" + + " \x01(\bR\aexplain\"\xa0\x04\n" + + "\x1aAuthorizationDecisionInput\x12\x16\n" + + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x128\n" + + "\x04mode\x18\x03 \x01(\x0e2$.workflow.plugins.authz.v1.AuthzModeR\x04mode\x12\x18\n" + + "\asubject\x18\x04 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x05 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x06 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\a \x01(\tR\x06action\x12\x14\n" + + "\x05scope\x18\b \x01(\tR\x05scope\x12\x1a\n" + + "\brelation\x18\t \x01(\tR\brelation\x12F\n" + + "\x12subject_attributes\x18\n" + + " \x01(\v2\x17.google.protobuf.StructR\x11subjectAttributes\x12H\n" + + "\x13resource_attributes\x18\v \x01(\v2\x17.google.protobuf.StructR\x12resourceAttributes\x12N\n" + + "\x16environment_attributes\x18\f \x01(\v2\x17.google.protobuf.StructR\x15environmentAttributes\x12\x18\n" + + "\aexplain\x18\r \x01(\bR\aexplain\"\xed\x01\n" + + "\x1bAuthorizationDecisionOutput\x12\x18\n" + + "\aallowed\x18\x01 \x01(\bR\aallowed\x128\n" + + "\x04mode\x18\x02 \x01(\x0e2$.workflow.plugins.authz.v1.AuthzModeR\x04mode\x12\x18\n" + + "\asubject\x18\x03 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\x12\x16\n" + + "\x06reason\x18\x05 \x01(\tR\x06reason\x12\x18\n" + + "\aexplain\x18\x06 \x01(\tR\aexplain\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\xa5\x01\n" + + "\x19RequireCapabilitiesConfig\x12\x16\n" + + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12T\n" + + "\frequirements\x18\x03 \x03(\v20.workflow.plugins.authz.v1.CapabilityRequirementR\frequirements\"\xa4\x01\n" + + "\x18RequireCapabilitiesInput\x12\x16\n" + + "\x06module\x18\x01 \x01(\tR\x06module\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12T\n" + + "\frequirements\x18\x03 \x03(\v20.workflow.plugins.authz.v1.CapabilityRequirementR\frequirements\"}\n" + "\x19SubjectObjectActionConfig\x12\x16\n" + "\x06module\x18\x01 \x01(\tR\x06module\x12\x18\n" + "\asubject\x18\x02 \x01(\tR\asubject\x12\x16\n" + @@ -2059,7 +6953,329 @@ const file_internal_contracts_authz_proto_rawDesc = "" + "\x06values\x18\x02 \x01(\v2\x17.google.protobuf.StructR\x06values\"Z\n" + "\x0fPermitStepInput\x12\x16\n" + "\x06module\x18\x01 \x01(\tR\x06module\x12/\n" + - "\x06values\x18\x02 \x01(\v2\x17.google.protobuf.StructR\x06valuesBAZ?github.com/GoCodeAlone/workflow-plugin-authz/internal/contractsb\x06proto3" + "\x06values\x18\x02 \x01(\v2\x17.google.protobuf.StructR\x06values\"\xfa\x01\n" + + "\x10ScopeDeclaration\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x03 \x01(\tR\bresource\x12\x18\n" + + "\aactions\x18\x04 \x03(\tR\aactions\x12 \n" + + "\vdescription\x18\x05 \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\x06 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\a \x01(\tR\vownerModule\x12\x1a\n" + + "\bcategory\x18\b \x01(\tR\bcategory\"\xeb\x01\n" + + "\x12ScopeCatalogConfig\x12C\n" + + "\x06scopes\x18\x01 \x03(\v2+.workflow.plugins.authz.v1.ScopeDeclarationR\x06scopes\x12<\n" + + "\x1aallow_runtime_registration\x18\x02 \x01(\bR\x18allowRuntimeRegistration\x12R\n" + + "\fdeclarations\x18\x03 \x01(\v2..workflow.plugins.authz.v1.AuthzDeclarationSetR\fdeclarations\"\xa0\x01\n" + + "\x13RegisterScopesInput\x12C\n" + + "\x06scopes\x18\x01 \x03(\v2+.workflow.plugins.authz.v1.ScopeDeclarationR\x06scopes\x12!\n" + + "\fowner_plugin\x18\x02 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x03 \x01(\tR\vownerModule\"\x91\x01\n" + + "\x14RegisterScopesOutput\x12\x1e\n" + + "\n" + + "registered\x18\x01 \x01(\x05R\n" + + "registered\x12C\n" + + "\x06scopes\x18\x02 \x03(\v2+.workflow.plugins.authz.v1.ScopeDeclarationR\x06scopes\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"q\n" + + "\x0fListScopesInput\x12\x18\n" + + "\acontext\x18\x01 \x01(\tR\acontext\x12!\n" + + "\fowner_plugin\x18\x02 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x03 \x01(\tR\vownerModule\"m\n" + + "\x10ListScopesOutput\x12C\n" + + "\x06scopes\x18\x01 \x03(\v2+.workflow.plugins.authz.v1.ScopeDeclarationR\x06scopes\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\x94\x02\n" + + "\x13ResourceDeclaration\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12!\n" + + "\fdisplay_name\x18\x03 \x01(\tR\vdisplayName\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\x05 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x06 \x01(\tR\vownerModule\x12\x1a\n" + + "\bcategory\x18\a \x01(\tR\bcategory\x12(\n" + + "\x10lookup_source_id\x18\b \x01(\tR\x0elookupSourceId\"\xe1\x01\n" + + "\x11ActionDeclaration\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x03 \x01(\tR\bresource\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\x05 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x06 \x01(\tR\vownerModule\x12\x1a\n" + + "\bcategory\x18\a \x01(\tR\bcategory\"<\n" + + "\x0eAttributeValue\x12\x14\n" + + "\x05value\x18\x01 \x01(\tR\x05value\x12\x14\n" + + "\x05label\x18\x02 \x01(\tR\x05label\"\xf9\x02\n" + + "\x14AttributeDeclaration\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x16\n" + + "\x06target\x18\x03 \x01(\tR\x06target\x12\x1b\n" + + "\tdata_type\x18\x04 \x01(\tR\bdataType\x12P\n" + + "\x0eallowed_values\x18\x05 \x03(\v2).workflow.plugins.authz.v1.AttributeValueR\rallowedValues\x12(\n" + + "\x10lookup_source_id\x18\x06 \x01(\tR\x0elookupSourceId\x12 \n" + + "\vdescription\x18\a \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\b \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\t \x01(\tR\vownerModule\x12\x1a\n" + + "\bcategory\x18\n" + + " \x01(\tR\bcategory\"~\n" + + "\x12AttributeCondition\x12\x16\n" + + "\x06target\x18\x01 \x01(\tR\x06target\x12\x1c\n" + + "\tattribute\x18\x02 \x01(\tR\tattribute\x12\x1a\n" + + "\boperator\x18\x03 \x01(\tR\boperator\x12\x16\n" + + "\x06values\x18\x04 \x03(\tR\x06values\"\xbe\x02\n" + + "\x0fAttributePolicy\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x03 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\x04 \x01(\tR\x06action\x12\x16\n" + + "\x06effect\x18\x05 \x01(\tR\x06effect\x12M\n" + + "\n" + + "conditions\x18\x06 \x03(\v2-.workflow.plugins.authz.v1.AttributeConditionR\n" + + "conditions\x12 \n" + + "\vdescription\x18\a \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\b \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\t \x01(\tR\vownerModule\"\xbb\x01\n" + + "\x15AttributePolicyFilter\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x03 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\x04 \x01(\tR\x06action\x12!\n" + + "\fowner_plugin\x18\x05 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x06 \x01(\tR\vownerModule\"\xdf\x02\n" + + "\x13AttributeCheckInput\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x03 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\x04 \x01(\tR\x06action\x12F\n" + + "\x12subject_attributes\x18\x05 \x01(\v2\x17.google.protobuf.StructR\x11subjectAttributes\x12H\n" + + "\x13resource_attributes\x18\x06 \x01(\v2\x17.google.protobuf.StructR\x12resourceAttributes\x12N\n" + + "\x16environment_attributes\x18\a \x01(\v2\x17.google.protobuf.StructR\x15environmentAttributes\"\xf2\x01\n" + + "\x14AttributeCheckOutput\x12\x18\n" + + "\aallowed\x18\x01 \x01(\bR\aallowed\x12\x18\n" + + "\asubject\x18\x02 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x03 \x01(\tR\acontext\x12\x1a\n" + + "\bresource\x18\x04 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\x05 \x01(\tR\x06action\x12*\n" + + "\x11matched_policy_id\x18\x06 \x01(\tR\x0fmatchedPolicyId\x12\x16\n" + + "\x06reason\x18\a \x01(\tR\x06reason\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\xaf\x01\n" + + "\x16DeclareAttributesInput\x12O\n" + + "\n" + + "attributes\x18\x01 \x03(\v2/.workflow.plugins.authz.v1.AttributeDeclarationR\n" + + "attributes\x12!\n" + + "\fowner_plugin\x18\x02 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x03 \x01(\tR\vownerModule\"\xa0\x01\n" + + "\x17DeclareAttributesOutput\x12\x1e\n" + + "\n" + + "registered\x18\x01 \x01(\x05R\n" + + "registered\x12O\n" + + "\n" + + "attributes\x18\x02 \x03(\v2/.workflow.plugins.authz.v1.AttributeDeclarationR\n" + + "attributes\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"`\n" + + "\x1aUpsertAttributePolicyInput\x12B\n" + + "\x06policy\x18\x01 \x01(\v2*.workflow.plugins.authz.v1.AttributePolicyR\x06policy\"\x91\x01\n" + + "\x1bUpsertAttributePolicyOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12B\n" + + "\x06policy\x18\x02 \x01(\v2*.workflow.plugins.authz.v1.AttributePolicyR\x06policy\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"f\n" + + "\x1aListAttributePoliciesInput\x12H\n" + + "\x06filter\x18\x01 \x01(\v20.workflow.plugins.authz.v1.AttributePolicyFilterR\x06filter\"{\n" + + "\x1bListAttributePoliciesOutput\x12F\n" + + "\bpolicies\x18\x01 \x03(\v2*.workflow.plugins.authz.v1.AttributePolicyR\bpolicies\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"f\n" + + "\x1aRemoveAttributePolicyInput\x12H\n" + + "\x06filter\x18\x01 \x01(\v20.workflow.plugins.authz.v1.AttributePolicyFilterR\x06filter\"M\n" + + "\x1bRemoveAttributePolicyOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\x8b\x02\n" + + "\x13RelationDeclaration\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12!\n" + + "\fsubject_type\x18\x03 \x01(\tR\vsubjectType\x12\x1f\n" + + "\vobject_type\x18\x04 \x01(\tR\n" + + "objectType\x12 \n" + + "\vdescription\x18\x05 \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\x06 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\a \x01(\tR\vownerModule\x12\x1a\n" + + "\bcategory\x18\b \x01(\tR\bcategory\"w\n" + + "\rRelationTuple\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x1a\n" + + "\brelation\x18\x02 \x01(\tR\brelation\x12\x16\n" + + "\x06object\x18\x03 \x01(\tR\x06object\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"}\n" + + "\x13RelationTupleFilter\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x1a\n" + + "\brelation\x18\x02 \x01(\tR\brelation\x12\x16\n" + + "\x06object\x18\x03 \x01(\tR\x06object\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"|\n" + + "\x12RelationCheckInput\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x1a\n" + + "\brelation\x18\x02 \x01(\tR\brelation\x12\x16\n" + + "\x06object\x18\x03 \x01(\tR\x06object\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"\xc5\x01\n" + + "\x13RelationCheckOutput\x12\x18\n" + + "\aallowed\x18\x01 \x01(\bR\aallowed\x12\x18\n" + + "\asubject\x18\x02 \x01(\tR\asubject\x12\x1a\n" + + "\brelation\x18\x03 \x01(\tR\brelation\x12\x16\n" + + "\x06object\x18\x04 \x01(\tR\x06object\x12\x18\n" + + "\acontext\x18\x05 \x01(\tR\acontext\x12\x16\n" + + "\x06reason\x18\x06 \x01(\tR\x06reason\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"Z\n" + + "\x18UpsertRelationTupleInput\x12>\n" + + "\x05tuple\x18\x01 \x01(\v2(.workflow.plugins.authz.v1.RelationTupleR\x05tuple\"\x8b\x01\n" + + "\x19UpsertRelationTupleOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12>\n" + + "\x05tuple\x18\x02 \x01(\v2(.workflow.plugins.authz.v1.RelationTupleR\x05tuple\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"a\n" + + "\x17ListRelationTuplesInput\x12F\n" + + "\x06filter\x18\x01 \x01(\v2..workflow.plugins.authz.v1.RelationTupleFilterR\x06filter\"r\n" + + "\x18ListRelationTuplesOutput\x12@\n" + + "\x06tuples\x18\x01 \x03(\v2(.workflow.plugins.authz.v1.RelationTupleR\x06tuples\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"Z\n" + + "\x18RemoveRelationTupleInput\x12>\n" + + "\x05tuple\x18\x01 \x01(\v2(.workflow.plugins.authz.v1.RelationTupleR\x05tuple\"K\n" + + "\x19RemoveRelationTupleOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\xff\x02\n" + + "\x13UIActionDeclaration\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x14\n" + + "\x05label\x18\x03 \x01(\tR\x05label\x12\x14\n" + + "\x05route\x18\x04 \x01(\tR\x05route\x12'\n" + + "\x0frequired_scopes\x18\x05 \x03(\tR\x0erequiredScopes\x12e\n" + + "\x15required_capabilities\x18\x06 \x03(\v20.workflow.plugins.authz.v1.CapabilityRequirementR\x14requiredCapabilities\x12 \n" + + "\vdescription\x18\a \x01(\tR\vdescription\x12!\n" + + "\fowner_plugin\x18\b \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\t \x01(\tR\vownerModule\x12\x1a\n" + + "\bcategory\x18\n" + + " \x01(\tR\bcategory\"\xa4\x04\n" + + "\x13AuthzDeclarationSet\x12C\n" + + "\x06scopes\x18\x01 \x03(\v2+.workflow.plugins.authz.v1.ScopeDeclarationR\x06scopes\x12L\n" + + "\tresources\x18\x02 \x03(\v2..workflow.plugins.authz.v1.ResourceDeclarationR\tresources\x12F\n" + + "\aactions\x18\x03 \x03(\v2,.workflow.plugins.authz.v1.ActionDeclarationR\aactions\x12O\n" + + "\n" + + "attributes\x18\x04 \x03(\v2/.workflow.plugins.authz.v1.AttributeDeclarationR\n" + + "attributes\x12L\n" + + "\trelations\x18\x05 \x03(\v2..workflow.plugins.authz.v1.RelationDeclarationR\trelations\x12M\n" + + "\n" + + "ui_actions\x18\x06 \x03(\v2..workflow.plugins.authz.v1.UIActionDeclarationR\tuiActions\x12!\n" + + "\fowner_plugin\x18\a \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\b \x01(\tR\vownerModule\"\xb5\x01\n" + + "\x19RegisterDeclarationsInput\x12R\n" + + "\fdeclarations\x18\x01 \x01(\v2..workflow.plugins.authz.v1.AuthzDeclarationSetR\fdeclarations\x12!\n" + + "\fowner_plugin\x18\x02 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x03 \x01(\tR\vownerModule\"\xa6\x01\n" + + "\x1aRegisterDeclarationsOutput\x12\x1e\n" + + "\n" + + "registered\x18\x01 \x01(\x05R\n" + + "registered\x12R\n" + + "\fdeclarations\x18\x02 \x01(\v2..workflow.plugins.authz.v1.AuthzDeclarationSetR\fdeclarations\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"w\n" + + "\x15ListDeclarationsInput\x12\x18\n" + + "\acontext\x18\x01 \x01(\tR\acontext\x12!\n" + + "\fowner_plugin\x18\x02 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x03 \x01(\tR\vownerModule\"\x82\x01\n" + + "\x16ListDeclarationsOutput\x12R\n" + + "\fdeclarations\x18\x01 \x01(\v2..workflow.plugins.authz.v1.AuthzDeclarationSetR\fdeclarations\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"~\n" + + "\x1cResolveProjectionInputsInput\x12\x18\n" + + "\acontext\x18\x01 \x01(\tR\acontext\x12!\n" + + "\fowner_plugin\x18\x02 \x01(\tR\vownerPlugin\x12!\n" + + "\fowner_module\x18\x03 \x01(\tR\vownerModule\"\x9d\x02\n" + + "\x10ProjectionInputs\x12\x1f\n" + + "\vscope_names\x18\x01 \x03(\tR\n" + + "scopeNames\x12%\n" + + "\x0eresource_names\x18\x02 \x03(\tR\rresourceNames\x12!\n" + + "\faction_names\x18\x03 \x03(\tR\vactionNames\x12'\n" + + "\x0fattribute_names\x18\x04 \x03(\tR\x0eattributeNames\x12%\n" + + "\x0erelation_names\x18\x05 \x03(\tR\rrelationNames\x12\"\n" + + "\rui_action_ids\x18\x06 \x03(\tR\vuiActionIds\x12*\n" + + "\x11lookup_source_ids\x18\a \x03(\tR\x0flookupSourceIds\"\x82\x01\n" + + "\x1dResolveProjectionInputsOutput\x12K\n" + + "\n" + + "projection\x18\x01 \x01(\v2+.workflow.plugins.authz.v1.ProjectionInputsR\n" + + "projection\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"\x95\x01\n" + + "\x19ResolveSubjectScopesInput\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12#\n" + + "\rdirect_scopes\x18\x02 \x03(\tR\fdirectScopes\x12\x1f\n" + + "\vrole_scopes\x18\x03 \x03(\tR\n" + + "roleScopes\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\"\xba\x01\n" + + "\x1aResolveSubjectScopesOutput\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x16\n" + + "\x06scopes\x18\x02 \x03(\tR\x06scopes\x12T\n" + + "\x0fdeclared_scopes\x18\x03 \x03(\v2+.workflow.plugins.authz.v1.ScopeDeclarationR\x0edeclaredScopes\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"V\n" + + "\x0eRoleScopeGrant\x12\x12\n" + + "\x04role\x18\x01 \x01(\tR\x04role\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x16\n" + + "\x06scopes\x18\x03 \x03(\tR\x06scopes\"\x84\x01\n" + + "\x15SubjectRoleAssignment\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x12\n" + + "\x04role\x18\x02 \x01(\tR\x04role\x12\x18\n" + + "\acontext\x18\x03 \x01(\tR\acontext\x12#\n" + + "\rdirect_scopes\x18\x04 \x03(\tR\fdirectScopes\"Z\n" + + "\x10AssignmentFilter\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x12\n" + + "\x04role\x18\x02 \x01(\tR\x04role\x12\x18\n" + + "\acontext\x18\x03 \x01(\tR\acontext\"\x8f\x01\n" + + "\x0fScopeCheckInput\x12\x18\n" + + "\asubject\x18\x01 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x02 \x01(\tR\acontext\x12\x14\n" + + "\x05scope\x18\x03 \x01(\tR\x05scope\x12\x1a\n" + + "\bresource\x18\x04 \x01(\tR\bresource\x12\x16\n" + + "\x06action\x18\x05 \x01(\tR\x06action\"\x8a\x02\n" + + "\x10ScopeCheckOutput\x12\x18\n" + + "\aallowed\x18\x01 \x01(\bR\aallowed\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12\x18\n" + + "\asubject\x18\x03 \x01(\tR\asubject\x12\x18\n" + + "\acontext\x18\x04 \x01(\tR\acontext\x12\x14\n" + + "\x05scope\x18\x05 \x01(\tR\x05scope\x12!\n" + + "\fmatched_role\x18\x06 \x01(\tR\vmatchedRole\x12%\n" + + "\x0ematched_scopes\x18\a \x03(\tR\rmatchedScopes\x12\x16\n" + + "\x06reason\x18\b \x01(\tR\x06reason\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"R\n" + + "\x0fUpsertRoleInput\x12?\n" + + "\x05grant\x18\x01 \x01(\v2).workflow.plugins.authz.v1.RoleScopeGrantR\x05grant\"\x83\x01\n" + + "\x10UpsertRoleOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12?\n" + + "\x05grant\x18\x02 \x01(\v2).workflow.plugins.authz.v1.RoleScopeGrantR\x05grant\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"c\n" + + "\x0fAssignRoleInput\x12P\n" + + "\n" + + "assignment\x18\x01 \x01(\v20.workflow.plugins.authz.v1.SubjectRoleAssignmentR\n" + + "assignment\"\x94\x01\n" + + "\x10AssignRoleOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12P\n" + + "\n" + + "assignment\x18\x02 \x01(\v20.workflow.plugins.authz.v1.SubjectRoleAssignmentR\n" + + "assignment\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"_\n" + + "\x18ListRoleAssignmentsInput\x12C\n" + + "\x06filter\x18\x01 \x01(\v2+.workflow.plugins.authz.v1.AssignmentFilterR\x06filter\"\x85\x01\n" + + "\x19ListRoleAssignmentsOutput\x12R\n" + + "\vassignments\x18\x01 \x03(\v20.workflow.plugins.authz.v1.SubjectRoleAssignmentR\vassignments\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error\"m\n" + + "\x19RemoveRoleAssignmentInput\x12P\n" + + "\n" + + "assignment\x18\x01 \x01(\v20.workflow.plugins.authz.v1.SubjectRoleAssignmentR\n" + + "assignment\"L\n" + + "\x1aRemoveRoleAssignmentOutput\x12\x18\n" + + "\achanged\x18\x01 \x01(\bR\achanged\x12\x14\n" + + "\x05error\x18d \x01(\tR\x05error*{\n" + + "\tAuthzMode\x12\x1a\n" + + "\x16AUTHZ_MODE_UNSPECIFIED\x10\x00\x12\x13\n" + + "\x0fAUTHZ_MODE_RBAC\x10\x01\x12\x13\n" + + "\x0fAUTHZ_MODE_ABAC\x10\x02\x12\x14\n" + + "\x10AUTHZ_MODE_REBAC\x10\x03\x12\x12\n" + + "\x0eAUTHZ_MODE_ACL\x10\x04*\xd3\x01\n" + + "\x0eAuthzOperation\x12\x1f\n" + + "\x1bAUTHZ_OPERATION_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15AUTHZ_OPERATION_CHECK\x10\x01\x12 \n" + + "\x1cAUTHZ_OPERATION_MANAGE_ROLES\x10\x02\x12#\n" + + "\x1fAUTHZ_OPERATION_MANAGE_POLICIES\x10\x03\x12$\n" + + " AUTHZ_OPERATION_MANAGE_RELATIONS\x10\x04\x12\x18\n" + + "\x14AUTHZ_OPERATION_LIST\x10\x05BAZ?github.com/GoCodeAlone/workflow-plugin-authz/internal/contractsb\x06proto3" var ( file_internal_contracts_authz_proto_rawDescOnce sync.Once @@ -2073,58 +7289,185 @@ func file_internal_contracts_authz_proto_rawDescGZIP() []byte { return file_internal_contracts_authz_proto_rawDescData } -var file_internal_contracts_authz_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_internal_contracts_authz_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_internal_contracts_authz_proto_msgTypes = make([]protoimpl.MessageInfo, 97) var file_internal_contracts_authz_proto_goTypes = []any{ - (*StringList)(nil), // 0: workflow.plugins.authz.v1.StringList - (*AdapterConfig)(nil), // 1: workflow.plugins.authz.v1.AdapterConfig - (*WatcherConfig)(nil), // 2: workflow.plugins.authz.v1.WatcherConfig - (*CasbinModuleConfig)(nil), // 3: workflow.plugins.authz.v1.CasbinModuleConfig - (*PermitModuleConfig)(nil), // 4: workflow.plugins.authz.v1.PermitModuleConfig - (*ExtraField)(nil), // 5: workflow.plugins.authz.v1.ExtraField - (*AuthzCheckConfig)(nil), // 6: workflow.plugins.authz.v1.AuthzCheckConfig - (*AuthzCheckInput)(nil), // 7: workflow.plugins.authz.v1.AuthzCheckInput - (*AuthzCheckOutput)(nil), // 8: workflow.plugins.authz.v1.AuthzCheckOutput - (*PolicyRuleConfig)(nil), // 9: workflow.plugins.authz.v1.PolicyRuleConfig - (*PolicyRuleInput)(nil), // 10: workflow.plugins.authz.v1.PolicyRuleInput - (*PolicyRuleOutput)(nil), // 11: workflow.plugins.authz.v1.PolicyRuleOutput - (*RoleAssignConfig)(nil), // 12: workflow.plugins.authz.v1.RoleAssignConfig - (*RoleAssignInput)(nil), // 13: workflow.plugins.authz.v1.RoleAssignInput - (*RoleAssignOutput)(nil), // 14: workflow.plugins.authz.v1.RoleAssignOutput - (*CapabilitiesConfig)(nil), // 15: workflow.plugins.authz.v1.CapabilitiesConfig - (*CapabilitiesInput)(nil), // 16: workflow.plugins.authz.v1.CapabilitiesInput - (*CapabilitiesOutput)(nil), // 17: workflow.plugins.authz.v1.CapabilitiesOutput - (*SubjectObjectActionConfig)(nil), // 18: workflow.plugins.authz.v1.SubjectObjectActionConfig - (*SubjectObjectActionInput)(nil), // 19: workflow.plugins.authz.v1.SubjectObjectActionInput - (*SubjectObjectActionOutput)(nil), // 20: workflow.plugins.authz.v1.SubjectObjectActionOutput - (*ListConfig)(nil), // 21: workflow.plugins.authz.v1.ListConfig - (*ListInput)(nil), // 22: workflow.plugins.authz.v1.ListInput - (*GenericStepOutput)(nil), // 23: workflow.plugins.authz.v1.GenericStepOutput - (*RelationConfig)(nil), // 24: workflow.plugins.authz.v1.RelationConfig - (*RelationInput)(nil), // 25: workflow.plugins.authz.v1.RelationInput - (*RelationOutput)(nil), // 26: workflow.plugins.authz.v1.RelationOutput - (*PermitStepConfig)(nil), // 27: workflow.plugins.authz.v1.PermitStepConfig - (*PermitStepInput)(nil), // 28: workflow.plugins.authz.v1.PermitStepInput - (*structpb.Struct)(nil), // 29: google.protobuf.Struct + (AuthzMode)(0), // 0: workflow.plugins.authz.v1.AuthzMode + (AuthzOperation)(0), // 1: workflow.plugins.authz.v1.AuthzOperation + (*StringList)(nil), // 2: workflow.plugins.authz.v1.StringList + (*AdapterConfig)(nil), // 3: workflow.plugins.authz.v1.AdapterConfig + (*WatcherConfig)(nil), // 4: workflow.plugins.authz.v1.WatcherConfig + (*CasbinModuleConfig)(nil), // 5: workflow.plugins.authz.v1.CasbinModuleConfig + (*PermitModuleConfig)(nil), // 6: workflow.plugins.authz.v1.PermitModuleConfig + (*KetoModuleConfig)(nil), // 7: workflow.plugins.authz.v1.KetoModuleConfig + (*ExtraField)(nil), // 8: workflow.plugins.authz.v1.ExtraField + (*AuthzCheckConfig)(nil), // 9: workflow.plugins.authz.v1.AuthzCheckConfig + (*AuthzCheckInput)(nil), // 10: workflow.plugins.authz.v1.AuthzCheckInput + (*AuthzCheckOutput)(nil), // 11: workflow.plugins.authz.v1.AuthzCheckOutput + (*PolicyRuleConfig)(nil), // 12: workflow.plugins.authz.v1.PolicyRuleConfig + (*PolicyRuleInput)(nil), // 13: workflow.plugins.authz.v1.PolicyRuleInput + (*PolicyRuleOutput)(nil), // 14: workflow.plugins.authz.v1.PolicyRuleOutput + (*RoleAssignConfig)(nil), // 15: workflow.plugins.authz.v1.RoleAssignConfig + (*RoleAssignInput)(nil), // 16: workflow.plugins.authz.v1.RoleAssignInput + (*RoleAssignOutput)(nil), // 17: workflow.plugins.authz.v1.RoleAssignOutput + (*CapabilitiesConfig)(nil), // 18: workflow.plugins.authz.v1.CapabilitiesConfig + (*CapabilitiesInput)(nil), // 19: workflow.plugins.authz.v1.CapabilitiesInput + (*CapabilitiesOutput)(nil), // 20: workflow.plugins.authz.v1.CapabilitiesOutput + (*CapabilityDescriptor)(nil), // 21: workflow.plugins.authz.v1.CapabilityDescriptor + (*CapabilityRequirement)(nil), // 22: workflow.plugins.authz.v1.CapabilityRequirement + (*ProviderCapabilitiesInput)(nil), // 23: workflow.plugins.authz.v1.ProviderCapabilitiesInput + (*ProviderCapabilitiesOutput)(nil), // 24: workflow.plugins.authz.v1.ProviderCapabilitiesOutput + (*AuthorizationDecisionConfig)(nil), // 25: workflow.plugins.authz.v1.AuthorizationDecisionConfig + (*AuthorizationDecisionInput)(nil), // 26: workflow.plugins.authz.v1.AuthorizationDecisionInput + (*AuthorizationDecisionOutput)(nil), // 27: workflow.plugins.authz.v1.AuthorizationDecisionOutput + (*RequireCapabilitiesConfig)(nil), // 28: workflow.plugins.authz.v1.RequireCapabilitiesConfig + (*RequireCapabilitiesInput)(nil), // 29: workflow.plugins.authz.v1.RequireCapabilitiesInput + (*SubjectObjectActionConfig)(nil), // 30: workflow.plugins.authz.v1.SubjectObjectActionConfig + (*SubjectObjectActionInput)(nil), // 31: workflow.plugins.authz.v1.SubjectObjectActionInput + (*SubjectObjectActionOutput)(nil), // 32: workflow.plugins.authz.v1.SubjectObjectActionOutput + (*ListConfig)(nil), // 33: workflow.plugins.authz.v1.ListConfig + (*ListInput)(nil), // 34: workflow.plugins.authz.v1.ListInput + (*GenericStepOutput)(nil), // 35: workflow.plugins.authz.v1.GenericStepOutput + (*RelationConfig)(nil), // 36: workflow.plugins.authz.v1.RelationConfig + (*RelationInput)(nil), // 37: workflow.plugins.authz.v1.RelationInput + (*RelationOutput)(nil), // 38: workflow.plugins.authz.v1.RelationOutput + (*PermitStepConfig)(nil), // 39: workflow.plugins.authz.v1.PermitStepConfig + (*PermitStepInput)(nil), // 40: workflow.plugins.authz.v1.PermitStepInput + (*ScopeDeclaration)(nil), // 41: workflow.plugins.authz.v1.ScopeDeclaration + (*ScopeCatalogConfig)(nil), // 42: workflow.plugins.authz.v1.ScopeCatalogConfig + (*RegisterScopesInput)(nil), // 43: workflow.plugins.authz.v1.RegisterScopesInput + (*RegisterScopesOutput)(nil), // 44: workflow.plugins.authz.v1.RegisterScopesOutput + (*ListScopesInput)(nil), // 45: workflow.plugins.authz.v1.ListScopesInput + (*ListScopesOutput)(nil), // 46: workflow.plugins.authz.v1.ListScopesOutput + (*ResourceDeclaration)(nil), // 47: workflow.plugins.authz.v1.ResourceDeclaration + (*ActionDeclaration)(nil), // 48: workflow.plugins.authz.v1.ActionDeclaration + (*AttributeValue)(nil), // 49: workflow.plugins.authz.v1.AttributeValue + (*AttributeDeclaration)(nil), // 50: workflow.plugins.authz.v1.AttributeDeclaration + (*AttributeCondition)(nil), // 51: workflow.plugins.authz.v1.AttributeCondition + (*AttributePolicy)(nil), // 52: workflow.plugins.authz.v1.AttributePolicy + (*AttributePolicyFilter)(nil), // 53: workflow.plugins.authz.v1.AttributePolicyFilter + (*AttributeCheckInput)(nil), // 54: workflow.plugins.authz.v1.AttributeCheckInput + (*AttributeCheckOutput)(nil), // 55: workflow.plugins.authz.v1.AttributeCheckOutput + (*DeclareAttributesInput)(nil), // 56: workflow.plugins.authz.v1.DeclareAttributesInput + (*DeclareAttributesOutput)(nil), // 57: workflow.plugins.authz.v1.DeclareAttributesOutput + (*UpsertAttributePolicyInput)(nil), // 58: workflow.plugins.authz.v1.UpsertAttributePolicyInput + (*UpsertAttributePolicyOutput)(nil), // 59: workflow.plugins.authz.v1.UpsertAttributePolicyOutput + (*ListAttributePoliciesInput)(nil), // 60: workflow.plugins.authz.v1.ListAttributePoliciesInput + (*ListAttributePoliciesOutput)(nil), // 61: workflow.plugins.authz.v1.ListAttributePoliciesOutput + (*RemoveAttributePolicyInput)(nil), // 62: workflow.plugins.authz.v1.RemoveAttributePolicyInput + (*RemoveAttributePolicyOutput)(nil), // 63: workflow.plugins.authz.v1.RemoveAttributePolicyOutput + (*RelationDeclaration)(nil), // 64: workflow.plugins.authz.v1.RelationDeclaration + (*RelationTuple)(nil), // 65: workflow.plugins.authz.v1.RelationTuple + (*RelationTupleFilter)(nil), // 66: workflow.plugins.authz.v1.RelationTupleFilter + (*RelationCheckInput)(nil), // 67: workflow.plugins.authz.v1.RelationCheckInput + (*RelationCheckOutput)(nil), // 68: workflow.plugins.authz.v1.RelationCheckOutput + (*UpsertRelationTupleInput)(nil), // 69: workflow.plugins.authz.v1.UpsertRelationTupleInput + (*UpsertRelationTupleOutput)(nil), // 70: workflow.plugins.authz.v1.UpsertRelationTupleOutput + (*ListRelationTuplesInput)(nil), // 71: workflow.plugins.authz.v1.ListRelationTuplesInput + (*ListRelationTuplesOutput)(nil), // 72: workflow.plugins.authz.v1.ListRelationTuplesOutput + (*RemoveRelationTupleInput)(nil), // 73: workflow.plugins.authz.v1.RemoveRelationTupleInput + (*RemoveRelationTupleOutput)(nil), // 74: workflow.plugins.authz.v1.RemoveRelationTupleOutput + (*UIActionDeclaration)(nil), // 75: workflow.plugins.authz.v1.UIActionDeclaration + (*AuthzDeclarationSet)(nil), // 76: workflow.plugins.authz.v1.AuthzDeclarationSet + (*RegisterDeclarationsInput)(nil), // 77: workflow.plugins.authz.v1.RegisterDeclarationsInput + (*RegisterDeclarationsOutput)(nil), // 78: workflow.plugins.authz.v1.RegisterDeclarationsOutput + (*ListDeclarationsInput)(nil), // 79: workflow.plugins.authz.v1.ListDeclarationsInput + (*ListDeclarationsOutput)(nil), // 80: workflow.plugins.authz.v1.ListDeclarationsOutput + (*ResolveProjectionInputsInput)(nil), // 81: workflow.plugins.authz.v1.ResolveProjectionInputsInput + (*ProjectionInputs)(nil), // 82: workflow.plugins.authz.v1.ProjectionInputs + (*ResolveProjectionInputsOutput)(nil), // 83: workflow.plugins.authz.v1.ResolveProjectionInputsOutput + (*ResolveSubjectScopesInput)(nil), // 84: workflow.plugins.authz.v1.ResolveSubjectScopesInput + (*ResolveSubjectScopesOutput)(nil), // 85: workflow.plugins.authz.v1.ResolveSubjectScopesOutput + (*RoleScopeGrant)(nil), // 86: workflow.plugins.authz.v1.RoleScopeGrant + (*SubjectRoleAssignment)(nil), // 87: workflow.plugins.authz.v1.SubjectRoleAssignment + (*AssignmentFilter)(nil), // 88: workflow.plugins.authz.v1.AssignmentFilter + (*ScopeCheckInput)(nil), // 89: workflow.plugins.authz.v1.ScopeCheckInput + (*ScopeCheckOutput)(nil), // 90: workflow.plugins.authz.v1.ScopeCheckOutput + (*UpsertRoleInput)(nil), // 91: workflow.plugins.authz.v1.UpsertRoleInput + (*UpsertRoleOutput)(nil), // 92: workflow.plugins.authz.v1.UpsertRoleOutput + (*AssignRoleInput)(nil), // 93: workflow.plugins.authz.v1.AssignRoleInput + (*AssignRoleOutput)(nil), // 94: workflow.plugins.authz.v1.AssignRoleOutput + (*ListRoleAssignmentsInput)(nil), // 95: workflow.plugins.authz.v1.ListRoleAssignmentsInput + (*ListRoleAssignmentsOutput)(nil), // 96: workflow.plugins.authz.v1.ListRoleAssignmentsOutput + (*RemoveRoleAssignmentInput)(nil), // 97: workflow.plugins.authz.v1.RemoveRoleAssignmentInput + (*RemoveRoleAssignmentOutput)(nil), // 98: workflow.plugins.authz.v1.RemoveRoleAssignmentOutput + (*structpb.Struct)(nil), // 99: google.protobuf.Struct } var file_internal_contracts_authz_proto_depIdxs = []int32{ - 0, // 0: workflow.plugins.authz.v1.CasbinModuleConfig.policies:type_name -> workflow.plugins.authz.v1.StringList - 0, // 1: workflow.plugins.authz.v1.CasbinModuleConfig.role_assignments:type_name -> workflow.plugins.authz.v1.StringList - 1, // 2: workflow.plugins.authz.v1.CasbinModuleConfig.adapter:type_name -> workflow.plugins.authz.v1.AdapterConfig - 2, // 3: workflow.plugins.authz.v1.CasbinModuleConfig.watcher:type_name -> workflow.plugins.authz.v1.WatcherConfig - 5, // 4: workflow.plugins.authz.v1.AuthzCheckConfig.extra_fields:type_name -> workflow.plugins.authz.v1.ExtraField - 5, // 5: workflow.plugins.authz.v1.AuthzCheckInput.extra_fields:type_name -> workflow.plugins.authz.v1.ExtraField - 29, // 6: workflow.plugins.authz.v1.AuthzCheckOutput.response_headers:type_name -> google.protobuf.Struct - 0, // 7: workflow.plugins.authz.v1.RoleAssignConfig.assignments:type_name -> workflow.plugins.authz.v1.StringList - 0, // 8: workflow.plugins.authz.v1.RoleAssignInput.assignments:type_name -> workflow.plugins.authz.v1.StringList - 0, // 9: workflow.plugins.authz.v1.RoleAssignOutput.assignments:type_name -> workflow.plugins.authz.v1.StringList - 29, // 10: workflow.plugins.authz.v1.GenericStepOutput.output:type_name -> google.protobuf.Struct - 29, // 11: workflow.plugins.authz.v1.PermitStepConfig.values:type_name -> google.protobuf.Struct - 29, // 12: workflow.plugins.authz.v1.PermitStepInput.values:type_name -> google.protobuf.Struct - 13, // [13:13] is the sub-list for method output_type - 13, // [13:13] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 2, // 0: workflow.plugins.authz.v1.CasbinModuleConfig.policies:type_name -> workflow.plugins.authz.v1.StringList + 2, // 1: workflow.plugins.authz.v1.CasbinModuleConfig.role_assignments:type_name -> workflow.plugins.authz.v1.StringList + 3, // 2: workflow.plugins.authz.v1.CasbinModuleConfig.adapter:type_name -> workflow.plugins.authz.v1.AdapterConfig + 4, // 3: workflow.plugins.authz.v1.CasbinModuleConfig.watcher:type_name -> workflow.plugins.authz.v1.WatcherConfig + 8, // 4: workflow.plugins.authz.v1.AuthzCheckConfig.extra_fields:type_name -> workflow.plugins.authz.v1.ExtraField + 8, // 5: workflow.plugins.authz.v1.AuthzCheckInput.extra_fields:type_name -> workflow.plugins.authz.v1.ExtraField + 99, // 6: workflow.plugins.authz.v1.AuthzCheckOutput.response_headers:type_name -> google.protobuf.Struct + 2, // 7: workflow.plugins.authz.v1.RoleAssignConfig.assignments:type_name -> workflow.plugins.authz.v1.StringList + 2, // 8: workflow.plugins.authz.v1.RoleAssignInput.assignments:type_name -> workflow.plugins.authz.v1.StringList + 2, // 9: workflow.plugins.authz.v1.RoleAssignOutput.assignments:type_name -> workflow.plugins.authz.v1.StringList + 21, // 10: workflow.plugins.authz.v1.CapabilitiesOutput.descriptors:type_name -> workflow.plugins.authz.v1.CapabilityDescriptor + 0, // 11: workflow.plugins.authz.v1.CapabilityDescriptor.mode:type_name -> workflow.plugins.authz.v1.AuthzMode + 1, // 12: workflow.plugins.authz.v1.CapabilityDescriptor.operations:type_name -> workflow.plugins.authz.v1.AuthzOperation + 0, // 13: workflow.plugins.authz.v1.CapabilityRequirement.mode:type_name -> workflow.plugins.authz.v1.AuthzMode + 1, // 14: workflow.plugins.authz.v1.CapabilityRequirement.operations:type_name -> workflow.plugins.authz.v1.AuthzOperation + 22, // 15: workflow.plugins.authz.v1.ProviderCapabilitiesInput.requirements:type_name -> workflow.plugins.authz.v1.CapabilityRequirement + 21, // 16: workflow.plugins.authz.v1.ProviderCapabilitiesOutput.descriptors:type_name -> workflow.plugins.authz.v1.CapabilityDescriptor + 0, // 17: workflow.plugins.authz.v1.AuthorizationDecisionConfig.mode:type_name -> workflow.plugins.authz.v1.AuthzMode + 0, // 18: workflow.plugins.authz.v1.AuthorizationDecisionInput.mode:type_name -> workflow.plugins.authz.v1.AuthzMode + 99, // 19: workflow.plugins.authz.v1.AuthorizationDecisionInput.subject_attributes:type_name -> google.protobuf.Struct + 99, // 20: workflow.plugins.authz.v1.AuthorizationDecisionInput.resource_attributes:type_name -> google.protobuf.Struct + 99, // 21: workflow.plugins.authz.v1.AuthorizationDecisionInput.environment_attributes:type_name -> google.protobuf.Struct + 0, // 22: workflow.plugins.authz.v1.AuthorizationDecisionOutput.mode:type_name -> workflow.plugins.authz.v1.AuthzMode + 22, // 23: workflow.plugins.authz.v1.RequireCapabilitiesConfig.requirements:type_name -> workflow.plugins.authz.v1.CapabilityRequirement + 22, // 24: workflow.plugins.authz.v1.RequireCapabilitiesInput.requirements:type_name -> workflow.plugins.authz.v1.CapabilityRequirement + 99, // 25: workflow.plugins.authz.v1.GenericStepOutput.output:type_name -> google.protobuf.Struct + 99, // 26: workflow.plugins.authz.v1.PermitStepConfig.values:type_name -> google.protobuf.Struct + 99, // 27: workflow.plugins.authz.v1.PermitStepInput.values:type_name -> google.protobuf.Struct + 41, // 28: workflow.plugins.authz.v1.ScopeCatalogConfig.scopes:type_name -> workflow.plugins.authz.v1.ScopeDeclaration + 76, // 29: workflow.plugins.authz.v1.ScopeCatalogConfig.declarations:type_name -> workflow.plugins.authz.v1.AuthzDeclarationSet + 41, // 30: workflow.plugins.authz.v1.RegisterScopesInput.scopes:type_name -> workflow.plugins.authz.v1.ScopeDeclaration + 41, // 31: workflow.plugins.authz.v1.RegisterScopesOutput.scopes:type_name -> workflow.plugins.authz.v1.ScopeDeclaration + 41, // 32: workflow.plugins.authz.v1.ListScopesOutput.scopes:type_name -> workflow.plugins.authz.v1.ScopeDeclaration + 49, // 33: workflow.plugins.authz.v1.AttributeDeclaration.allowed_values:type_name -> workflow.plugins.authz.v1.AttributeValue + 51, // 34: workflow.plugins.authz.v1.AttributePolicy.conditions:type_name -> workflow.plugins.authz.v1.AttributeCondition + 99, // 35: workflow.plugins.authz.v1.AttributeCheckInput.subject_attributes:type_name -> google.protobuf.Struct + 99, // 36: workflow.plugins.authz.v1.AttributeCheckInput.resource_attributes:type_name -> google.protobuf.Struct + 99, // 37: workflow.plugins.authz.v1.AttributeCheckInput.environment_attributes:type_name -> google.protobuf.Struct + 50, // 38: workflow.plugins.authz.v1.DeclareAttributesInput.attributes:type_name -> workflow.plugins.authz.v1.AttributeDeclaration + 50, // 39: workflow.plugins.authz.v1.DeclareAttributesOutput.attributes:type_name -> workflow.plugins.authz.v1.AttributeDeclaration + 52, // 40: workflow.plugins.authz.v1.UpsertAttributePolicyInput.policy:type_name -> workflow.plugins.authz.v1.AttributePolicy + 52, // 41: workflow.plugins.authz.v1.UpsertAttributePolicyOutput.policy:type_name -> workflow.plugins.authz.v1.AttributePolicy + 53, // 42: workflow.plugins.authz.v1.ListAttributePoliciesInput.filter:type_name -> workflow.plugins.authz.v1.AttributePolicyFilter + 52, // 43: workflow.plugins.authz.v1.ListAttributePoliciesOutput.policies:type_name -> workflow.plugins.authz.v1.AttributePolicy + 53, // 44: workflow.plugins.authz.v1.RemoveAttributePolicyInput.filter:type_name -> workflow.plugins.authz.v1.AttributePolicyFilter + 65, // 45: workflow.plugins.authz.v1.UpsertRelationTupleInput.tuple:type_name -> workflow.plugins.authz.v1.RelationTuple + 65, // 46: workflow.plugins.authz.v1.UpsertRelationTupleOutput.tuple:type_name -> workflow.plugins.authz.v1.RelationTuple + 66, // 47: workflow.plugins.authz.v1.ListRelationTuplesInput.filter:type_name -> workflow.plugins.authz.v1.RelationTupleFilter + 65, // 48: workflow.plugins.authz.v1.ListRelationTuplesOutput.tuples:type_name -> workflow.plugins.authz.v1.RelationTuple + 65, // 49: workflow.plugins.authz.v1.RemoveRelationTupleInput.tuple:type_name -> workflow.plugins.authz.v1.RelationTuple + 22, // 50: workflow.plugins.authz.v1.UIActionDeclaration.required_capabilities:type_name -> workflow.plugins.authz.v1.CapabilityRequirement + 41, // 51: workflow.plugins.authz.v1.AuthzDeclarationSet.scopes:type_name -> workflow.plugins.authz.v1.ScopeDeclaration + 47, // 52: workflow.plugins.authz.v1.AuthzDeclarationSet.resources:type_name -> workflow.plugins.authz.v1.ResourceDeclaration + 48, // 53: workflow.plugins.authz.v1.AuthzDeclarationSet.actions:type_name -> workflow.plugins.authz.v1.ActionDeclaration + 50, // 54: workflow.plugins.authz.v1.AuthzDeclarationSet.attributes:type_name -> workflow.plugins.authz.v1.AttributeDeclaration + 64, // 55: workflow.plugins.authz.v1.AuthzDeclarationSet.relations:type_name -> workflow.plugins.authz.v1.RelationDeclaration + 75, // 56: workflow.plugins.authz.v1.AuthzDeclarationSet.ui_actions:type_name -> workflow.plugins.authz.v1.UIActionDeclaration + 76, // 57: workflow.plugins.authz.v1.RegisterDeclarationsInput.declarations:type_name -> workflow.plugins.authz.v1.AuthzDeclarationSet + 76, // 58: workflow.plugins.authz.v1.RegisterDeclarationsOutput.declarations:type_name -> workflow.plugins.authz.v1.AuthzDeclarationSet + 76, // 59: workflow.plugins.authz.v1.ListDeclarationsOutput.declarations:type_name -> workflow.plugins.authz.v1.AuthzDeclarationSet + 82, // 60: workflow.plugins.authz.v1.ResolveProjectionInputsOutput.projection:type_name -> workflow.plugins.authz.v1.ProjectionInputs + 41, // 61: workflow.plugins.authz.v1.ResolveSubjectScopesOutput.declared_scopes:type_name -> workflow.plugins.authz.v1.ScopeDeclaration + 86, // 62: workflow.plugins.authz.v1.UpsertRoleInput.grant:type_name -> workflow.plugins.authz.v1.RoleScopeGrant + 86, // 63: workflow.plugins.authz.v1.UpsertRoleOutput.grant:type_name -> workflow.plugins.authz.v1.RoleScopeGrant + 87, // 64: workflow.plugins.authz.v1.AssignRoleInput.assignment:type_name -> workflow.plugins.authz.v1.SubjectRoleAssignment + 87, // 65: workflow.plugins.authz.v1.AssignRoleOutput.assignment:type_name -> workflow.plugins.authz.v1.SubjectRoleAssignment + 88, // 66: workflow.plugins.authz.v1.ListRoleAssignmentsInput.filter:type_name -> workflow.plugins.authz.v1.AssignmentFilter + 87, // 67: workflow.plugins.authz.v1.ListRoleAssignmentsOutput.assignments:type_name -> workflow.plugins.authz.v1.SubjectRoleAssignment + 87, // 68: workflow.plugins.authz.v1.RemoveRoleAssignmentInput.assignment:type_name -> workflow.plugins.authz.v1.SubjectRoleAssignment + 69, // [69:69] is the sub-list for method output_type + 69, // [69:69] is the sub-list for method input_type + 69, // [69:69] is the sub-list for extension type_name + 69, // [69:69] is the sub-list for extension extendee + 0, // [0:69] is the sub-list for field type_name } func init() { file_internal_contracts_authz_proto_init() } @@ -2137,13 +7480,14 @@ func file_internal_contracts_authz_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_internal_contracts_authz_proto_rawDesc), len(file_internal_contracts_authz_proto_rawDesc)), - NumEnums: 0, - NumMessages: 29, + NumEnums: 2, + NumMessages: 97, NumExtensions: 0, NumServices: 0, }, GoTypes: file_internal_contracts_authz_proto_goTypes, DependencyIndexes: file_internal_contracts_authz_proto_depIdxs, + EnumInfos: file_internal_contracts_authz_proto_enumTypes, MessageInfos: file_internal_contracts_authz_proto_msgTypes, }.Build() File_internal_contracts_authz_proto = out.File diff --git a/internal/contracts/authz.proto b/internal/contracts/authz.proto index ac3076d..f4e8ac2 100644 --- a/internal/contracts/authz.proto +++ b/internal/contracts/authz.proto @@ -42,6 +42,11 @@ message PermitModuleConfig { string environment = 5; } +message KetoModuleConfig { + string read_url = 1; + string write_url = 2; +} + message ExtraField { string key = 1; string value = 2; @@ -124,9 +129,110 @@ message CapabilitiesOutput { string module = 1; string provider = 2; repeated string capabilities = 3; + repeated CapabilityDescriptor descriptors = 4; + string health = 5; + repeated string missing_requirements = 6; string error = 100; } +enum AuthzMode { + AUTHZ_MODE_UNSPECIFIED = 0; + AUTHZ_MODE_RBAC = 1; + AUTHZ_MODE_ABAC = 2; + AUTHZ_MODE_REBAC = 3; + AUTHZ_MODE_ACL = 4; +} + +enum AuthzOperation { + AUTHZ_OPERATION_UNSPECIFIED = 0; + AUTHZ_OPERATION_CHECK = 1; + AUTHZ_OPERATION_MANAGE_ROLES = 2; + AUTHZ_OPERATION_MANAGE_POLICIES = 3; + AUTHZ_OPERATION_MANAGE_RELATIONS = 4; + AUTHZ_OPERATION_LIST = 5; +} + +message CapabilityDescriptor { + AuthzMode mode = 1; + repeated AuthzOperation operations = 2; + bool configured = 3; + string source = 4; + string health = 5; + string unsupported_reason = 6; +} + +message CapabilityRequirement { + AuthzMode mode = 1; + repeated AuthzOperation operations = 2; +} + +message ProviderCapabilitiesInput { + string module = 1; + string provider = 2; + repeated CapabilityRequirement requirements = 3; +} + +message ProviderCapabilitiesOutput { + string module = 1; + string provider = 2; + repeated string capabilities = 3; + repeated CapabilityDescriptor descriptors = 4; + string health = 5; + repeated string missing_requirements = 6; + string error = 100; +} + +message AuthorizationDecisionConfig { + string module = 1; + string provider = 2; + AuthzMode mode = 3; + string subject = 4; + string context = 5; + string resource = 6; + string action = 7; + string scope = 8; + string relation = 9; + bool explain = 10; +} + +message AuthorizationDecisionInput { + string module = 1; + string provider = 2; + AuthzMode mode = 3; + string subject = 4; + string context = 5; + string resource = 6; + string action = 7; + string scope = 8; + string relation = 9; + google.protobuf.Struct subject_attributes = 10; + google.protobuf.Struct resource_attributes = 11; + google.protobuf.Struct environment_attributes = 12; + bool explain = 13; +} + +message AuthorizationDecisionOutput { + bool allowed = 1; + AuthzMode mode = 2; + string subject = 3; + string context = 4; + string reason = 5; + string explain = 6; + string error = 100; +} + +message RequireCapabilitiesConfig { + string module = 1; + string provider = 2; + repeated CapabilityRequirement requirements = 3; +} + +message RequireCapabilitiesInput { + string module = 1; + string provider = 2; + repeated CapabilityRequirement requirements = 3; +} + message SubjectObjectActionConfig { string module = 1; string subject = 2; @@ -198,3 +304,400 @@ message PermitStepInput { string module = 1; google.protobuf.Struct values = 2; } + +message ScopeDeclaration { + string name = 1; + string context = 2; + string resource = 3; + repeated string actions = 4; + string description = 5; + string owner_plugin = 6; + string owner_module = 7; + string category = 8; +} + +message ScopeCatalogConfig { + repeated ScopeDeclaration scopes = 1; + bool allow_runtime_registration = 2; + AuthzDeclarationSet declarations = 3; +} + +message RegisterScopesInput { + repeated ScopeDeclaration scopes = 1; + string owner_plugin = 2; + string owner_module = 3; +} + +message RegisterScopesOutput { + int32 registered = 1; + repeated ScopeDeclaration scopes = 2; + string error = 100; +} + +message ListScopesInput { + string context = 1; + string owner_plugin = 2; + string owner_module = 3; +} + +message ListScopesOutput { + repeated ScopeDeclaration scopes = 1; + string error = 100; +} + +message ResourceDeclaration { + string name = 1; + string context = 2; + string display_name = 3; + string description = 4; + string owner_plugin = 5; + string owner_module = 6; + string category = 7; + string lookup_source_id = 8; +} + +message ActionDeclaration { + string name = 1; + string context = 2; + string resource = 3; + string description = 4; + string owner_plugin = 5; + string owner_module = 6; + string category = 7; +} + +message AttributeValue { + string value = 1; + string label = 2; +} + +message AttributeDeclaration { + string name = 1; + string context = 2; + string target = 3; + string data_type = 4; + repeated AttributeValue allowed_values = 5; + string lookup_source_id = 6; + string description = 7; + string owner_plugin = 8; + string owner_module = 9; + string category = 10; +} + +message AttributeCondition { + string target = 1; + string attribute = 2; + string operator = 3; + repeated string values = 4; +} + +message AttributePolicy { + string id = 1; + string context = 2; + string resource = 3; + string action = 4; + string effect = 5; + repeated AttributeCondition conditions = 6; + string description = 7; + string owner_plugin = 8; + string owner_module = 9; +} + +message AttributePolicyFilter { + string id = 1; + string context = 2; + string resource = 3; + string action = 4; + string owner_plugin = 5; + string owner_module = 6; +} + +message AttributeCheckInput { + string subject = 1; + string context = 2; + string resource = 3; + string action = 4; + google.protobuf.Struct subject_attributes = 5; + google.protobuf.Struct resource_attributes = 6; + google.protobuf.Struct environment_attributes = 7; +} + +message AttributeCheckOutput { + bool allowed = 1; + string subject = 2; + string context = 3; + string resource = 4; + string action = 5; + string matched_policy_id = 6; + string reason = 7; + string error = 100; +} + +message DeclareAttributesInput { + repeated AttributeDeclaration attributes = 1; + string owner_plugin = 2; + string owner_module = 3; +} + +message DeclareAttributesOutput { + int32 registered = 1; + repeated AttributeDeclaration attributes = 2; + string error = 100; +} + +message UpsertAttributePolicyInput { + AttributePolicy policy = 1; +} + +message UpsertAttributePolicyOutput { + bool changed = 1; + AttributePolicy policy = 2; + string error = 100; +} + +message ListAttributePoliciesInput { + AttributePolicyFilter filter = 1; +} + +message ListAttributePoliciesOutput { + repeated AttributePolicy policies = 1; + string error = 100; +} + +message RemoveAttributePolicyInput { + AttributePolicyFilter filter = 1; +} + +message RemoveAttributePolicyOutput { + bool changed = 1; + string error = 100; +} + +message RelationDeclaration { + string name = 1; + string context = 2; + string subject_type = 3; + string object_type = 4; + string description = 5; + string owner_plugin = 6; + string owner_module = 7; + string category = 8; +} + +message RelationTuple { + string subject = 1; + string relation = 2; + string object = 3; + string context = 4; +} + +message RelationTupleFilter { + string subject = 1; + string relation = 2; + string object = 3; + string context = 4; +} + +message RelationCheckInput { + string subject = 1; + string relation = 2; + string object = 3; + string context = 4; +} + +message RelationCheckOutput { + bool allowed = 1; + string subject = 2; + string relation = 3; + string object = 4; + string context = 5; + string reason = 6; + string error = 100; +} + +message UpsertRelationTupleInput { + RelationTuple tuple = 1; +} + +message UpsertRelationTupleOutput { + bool changed = 1; + RelationTuple tuple = 2; + string error = 100; +} + +message ListRelationTuplesInput { + RelationTupleFilter filter = 1; +} + +message ListRelationTuplesOutput { + repeated RelationTuple tuples = 1; + string error = 100; +} + +message RemoveRelationTupleInput { + RelationTuple tuple = 1; +} + +message RemoveRelationTupleOutput { + bool changed = 1; + string error = 100; +} + +message UIActionDeclaration { + string id = 1; + string context = 2; + string label = 3; + string route = 4; + repeated string required_scopes = 5; + repeated CapabilityRequirement required_capabilities = 6; + string description = 7; + string owner_plugin = 8; + string owner_module = 9; + string category = 10; +} + +message AuthzDeclarationSet { + repeated ScopeDeclaration scopes = 1; + repeated ResourceDeclaration resources = 2; + repeated ActionDeclaration actions = 3; + repeated AttributeDeclaration attributes = 4; + repeated RelationDeclaration relations = 5; + repeated UIActionDeclaration ui_actions = 6; + string owner_plugin = 7; + string owner_module = 8; +} + +message RegisterDeclarationsInput { + AuthzDeclarationSet declarations = 1; + string owner_plugin = 2; + string owner_module = 3; +} + +message RegisterDeclarationsOutput { + int32 registered = 1; + AuthzDeclarationSet declarations = 2; + string error = 100; +} + +message ListDeclarationsInput { + string context = 1; + string owner_plugin = 2; + string owner_module = 3; +} + +message ListDeclarationsOutput { + AuthzDeclarationSet declarations = 1; + string error = 100; +} + +message ResolveProjectionInputsInput { + string context = 1; + string owner_plugin = 2; + string owner_module = 3; +} + +message ProjectionInputs { + repeated string scope_names = 1; + repeated string resource_names = 2; + repeated string action_names = 3; + repeated string attribute_names = 4; + repeated string relation_names = 5; + repeated string ui_action_ids = 6; + repeated string lookup_source_ids = 7; +} + +message ResolveProjectionInputsOutput { + ProjectionInputs projection = 1; + string error = 100; +} + +message ResolveSubjectScopesInput { + string subject = 1; + repeated string direct_scopes = 2; + repeated string role_scopes = 3; + string context = 4; +} + +message ResolveSubjectScopesOutput { + string subject = 1; + repeated string scopes = 2; + repeated ScopeDeclaration declared_scopes = 3; + string error = 100; +} + +message RoleScopeGrant { + string role = 1; + string context = 2; + repeated string scopes = 3; +} + +message SubjectRoleAssignment { + string subject = 1; + string role = 2; + string context = 3; + repeated string direct_scopes = 4; +} + +message AssignmentFilter { + string subject = 1; + string role = 2; + string context = 3; +} + +message ScopeCheckInput { + string subject = 1; + string context = 2; + string scope = 3; + string resource = 4; + string action = 5; +} + +message ScopeCheckOutput { + bool allowed = 1; + string provider = 2; + string subject = 3; + string context = 4; + string scope = 5; + string matched_role = 6; + repeated string matched_scopes = 7; + string reason = 8; + string error = 100; +} + +message UpsertRoleInput { + RoleScopeGrant grant = 1; +} + +message UpsertRoleOutput { + bool changed = 1; + RoleScopeGrant grant = 2; + string error = 100; +} + +message AssignRoleInput { + SubjectRoleAssignment assignment = 1; +} + +message AssignRoleOutput { + bool changed = 1; + SubjectRoleAssignment assignment = 2; + string error = 100; +} + +message ListRoleAssignmentsInput { + AssignmentFilter filter = 1; +} + +message ListRoleAssignmentsOutput { + repeated SubjectRoleAssignment assignments = 1; + string error = 100; +} + +message RemoveRoleAssignmentInput { + SubjectRoleAssignment assignment = 1; +} + +message RemoveRoleAssignmentOutput { + bool changed = 1; + string error = 100; +} diff --git a/internal/decision_provider.go b/internal/decision_provider.go new file mode 100644 index 0000000..91dd013 --- /dev/null +++ b/internal/decision_provider.go @@ -0,0 +1,123 @@ +package internal + +import ( + "context" + "fmt" +) + +type AuthorizationDecisionInput struct { + Provider string + Mode AuthzCapability + Subject string + Context string + Resource string + Action string + Scope string + Relation string + SubjectAttributes map[string]string + ResourceAttributes map[string]string + EnvironmentAttributes map[string]string + Explain bool +} + +type AuthorizationDecisionOutput struct { + Allowed bool + Mode AuthzCapability + Subject string + Context string + Reason string + Explain string +} + +func DecideAuthorization(ctx context.Context, provider any, input AuthorizationDecisionInput) (AuthorizationDecisionOutput, error) { + mode, err := selectDecisionMode(provider, input) + if err != nil { + return AuthorizationDecisionOutput{}, err + } + switch mode { + case CapabilityRBAC: + scopeProvider, ok := provider.(ScopeRoleProvider) + if !ok { + return AuthorizationDecisionOutput{}, fmt.Errorf("provider does not implement RBAC scope checks") + } + result, err := scopeProvider.CheckScope(ctx, ScopeCheck{ + Subject: input.Subject, + Context: input.Context, + Scope: input.Scope, + Resource: input.Resource, + Action: input.Action, + }) + if err != nil { + return AuthorizationDecisionOutput{}, err + } + return AuthorizationDecisionOutput{Allowed: result.Allowed, Mode: CapabilityRBAC, Subject: result.Subject, Context: result.Context, Reason: result.Reason, Explain: result.MatchedRole}, nil + case CapabilityABAC: + attributeProvider, ok := provider.(AttributePolicyProvider) + if !ok { + return AuthorizationDecisionOutput{}, fmt.Errorf("provider does not implement ABAC checks") + } + result, err := attributeProvider.CheckAttributes(ctx, AttributeCheck{ + Subject: input.Subject, + Context: input.Context, + Resource: input.Resource, + Action: input.Action, + SubjectAttributes: input.SubjectAttributes, + ResourceAttributes: input.ResourceAttributes, + EnvironmentAttributes: input.EnvironmentAttributes, + }) + if err != nil { + return AuthorizationDecisionOutput{}, err + } + return AuthorizationDecisionOutput{Allowed: result.Allowed, Mode: CapabilityABAC, Subject: result.Subject, Context: result.Context, Reason: result.Reason, Explain: result.MatchedPolicyID}, nil + case CapabilityReBAC: + relationshipProvider, ok := provider.(RelationshipProvider) + if !ok { + return AuthorizationDecisionOutput{}, fmt.Errorf("provider does not implement ReBAC checks") + } + result, err := relationshipProvider.CheckRelation(ctx, RelationCheck{ + Subject: input.Subject, + Context: input.Context, + Object: input.Resource, + Relation: input.Relation, + }) + if err != nil { + return AuthorizationDecisionOutput{}, err + } + return AuthorizationDecisionOutput{Allowed: result.Allowed, Mode: CapabilityReBAC, Subject: result.Subject, Context: result.Context, Reason: result.Reason, Explain: result.Relation}, nil + default: + return AuthorizationDecisionOutput{}, fmt.Errorf("unsupported authorization mode %q", mode) + } +} + +func selectDecisionMode(provider any, input AuthorizationDecisionInput) (AuthzCapability, error) { + if input.Mode != "" { + if authzProvider, ok := provider.(AuthzProvider); ok && !authzProvider.SupportsCapability(input.Mode) { + return "", fmt.Errorf("provider does not support authorization mode %q", input.Mode) + } + return input.Mode, nil + } + hasScope := input.Scope != "" || (input.Resource != "" && input.Action != "" && input.Relation == "" && len(input.SubjectAttributes) == 0 && len(input.ResourceAttributes) == 0) + hasAttrs := len(input.SubjectAttributes) > 0 || len(input.ResourceAttributes) > 0 || len(input.EnvironmentAttributes) > 0 + hasRelation := input.Relation != "" + count := 0 + var mode AuthzCapability + if hasScope { + count++ + mode = CapabilityRBAC + } + if hasAttrs { + count++ + mode = CapabilityABAC + } + if hasRelation { + count++ + mode = CapabilityReBAC + } + if count != 1 { + return "", fmt.Errorf("authorization decision mode is ambiguous; set mode explicitly") + } + if authzProvider, ok := provider.(AuthzProvider); ok && !authzProvider.SupportsCapability(mode) { + return "", fmt.Errorf("provider does not support authorization mode %q", mode) + } + return mode, nil +} diff --git a/internal/decision_provider_test.go b/internal/decision_provider_test.go new file mode 100644 index 0000000..64c9440 --- /dev/null +++ b/internal/decision_provider_test.go @@ -0,0 +1,95 @@ +package internal + +import ( + "context" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestAuthorizationDecisionRoutesRBACABACAndReBAC(t *testing.T) { + ctx := context.Background() + + rbac := rbacTestModule(t, nil, nil) + scope := &contracts.ScopeDeclaration{Name: "frontend:orders:read", Context: "frontend", Resource: "orders", Actions: []string{"read"}} + if err := rbac.DeclareScopes(ctx, []*contracts.ScopeDeclaration{scope}); err != nil { + t.Fatalf("DeclareScopes: %v", err) + } + if err := rbac.UpsertRole(ctx, RoleScopeGrant{Role: "reader", Context: "frontend", Scopes: []string{scope.GetName()}}); err != nil { + t.Fatalf("UpsertRole: %v", err) + } + if err := rbac.AssignRole(ctx, SubjectRoleAssignment{Subject: "alice", Role: "reader", Context: "frontend"}); err != nil { + t.Fatalf("AssignRole: %v", err) + } + rbacDecision, err := DecideAuthorization(ctx, rbac, AuthorizationDecisionInput{ + Mode: CapabilityRBAC, + Subject: "alice", + Context: "frontend", + Scope: scope.GetName(), + }) + if err != nil { + t.Fatalf("RBAC decision: %v", err) + } + if !rbacDecision.Allowed || rbacDecision.Mode != CapabilityRBAC { + t.Fatalf("RBAC decision = %#v", rbacDecision) + } + + abac := abacAttributeTestModule(t) + if err := abac.DeclareAttributes(ctx, []*contracts.AttributeDeclaration{{Name: "department", Context: "frontend", Target: "subject", DataType: "string"}}); err != nil { + t.Fatalf("DeclareAttributes: %v", err) + } + if err := abac.UpsertAttributePolicy(ctx, AttributePolicy{ID: "support-read", Context: "frontend", Resource: "ticket", Action: "read", Conditions: []AttributeCondition{{Target: "subject", Attribute: "department", Values: []string{"support"}}}}); err != nil { + t.Fatalf("UpsertAttributePolicy: %v", err) + } + abacDecision, err := DecideAuthorization(ctx, abac, AuthorizationDecisionInput{ + Mode: CapabilityABAC, + Subject: "bob", + Context: "frontend", + Resource: "ticket", + Action: "read", + SubjectAttributes: map[string]string{"department": "support"}, + }) + if err != nil { + t.Fatalf("ABAC decision: %v", err) + } + if !abacDecision.Allowed || abacDecision.Mode != CapabilityABAC { + t.Fatalf("ABAC decision = %#v", abacDecision) + } + + rebac := rebacTestModule(t) + if err := rebac.UpsertRelationTuple(ctx, RelationTuple{Subject: "carol", Relation: "owner", Object: "doc1", Context: "frontend"}); err != nil { + t.Fatalf("UpsertRelationTuple: %v", err) + } + rebacDecision, err := DecideAuthorization(ctx, rebac, AuthorizationDecisionInput{ + Mode: CapabilityReBAC, + Subject: "carol", + Context: "frontend", + Resource: "doc1", + Relation: "owner", + }) + if err != nil { + t.Fatalf("ReBAC decision: %v", err) + } + if !rebacDecision.Allowed || rebacDecision.Mode != CapabilityReBAC { + t.Fatalf("ReBAC decision = %#v", rebacDecision) + } +} + +func TestAuthorizationDecisionRejectsAmbiguousOrUnsupportedMode(t *testing.T) { + _, err := DecideAuthorization(context.Background(), aclTestModule(t, nil), AuthorizationDecisionInput{ + Subject: "alice", + Context: "frontend", + }) + if err == nil { + t.Fatal("expected ambiguous decision request to fail") + } + + _, err = DecideAuthorization(context.Background(), aclTestModule(t, nil), AuthorizationDecisionInput{ + Mode: CapabilityABAC, + Subject: "alice", + Context: "frontend", + }) + if err == nil { + t.Fatal("expected unsupported ABAC decision to fail") + } +} diff --git a/internal/module_casbin.go b/internal/module_casbin.go index 3ba8de9..88b729e 100644 --- a/internal/module_casbin.go +++ b/internal/module_casbin.go @@ -9,6 +9,7 @@ import ( "text/template" "time" + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" "github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2/model" "github.com/casbin/casbin/v2/persist" @@ -23,10 +24,13 @@ import ( // loaded from inline config (model text + policy rows + role assignments), // a file adapter, or a GORM adapter backed by postgres/mysql/sqlite3. type CasbinModule struct { - name string - config casbinConfig - mu sync.RWMutex - enforcer *casbin.Enforcer + name string + config casbinConfig + mu sync.RWMutex + enforcer *casbin.Enforcer + scopeRoles *scopeRoleStore + abac *attributePolicyStore + relations *relationTupleStore // polling watcher fields stopCh chan struct{} @@ -88,8 +92,11 @@ func newCasbinModule(name string, config map[string]any) (*CasbinModule, error) return nil, fmt.Errorf("authz.casbin %q: %w", name, err) } return &CasbinModule{ - name: name, - config: cfg, + name: name, + config: cfg, + scopeRoles: newScopeRoleStore("casbin"), + abac: newAttributePolicyStore("casbin", nil), + relations: newRelationTupleStore(), }, nil } @@ -463,6 +470,198 @@ func toInterfaceSlice(ss []string) []interface{} { // Name returns the module name. func (m *CasbinModule) Name() string { return m.name } +func (m *CasbinModule) scopeRoleStore() *scopeRoleStore { + if m.scopeRoles == nil { + m.scopeRoles = newScopeRoleStore("casbin") + } + return m.scopeRoles +} + +func (m *CasbinModule) DeclareScopes(ctx context.Context, scopes []*contracts.ScopeDeclaration) error { + return m.scopeRoleStore().DeclareScopes(ctx, scopes) +} + +func (m *CasbinModule) UpsertRole(ctx context.Context, grant RoleScopeGrant) error { + return m.scopeRoleStore().UpsertRole(ctx, grant) +} + +func (m *CasbinModule) AssignRole(ctx context.Context, assignment SubjectRoleAssignment) error { + return m.scopeRoleStore().AssignRole(ctx, assignment) +} + +func (m *CasbinModule) ListAssignments(ctx context.Context, filter AssignmentFilter) ([]SubjectRoleAssignment, error) { + return m.scopeRoleStore().ListAssignments(ctx, filter) +} + +func (m *CasbinModule) RemoveAssignment(ctx context.Context, assignment SubjectRoleAssignment) error { + return m.scopeRoleStore().RemoveAssignment(ctx, assignment) +} + +func (m *CasbinModule) CheckScope(ctx context.Context, check ScopeCheck) (ScopeCheckResult, error) { + return m.scopeRoleStore().CheckScope(ctx, check) +} + +func (m *CasbinModule) DeclareAttributes(ctx context.Context, attrs []*contracts.AttributeDeclaration) error { + if !m.SupportsCapability(CapabilityABAC) { + return errUnsupportedABAC + } + return m.abac.DeclareAttributes(ctx, attrs) +} + +func (m *CasbinModule) UpsertAttributePolicy(ctx context.Context, policy AttributePolicy) error { + if !m.SupportsCapability(CapabilityABAC) { + return errUnsupportedABAC + } + return m.abac.UpsertAttributePolicy(ctx, policy) +} + +func (m *CasbinModule) ListAttributePolicies(ctx context.Context, filter AttributePolicyFilter) ([]AttributePolicy, error) { + if !m.SupportsCapability(CapabilityABAC) { + return nil, errUnsupportedABAC + } + return m.abac.ListAttributePolicies(ctx, filter) +} + +func (m *CasbinModule) RemoveAttributePolicy(ctx context.Context, filter AttributePolicyFilter) error { + if !m.SupportsCapability(CapabilityABAC) { + return errUnsupportedABAC + } + return m.abac.RemoveAttributePolicy(ctx, filter) +} + +func (m *CasbinModule) CheckAttributes(ctx context.Context, check AttributeCheck) (AttributeCheckResult, error) { + if !m.SupportsCapability(CapabilityABAC) { + return AttributeCheckResult{}, errUnsupportedABAC + } + return m.abac.CheckAttributes(ctx, check) +} + +func (m *CasbinModule) UpsertRelationTuple(_ context.Context, tuple RelationTuple) error { + if !m.SupportsCapability(CapabilityReBAC) { + return errUnsupportedReBAC + } + tuple = normalizeRelationTuple(tuple) + if err := validateRelationTuple(tuple); err != nil { + return err + } + m.mu.Lock() + defer m.mu.Unlock() + if m.enforcer == nil { + return fmt.Errorf("casbin enforcer is not initialized") + } + if _, err := m.enforcer.AddNamedGroupingPolicy("g2", tuple.Subject, tuple.Relation, tuple.Object); err != nil { + return err + } + return m.relations.Upsert(tuple) +} + +func (m *CasbinModule) RemoveRelationTuple(_ context.Context, tuple RelationTuple) error { + if !m.SupportsCapability(CapabilityReBAC) { + return errUnsupportedReBAC + } + tuple = normalizeRelationTuple(tuple) + if err := validateRelationTuple(tuple); err != nil { + return err + } + m.mu.Lock() + defer m.mu.Unlock() + if m.enforcer == nil { + return fmt.Errorf("casbin enforcer is not initialized") + } + if _, err := m.enforcer.RemoveNamedGroupingPolicy("g2", tuple.Subject, tuple.Relation, tuple.Object); err != nil { + return err + } + return m.relations.Remove(tuple) +} + +func (m *CasbinModule) ListRelationTuples(_ context.Context, filter RelationTupleFilter) ([]RelationTuple, error) { + if !m.SupportsCapability(CapabilityReBAC) { + return nil, errUnsupportedReBAC + } + return m.relations.List(filter), nil +} + +func (m *CasbinModule) CheckRelation(ctx context.Context, check RelationCheck) (RelationCheckResult, error) { + if !m.SupportsCapability(CapabilityReBAC) { + return RelationCheckResult{}, errUnsupportedReBAC + } + tuples, err := m.ListRelationTuples(ctx, RelationTupleFilter{Subject: check.Subject, Relation: check.Relation, Object: check.Object, Context: check.Context}) + if err != nil { + return RelationCheckResult{}, err + } + result := RelationCheckResult{Subject: check.Subject, Relation: check.Relation, Object: check.Object, Context: check.Context} + result.Allowed = len(tuples) > 0 + if !result.Allowed { + result.Reason = "relation tuple not found" + } + return result, nil +} + +func (m *CasbinModule) InvokeMethod(method string, input map[string]any) (map[string]any, error) { + ctx := context.Background() + switch method { + case "DeclareScopes": + scopes := scopeDeclarationsFromAny(input["scopes"], stringValue(input["owner_plugin"]), stringValue(input["owner_module"])) + if err := m.DeclareScopes(ctx, scopes); err != nil { + return nil, err + } + return map[string]any{"registered": len(scopes), "scopes": scopeDeclarationsToMaps(scopes)}, nil + case "UpsertRole": + grant := roleScopeGrantFromMap(mapValue(input["grant"])) + if err := m.UpsertRole(ctx, grant); err != nil { + return nil, err + } + return map[string]any{"changed": true, "grant": roleScopeGrantToMap(grant)}, nil + case "AssignRole": + assignment := subjectRoleAssignmentFromMap(mapValue(input["assignment"])) + if err := m.AssignRole(ctx, assignment); err != nil { + return nil, err + } + return map[string]any{"changed": true, "assignment": subjectRoleAssignmentToMap(assignment)}, nil + case "ListAssignments": + assignments, err := m.ListAssignments(ctx, assignmentFilterFromMap(mapValue(input["filter"]))) + if err != nil { + return nil, err + } + return map[string]any{"assignments": subjectRoleAssignmentsToMaps(assignments)}, nil + case "RemoveAssignment": + if err := m.RemoveAssignment(ctx, subjectRoleAssignmentFromMap(mapValue(input["assignment"]))); err != nil { + return nil, err + } + return map[string]any{"changed": true}, nil + case "CheckScope": + result, err := m.CheckScope(ctx, scopeCheckFromMap(input)) + if err != nil { + return nil, err + } + return scopeCheckResultToMap(result), nil + case "GetCapabilities": + return providerCapabilitiesInvoke(m.name, "casbin", m, input, false) + case "RequireCapabilities": + return providerCapabilitiesInvoke(m.name, "casbin", m, input, true) + case "DeclareAttributes": + return declareAttributesInvoke(ctx, m, input) + case "UpsertAttributePolicy": + return upsertAttributePolicyInvoke(ctx, m, input) + case "ListAttributePolicies": + return listAttributePoliciesInvoke(ctx, m, input) + case "RemoveAttributePolicy": + return removeAttributePolicyInvoke(ctx, m, input) + case "CheckAttributes": + return checkAttributesInvoke(ctx, m, input) + case "UpsertRelationTuple": + return upsertRelationTupleInvoke(ctx, m, input) + case "ListRelationTuples": + return listRelationTuplesInvoke(ctx, m, input) + case "RemoveRelationTuple": + return removeRelationTupleInvoke(ctx, m, input) + case "CheckRelation": + return checkRelationInvoke(ctx, m, input) + default: + return nil, fmt.Errorf("authz casbin method %q is not supported", method) + } +} + // --- in-memory Casbin adapter --- // inMemoryAdapter implements persist.Adapter with an in-memory policy store diff --git a/internal/module_keto.go b/internal/module_keto.go new file mode 100644 index 0000000..e782678 --- /dev/null +++ b/internal/module_keto.go @@ -0,0 +1,106 @@ +package internal + +import ( + "context" + "fmt" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +type KetoModule struct { + name string + config ketoModuleConfig + provider *ketoScopeProvider +} + +type ketoModuleConfig struct { + ReadURL string `yaml:"readUrl"` + WriteURL string `yaml:"writeUrl"` +} + +func newKetoModule(name string, config map[string]any) (*KetoModule, error) { + return &KetoModule{name: name, config: ketoModuleConfig{ + ReadURL: stringValue(firstNonNil(config["readUrl"], config["read_url"])), + WriteURL: stringValue(firstNonNil(config["writeUrl"], config["write_url"])), + }}, nil +} + +func (m *KetoModule) Init() error { + m.provider = newKetoScopeProvider(m.name, newKetoSDKClient(m.config.ReadURL, m.config.WriteURL)) + return nil +} + +func (m *KetoModule) Start(_ context.Context) error { return nil } + +func (m *KetoModule) Stop(_ context.Context) error { return nil } + +func (m *KetoModule) Name() string { return m.name } + +func (m *KetoModule) DeclareScopes(ctx context.Context, scopes []*contracts.ScopeDeclaration) error { + return m.provider.DeclareScopes(ctx, scopes) +} + +func (m *KetoModule) UpsertRole(ctx context.Context, grant RoleScopeGrant) error { + return m.provider.UpsertRole(ctx, grant) +} + +func (m *KetoModule) AssignRole(ctx context.Context, assignment SubjectRoleAssignment) error { + return m.provider.AssignRole(ctx, assignment) +} + +func (m *KetoModule) ListAssignments(ctx context.Context, filter AssignmentFilter) ([]SubjectRoleAssignment, error) { + return m.provider.ListAssignments(ctx, filter) +} + +func (m *KetoModule) RemoveAssignment(ctx context.Context, assignment SubjectRoleAssignment) error { + return m.provider.RemoveAssignment(ctx, assignment) +} + +func (m *KetoModule) CheckScope(ctx context.Context, check ScopeCheck) (ScopeCheckResult, error) { + return m.provider.CheckScope(ctx, check) +} + +func (m *KetoModule) UpsertRelationTuple(ctx context.Context, tuple RelationTuple) error { + return m.provider.UpsertRelationTuple(ctx, tuple) +} + +func (m *KetoModule) RemoveRelationTuple(ctx context.Context, tuple RelationTuple) error { + return m.provider.RemoveRelationTuple(ctx, tuple) +} + +func (m *KetoModule) ListRelationTuples(ctx context.Context, filter RelationTupleFilter) ([]RelationTuple, error) { + return m.provider.ListRelationTuples(ctx, filter) +} + +func (m *KetoModule) CheckRelation(ctx context.Context, check RelationCheck) (RelationCheckResult, error) { + return m.provider.CheckRelation(ctx, check) +} + +func (m *KetoModule) InvokeMethod(method string, input map[string]any) (map[string]any, error) { + switch method { + case "GetCapabilities": + return providerCapabilitiesInvoke(m.name, "keto", m, input, false) + case "RequireCapabilities": + return providerCapabilitiesInvoke(m.name, "keto", m, input, true) + case "UpsertRelationTuple": + return upsertRelationTupleInvoke(context.Background(), m, input) + case "ListRelationTuples": + return listRelationTuplesInvoke(context.Background(), m, input) + case "RemoveRelationTuple": + return removeRelationTupleInvoke(context.Background(), m, input) + case "CheckRelation": + return checkRelationInvoke(context.Background(), m, input) + default: + return nil, fmt.Errorf("authz keto method %q is not supported", method) + } +} + +func ketoModuleConfigToMap(cfg *contracts.KetoModuleConfig) map[string]any { + if cfg == nil { + return nil + } + return compactMap(map[string]any{ + "readUrl": cfg.GetReadUrl(), + "writeUrl": cfg.GetWriteUrl(), + }) +} diff --git a/internal/module_permit.go b/internal/module_permit.go index 996b2ff..5fbb79f 100644 --- a/internal/module_permit.go +++ b/internal/module_permit.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" "time" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" ) const ( @@ -13,12 +15,13 @@ const ( ) // PermitModule implements sdk.ModuleInstance for the permit.provider module type. -// It creates and registers a permitClient backed by direct HTTP calls to the -// Permit.io management API and PDP API. +// Scope-role APIs use the official Permit.io Go SDK. Legacy step helpers still +// use the old registered client until those unused step contracts are removed. type PermitModule struct { - name string - config permitModuleConfig - client *permitClient + name string + config permitModuleConfig + client *permitClient + scopeProvider *permitScopeProvider } // permitModuleConfig holds parsed configuration for a permit.provider module. @@ -64,6 +67,7 @@ func newPermitModule(name string, config map[string]any) (*PermitModule, error) // Init creates the HTTP client and registers it in the global permit registry. func (m *PermitModule) Init() error { + m.scopeProvider = newPermitScopeProvider(m.name, newPermitSDKScopeClient(m.config)) m.client = &permitClient{ httpClient: &http.Client{ Timeout: 30 * time.Second, @@ -89,3 +93,104 @@ func (m *PermitModule) Stop(_ context.Context) error { // Name returns the module name. func (m *PermitModule) Name() string { return m.name } + +func (m *PermitModule) DeclareScopes(ctx context.Context, scopes []*contracts.ScopeDeclaration) error { + return m.scopeProvider.DeclareScopes(ctx, scopes) +} + +func (m *PermitModule) UpsertRole(ctx context.Context, grant RoleScopeGrant) error { + return m.scopeProvider.UpsertRole(ctx, grant) +} + +func (m *PermitModule) AssignRole(ctx context.Context, assignment SubjectRoleAssignment) error { + return m.scopeProvider.AssignRole(ctx, assignment) +} + +func (m *PermitModule) ListAssignments(ctx context.Context, filter AssignmentFilter) ([]SubjectRoleAssignment, error) { + return m.scopeProvider.ListAssignments(ctx, filter) +} + +func (m *PermitModule) RemoveAssignment(ctx context.Context, assignment SubjectRoleAssignment) error { + return m.scopeProvider.RemoveAssignment(ctx, assignment) +} + +func (m *PermitModule) CheckScope(ctx context.Context, check ScopeCheck) (ScopeCheckResult, error) { + return m.scopeProvider.CheckScope(ctx, check) +} + +func (m *PermitModule) DeclareAttributes(context.Context, []*contracts.AttributeDeclaration) error { + return errUnsupportedABAC +} + +func (m *PermitModule) UpsertAttributePolicy(context.Context, AttributePolicy) error { + return errUnsupportedABAC +} + +func (m *PermitModule) ListAttributePolicies(context.Context, AttributePolicyFilter) ([]AttributePolicy, error) { + return nil, errUnsupportedABAC +} + +func (m *PermitModule) RemoveAttributePolicy(context.Context, AttributePolicyFilter) error { + return errUnsupportedABAC +} + +func (m *PermitModule) CheckAttributes(_ context.Context, check AttributeCheck) (AttributeCheckResult, error) { + return AttributeCheckResult{ + Subject: check.Subject, + Context: check.Context, + Resource: check.Resource, + Action: check.Action, + Reason: errUnsupportedABAC.Error(), + }, errUnsupportedABAC +} + +func (m *PermitModule) UpsertRelationTuple(context.Context, RelationTuple) error { + return errUnsupportedReBAC +} + +func (m *PermitModule) RemoveRelationTuple(context.Context, RelationTuple) error { + return errUnsupportedReBAC +} + +func (m *PermitModule) ListRelationTuples(context.Context, RelationTupleFilter) ([]RelationTuple, error) { + return nil, errUnsupportedReBAC +} + +func (m *PermitModule) CheckRelation(_ context.Context, check RelationCheck) (RelationCheckResult, error) { + return RelationCheckResult{ + Subject: check.Subject, + Relation: check.Relation, + Object: check.Object, + Context: check.Context, + Reason: errUnsupportedReBAC.Error(), + }, errUnsupportedReBAC +} + +func (m *PermitModule) InvokeMethod(method string, input map[string]any) (map[string]any, error) { + switch method { + case "GetCapabilities": + return providerCapabilitiesInvoke(m.name, "permit", m, input, false) + case "RequireCapabilities": + return providerCapabilitiesInvoke(m.name, "permit", m, input, true) + case "DeclareAttributes": + return declareAttributesInvoke(context.Background(), m, input) + case "UpsertAttributePolicy": + return upsertAttributePolicyInvoke(context.Background(), m, input) + case "ListAttributePolicies": + return listAttributePoliciesInvoke(context.Background(), m, input) + case "RemoveAttributePolicy": + return removeAttributePolicyInvoke(context.Background(), m, input) + case "CheckAttributes": + return checkAttributesInvoke(context.Background(), m, input) + case "UpsertRelationTuple": + return upsertRelationTupleInvoke(context.Background(), m, input) + case "ListRelationTuples": + return listRelationTuplesInvoke(context.Background(), m, input) + case "RemoveRelationTuple": + return removeRelationTupleInvoke(context.Background(), m, input) + case "CheckRelation": + return checkRelationInvoke(context.Background(), m, input) + default: + return nil, fmt.Errorf("permit provider method %q is not supported", method) + } +} diff --git a/internal/module_scope_catalog.go b/internal/module_scope_catalog.go new file mode 100644 index 0000000..9bfe71e --- /dev/null +++ b/internal/module_scope_catalog.go @@ -0,0 +1,388 @@ +package internal + +import ( + "context" + "fmt" + "sort" + "sync" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +type scopeCatalogModule struct { + name string + mu sync.RWMutex + scopes map[string]*contracts.ScopeDeclaration + resources map[string]*contracts.ResourceDeclaration + actions map[string]*contracts.ActionDeclaration + attributes map[string]*contracts.AttributeDeclaration + relations map[string]*contracts.RelationDeclaration + uiActions map[string]*contracts.UIActionDeclaration +} + +func newScopeCatalogModule(name string, config map[string]any) *scopeCatalogModule { + m := &scopeCatalogModule{ + name: name, + scopes: map[string]*contracts.ScopeDeclaration{}, + resources: map[string]*contracts.ResourceDeclaration{}, + actions: map[string]*contracts.ActionDeclaration{}, + attributes: map[string]*contracts.AttributeDeclaration{}, + relations: map[string]*contracts.RelationDeclaration{}, + uiActions: map[string]*contracts.UIActionDeclaration{}, + } + for _, scope := range scopeDeclarationsFromAny(config["scopes"], "", "") { + m.upsert(scope) + } + if declarations := declarationSetFromAny(config["declarations"], "", ""); declarations != nil { + _, _ = m.registerDeclarations(&contracts.RegisterDeclarationsInput{Declarations: declarations}) + } + return m +} + +func (m *scopeCatalogModule) Init() error { return nil } + +func (m *scopeCatalogModule) Start(_ context.Context) error { return nil } + +func (m *scopeCatalogModule) Stop(_ context.Context) error { return nil } + +func (m *scopeCatalogModule) InvokeMethod(method string, input map[string]any) (map[string]any, error) { + switch method { + case "RegisterScopes": + out, err := m.registerScopes(registerScopesInputFromMap(input)) + if err != nil { + return nil, err + } + return registerScopesOutputToMap(out), nil + case "ListScopes": + return map[string]any{"scopes": scopeDeclarationsToMaps(m.listScopes(listScopesInputFromMap(input)))}, nil + case "ResolveSubjectScopes": + out := m.resolveSubjectScopes(resolveSubjectScopesInputFromMap(input)) + return resolveSubjectScopesOutputToMap(out), nil + case "RegisterDeclarations": + out, err := m.registerDeclarations(registerDeclarationsInputFromMap(input)) + if err != nil { + return nil, err + } + return registerDeclarationsOutputToMap(out), nil + case "ListDeclarations": + return map[string]any{"declarations": declarationSetToMap(m.listDeclarations(listDeclarationsInputFromMap(input)))}, nil + case "ResolveProjectionInputs": + return map[string]any{"projection": projectionInputsToMap(m.resolveProjectionInputs(resolveProjectionInputsInputFromMap(input)))}, nil + default: + return nil, fmt.Errorf("authz scope catalog method %q is not supported", method) + } +} + +func (m *scopeCatalogModule) registerScopes(input *contracts.RegisterScopesInput) (*contracts.RegisterScopesOutput, error) { + if input == nil { + return &contracts.RegisterScopesOutput{}, nil + } + m.mu.Lock() + defer m.mu.Unlock() + registered := int32(0) + out := make([]*contracts.ScopeDeclaration, 0, len(input.GetScopes())) + for _, incoming := range input.GetScopes() { + scope := cloneScopeDeclaration(incoming) + if scope.GetName() == "" { + continue + } + if scope.OwnerPlugin == "" { + scope.OwnerPlugin = input.GetOwnerPlugin() + } + if scope.OwnerModule == "" { + scope.OwnerModule = input.GetOwnerModule() + } + if _, exists := m.scopes[scope.GetName()]; !exists { + registered++ + } + m.upsertLocked(scope) + out = append(out, cloneScopeDeclaration(scope)) + } + return &contracts.RegisterScopesOutput{Registered: registered, Scopes: out}, nil +} + +func (m *scopeCatalogModule) listScopes(input *contracts.ListScopesInput) []*contracts.ScopeDeclaration { + m.mu.RLock() + defer m.mu.RUnlock() + out := make([]*contracts.ScopeDeclaration, 0, len(m.scopes)) + for _, scope := range m.scopes { + if input != nil { + if input.GetContext() != "" && scope.GetContext() != input.GetContext() { + continue + } + if input.GetOwnerPlugin() != "" && scope.GetOwnerPlugin() != input.GetOwnerPlugin() { + continue + } + if input.GetOwnerModule() != "" && scope.GetOwnerModule() != input.GetOwnerModule() { + continue + } + } + out = append(out, cloneScopeDeclaration(scope)) + } + sortScopes(out) + return out +} + +func (m *scopeCatalogModule) resolveSubjectScopes(input *contracts.ResolveSubjectScopesInput) *contracts.ResolveSubjectScopesOutput { + if input == nil { + return &contracts.ResolveSubjectScopesOutput{} + } + m.mu.RLock() + defer m.mu.RUnlock() + names := map[string]struct{}{} + for _, scope := range append(input.GetRoleScopes(), input.GetDirectScopes()...) { + if scope != "" { + names[scope] = struct{}{} + } + } + resolved := make([]string, 0, len(names)) + declared := make([]*contracts.ScopeDeclaration, 0, len(names)) + for name := range names { + scope, ok := m.scopes[name] + if input.GetContext() != "" && ok && scope.GetContext() != input.GetContext() { + continue + } + resolved = append(resolved, name) + if ok { + declared = append(declared, cloneScopeDeclaration(scope)) + } + } + sort.Strings(resolved) + sortScopes(declared) + return &contracts.ResolveSubjectScopesOutput{ + Subject: input.GetSubject(), + Scopes: resolved, + DeclaredScopes: declared, + } +} + +func (m *scopeCatalogModule) upsert(scope *contracts.ScopeDeclaration) { + m.mu.Lock() + defer m.mu.Unlock() + m.upsertLocked(scope) +} + +func (m *scopeCatalogModule) upsertLocked(scope *contracts.ScopeDeclaration) { + if scope == nil || scope.GetName() == "" { + return + } + m.scopes[scope.GetName()] = cloneScopeDeclaration(scope) +} + +func scopeCatalogConfigToMap(cfg *contracts.ScopeCatalogConfig) map[string]any { + if cfg == nil { + return nil + } + out := map[string]any{} + if scopes := scopeDeclarationsToMaps(cfg.GetScopes()); len(scopes) > 0 { + out["scopes"] = scopes + } + if cfg.GetAllowRuntimeRegistration() { + out["allow_runtime_registration"] = true + } + if cfg.GetDeclarations() != nil { + out["declarations"] = declarationSetToMap(cfg.GetDeclarations()) + } + return out +} + +func typedRegisterScopes(module *scopeCatalogModule) sdk.TypedStepHandler[*contracts.RegisterScopesInput, *contracts.RegisterScopesInput, *contracts.RegisterScopesOutput] { + return func(_ context.Context, req sdk.TypedStepRequest[*contracts.RegisterScopesInput, *contracts.RegisterScopesInput]) (*sdk.TypedStepResult[*contracts.RegisterScopesOutput], error) { + config := cloneRegisterScopesInput(req.Config) + input := cloneRegisterScopesInput(req.Input) + if input.GetOwnerPlugin() == "" { + input.OwnerPlugin = config.GetOwnerPlugin() + } + if input.GetOwnerModule() == "" { + input.OwnerModule = config.GetOwnerModule() + } + if len(input.GetScopes()) == 0 { + input.Scopes = config.GetScopes() + } + out, err := module.registerScopes(input) + if err != nil { + return nil, err + } + return &sdk.TypedStepResult[*contracts.RegisterScopesOutput]{Output: out}, nil + } +} + +func registerScopesInputFromMap(values map[string]any) *contracts.RegisterScopesInput { + if values == nil { + return nil + } + return &contracts.RegisterScopesInput{ + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + Scopes: scopeDeclarationsFromAny(values["scopes"], stringValue(values["owner_plugin"]), stringValue(values["owner_module"])), + } +} + +func listScopesInputFromMap(values map[string]any) *contracts.ListScopesInput { + if values == nil { + return nil + } + return &contracts.ListScopesInput{ + Context: stringValue(values["context"]), + OwnerPlugin: stringValue(values["owner_plugin"]), + OwnerModule: stringValue(values["owner_module"]), + } +} + +func resolveSubjectScopesInputFromMap(values map[string]any) *contracts.ResolveSubjectScopesInput { + if values == nil { + return nil + } + return &contracts.ResolveSubjectScopesInput{ + Subject: stringValue(values["subject"]), + DirectScopes: stringSliceValue(values["direct_scopes"]), + RoleScopes: stringSliceValue(values["role_scopes"]), + Context: stringValue(values["context"]), + } +} + +func registerScopesOutputToMap(out *contracts.RegisterScopesOutput) map[string]any { + if out == nil { + return map[string]any{"registered": 0, "scopes": []map[string]any{}} + } + return map[string]any{"registered": int(out.GetRegistered()), "scopes": scopeDeclarationsToMaps(out.GetScopes())} +} + +func resolveSubjectScopesOutputToMap(out *contracts.ResolveSubjectScopesOutput) map[string]any { + if out == nil { + return map[string]any{"scopes": []string{}, "declared_scopes": []map[string]any{}} + } + return map[string]any{ + "subject": out.GetSubject(), + "scopes": append([]string(nil), out.GetScopes()...), + "declared_scopes": scopeDeclarationsToMaps(out.GetDeclaredScopes()), + } +} + +func scopeDeclarationsFromAny(value any, ownerPlugin, ownerModule string) []*contracts.ScopeDeclaration { + switch scopes := value.(type) { + case []*contracts.ScopeDeclaration: + out := make([]*contracts.ScopeDeclaration, 0, len(scopes)) + for _, scope := range scopes { + cloned := cloneScopeDeclaration(scope) + if cloned.OwnerPlugin == "" { + cloned.OwnerPlugin = ownerPlugin + } + if cloned.OwnerModule == "" { + cloned.OwnerModule = ownerModule + } + out = append(out, cloned) + } + return out + case []map[string]any: + out := make([]*contracts.ScopeDeclaration, 0, len(scopes)) + for _, scope := range scopes { + out = append(out, scopeDeclarationFromMap(scope, ownerPlugin, ownerModule)) + } + return out + case []any: + out := make([]*contracts.ScopeDeclaration, 0, len(scopes)) + for _, scope := range scopes { + if values := mapValue(scope); len(values) > 0 { + out = append(out, scopeDeclarationFromMap(values, ownerPlugin, ownerModule)) + } + } + return out + default: + return nil + } +} + +func scopeDeclarationFromMap(values map[string]any, ownerPlugin, ownerModule string) *contracts.ScopeDeclaration { + if values == nil { + return nil + } + return &contracts.ScopeDeclaration{ + Name: stringValue(values["name"]), + Context: stringValue(values["context"]), + Resource: stringValue(values["resource"]), + Actions: stringSliceValue(values["actions"]), + Description: stringValue(values["description"]), + OwnerPlugin: defaultString(stringValue(values["owner_plugin"]), ownerPlugin), + OwnerModule: defaultString(stringValue(values["owner_module"]), ownerModule), + Category: stringValue(values["category"]), + } +} + +func mapValue(value any) map[string]any { + switch v := value.(type) { + case map[string]any: + return v + case map[any]any: + out := make(map[string]any, len(v)) + for key, item := range v { + if s, ok := key.(string); ok { + out[s] = item + } + } + return out + default: + return map[string]any{} + } +} + +func defaultString(value, fallback string) string { + if value != "" { + return value + } + return fallback +} + +func scopeDeclarationsToMaps(scopes []*contracts.ScopeDeclaration) []map[string]any { + out := make([]map[string]any, 0, len(scopes)) + for _, scope := range scopes { + if scope == nil { + continue + } + out = append(out, compactMap(map[string]any{ + "name": scope.GetName(), + "context": scope.GetContext(), + "resource": scope.GetResource(), + "actions": stringsToAny(scope.GetActions()), + "description": scope.GetDescription(), + "owner_plugin": scope.GetOwnerPlugin(), + "owner_module": scope.GetOwnerModule(), + "category": scope.GetCategory(), + })) + } + return out +} + +func cloneScopeDeclaration(scope *contracts.ScopeDeclaration) *contracts.ScopeDeclaration { + if scope == nil { + return nil + } + return &contracts.ScopeDeclaration{ + Name: scope.GetName(), + Context: scope.GetContext(), + Resource: scope.GetResource(), + Actions: append([]string(nil), scope.GetActions()...), + Description: scope.GetDescription(), + OwnerPlugin: scope.GetOwnerPlugin(), + OwnerModule: scope.GetOwnerModule(), + Category: scope.GetCategory(), + } +} + +func cloneRegisterScopesInput(input *contracts.RegisterScopesInput) *contracts.RegisterScopesInput { + if input == nil { + return &contracts.RegisterScopesInput{} + } + return &contracts.RegisterScopesInput{ + OwnerPlugin: input.GetOwnerPlugin(), + OwnerModule: input.GetOwnerModule(), + Scopes: scopeDeclarationsFromAny(input.GetScopes(), input.GetOwnerPlugin(), input.GetOwnerModule()), + } +} + +func sortScopes(scopes []*contracts.ScopeDeclaration) { + sort.Slice(scopes, func(i, j int) bool { + return scopes[i].GetName() < scopes[j].GetName() + }) +} diff --git a/internal/plugin.go b/internal/plugin.go index 7dcb784..1ade63a 100644 --- a/internal/plugin.go +++ b/internal/plugin.go @@ -21,9 +21,11 @@ var Version = "0.0.0" // authzPlugin implements sdk.PluginProvider, sdk.ModuleProvider, and sdk.StepProvider. type authzPlugin struct{} -var moduleTypes = []string{"authz.casbin", "permit.provider"} +var moduleTypes = []string{"authz.casbin", "permit.provider", "authz.keto", "authz.scope_catalog"} var casbinStepTypes = []string{ + "step.authz_check", + "step.authz_require_capabilities", "step.authz_check_casbin", "step.authz_add_policy", "step.authz_remove_policy", @@ -82,7 +84,17 @@ func (p *authzPlugin) CreateModule(typeName, name string, config map[string]any) if err != nil { return nil, err } + RegisterAuthzProvider(name, m) return m, nil + case "authz.keto": + m, err := newKetoModule(name, config) + if err != nil { + return nil, err + } + RegisterAuthzProvider(name, m) + return m, nil + case "authz.scope_catalog": + return newScopeCatalogModule(name, config), nil default: return nil, fmt.Errorf("authz plugin: unknown module type %q", typeName) } @@ -103,7 +115,27 @@ func (p *authzPlugin) CreateTypedModule(typeName, name string, config *anypb.Any return factory.CreateTypedModule(typeName, name, config) case "permit.provider": factory := sdk.NewTypedModuleFactory(typeName, &contracts.PermitModuleConfig{}, func(name string, cfg *contracts.PermitModuleConfig) (sdk.ModuleInstance, error) { - return newPermitModule(name, permitModuleConfigToMap(cfg)) + m, err := newPermitModule(name, permitModuleConfigToMap(cfg)) + if err != nil { + return nil, err + } + RegisterAuthzProvider(name, m) + return m, nil + }) + return factory.CreateTypedModule(typeName, name, config) + case "authz.keto": + factory := sdk.NewTypedModuleFactory(typeName, &contracts.KetoModuleConfig{}, func(name string, cfg *contracts.KetoModuleConfig) (sdk.ModuleInstance, error) { + m, err := newKetoModule(name, ketoModuleConfigToMap(cfg)) + if err != nil { + return nil, err + } + RegisterAuthzProvider(name, m) + return m, nil + }) + return factory.CreateTypedModule(typeName, name, config) + case "authz.scope_catalog": + factory := sdk.NewTypedModuleFactory(typeName, &contracts.ScopeCatalogConfig{}, func(name string, cfg *contracts.ScopeCatalogConfig) (sdk.ModuleInstance, error) { + return newScopeCatalogModule(name, scopeCatalogConfigToMap(cfg)), nil }) return factory.CreateTypedModule(typeName, name, config) default: @@ -125,6 +157,10 @@ func (p *authzPlugin) TypedStepTypes() []string { // CreateStep creates a step instance of the given type. func (p *authzPlugin) CreateStep(typeName, name string, config map[string]any) (sdk.StepInstance, error) { switch typeName { + case "step.authz_check": + return newAuthzDecisionStep(name, config) + case "step.authz_require_capabilities": + return newAuthzRequireCapabilitiesStep(name, config) case "step.authz_check_casbin": return newAuthzCheckStep(name, config) case "step.authz_add_policy": @@ -170,6 +206,10 @@ func (p *authzPlugin) CreateStep(typeName, name string, config map[string]any) ( // CreateTypedStep creates a protobuf-backed step instance. func (p *authzPlugin) CreateTypedStep(typeName, name string, config *anypb.Any) (sdk.StepInstance, error) { switch typeName { + case "step.authz_check": + return sdk.NewTypedStepFactory(typeName, &contracts.AuthorizationDecisionConfig{}, &contracts.AuthorizationDecisionInput{}, typedAuthzDecision(globalRegistry)).CreateTypedStep(typeName, name, config) + case "step.authz_require_capabilities": + return sdk.NewTypedStepFactory(typeName, &contracts.RequireCapabilitiesConfig{}, &contracts.RequireCapabilitiesInput{}, typedAuthzRequireCapabilities(globalRegistry)).CreateTypedStep(typeName, name, config) case "step.authz_check_casbin": return sdk.NewTypedStepFactory(typeName, &contracts.AuthzCheckConfig{}, &contracts.AuthzCheckInput{}, typedAuthzCheck(globalRegistry)).CreateTypedStep(typeName, name, config) case "step.authz_add_policy": @@ -213,6 +253,10 @@ func (p *authzPlugin) ContractRegistry() *pb.ContractRegistry { contractsList := []*pb.ContractDescriptor{ moduleContract("authz.casbin", "CasbinModuleConfig"), moduleContract("permit.provider", "PermitModuleConfig"), + moduleContract("authz.keto", "KetoModuleConfig"), + moduleContract("authz.scope_catalog", "ScopeCatalogConfig"), + stepContract("step.authz_check", "AuthorizationDecisionConfig", "AuthorizationDecisionInput", "AuthorizationDecisionOutput"), + stepContract("step.authz_require_capabilities", "RequireCapabilitiesConfig", "RequireCapabilitiesInput", "ProviderCapabilitiesOutput"), stepContract("step.authz_check_casbin", "AuthzCheckConfig", "AuthzCheckInput", "AuthzCheckOutput"), stepContract("step.authz_add_policy", "PolicyRuleConfig", "PolicyRuleInput", "PolicyRuleOutput"), stepContract("step.authz_remove_policy", "PolicyRuleConfig", "PolicyRuleInput", "PolicyRuleOutput"), @@ -228,6 +272,29 @@ func (p *authzPlugin) ContractRegistry() *pb.ContractRegistry { stepContract("step.authz_rebac_remove_relation", "RelationConfig", "RelationInput", "RelationOutput"), stepContract("step.authz_rebac_check", "SubjectObjectActionConfig", "SubjectObjectActionInput", "SubjectObjectActionOutput"), stepContract("step.authz_rebac_list_relations", "ListConfig", "ListInput", "GenericStepOutput"), + serviceContract("authz.scope_catalog", "ScopeCatalog", "RegisterScopes", "RegisterScopesInput", "RegisterScopesOutput"), + serviceContract("authz.scope_catalog", "ScopeCatalog", "ListScopes", "ListScopesInput", "ListScopesOutput"), + serviceContract("authz.scope_catalog", "ScopeCatalog", "ResolveSubjectScopes", "ResolveSubjectScopesInput", "ResolveSubjectScopesOutput"), + serviceContract("authz.scope_catalog", "ScopeCatalog", "RegisterDeclarations", "RegisterDeclarationsInput", "RegisterDeclarationsOutput"), + serviceContract("authz.scope_catalog", "ScopeCatalog", "ListDeclarations", "ListDeclarationsInput", "ListDeclarationsOutput"), + serviceContract("authz.scope_catalog", "ScopeCatalog", "ResolveProjectionInputs", "ResolveProjectionInputsInput", "ResolveProjectionInputsOutput"), + serviceContract("authz.casbin", "ProviderCapabilities", "GetCapabilities", "ProviderCapabilitiesInput", "ProviderCapabilitiesOutput"), + serviceContract("authz.casbin", "ProviderCapabilities", "RequireCapabilities", "ProviderCapabilitiesInput", "ProviderCapabilitiesOutput"), + serviceContract("authz.casbin", "AttributePolicyProvider", "DeclareAttributes", "DeclareAttributesInput", "DeclareAttributesOutput"), + serviceContract("authz.casbin", "AttributePolicyProvider", "UpsertAttributePolicy", "UpsertAttributePolicyInput", "UpsertAttributePolicyOutput"), + serviceContract("authz.casbin", "AttributePolicyProvider", "ListAttributePolicies", "ListAttributePoliciesInput", "ListAttributePoliciesOutput"), + serviceContract("authz.casbin", "AttributePolicyProvider", "RemoveAttributePolicy", "RemoveAttributePolicyInput", "RemoveAttributePolicyOutput"), + serviceContract("authz.casbin", "AttributePolicyProvider", "CheckAttributes", "AttributeCheckInput", "AttributeCheckOutput"), + serviceContract("authz.casbin", "RelationshipProvider", "UpsertRelationTuple", "UpsertRelationTupleInput", "UpsertRelationTupleOutput"), + serviceContract("authz.casbin", "RelationshipProvider", "ListRelationTuples", "ListRelationTuplesInput", "ListRelationTuplesOutput"), + serviceContract("authz.casbin", "RelationshipProvider", "RemoveRelationTuple", "RemoveRelationTupleInput", "RemoveRelationTupleOutput"), + serviceContract("authz.casbin", "RelationshipProvider", "CheckRelation", "RelationCheckInput", "RelationCheckOutput"), + serviceContract("authz.casbin", "ScopeRoleProvider", "DeclareScopes", "RegisterScopesInput", "RegisterScopesOutput"), + serviceContract("authz.casbin", "ScopeRoleProvider", "UpsertRole", "UpsertRoleInput", "UpsertRoleOutput"), + serviceContract("authz.casbin", "ScopeRoleProvider", "AssignRole", "AssignRoleInput", "AssignRoleOutput"), + serviceContract("authz.casbin", "ScopeRoleProvider", "ListAssignments", "ListRoleAssignmentsInput", "ListRoleAssignmentsOutput"), + serviceContract("authz.casbin", "ScopeRoleProvider", "RemoveAssignment", "RemoveRoleAssignmentInput", "RemoveRoleAssignmentOutput"), + serviceContract("authz.casbin", "ScopeRoleProvider", "CheckScope", "ScopeCheckInput", "ScopeCheckOutput"), } for _, stepType := range permitStepTypes() { contractsList = append(contractsList, stepContract(stepType, "PermitStepConfig", "PermitStepInput", "GenericStepOutput")) @@ -263,6 +330,19 @@ func stepContract(stepType, configMessage, inputMessage, outputMessage string) * } } +func serviceContract(moduleType, serviceName, method, inputMessage, outputMessage string) *pb.ContractDescriptor { + const pkg = "workflow.plugins.authz.v1." + return &pb.ContractDescriptor{ + Kind: pb.ContractKind_CONTRACT_KIND_SERVICE, + ModuleType: moduleType, + ServiceName: serviceName, + Method: method, + InputMessage: pkg + inputMessage, + OutputMessage: pkg + outputMessage, + Mode: pb.ContractMode_CONTRACT_MODE_STRICT_PROTO, + } +} + func wrapStepConstructor[T sdk.StepInstance](ctor func(string, map[string]any) (T, error)) func(string, map[string]any) (sdk.StepInstance, error) { return func(name string, config map[string]any) (sdk.StepInstance, error) { return ctor(name, config) diff --git a/internal/plugin_contracts_test.go b/internal/plugin_contracts_test.go index bc78357..97e6032 100644 --- a/internal/plugin_contracts_test.go +++ b/internal/plugin_contracts_test.go @@ -75,6 +75,57 @@ func TestContractRegistryDeclaresStrictContracts(t *testing.T) { } } +func TestProviderCapabilitiesStrictProtoTypes(t *testing.T) { + req := &contracts.ProviderCapabilitiesInput{ + Module: "authz", + Provider: "casbin", + Requirements: []*contracts.CapabilityRequirement{{ + Mode: contracts.AuthzMode_AUTHZ_MODE_RBAC, + Operations: []contracts.AuthzOperation{contracts.AuthzOperation_AUTHZ_OPERATION_CHECK}, + }}, + } + out := &contracts.ProviderCapabilitiesOutput{ + Module: req.GetModule(), + Provider: req.GetProvider(), + Capabilities: []string{"rbac"}, + Descriptors: []*contracts.CapabilityDescriptor{{ + Mode: contracts.AuthzMode_AUTHZ_MODE_RBAC, + Operations: []contracts.AuthzOperation{contracts.AuthzOperation_AUTHZ_OPERATION_CHECK}, + Configured: true, + Source: "detected", + Health: "ok", + }}, + } + if out.GetDescriptors()[0].GetMode() != req.GetRequirements()[0].GetMode() { + t.Fatal("capability descriptor mode should use strict proto enums") + } +} + +func TestContractRegistryDeclaresProviderCapabilitiesService(t *testing.T) { + provider := NewAuthzPlugin().(sdk.ContractProvider) + registry := provider.ContractRegistry() + var getFound, requireFound bool + for _, contract := range registry.Contracts { + if contract.Kind != pb.ContractKind_CONTRACT_KIND_SERVICE || contract.ServiceName != "ProviderCapabilities" { + continue + } + switch contract.Method { + case "GetCapabilities": + getFound = contract.InputMessage == "workflow.plugins.authz.v1.ProviderCapabilitiesInput" && + contract.OutputMessage == "workflow.plugins.authz.v1.ProviderCapabilitiesOutput" + case "RequireCapabilities": + requireFound = contract.InputMessage == "workflow.plugins.authz.v1.ProviderCapabilitiesInput" && + contract.OutputMessage == "workflow.plugins.authz.v1.ProviderCapabilitiesOutput" + } + } + if !getFound { + t.Fatal("missing ProviderCapabilities/GetCapabilities strict service contract") + } + if !requireFound { + t.Fatal("missing ProviderCapabilities/RequireCapabilities strict service contract") + } +} + func TestTypeListsAreDefensiveCopies(t *testing.T) { provider := NewAuthzPlugin() moduleProvider := provider.(sdk.ModuleProvider) diff --git a/internal/provider_keto.go b/internal/provider_keto.go new file mode 100644 index 0000000..aba898d --- /dev/null +++ b/internal/provider_keto.go @@ -0,0 +1,281 @@ +package internal + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" + keto "github.com/ory/keto-client-go/v25" +) + +type ketoClient interface { + CreateRelationship(context.Context, ketoTuple) error + DeleteRelationship(context.Context, ketoTuple) error + Check(context.Context, ketoTuple) (bool, error) +} + +type ketoScopeProvider struct { + name string + store *scopeRoleStore + relations *relationTupleStore + client ketoClient +} + +type ketoTuple struct { + Namespace string + Object string + Relation string + SubjectID string + SubjectSet *ketoSubjectSet +} + +type ketoSubjectSet struct { + Namespace string + Object string + Relation string +} + +func newKetoScopeProvider(name string, client ketoClient) *ketoScopeProvider { + return &ketoScopeProvider{name: name, store: newScopeRoleStore("keto"), relations: newRelationTupleStore(), client: client} +} + +func (p *ketoScopeProvider) Name() string { return p.name } + +func (p *ketoScopeProvider) DeclareScopes(ctx context.Context, scopes []*contracts.ScopeDeclaration) error { + return p.store.DeclareScopes(ctx, scopes) +} + +func (p *ketoScopeProvider) UpsertRole(ctx context.Context, grant RoleScopeGrant) error { + if err := p.store.UpsertRole(ctx, grant); err != nil { + return err + } + for _, scope := range grant.Scopes { + if err := p.client.CreateRelationship(ctx, ketoRoleScopeTuple(grant.Context, grant.Role, scope)); err != nil { + return err + } + } + return nil +} + +func (p *ketoScopeProvider) AssignRole(ctx context.Context, assignment SubjectRoleAssignment) error { + if err := p.store.AssignRole(ctx, assignment); err != nil { + return err + } + if assignment.Role != "" { + if err := p.client.CreateRelationship(ctx, ketoSubjectRoleTuple(assignment.Context, assignment.Role, assignment.Subject)); err != nil { + return err + } + } + for _, scope := range assignment.DirectScopes { + if err := p.client.CreateRelationship(ctx, ketoDirectScopeTuple(assignment.Subject, scope)); err != nil { + return err + } + } + return nil +} + +func (p *ketoScopeProvider) ListAssignments(ctx context.Context, filter AssignmentFilter) ([]SubjectRoleAssignment, error) { + return p.store.ListAssignments(ctx, filter) +} + +func (p *ketoScopeProvider) RemoveAssignment(ctx context.Context, assignment SubjectRoleAssignment) error { + if err := p.store.RemoveAssignment(ctx, assignment); err != nil { + return err + } + if assignment.Role != "" { + return p.client.DeleteRelationship(ctx, ketoSubjectRoleTuple(assignment.Context, assignment.Role, assignment.Subject)) + } + return nil +} + +func (p *ketoScopeProvider) CheckScope(ctx context.Context, check ScopeCheck) (ScopeCheckResult, error) { + scopeName := normalizeCheckScope(check) + result := ScopeCheckResult{ + Provider: p.store.provider, + Subject: strings.TrimSpace(check.Subject), + Context: strings.TrimSpace(check.Context), + Scope: scopeName, + } + local, err := p.store.CheckScope(ctx, check) + if err != nil { + return result, err + } + if !local.Allowed { + local.Provider = p.store.provider + return local, nil + } + allowed, err := p.client.Check(ctx, ketoDirectScopeTuple(result.Subject, scopeName)) + if err != nil { + return result, err + } + result.Allowed = allowed + result.MatchedRole = local.MatchedRole + result.MatchedScopes = local.MatchedScopes + if !allowed { + result.Reason = "keto denied" + } + return result, nil +} + +func ketoRoleObject(contextName, role string) string { + return contextName + ":" + role +} + +func ketoRoleScopeTuple(contextName, role, scope string) ketoTuple { + return ketoTuple{ + Namespace: "scope", + Object: scope, + Relation: "granted", + SubjectSet: &ketoSubjectSet{ + Namespace: "role", + Object: ketoRoleObject(contextName, role), + Relation: "member", + }, + } +} + +func ketoSubjectRoleTuple(contextName, role, subject string) ketoTuple { + return ketoTuple{ + Namespace: "role", + Object: ketoRoleObject(contextName, role), + Relation: "member", + SubjectID: subject, + } +} + +func ketoDirectScopeTuple(subject, scope string) ketoTuple { + return ketoTuple{ + Namespace: "scope", + Object: scope, + Relation: "granted", + SubjectID: subject, + } +} + +func (p *ketoScopeProvider) UpsertRelationTuple(ctx context.Context, tuple RelationTuple) error { + if err := p.relations.Upsert(tuple); err != nil { + return err + } + return p.client.CreateRelationship(ctx, ketoRelationshipTuple(tuple)) +} + +func (p *ketoScopeProvider) RemoveRelationTuple(ctx context.Context, tuple RelationTuple) error { + if err := p.relations.Remove(tuple); err != nil { + return err + } + return p.client.DeleteRelationship(ctx, ketoRelationshipTuple(tuple)) +} + +func (p *ketoScopeProvider) ListRelationTuples(_ context.Context, filter RelationTupleFilter) ([]RelationTuple, error) { + return p.relations.List(filter), nil +} + +func (p *ketoScopeProvider) CheckRelation(ctx context.Context, check RelationCheck) (RelationCheckResult, error) { + tuple := RelationTuple{Subject: check.Subject, Relation: check.Relation, Object: check.Object, Context: check.Context} + result := RelationCheckResult{Subject: check.Subject, Relation: check.Relation, Object: check.Object, Context: check.Context} + if err := validateRelationTuple(normalizeRelationTuple(tuple)); err != nil { + result.Reason = err.Error() + return result, nil + } + allowed, err := p.client.Check(ctx, ketoRelationshipTuple(tuple)) + if err != nil { + return result, err + } + result.Allowed = allowed + if !allowed { + result.Reason = "keto denied" + } + return result, nil +} + +func ketoRelationshipTuple(tuple RelationTuple) ketoTuple { + tuple = normalizeRelationTuple(tuple) + return ketoTuple{ + Namespace: "resource", + Object: tuple.Context + ":" + tuple.Object, + Relation: tuple.Relation, + SubjectID: tuple.Subject, + } +} + +func (t ketoTuple) equal(other ketoTuple) bool { + if t.Namespace != other.Namespace || t.Object != other.Object || t.Relation != other.Relation || t.SubjectID != other.SubjectID { + return false + } + if t.SubjectSet == nil || other.SubjectSet == nil { + return t.SubjectSet == nil && other.SubjectSet == nil + } + return *t.SubjectSet == *other.SubjectSet +} + +type ketoSDKClient struct { + relationships keto.RelationshipAPI + permissions keto.PermissionAPI +} + +func newKetoSDKClient(readURL, writeURL string) *ketoSDKClient { + readCfg := keto.NewConfiguration() + readCfg.Servers = keto.ServerConfigurations{{URL: defaultString(readURL, "http://localhost:4466")}} + readCfg.HTTPClient = &http.Client{Timeout: 15 * time.Second} + writeCfg := keto.NewConfiguration() + writeCfg.Servers = keto.ServerConfigurations{{URL: defaultString(writeURL, defaultString(readURL, "http://localhost:4467"))}} + writeCfg.HTTPClient = &http.Client{Timeout: 15 * time.Second} + return &ketoSDKClient{ + permissions: keto.NewAPIClient(readCfg).PermissionAPI, + relationships: keto.NewAPIClient(writeCfg).RelationshipAPI, + } +} + +func (c *ketoSDKClient) CreateRelationship(ctx context.Context, tuple ketoTuple) error { + body := keto.NewCreateRelationshipBody() + body.SetNamespace(tuple.Namespace) + body.SetObject(tuple.Object) + body.SetRelation(tuple.Relation) + if tuple.SubjectSet != nil { + body.SetSubjectSet(*keto.NewSubjectSet(tuple.SubjectSet.Namespace, tuple.SubjectSet.Object, tuple.SubjectSet.Relation)) + } else { + body.SetSubjectId(tuple.SubjectID) + } + _, _, err := c.relationships.CreateRelationship(ctx).CreateRelationshipBody(*body).Execute() + return ignoreKetoConflict(err) +} + +func (c *ketoSDKClient) DeleteRelationship(ctx context.Context, tuple ketoTuple) error { + req := c.relationships.DeleteRelationships(ctx).Namespace(tuple.Namespace).Object(tuple.Object).Relation(tuple.Relation) + if tuple.SubjectSet != nil { + req = req.SubjectSetNamespace(tuple.SubjectSet.Namespace).SubjectSetObject(tuple.SubjectSet.Object).SubjectSetRelation(tuple.SubjectSet.Relation) + } else { + req = req.SubjectId(tuple.SubjectID) + } + _, err := req.Execute() + return ignoreKetoConflict(err) +} + +func (c *ketoSDKClient) Check(ctx context.Context, tuple ketoTuple) (bool, error) { + req := c.permissions.CheckPermission(ctx).Namespace(tuple.Namespace).Object(tuple.Object).Relation(tuple.Relation).SubjectId(tuple.SubjectID).MaxDepth(32) + result, _, err := req.Execute() + if err != nil { + return false, err + } + if result == nil { + return false, nil + } + return result.GetAllowed(), nil +} + +func ignoreKetoConflict(err error) error { + if err == nil { + return nil + } + text := strings.ToLower(err.Error()) + if strings.Contains(text, "409") || strings.Contains(text, "already") { + return nil + } + return fmt.Errorf("keto sdk: %w", err) +} + +var _ ScopeRoleProvider = (*ketoScopeProvider)(nil) +var _ RelationshipProvider = (*ketoScopeProvider)(nil) diff --git a/internal/provider_keto_test.go b/internal/provider_keto_test.go new file mode 100644 index 0000000..b886e6b --- /dev/null +++ b/internal/provider_keto_test.go @@ -0,0 +1,105 @@ +package internal + +import ( + "context" + "os" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestKetoProviderWritesRoleScopeTuplesAndChecks(t *testing.T) { + ctx := context.Background() + client := &fakeKetoClient{checks: map[ketoTuple]bool{}} + provider := newKetoScopeProvider("keto", client) + + scope := scopeDeclarationFromName("admin:authz.roles:update") + if err := provider.DeclareScopes(ctx, []*contracts.ScopeDeclaration{scope}); err != nil { + t.Fatalf("DeclareScopes: %v", err) + } + if err := provider.UpsertRole(ctx, RoleScopeGrant{ + Role: "authz-admin", + Context: "admin", + Scopes: []string{scope.GetName()}, + }); err != nil { + t.Fatalf("UpsertRole: %v", err) + } + if !client.wrote(ketoTuple{ + Namespace: "scope", + Object: "admin:authz.roles:update", + Relation: "granted", + SubjectSet: &ketoSubjectSet{ + Namespace: "role", + Object: "admin:authz-admin", + Relation: "member", + }, + }) { + t.Fatalf("missing role-scope tuple, wrote %#v", client.tuples) + } + + if err := provider.AssignRole(ctx, SubjectRoleAssignment{Subject: "alice", Role: "authz-admin", Context: "admin"}); err != nil { + t.Fatalf("AssignRole: %v", err) + } + if !client.wrote(ketoTuple{Namespace: "role", Object: "admin:authz-admin", Relation: "member", SubjectID: "alice"}) { + t.Fatalf("missing subject-role tuple, wrote %#v", client.tuples) + } + + client.checks[ketoTuple{Namespace: "scope", Object: "admin:authz.roles:update", Relation: "granted", SubjectID: "alice"}] = true + result, err := provider.CheckScope(ctx, ScopeCheck{Subject: "alice", Context: "admin", Scope: scope.GetName()}) + if err != nil { + t.Fatalf("CheckScope: %v", err) + } + if !result.Allowed { + t.Fatalf("expected Keto check to allow, result=%#v", result) + } +} + +func TestKetoRealIntegration(t *testing.T) { + if os.Getenv("KETO_INTEGRATION") != "1" { + t.Skip("KETO_INTEGRATION=1 not set; skipping real Ory Keto SDK integration") + } + readURL := os.Getenv("KETO_READ_URL") + writeURL := os.Getenv("KETO_WRITE_URL") + if readURL == "" || writeURL == "" { + t.Skip("missing KETO_READ_URL or KETO_WRITE_URL for real Ory Keto SDK integration") + } + provider := newKetoScopeProvider("keto", newKetoSDKClient(readURL, writeURL)) + ctx := context.Background() + scope := scopeDeclarationFromName("admin:authz.roles:read") + if err := provider.DeclareScopes(ctx, []*contracts.ScopeDeclaration{scope}); err != nil { + t.Fatalf("DeclareScopes: %v", err) + } + if err := provider.UpsertRole(ctx, RoleScopeGrant{Role: "workflow-test-authz-viewer", Context: "admin", Scopes: []string{scope.GetName()}}); err != nil { + t.Fatalf("UpsertRole through Keto SDK: %v", err) + } + if err := provider.AssignRole(ctx, SubjectRoleAssignment{Subject: "workflow-test-user", Role: "workflow-test-authz-viewer", Context: "admin"}); err != nil { + t.Fatalf("AssignRole through Keto SDK: %v", err) + } +} + +type fakeKetoClient struct { + tuples []ketoTuple + checks map[ketoTuple]bool +} + +func (f *fakeKetoClient) CreateRelationship(_ context.Context, tuple ketoTuple) error { + f.tuples = append(f.tuples, tuple) + return nil +} + +func (f *fakeKetoClient) DeleteRelationship(_ context.Context, tuple ketoTuple) error { + return nil +} + +func (f *fakeKetoClient) Check(_ context.Context, tuple ketoTuple) (bool, error) { + return f.checks[tuple], nil +} + +func (f *fakeKetoClient) wrote(want ketoTuple) bool { + for _, tuple := range f.tuples { + if tuple.equal(want) { + return true + } + } + return false +} diff --git a/internal/provider_permit.go b/internal/provider_permit.go new file mode 100644 index 0000000..facfd68 --- /dev/null +++ b/internal/provider_permit.go @@ -0,0 +1,263 @@ +package internal + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" + permitapi "github.com/permitio/permit-golang/pkg/api" + permitconfig "github.com/permitio/permit-golang/pkg/config" + "github.com/permitio/permit-golang/pkg/enforcement" + permitmodels "github.com/permitio/permit-golang/pkg/models" + "github.com/permitio/permit-golang/pkg/permit" +) + +type permitScopeClient interface { + DeclareResource(ctx context.Context, resource string, actions []string) error + UpsertRole(ctx context.Context, role string, permissions []string) error + AssignRole(ctx context.Context, subject, role, tenant string) error + UnassignRole(ctx context.Context, subject, role, tenant string) error + Check(ctx context.Context, subject, action, resource string) (bool, error) +} + +type permitScopeProvider struct { + name string + tenant string + store *scopeRoleStore + client permitScopeClient +} + +func newPermitScopeProvider(name string, client permitScopeClient) *permitScopeProvider { + return &permitScopeProvider{ + name: name, + tenant: "default", + store: newScopeRoleStore("permit"), + client: client, + } +} + +func (p *permitScopeProvider) Name() string { return p.name } + +func (p *permitScopeProvider) DeclareScopes(ctx context.Context, scopes []*contracts.ScopeDeclaration) error { + if err := p.store.DeclareScopes(ctx, scopes); err != nil { + return err + } + grouped := map[string]map[string]struct{}{} + for _, scope := range scopes { + cloned := cloneScopeDeclaration(scope) + normalizeScopeDeclaration(cloned) + if cloned == nil || cloned.GetResource() == "" { + continue + } + if grouped[cloned.GetResource()] == nil { + grouped[cloned.GetResource()] = map[string]struct{}{} + } + for _, action := range cloned.GetActions() { + grouped[cloned.GetResource()][action] = struct{}{} + } + } + for resource, actionSet := range grouped { + actions := make([]string, 0, len(actionSet)) + for action := range actionSet { + actions = append(actions, action) + } + if err := p.client.DeclareResource(ctx, resource, actions); err != nil { + return err + } + } + return nil +} + +func (p *permitScopeProvider) UpsertRole(ctx context.Context, grant RoleScopeGrant) error { + if err := p.store.UpsertRole(ctx, grant); err != nil { + return err + } + permissions := make([]string, 0, len(grant.Scopes)) + for _, scopeName := range grant.Scopes { + scope := scopeDeclarationFromName(scopeName) + permissions = append(permissions, permitPermission(scope.GetResource(), firstScopeAction(scope))) + } + return p.client.UpsertRole(ctx, permitRoleKey(grant.Context, grant.Role), permissions) +} + +func (p *permitScopeProvider) AssignRole(ctx context.Context, assignment SubjectRoleAssignment) error { + if err := p.store.AssignRole(ctx, assignment); err != nil { + return err + } + if assignment.Role != "" { + if err := p.client.AssignRole(ctx, assignment.Subject, permitRoleKey(assignment.Context, assignment.Role), p.tenant); err != nil { + return err + } + } + if len(assignment.DirectScopes) > 0 { + directRole := permitDirectRoleKey(assignment.Context, assignment.Subject) + direct := RoleScopeGrant{Role: directRole, Context: assignment.Context, Scopes: assignment.DirectScopes} + permissions := make([]string, 0, len(direct.Scopes)) + for _, scopeName := range direct.Scopes { + scope := scopeDeclarationFromName(scopeName) + permissions = append(permissions, permitPermission(scope.GetResource(), firstScopeAction(scope))) + } + if err := p.client.UpsertRole(ctx, directRole, permissions); err != nil { + return err + } + if err := p.client.AssignRole(ctx, assignment.Subject, directRole, p.tenant); err != nil { + return err + } + } + return nil +} + +func (p *permitScopeProvider) ListAssignments(ctx context.Context, filter AssignmentFilter) ([]SubjectRoleAssignment, error) { + return p.store.ListAssignments(ctx, filter) +} + +func (p *permitScopeProvider) RemoveAssignment(ctx context.Context, assignment SubjectRoleAssignment) error { + if err := p.store.RemoveAssignment(ctx, assignment); err != nil { + return err + } + if assignment.Role != "" { + return p.client.UnassignRole(ctx, assignment.Subject, permitRoleKey(assignment.Context, assignment.Role), p.tenant) + } + return nil +} + +func (p *permitScopeProvider) CheckScope(ctx context.Context, check ScopeCheck) (ScopeCheckResult, error) { + scopeName := normalizeCheckScope(check) + scope := scopeDeclarationFromName(scopeName) + result := ScopeCheckResult{ + Provider: p.store.provider, + Subject: strings.TrimSpace(check.Subject), + Context: strings.TrimSpace(check.Context), + Scope: scopeName, + } + if result.Subject == "" || result.Context == "" || result.Scope == "" { + result.Reason = "subject, context, and scope are required" + return result, nil + } + local, err := p.store.CheckScope(ctx, check) + if err != nil { + return result, err + } + if !local.Allowed { + local.Provider = p.store.provider + return local, nil + } + allowed, err := p.client.Check(ctx, result.Subject, firstScopeAction(scope), scope.GetResource()) + if err != nil { + return result, err + } + result.Allowed = allowed + result.MatchedRole = local.MatchedRole + result.MatchedScopes = local.MatchedScopes + if !allowed { + result.Reason = "permit denied" + } + return result, nil +} + +type permitSDKScopeClient struct { + client *permit.Client + tenant string +} + +func newPermitSDKScopeClient(cfg permitModuleConfig) *permitSDKScopeClient { + context := permitconfig.NewPermitContext(permitconfig.ProjectAPIKeyLevel, cfg.Project, cfg.Environment) + permitCfg := permitconfig.NewPermitConfig(cfg.APIURL, cfg.APIKey, cfg.PDPURL, false, context, nil).Build() + return &permitSDKScopeClient{ + client: permit.New(permitCfg), + tenant: "default", + } +} + +func (c *permitSDKScopeClient) DeclareResource(ctx context.Context, resource string, actions []string) error { + actionBlocks := make(map[string]permitmodels.ActionBlockEditable, len(actions)) + for _, action := range actions { + block := permitmodels.NewActionBlockEditable() + block.SetName(action) + actionBlocks[action] = *block + } + _, err := c.client.Api.Resources.Get(ctx, resource) + if err == nil { + return nil + } + _, err = c.client.Api.Resources.Create(ctx, *permitmodels.NewResourceCreate(resource, resource, actionBlocks)) + return ignorePermitConflict(err) +} + +func (c *permitSDKScopeClient) UpsertRole(ctx context.Context, role string, permissions []string) error { + if _, err := c.client.Api.Roles.Get(ctx, role); err != nil { + if _, createErr := c.client.Api.Roles.Create(ctx, *permitmodels.NewRoleCreate(role, role)); createErr != nil { + if err := ignorePermitConflict(createErr); err != nil { + return err + } + } + } + return ignorePermitConflict(c.client.Api.Roles.AssignPermissions(ctx, role, permissions)) +} + +func (c *permitSDKScopeClient) AssignRole(ctx context.Context, subject, role, tenant string) error { + if _, err := c.client.SyncUser(ctx, *permitmodels.NewUserCreate(subject)); err != nil { + return err + } + _, err := c.client.Api.Users.AssignRole(ctx, subject, role, defaultString(tenant, c.tenant)) + return ignorePermitConflict(err) +} + +func (c *permitSDKScopeClient) UnassignRole(ctx context.Context, subject, role, tenant string) error { + _, err := c.client.Api.Users.UnassignRole(ctx, subject, role, defaultString(tenant, c.tenant)) + return ignorePermitConflict(err) +} + +func (c *permitSDKScopeClient) Check(ctx context.Context, subject, action, resource string) (bool, error) { + _ = ctx + user := enforcement.UserBuilder(subject).Build() + res := enforcement.ResourceBuilder(resource).WithKey(resource).WithTenant(c.tenant).Build() + return c.client.Check(user, enforcement.Action(action), res) +} + +func permitPermission(resource, action string) string { + return resource + ":" + action +} + +func permitRoleKey(contextName, role string) string { + return sanitizePermitKey(contextName) + "__" + sanitizePermitKey(role) +} + +func permitDirectRoleKey(contextName, subject string) string { + return "direct__" + sanitizePermitKey(contextName) + "__" + sanitizePermitKey(subject) +} + +func sanitizePermitKey(value string) string { + value = strings.TrimSpace(value) + value = strings.NewReplacer("@", "_at_", ".", "_", ":", "_", "/", "_", " ", "_").Replace(value) + if value == "" { + return "default" + } + return value +} + +func firstScopeAction(scope *contracts.ScopeDeclaration) string { + if len(scope.GetActions()) == 0 { + return "" + } + return scope.GetActions()[0] +} + +func ignorePermitConflict(err error) error { + if err == nil { + return nil + } + if strings.Contains(strings.ToLower(err.Error()), "409") || strings.Contains(strings.ToLower(err.Error()), "already") { + return nil + } + var target interface{ Error() string } + if errors.As(err, &target) && strings.Contains(strings.ToLower(target.Error()), "conflict") { + return nil + } + return fmt.Errorf("permit sdk: %w", err) +} + +var _ ScopeRoleProvider = (*permitScopeProvider)(nil) +var _ = permitapi.DefaultPerPageLimit diff --git a/internal/provider_permit_test.go b/internal/provider_permit_test.go new file mode 100644 index 0000000..3863fe3 --- /dev/null +++ b/internal/provider_permit_test.go @@ -0,0 +1,146 @@ +package internal + +import ( + "context" + "os" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestPermitSDKProviderMapsScopeRolesAndChecks(t *testing.T) { + ctx := context.Background() + client := &fakePermitScopeClient{allowed: map[string]bool{}} + provider := newPermitScopeProvider("permit", client) + + if err := provider.DeclareScopes(ctx, []*contracts.ScopeDeclaration{ + scopeDeclarationFromName("admin:authz.roles:update"), + }); err != nil { + t.Fatalf("DeclareScopes: %v", err) + } + if got := client.resources["authz.roles"]["update"]; !got { + t.Fatalf("expected Permit resource/action authz.roles:update, got %#v", client.resources) + } + + if err := provider.UpsertRole(ctx, RoleScopeGrant{ + Role: "authz-admin", + Context: "admin", + Scopes: []string{"admin:authz.roles:update"}, + }); err != nil { + t.Fatalf("UpsertRole: %v", err) + } + if got := client.rolePermissions["admin__authz-admin"]["authz.roles:update"]; !got { + t.Fatalf("expected Permit role permission, got %#v", client.rolePermissions) + } + + if err := provider.AssignRole(ctx, SubjectRoleAssignment{ + Subject: "alice", + Role: "authz-admin", + Context: "admin", + }); err != nil { + t.Fatalf("AssignRole: %v", err) + } + if got := client.assignments["alice"]["admin__authz-admin"]; !got { + t.Fatalf("expected Permit role assignment, got %#v", client.assignments) + } + + client.allowed["alice|update|authz.roles"] = true + result, err := provider.CheckScope(ctx, ScopeCheck{Subject: "alice", Context: "admin", Scope: "admin:authz.roles:update"}) + if err != nil { + t.Fatalf("CheckScope: %v", err) + } + if !result.Allowed { + t.Fatalf("expected Permit check to allow, result=%#v", result) + } + if result.Provider != "permit" { + t.Fatalf("provider = %q, want permit", result.Provider) + } +} + +func TestPermitRealIntegration(t *testing.T) { + if os.Getenv("PERMIT_INTEGRATION") != "1" { + t.Skip("PERMIT_INTEGRATION=1 not set; skipping real Permit.io SDK integration") + } + missing := []string{} + for _, key := range []string{"PERMIT_API_KEY", "PERMIT_PROJECT", "PERMIT_ENVIRONMENT"} { + if os.Getenv(key) == "" { + missing = append(missing, key) + } + } + if len(missing) > 0 { + t.Skipf("missing Permit.io integration env vars: %v", missing) + } + provider := newPermitScopeProvider("permit", newPermitSDKScopeClient(permitModuleConfig{ + APIKey: os.Getenv("PERMIT_API_KEY"), + APIURL: defaultString(os.Getenv("PERMIT_API_URL"), defaultPermitAPIURL), + PDPURL: defaultString(os.Getenv("PERMIT_PDP_URL"), defaultPermitPDPURL), + Project: os.Getenv("PERMIT_PROJECT"), + Environment: os.Getenv("PERMIT_ENVIRONMENT"), + })) + ctx := context.Background() + scope := scopeDeclarationFromName("admin:authz.roles:read") + if err := provider.DeclareScopes(ctx, []*contracts.ScopeDeclaration{scope}); err != nil { + t.Fatalf("DeclareScopes through Permit SDK: %v", err) + } + if err := provider.UpsertRole(ctx, RoleScopeGrant{Role: "workflow-test-authz-viewer", Context: "admin", Scopes: []string{scope.GetName()}}); err != nil { + t.Fatalf("UpsertRole through Permit SDK: %v", err) + } +} + +type fakePermitScopeClient struct { + resources map[string]map[string]bool + rolePermissions map[string]map[string]bool + assignments map[string]map[string]bool + allowed map[string]bool +} + +func (f *fakePermitScopeClient) DeclareResource(_ context.Context, resource string, actions []string) error { + if f.resources == nil { + f.resources = map[string]map[string]bool{} + } + if f.resources[resource] == nil { + f.resources[resource] = map[string]bool{} + } + for _, action := range actions { + f.resources[resource][action] = true + } + return nil +} + +func (f *fakePermitScopeClient) UpsertRole(_ context.Context, role string, permissions []string) error { + if f.rolePermissions == nil { + f.rolePermissions = map[string]map[string]bool{} + } + if f.rolePermissions[role] == nil { + f.rolePermissions[role] = map[string]bool{} + } + for _, permission := range permissions { + f.rolePermissions[role][permission] = true + } + return nil +} + +func (f *fakePermitScopeClient) AssignRole(_ context.Context, subject, role, _ string) error { + if f.assignments == nil { + f.assignments = map[string]map[string]bool{} + } + if f.assignments[subject] == nil { + f.assignments[subject] = map[string]bool{} + } + f.assignments[subject][role] = true + return nil +} + +func (f *fakePermitScopeClient) UnassignRole(_ context.Context, subject, role, _ string) error { + if f.assignments != nil && f.assignments[subject] != nil { + delete(f.assignments[subject], role) + } + return nil +} + +func (f *fakePermitScopeClient) Check(_ context.Context, subject, action, resource string) (bool, error) { + if f.allowed == nil { + return false, nil + } + return f.allowed[subject+"|"+action+"|"+resource], nil +} diff --git a/internal/rebac_provider.go b/internal/rebac_provider.go new file mode 100644 index 0000000..2071428 --- /dev/null +++ b/internal/rebac_provider.go @@ -0,0 +1,202 @@ +package internal + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + "sync" +) + +var errUnsupportedReBAC = errors.New("rebac provider is not supported by this module configuration") + +type RelationshipProvider interface { + Name() string + UpsertRelationTuple(context.Context, RelationTuple) error + RemoveRelationTuple(context.Context, RelationTuple) error + ListRelationTuples(context.Context, RelationTupleFilter) ([]RelationTuple, error) + CheckRelation(context.Context, RelationCheck) (RelationCheckResult, error) +} + +type RelationTuple struct { + Subject string + Relation string + Object string + Context string +} + +type RelationTupleFilter struct { + Subject string + Relation string + Object string + Context string +} + +type RelationCheck struct { + Subject string + Relation string + Object string + Context string +} + +type RelationCheckResult struct { + Allowed bool + Subject string + Relation string + Object string + Context string + Reason string +} + +type relationTupleStore struct { + mu sync.RWMutex + tuples map[string]RelationTuple +} + +func newRelationTupleStore() *relationTupleStore { + return &relationTupleStore{tuples: map[string]RelationTuple{}} +} + +func (s *relationTupleStore) Upsert(tuple RelationTuple) error { + tuple = normalizeRelationTuple(tuple) + if err := validateRelationTuple(tuple); err != nil { + return err + } + s.mu.Lock() + defer s.mu.Unlock() + s.tuples[relationTupleKey(tuple)] = tuple + return nil +} + +func (s *relationTupleStore) Remove(tuple RelationTuple) error { + tuple = normalizeRelationTuple(tuple) + if err := validateRelationTuple(tuple); err != nil { + return err + } + s.mu.Lock() + defer s.mu.Unlock() + delete(s.tuples, relationTupleKey(tuple)) + return nil +} + +func (s *relationTupleStore) List(filter RelationTupleFilter) []RelationTuple { + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]RelationTuple, 0, len(s.tuples)) + for _, tuple := range s.tuples { + if relationTupleMatches(tuple, filter) { + out = append(out, tuple) + } + } + sort.Slice(out, func(i, j int) bool { return relationTupleKey(out[i]) < relationTupleKey(out[j]) }) + return out +} + +func (s *relationTupleStore) Contains(check RelationCheck) bool { + filter := RelationTupleFilter{Subject: check.Subject, Relation: check.Relation, Object: check.Object, Context: check.Context} + return len(s.List(filter)) > 0 +} + +func normalizeRelationTuple(tuple RelationTuple) RelationTuple { + tuple.Subject = strings.TrimSpace(tuple.Subject) + tuple.Relation = strings.TrimSpace(tuple.Relation) + tuple.Object = strings.TrimSpace(tuple.Object) + tuple.Context = strings.TrimSpace(tuple.Context) + return tuple +} + +func validateRelationTuple(tuple RelationTuple) error { + if tuple.Subject == "" || tuple.Relation == "" || tuple.Object == "" || tuple.Context == "" { + return fmt.Errorf("relation tuple requires subject, relation, object, and context") + } + return nil +} + +func relationTupleMatches(tuple RelationTuple, filter RelationTupleFilter) bool { + return (filter.Subject == "" || tuple.Subject == filter.Subject) && + (filter.Relation == "" || tuple.Relation == filter.Relation) && + (filter.Object == "" || tuple.Object == filter.Object) && + (filter.Context == "" || tuple.Context == filter.Context) +} + +func relationTupleKey(tuple RelationTuple) string { + return tuple.Context + "/" + tuple.Object + "/" + tuple.Relation + "/" + tuple.Subject +} + +func upsertRelationTupleInvoke(ctx context.Context, provider RelationshipProvider, input map[string]any) (map[string]any, error) { + tuple := relationTupleFromMap(mapValue(input["tuple"])) + if err := provider.UpsertRelationTuple(ctx, tuple); err != nil { + return nil, err + } + return map[string]any{"changed": true, "tuple": relationTupleToMap(tuple)}, nil +} + +func listRelationTuplesInvoke(ctx context.Context, provider RelationshipProvider, input map[string]any) (map[string]any, error) { + tuples, err := provider.ListRelationTuples(ctx, relationTupleFilterFromMap(mapValue(input["filter"]))) + if err != nil { + return nil, err + } + items := make([]map[string]any, 0, len(tuples)) + for _, tuple := range tuples { + items = append(items, relationTupleToMap(tuple)) + } + return map[string]any{"tuples": items}, nil +} + +func removeRelationTupleInvoke(ctx context.Context, provider RelationshipProvider, input map[string]any) (map[string]any, error) { + if err := provider.RemoveRelationTuple(ctx, relationTupleFromMap(mapValue(input["tuple"]))); err != nil { + return nil, err + } + return map[string]any{"changed": true}, nil +} + +func checkRelationInvoke(ctx context.Context, provider RelationshipProvider, input map[string]any) (map[string]any, error) { + result, err := provider.CheckRelation(ctx, relationCheckFromMap(input)) + if err != nil { + return relationCheckResultToMap(result), err + } + return relationCheckResultToMap(result), nil +} + +func relationTupleFromMap(values map[string]any) RelationTuple { + return RelationTuple{ + Subject: stringValue(values["subject"]), + Relation: stringValue(values["relation"]), + Object: stringValue(values["object"]), + Context: stringValue(values["context"]), + } +} + +func relationTupleFilterFromMap(values map[string]any) RelationTupleFilter { + return RelationTupleFilter{ + Subject: stringValue(values["subject"]), + Relation: stringValue(values["relation"]), + Object: stringValue(values["object"]), + Context: stringValue(values["context"]), + } +} + +func relationCheckFromMap(values map[string]any) RelationCheck { + return RelationCheck{ + Subject: stringValue(values["subject"]), + Relation: stringValue(values["relation"]), + Object: stringValue(values["object"]), + Context: stringValue(values["context"]), + } +} + +func relationTupleToMap(tuple RelationTuple) map[string]any { + return compactMap(map[string]any{"subject": tuple.Subject, "relation": tuple.Relation, "object": tuple.Object, "context": tuple.Context}) +} + +func relationCheckResultToMap(result RelationCheckResult) map[string]any { + return compactMap(map[string]any{ + "allowed": result.Allowed, + "subject": result.Subject, + "relation": result.Relation, + "object": result.Object, + "context": result.Context, + "reason": result.Reason, + }) +} diff --git a/internal/rebac_provider_test.go b/internal/rebac_provider_test.go new file mode 100644 index 0000000..8c6e8b6 --- /dev/null +++ b/internal/rebac_provider_test.go @@ -0,0 +1,89 @@ +package internal + +import ( + "context" + "testing" +) + +func TestReBACProviderCasbinConformance(t *testing.T) { + ctx := context.Background() + provider, ok := any(rebacTestModule(t)).(RelationshipProvider) + if !ok { + t.Fatal("CasbinModule should implement RelationshipProvider") + } + tuple := RelationTuple{Subject: "alice", Relation: "owner", Object: "doc1", Context: "frontend"} + if err := provider.UpsertRelationTuple(ctx, tuple); err != nil { + t.Fatalf("UpsertRelationTuple: %v", err) + } + result, err := provider.CheckRelation(ctx, RelationCheck{Subject: "alice", Relation: "owner", Object: "doc1", Context: "frontend"}) + if err != nil { + t.Fatalf("CheckRelation: %v", err) + } + if !result.Allowed { + t.Fatalf("expected relation check to allow, got %#v", result) + } + listed, err := provider.ListRelationTuples(ctx, RelationTupleFilter{Subject: "alice", Context: "frontend"}) + if err != nil { + t.Fatalf("ListRelationTuples: %v", err) + } + if len(listed) != 1 || listed[0].Object != "doc1" { + t.Fatalf("listed tuples = %#v", listed) + } + if err := provider.RemoveRelationTuple(ctx, tuple); err != nil { + t.Fatalf("RemoveRelationTuple: %v", err) + } + result, err = provider.CheckRelation(ctx, RelationCheck{Subject: "alice", Relation: "owner", Object: "doc1", Context: "frontend"}) + if err != nil { + t.Fatalf("CheckRelation after remove: %v", err) + } + if result.Allowed { + t.Fatal("expected removed relation to stop allowing access") + } +} + +func TestReBACProviderKetoConformance(t *testing.T) { + ctx := context.Background() + client := &fakeKetoClient{checks: map[ketoTuple]bool{}} + provider := newKetoScopeProvider("keto", client) + tuple := RelationTuple{Subject: "alice", Relation: "viewer", Object: "doc1", Context: "frontend"} + if err := provider.UpsertRelationTuple(ctx, tuple); err != nil { + t.Fatalf("UpsertRelationTuple: %v", err) + } + want := ketoRelationshipTuple(tuple) + if !client.wrote(want) { + t.Fatalf("missing keto relation tuple, wrote %#v", client.tuples) + } + client.checks[want] = true + result, err := provider.CheckRelation(ctx, RelationCheck{Subject: "alice", Relation: "viewer", Object: "doc1", Context: "frontend"}) + if err != nil { + t.Fatalf("CheckRelation: %v", err) + } + if !result.Allowed { + t.Fatalf("expected Keto relation check to allow, got %#v", result) + } + listed, err := provider.ListRelationTuples(ctx, RelationTupleFilter{Object: "doc1", Context: "frontend"}) + if err != nil { + t.Fatalf("ListRelationTuples: %v", err) + } + if len(listed) != 1 || listed[0].Subject != "alice" { + t.Fatalf("listed tuples = %#v", listed) + } + if err := provider.RemoveRelationTuple(ctx, tuple); err != nil { + t.Fatalf("RemoveRelationTuple: %v", err) + } + listed, _ = provider.ListRelationTuples(ctx, RelationTupleFilter{Object: "doc1", Context: "frontend"}) + if len(listed) != 0 { + t.Fatalf("expected tuple removal to update local list, got %#v", listed) + } +} + +func TestReBACProviderFailsClosedForUnsupportedModels(t *testing.T) { + provider, ok := any(aclTestModule(t, nil)).(RelationshipProvider) + if !ok { + t.Fatal("CasbinModule should expose unsupported ReBAC errors through RelationshipProvider") + } + err := provider.UpsertRelationTuple(context.Background(), RelationTuple{Subject: "alice", Relation: "owner", Object: "doc1", Context: "frontend"}) + if err == nil { + t.Fatal("expected ACL model to reject ReBAC tuple writes") + } +} diff --git a/internal/scope_catalog_test.go b/internal/scope_catalog_test.go new file mode 100644 index 0000000..621cac3 --- /dev/null +++ b/internal/scope_catalog_test.go @@ -0,0 +1,133 @@ +package internal + +import ( + "context" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" + pb "github.com/GoCodeAlone/workflow/plugin/external/proto" + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" + "google.golang.org/protobuf/types/known/anypb" +) + +func TestScopeCatalogModuleRegistersListsAndResolvesScopes(t *testing.T) { + module := newScopeCatalogModule("catalog", map[string]any{ + "scopes": []any{ + map[string]any{ + "name": "frontend:orders:read", + "context": "frontend", + "resource": "orders", + "actions": []any{"read"}, + "description": "Read orders", + "owner_plugin": "workflow-plugin-orders", + "owner_module": "orders", + "category": "application", + }, + }, + }) + + registered, err := module.InvokeMethod("RegisterScopes", map[string]any{ + "owner_plugin": "workflow-plugin-admin", + "owner_module": "admin", + "scopes": []any{ + map[string]any{ + "name": "admin:authz.roles:update", + "context": "admin", + "resource": "authz.roles", + "actions": []any{"update"}, + "description": "Manage role assignments", + "category": "security", + }, + }, + }) + if err != nil { + t.Fatalf("RegisterScopes: %v", err) + } + if registered["registered"] != 1 { + t.Fatalf("registered = %v, want 1", registered["registered"]) + } + + listed, err := module.InvokeMethod("ListScopes", map[string]any{"context": "admin"}) + if err != nil { + t.Fatalf("ListScopes: %v", err) + } + scopes := listed["scopes"].([]map[string]any) + if len(scopes) != 1 || scopes[0]["owner_plugin"] != "workflow-plugin-admin" { + t.Fatalf("admin scopes = %#v, want registered admin owner metadata", scopes) + } + + resolved, err := module.InvokeMethod("ResolveSubjectScopes", map[string]any{ + "subject": "admin@tailnet", + "direct_scopes": []any{"frontend:orders:read"}, + "role_scopes": []any{"admin:authz.roles:update"}, + }) + if err != nil { + t.Fatalf("ResolveSubjectScopes: %v", err) + } + names := resolved["scopes"].([]string) + if len(names) != 2 || names[0] != "admin:authz.roles:update" || names[1] != "frontend:orders:read" { + t.Fatalf("resolved scopes = %#v, want sorted merged scopes", names) + } +} + +func TestScopeCatalogStrictContracts(t *testing.T) { + provider := NewAuthzPlugin().(interface { + sdk.TypedModuleProvider + sdk.ContractProvider + }) + + config, err := anypb.New(&contracts.ScopeCatalogConfig{ + Scopes: []*contracts.ScopeDeclaration{ + { + Name: "admin:authz.scopes:read", + Context: "admin", + Resource: "authz.scopes", + Actions: []string{"read"}, + Description: "View declared scopes", + }, + }, + }) + if err != nil { + t.Fatalf("pack scope catalog config: %v", err) + } + if _, err := provider.CreateTypedModule("authz.scope_catalog", "catalog", config); err != nil { + t.Fatalf("CreateTypedModule scope catalog: %v", err) + } + + contractsByKey := map[string]*pb.ContractDescriptor{} + for _, contract := range provider.ContractRegistry().Contracts { + contractsByKey[contractKey(contract)] = contract + } + for _, key := range []string{ + "module:authz.scope_catalog", + "service:ScopeCatalog/RegisterScopes", + "service:ScopeCatalog/ListScopes", + "service:ScopeCatalog/ResolveSubjectScopes", + "service:ScopeCatalog/RegisterDeclarations", + "service:ScopeCatalog/ListDeclarations", + "service:ScopeCatalog/ResolveProjectionInputs", + } { + if contractsByKey[key] == nil { + t.Fatalf("missing strict scope catalog contract %s", key) + } + } +} + +func TestTypedScopeCatalogInputs(t *testing.T) { + handler := typedRegisterScopes(newScopeCatalogModule("catalog", nil)) + result, err := handler(context.Background(), sdk.TypedStepRequest[*contracts.RegisterScopesInput, *contracts.RegisterScopesInput]{ + Config: &contracts.RegisterScopesInput{OwnerPlugin: "workflow-plugin-authz"}, + Input: &contracts.RegisterScopesInput{ + OwnerModule: "catalog", + Scopes: []*contracts.ScopeDeclaration{ + {Name: "admin:authz.scopes:read", Context: "admin", Resource: "authz.scopes", Actions: []string{"read"}}, + }, + }, + }) + if err != nil { + t.Fatalf("typedRegisterScopes: %v", err) + } + if result.Output.GetRegistered() != 1 { + t.Fatalf("registered = %d, want 1", result.Output.GetRegistered()) + } +} diff --git a/internal/scope_role_provider.go b/internal/scope_role_provider.go new file mode 100644 index 0000000..afb3f8c --- /dev/null +++ b/internal/scope_role_provider.go @@ -0,0 +1,433 @@ +package internal + +import ( + "context" + "fmt" + "sort" + "strings" + "sync" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +type ScopeRoleProvider interface { + Name() string + DeclareScopes(context.Context, []*contracts.ScopeDeclaration) error + UpsertRole(context.Context, RoleScopeGrant) error + AssignRole(context.Context, SubjectRoleAssignment) error + ListAssignments(context.Context, AssignmentFilter) ([]SubjectRoleAssignment, error) + RemoveAssignment(context.Context, SubjectRoleAssignment) error + CheckScope(context.Context, ScopeCheck) (ScopeCheckResult, error) +} + +type RoleScopeGrant struct { + Role string + Context string + Scopes []string +} + +type SubjectRoleAssignment struct { + Subject string + Role string + Context string + DirectScopes []string +} + +type AssignmentFilter struct { + Subject string + Role string + Context string +} + +type ScopeCheck struct { + Subject string + Context string + Scope string + Resource string + Action string +} + +type ScopeCheckResult struct { + Allowed bool + Provider string + Subject string + Context string + Scope string + MatchedRole string + MatchedScopes []string + Reason string +} + +type scopeRoleStore struct { + provider string + mu sync.RWMutex + scopes map[string]*contracts.ScopeDeclaration + roles map[string]RoleScopeGrant + assigns []SubjectRoleAssignment +} + +func newScopeRoleStore(provider string) *scopeRoleStore { + return &scopeRoleStore{ + provider: provider, + scopes: map[string]*contracts.ScopeDeclaration{}, + roles: map[string]RoleScopeGrant{}, + } +} + +func (s *scopeRoleStore) DeclareScopes(_ context.Context, scopes []*contracts.ScopeDeclaration) error { + s.mu.Lock() + defer s.mu.Unlock() + for _, incoming := range scopes { + scope := cloneScopeDeclaration(incoming) + if scope == nil || strings.TrimSpace(scope.GetName()) == "" { + continue + } + normalizeScopeDeclaration(scope) + if scope.GetContext() == "" || scope.GetResource() == "" || len(scope.GetActions()) == 0 { + return fmt.Errorf("scope %q must declare context, resource, and at least one action", scope.GetName()) + } + s.scopes[scope.GetName()] = scope + } + return nil +} + +func (s *scopeRoleStore) UpsertRole(_ context.Context, grant RoleScopeGrant) error { + grant.Role = strings.TrimSpace(grant.Role) + grant.Context = strings.TrimSpace(grant.Context) + if grant.Role == "" { + return fmt.Errorf("role is required") + } + if grant.Context == "" { + return fmt.Errorf("context is required") + } + scopes := uniqueStrings(grant.Scopes) + s.mu.RLock() + err := s.validateScopesLocked(grant.Context, scopes) + s.mu.RUnlock() + if err != nil { + return err + } + grant.Scopes = scopes + + s.mu.Lock() + defer s.mu.Unlock() + s.roles[roleKey(grant.Context, grant.Role)] = cloneRoleScopeGrant(grant) + return nil +} + +func (s *scopeRoleStore) AssignRole(_ context.Context, assignment SubjectRoleAssignment) error { + assignment.Subject = strings.TrimSpace(assignment.Subject) + assignment.Role = strings.TrimSpace(assignment.Role) + assignment.Context = strings.TrimSpace(assignment.Context) + if assignment.Subject == "" { + return fmt.Errorf("subject is required") + } + if assignment.Context == "" { + return fmt.Errorf("context is required") + } + assignment.DirectScopes = uniqueStrings(assignment.DirectScopes) + + s.mu.RLock() + if err := s.validateScopesLocked(assignment.Context, assignment.DirectScopes); err != nil { + s.mu.RUnlock() + return err + } + if assignment.Role != "" { + if _, ok := s.roles[roleKey(assignment.Context, assignment.Role)]; !ok && len(assignment.DirectScopes) == 0 { + s.mu.RUnlock() + return fmt.Errorf("role %q is not defined in context %q", assignment.Role, assignment.Context) + } + } + s.mu.RUnlock() + + s.mu.Lock() + defer s.mu.Unlock() + for i, existing := range s.assigns { + if sameAssignmentIdentity(existing, assignment) { + s.assigns[i] = cloneSubjectRoleAssignment(assignment) + return nil + } + } + s.assigns = append(s.assigns, cloneSubjectRoleAssignment(assignment)) + return nil +} + +func (s *scopeRoleStore) ListAssignments(_ context.Context, filter AssignmentFilter) ([]SubjectRoleAssignment, error) { + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]SubjectRoleAssignment, 0, len(s.assigns)) + for _, assignment := range s.assigns { + if filter.Subject != "" && assignment.Subject != filter.Subject { + continue + } + if filter.Role != "" && assignment.Role != filter.Role { + continue + } + if filter.Context != "" && assignment.Context != filter.Context { + continue + } + out = append(out, cloneSubjectRoleAssignment(assignment)) + } + sort.Slice(out, func(i, j int) bool { + if out[i].Subject != out[j].Subject { + return out[i].Subject < out[j].Subject + } + if out[i].Context != out[j].Context { + return out[i].Context < out[j].Context + } + return out[i].Role < out[j].Role + }) + return out, nil +} + +func (s *scopeRoleStore) RemoveAssignment(_ context.Context, target SubjectRoleAssignment) error { + s.mu.Lock() + defer s.mu.Unlock() + kept := s.assigns[:0] + for _, assignment := range s.assigns { + if sameAssignmentIdentity(assignment, target) { + continue + } + kept = append(kept, assignment) + } + s.assigns = kept + return nil +} + +func (s *scopeRoleStore) CheckScope(_ context.Context, check ScopeCheck) (ScopeCheckResult, error) { + scopeName := normalizeCheckScope(check) + result := ScopeCheckResult{ + Provider: s.provider, + Subject: strings.TrimSpace(check.Subject), + Context: strings.TrimSpace(check.Context), + Scope: scopeName, + } + if result.Subject == "" { + result.Reason = "subject is required" + return result, nil + } + if result.Context == "" { + result.Reason = "context is required" + return result, nil + } + if result.Scope == "" { + result.Reason = "scope is required" + return result, nil + } + + s.mu.RLock() + defer s.mu.RUnlock() + declared, ok := s.scopes[result.Scope] + if !ok { + result.Reason = "scope is not declared" + return result, nil + } + if declared.GetContext() != result.Context { + result.Reason = "scope context mismatch" + return result, nil + } + for _, assignment := range s.assigns { + if assignment.Subject != result.Subject || assignment.Context != result.Context { + continue + } + if containsString(assignment.DirectScopes, result.Scope) { + result.Allowed = true + result.MatchedRole = assignment.Role + result.MatchedScopes = []string{result.Scope} + return result, nil + } + grant, ok := s.roles[roleKey(assignment.Context, assignment.Role)] + if !ok { + continue + } + if containsString(grant.Scopes, result.Scope) { + result.Allowed = true + result.MatchedRole = grant.Role + result.MatchedScopes = []string{result.Scope} + return result, nil + } + } + result.Reason = "no matching role or direct scope grant" + return result, nil +} + +func (s *scopeRoleStore) validateScopesLocked(contextName string, scopes []string) error { + for _, name := range scopes { + scope, ok := s.scopes[name] + if !ok { + return fmt.Errorf("scope %q is not declared", name) + } + if scope.GetContext() != contextName { + return fmt.Errorf("scope %q belongs to context %q, not %q", name, scope.GetContext(), contextName) + } + } + return nil +} + +func roleKey(contextName, role string) string { + return contextName + "\x00" + role +} + +func normalizeScopeDeclaration(scope *contracts.ScopeDeclaration) { + if scope == nil { + return + } + scope.Name = strings.TrimSpace(scope.GetName()) + if scope.GetContext() == "" || scope.GetResource() == "" || len(scope.GetActions()) == 0 { + parsed := scopeDeclarationFromName(scope.GetName()) + if scope.GetContext() == "" { + scope.Context = parsed.GetContext() + } + if scope.GetResource() == "" { + scope.Resource = parsed.GetResource() + } + if len(scope.GetActions()) == 0 { + scope.Actions = parsed.GetActions() + } + } + scope.Actions = uniqueStrings(scope.GetActions()) +} + +func scopeDeclarationFromName(name string) *contracts.ScopeDeclaration { + parts := strings.Split(strings.TrimSpace(name), ":") + scope := &contracts.ScopeDeclaration{Name: strings.TrimSpace(name)} + if len(parts) >= 3 { + scope.Context = parts[0] + scope.Resource = strings.Join(parts[1:len(parts)-1], ".") + scope.Actions = []string{parts[len(parts)-1]} + } + return scope +} + +func normalizeCheckScope(check ScopeCheck) string { + if strings.TrimSpace(check.Scope) != "" { + return strings.TrimSpace(check.Scope) + } + if check.Context == "" || check.Resource == "" || check.Action == "" { + return "" + } + return strings.TrimSpace(check.Context) + ":" + strings.TrimSpace(check.Resource) + ":" + strings.TrimSpace(check.Action) +} + +func cloneRoleScopeGrant(grant RoleScopeGrant) RoleScopeGrant { + return RoleScopeGrant{ + Role: grant.Role, + Context: grant.Context, + Scopes: append([]string(nil), grant.Scopes...), + } +} + +func cloneSubjectRoleAssignment(assignment SubjectRoleAssignment) SubjectRoleAssignment { + return SubjectRoleAssignment{ + Subject: assignment.Subject, + Role: assignment.Role, + Context: assignment.Context, + DirectScopes: append([]string(nil), assignment.DirectScopes...), + } +} + +func sameAssignmentIdentity(a, b SubjectRoleAssignment) bool { + return a.Subject == b.Subject && a.Role == b.Role && a.Context == b.Context +} + +func uniqueStrings(values []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(values)) + for _, value := range values { + value = strings.TrimSpace(value) + if value == "" { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + sort.Strings(out) + return out +} + +func containsString(values []string, want string) bool { + for _, value := range values { + if value == want { + return true + } + } + return false +} + +func roleScopeGrantFromMap(values map[string]any) RoleScopeGrant { + return RoleScopeGrant{ + Role: stringValue(values["role"]), + Context: stringValue(values["context"]), + Scopes: stringSliceValue(values["scopes"]), + } +} + +func roleScopeGrantToMap(grant RoleScopeGrant) map[string]any { + return compactMap(map[string]any{ + "role": grant.Role, + "context": grant.Context, + "scopes": stringsToAny(grant.Scopes), + }) +} + +func subjectRoleAssignmentFromMap(values map[string]any) SubjectRoleAssignment { + return SubjectRoleAssignment{ + Subject: stringValue(firstNonNil(values["subject"], values["user"])), + Role: stringValue(values["role"]), + Context: stringValue(values["context"]), + DirectScopes: stringSliceValue(firstNonNil(values["direct_scopes"], values["scopes"])), + } +} + +func subjectRoleAssignmentToMap(assignment SubjectRoleAssignment) map[string]any { + return compactMap(map[string]any{ + "subject": assignment.Subject, + "user": assignment.Subject, + "role": assignment.Role, + "context": assignment.Context, + "direct_scopes": stringsToAny(assignment.DirectScopes), + "scopes": stringsToAny(assignment.DirectScopes), + }) +} + +func subjectRoleAssignmentsToMaps(assignments []SubjectRoleAssignment) []map[string]any { + out := make([]map[string]any, 0, len(assignments)) + for _, assignment := range assignments { + out = append(out, subjectRoleAssignmentToMap(assignment)) + } + return out +} + +func assignmentFilterFromMap(values map[string]any) AssignmentFilter { + return AssignmentFilter{ + Subject: stringValue(firstNonNil(values["subject"], values["user"])), + Role: stringValue(values["role"]), + Context: stringValue(values["context"]), + } +} + +func scopeCheckFromMap(values map[string]any) ScopeCheck { + return ScopeCheck{ + Subject: stringValue(firstNonNil(values["subject"], values["user"])), + Context: stringValue(values["context"]), + Scope: stringValue(values["scope"]), + Resource: stringValue(values["resource"]), + Action: stringValue(values["action"]), + } +} + +func scopeCheckResultToMap(result ScopeCheckResult) map[string]any { + return compactMap(map[string]any{ + "allowed": result.Allowed, + "provider": result.Provider, + "subject": result.Subject, + "context": result.Context, + "scope": result.Scope, + "matched_role": result.MatchedRole, + "matched_scopes": stringsToAny(result.MatchedScopes), + "reason": result.Reason, + }) +} diff --git a/internal/scope_role_provider_test.go b/internal/scope_role_provider_test.go new file mode 100644 index 0000000..b50804d --- /dev/null +++ b/internal/scope_role_provider_test.go @@ -0,0 +1,109 @@ +package internal + +import ( + "context" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestCasbinScopeRoleProviderRejectsUnknownScopes(t *testing.T) { + ctx := context.Background() + provider := newTestCasbinScopeProvider(t) + mustDeclareScopes(t, provider, "admin:authz.roles:read") + + err := provider.UpsertRole(ctx, RoleScopeGrant{ + Role: "authz-admin", + Context: "admin", + Scopes: []string{"admin:authz.roles:update"}, + }) + if err == nil { + t.Fatal("expected unknown scope grant to fail") + } +} + +func TestCasbinScopeRoleProviderConformance(t *testing.T) { + ctx := context.Background() + provider := newTestCasbinScopeProvider(t) + mustDeclareScopes(t, provider, + "frontend:orders:read", + "admin:dashboard:read", + "admin:authz.roles:update", + ) + + if err := provider.UpsertRole(ctx, RoleScopeGrant{ + Role: "requester", + Context: "frontend", + Scopes: []string{"frontend:orders:read"}, + }); err != nil { + t.Fatalf("UpsertRole requester: %v", err) + } + if err := provider.UpsertRole(ctx, RoleScopeGrant{ + Role: "authz-admin", + Context: "admin", + Scopes: []string{"admin:dashboard:read", "admin:authz.roles:update"}, + }); err != nil { + t.Fatalf("UpsertRole authz-admin: %v", err) + } + if err := provider.AssignRole(ctx, SubjectRoleAssignment{ + Subject: "alice", + Role: "requester", + Context: "frontend", + }); err != nil { + t.Fatalf("AssignRole frontend: %v", err) + } + if err := provider.AssignRole(ctx, SubjectRoleAssignment{ + Subject: "alice", + Role: "temporary-admin", + Context: "admin", + DirectScopes: []string{"admin:dashboard:read"}, + }); err != nil { + t.Fatalf("AssignRole direct admin: %v", err) + } + + assertScopeAllowed(t, provider, ScopeCheck{Subject: "alice", Context: "frontend", Scope: "frontend:orders:read"}, true) + assertScopeAllowed(t, provider, ScopeCheck{Subject: "alice", Context: "frontend", Scope: "admin:dashboard:read"}, false) + assertScopeAllowed(t, provider, ScopeCheck{Subject: "alice", Context: "admin", Scope: "admin:dashboard:read"}, true) + assertScopeAllowed(t, provider, ScopeCheck{Subject: "alice", Context: "admin", Scope: "admin:authz.roles:update"}, false) + + assignments, err := provider.ListAssignments(ctx, AssignmentFilter{Subject: "alice"}) + if err != nil { + t.Fatalf("ListAssignments: %v", err) + } + if len(assignments) != 2 { + t.Fatalf("assignments len = %d, want 2: %#v", len(assignments), assignments) + } +} + +func newTestCasbinScopeProvider(t *testing.T) ScopeRoleProvider { + t.Helper() + m := buildModule(t, nil, nil) + provider, ok := any(m).(ScopeRoleProvider) + if !ok { + t.Fatalf("%T does not implement ScopeRoleProvider", m) + } + return provider +} + +func mustDeclareScopes(t *testing.T, provider ScopeRoleProvider, names ...string) { + t.Helper() + scopes := make([]*contracts.ScopeDeclaration, 0, len(names)) + for _, name := range names { + scope := scopeDeclarationFromName(name) + scopes = append(scopes, scope) + } + if err := provider.DeclareScopes(context.Background(), scopes); err != nil { + t.Fatalf("DeclareScopes: %v", err) + } +} + +func assertScopeAllowed(t *testing.T, provider ScopeRoleProvider, check ScopeCheck, want bool) { + t.Helper() + result, err := provider.CheckScope(context.Background(), check) + if err != nil { + t.Fatalf("CheckScope(%+v): %v", check, err) + } + if result.Allowed != want { + t.Fatalf("CheckScope(%+v) allowed = %v, want %v; reason=%s", check, result.Allowed, want, result.Reason) + } +} diff --git a/internal/step_authz_check.go b/internal/step_authz_check.go index b01d2c5..0571e0a 100644 --- a/internal/step_authz_check.go +++ b/internal/step_authz_check.go @@ -57,22 +57,33 @@ type moduleRegistry interface { GetEnforcer(name string) (*CasbinModule, bool) } +type authzProviderRegistry interface { + GetAuthzProvider(name string) (AuthzProvider, bool) +} + // globalRegistry is the package-level registry shared between all steps created // by CreateModule. It maps module-name → *CasbinModule. var globalRegistry = &defaultRegistry{ - modules: make(map[string]*CasbinModule), + modules: make(map[string]*CasbinModule), + providers: make(map[string]AuthzProvider), } // RegisterModule adds a CasbinModule to the global registry. It is called by // CreateModule so that CreateStep can find the enforcer by name. func RegisterModule(m *CasbinModule) { globalRegistry.set(m.name, m) + RegisterAuthzProvider(m.name, m) +} + +func RegisterAuthzProvider(name string, provider AuthzProvider) { + globalRegistry.setAuthzProvider(name, provider) } // defaultRegistry is a simple thread-safe module registry backed by a map. type defaultRegistry struct { - mu sync.RWMutex - modules map[string]*CasbinModule + mu sync.RWMutex + modules map[string]*CasbinModule + providers map[string]AuthzProvider } func (r *defaultRegistry) set(name string, m *CasbinModule) { @@ -88,6 +99,22 @@ func (r *defaultRegistry) GetEnforcer(name string) (*CasbinModule, bool) { return m, ok } +func (r *defaultRegistry) setAuthzProvider(name string, provider AuthzProvider) { + r.mu.Lock() + defer r.mu.Unlock() + if r.providers == nil { + r.providers = make(map[string]AuthzProvider) + } + r.providers[name] = provider +} + +func (r *defaultRegistry) GetAuthzProvider(name string) (AuthzProvider, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + provider, ok := r.providers[name] + return provider, ok +} + // newAuthzCheckStep parses step config and returns an authzCheckStep. func newAuthzCheckStep(name string, config map[string]any) (*authzCheckStep, error) { s := &authzCheckStep{ diff --git a/internal/step_authz_decision.go b/internal/step_authz_decision.go new file mode 100644 index 0000000..16cd19b --- /dev/null +++ b/internal/step_authz_decision.go @@ -0,0 +1,159 @@ +package internal + +import ( + "context" + "fmt" + "text/template" + + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" +) + +type authzDecisionStep struct { + name string + moduleName string + provider string + mode AuthzCapability + static []string + tmpls []*template.Template + subjectAttrs map[string]string + resourceAttrs map[string]string + environmentAttrs map[string]string + registry moduleRegistry +} + +func newAuthzDecisionStep(name string, config map[string]any) (*authzDecisionStep, error) { + step := &authzDecisionStep{name: name, moduleName: "authz", provider: "casbin", registry: globalRegistry} + if v := stringValue(config["module"]); v != "" { + step.moduleName = v + } + if v := stringValue(config["provider"]); v != "" { + step.provider = v + } + step.mode = AuthzCapability(stringValue(config["mode"])) + step.static, step.tmpls = compileRuleTemplates([]string{ + stringValue(config["subject"]), + stringValue(config["context"]), + stringValue(config["resource"]), + stringValue(config["action"]), + stringValue(config["scope"]), + stringValue(config["relation"]), + }) + step.subjectAttrs = stringMapFromAny(config["subject_attributes"]) + step.resourceAttrs = stringMapFromAny(config["resource_attributes"]) + step.environmentAttrs = stringMapFromAny(config["environment_attributes"]) + return step, nil +} + +func (s *authzDecisionStep) Execute( + ctx context.Context, + triggerData map[string]any, + stepOutputs map[string]map[string]any, + current map[string]any, + metadata map[string]any, + _ map[string]any, +) (*sdk.StepResult, error) { + values, err := resolveRule(s.static, s.tmpls, buildTemplateData(triggerData, stepOutputs, current)) + if err != nil { + return nil, fmt.Errorf("step.authz_check %q: resolve: %w", s.name, err) + } + provider, err := resolveDecisionProvider(s.registry, s.moduleName, s.provider) + if err != nil { + return nil, fmt.Errorf("step.authz_check %q: %w", s.name, err) + } + decision, err := DecideAuthorization(ctx, provider, AuthorizationDecisionInput{ + Provider: s.provider, + Mode: s.mode, + Subject: values[0], + Context: values[1], + Resource: values[2], + Action: values[3], + Scope: values[4], + Relation: values[5], + SubjectAttributes: s.subjectAttrs, + ResourceAttributes: s.resourceAttrs, + EnvironmentAttributes: s.environmentAttrs, + Explain: boolValue(metadata["explain"]), + }) + if err != nil { + return nil, fmt.Errorf("step.authz_check %q: %w", s.name, err) + } + return &sdk.StepResult{Output: authorizationDecisionOutputToMap(decision)}, nil +} + +type authzRequireCapabilitiesStep struct { + name string + moduleName string + provider string + requirements []CapabilityRequirement + registry moduleRegistry +} + +func newAuthzRequireCapabilitiesStep(name string, config map[string]any) (*authzRequireCapabilitiesStep, error) { + step := &authzRequireCapabilitiesStep{name: name, moduleName: "authz", provider: "casbin", registry: globalRegistry} + if v := stringValue(config["module"]); v != "" { + step.moduleName = v + } + if v := stringValue(config["provider"]); v != "" { + step.provider = v + } + step.requirements = capabilityRequirementsFromAny(config["requirements"]) + return step, nil +} + +func (s *authzRequireCapabilitiesStep) Execute( + ctx context.Context, + _ map[string]any, + _ map[string]map[string]any, + _ map[string]any, + _ map[string]any, + _ map[string]any, +) (*sdk.StepResult, error) { + _ = ctx + provider, err := resolveDecisionProvider(s.registry, s.moduleName, s.provider) + if err != nil { + return nil, fmt.Errorf("step.authz_require_capabilities %q: %w", s.name, err) + } + authzProvider, ok := provider.(AuthzProvider) + if !ok { + return nil, fmt.Errorf("step.authz_require_capabilities %q: provider does not expose capabilities", s.name) + } + out := providerCapabilitiesOutputMap(s.moduleName, s.provider, authzProvider, s.requirements) + if err := authzProvider.RequireCapabilities(s.requirements); err != nil { + return &sdk.StepResult{Output: out}, err + } + return &sdk.StepResult{Output: out}, nil +} + +func resolveDecisionProvider(registry moduleRegistry, moduleName, providerName string) (any, error) { + switch providerName { + case "casbin": + mod, ok := registry.GetEnforcer(moduleName) + if !ok { + return nil, fmt.Errorf("casbin module %q not found", moduleName) + } + return mod, nil + case "permit", "keto": + reg, ok := registry.(authzProviderRegistry) + if !ok { + return nil, fmt.Errorf("registry cannot look up %s module %q", providerName, moduleName) + } + provider, ok := reg.GetAuthzProvider(moduleName) + if !ok { + return nil, fmt.Errorf("%s module %q not found", providerName, moduleName) + } + return provider, nil + default: + return nil, fmt.Errorf("unknown provider %q", providerName) + } +} + +func authorizationDecisionOutputToMap(decision AuthorizationDecisionOutput) map[string]any { + return compactMap(map[string]any{ + "allowed": decision.Allowed, + "mode": string(decision.Mode), + "subject": decision.Subject, + "context": decision.Context, + "reason": decision.Reason, + "explain": decision.Explain, + }) +} diff --git a/internal/step_authz_decision_test.go b/internal/step_authz_decision_test.go new file mode 100644 index 0000000..a85e406 --- /dev/null +++ b/internal/step_authz_decision_test.go @@ -0,0 +1,63 @@ +package internal + +import ( + "context" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-authz/internal/contracts" +) + +func TestAuthzDecisionStep(t *testing.T) { + ctx := context.Background() + mod := rbacTestModule(t, nil, nil) + scope := &contracts.ScopeDeclaration{Name: "admin:authz.roles:update", Context: "admin", Resource: "authz.roles", Actions: []string{"update"}} + if err := mod.DeclareScopes(ctx, []*contracts.ScopeDeclaration{scope}); err != nil { + t.Fatalf("DeclareScopes: %v", err) + } + if err := mod.UpsertRole(ctx, RoleScopeGrant{Role: "admin", Context: "admin", Scopes: []string{scope.GetName()}}); err != nil { + t.Fatalf("UpsertRole: %v", err) + } + if err := mod.AssignRole(ctx, SubjectRoleAssignment{Subject: "admin@tailnet", Role: "admin", Context: "admin"}); err != nil { + t.Fatalf("AssignRole: %v", err) + } + + step, err := newAuthzDecisionStep("gate", map[string]any{ + "provider": "casbin", + "mode": "rbac", + "subject": "{{.user}}", + "context": "admin", + "scope": scope.GetName(), + }) + if err != nil { + t.Fatalf("newAuthzDecisionStep: %v", err) + } + step.registry = &testRegistry{mod: mod} + result, err := step.Execute(ctx, nil, nil, map[string]any{"user": "admin@tailnet"}, nil, nil) + if err != nil { + t.Fatalf("Execute: %v", err) + } + if result.Output["allowed"] != true || result.Output["mode"] != "rbac" { + t.Fatalf("decision output = %#v", result.Output) + } +} + +func TestAuthzRequireCapabilitiesStep(t *testing.T) { + step, err := newAuthzRequireCapabilitiesStep("requirements", map[string]any{ + "provider": "keto", + "module": "keto", + "requirements": []any{ + map[string]any{"mode": "rebac", "operations": []any{"check", "manage_relations"}}, + }, + }) + if err != nil { + t.Fatalf("newAuthzRequireCapabilitiesStep: %v", err) + } + step.registry = &testCapabilityRegistry{providers: map[string]AuthzProvider{"keto": &KetoModule{name: "keto"}}} + result, err := step.Execute(context.Background(), nil, nil, nil, nil, nil) + if err != nil { + t.Fatalf("Execute: %v", err) + } + if result.Output["health"] != "ok" { + t.Fatalf("require capabilities output = %#v", result.Output) + } +} diff --git a/internal/typed.go b/internal/typed.go index 19df67f..29d0357 100644 --- a/internal/typed.go +++ b/internal/typed.go @@ -145,6 +145,41 @@ func typedAuthzCapabilities(registry moduleRegistry) sdk.TypedStepHandler[*contr } } +func typedAuthzDecision(registry moduleRegistry) sdk.TypedStepHandler[*contracts.AuthorizationDecisionConfig, *contracts.AuthorizationDecisionInput, *contracts.AuthorizationDecisionOutput] { + return func(ctx context.Context, req sdk.TypedStepRequest[*contracts.AuthorizationDecisionConfig, *contracts.AuthorizationDecisionInput]) (*sdk.TypedStepResult[*contracts.AuthorizationDecisionOutput], error) { + cfg := mergeStringFields(authorizationDecisionConfigToMap(req.Config), authorizationDecisionInputToMap(req.Input)) + step, err := newAuthzDecisionStep("typed", cfg) + if err != nil { + return nil, err + } + step.registry = registry + result, err := step.Execute(ctx, req.TriggerData, req.StepOutputs, req.Current, req.Metadata, nil) + if err != nil { + return nil, err + } + return &sdk.TypedStepResult[*contracts.AuthorizationDecisionOutput]{Output: authorizationDecisionOutputFromMap(result.Output)}, nil + } +} + +func typedAuthzRequireCapabilities(registry moduleRegistry) sdk.TypedStepHandler[*contracts.RequireCapabilitiesConfig, *contracts.RequireCapabilitiesInput, *contracts.ProviderCapabilitiesOutput] { + return func(ctx context.Context, req sdk.TypedStepRequest[*contracts.RequireCapabilitiesConfig, *contracts.RequireCapabilitiesInput]) (*sdk.TypedStepResult[*contracts.ProviderCapabilitiesOutput], error) { + cfg := requireCapabilitiesConfigToMap(req.Config) + for k, v := range requireCapabilitiesInputToMap(req.Input) { + cfg[k] = v + } + step, err := newAuthzRequireCapabilitiesStep("typed", cfg) + if err != nil { + return nil, err + } + step.registry = registry + result, err := step.Execute(ctx, req.TriggerData, req.StepOutputs, req.Current, req.Metadata, nil) + if err != nil { + return nil, err + } + return &sdk.TypedStepResult[*contracts.ProviderCapabilitiesOutput]{Output: providerCapabilitiesOutputFromMap(result.Output)}, nil + } +} + func typedSubjectObjectAction(create func(string, map[string]any) (sdk.StepInstance, error), registry moduleRegistry) sdk.TypedStepHandler[*contracts.SubjectObjectActionConfig, *contracts.SubjectObjectActionInput, *contracts.SubjectObjectActionOutput] { return func(ctx context.Context, req sdk.TypedStepRequest[*contracts.SubjectObjectActionConfig, *contracts.SubjectObjectActionInput]) (*sdk.TypedStepResult[*contracts.SubjectObjectActionOutput], error) { cfg := mergeStringFields(subjectObjectActionConfigToMap(req.Config), subjectObjectActionInputToMap(req.Input)) @@ -325,6 +360,34 @@ func capabilitiesInputToMap(input *contracts.CapabilitiesInput) map[string]any { return compactMap(map[string]any{"module": input.GetModule(), "provider": input.GetProvider()}) } +func authorizationDecisionConfigToMap(cfg *contracts.AuthorizationDecisionConfig) map[string]any { + if cfg == nil { + return map[string]any{} + } + return compactMap(map[string]any{"module": cfg.GetModule(), "provider": cfg.GetProvider(), "mode": authzModeString(cfg.GetMode()), "subject": cfg.GetSubject(), "context": cfg.GetContext(), "resource": cfg.GetResource(), "action": cfg.GetAction(), "scope": cfg.GetScope(), "relation": cfg.GetRelation(), "explain": cfg.GetExplain()}) +} + +func authorizationDecisionInputToMap(input *contracts.AuthorizationDecisionInput) map[string]any { + if input == nil { + return map[string]any{} + } + return compactMap(map[string]any{"module": input.GetModule(), "provider": input.GetProvider(), "mode": authzModeString(input.GetMode()), "subject": input.GetSubject(), "context": input.GetContext(), "resource": input.GetResource(), "action": input.GetAction(), "scope": input.GetScope(), "relation": input.GetRelation(), "subject_attributes": stringMapFromStruct(input.GetSubjectAttributes()), "resource_attributes": stringMapFromStruct(input.GetResourceAttributes()), "environment_attributes": stringMapFromStruct(input.GetEnvironmentAttributes()), "explain": input.GetExplain()}) +} + +func requireCapabilitiesConfigToMap(cfg *contracts.RequireCapabilitiesConfig) map[string]any { + if cfg == nil { + return map[string]any{} + } + return compactMap(map[string]any{"module": cfg.GetModule(), "provider": cfg.GetProvider(), "requirements": capabilityRequirementsToAny(cfg.GetRequirements())}) +} + +func requireCapabilitiesInputToMap(input *contracts.RequireCapabilitiesInput) map[string]any { + if input == nil { + return map[string]any{} + } + return compactMap(map[string]any{"module": input.GetModule(), "provider": input.GetProvider(), "requirements": capabilityRequirementsToAny(input.GetRequirements())}) +} + func subjectObjectActionConfigToMap(cfg *contracts.SubjectObjectActionConfig) map[string]any { if cfg == nil { return nil @@ -417,9 +480,34 @@ func roleAssignOutputFromMap(values map[string]any) *contracts.RoleAssignOutput func capabilitiesOutputFromMap(values map[string]any) *contracts.CapabilitiesOutput { return &contracts.CapabilitiesOutput{ - Module: stringValue(values["module"]), - Provider: stringValue(values["provider"]), - Capabilities: stringSliceValue(values["capabilities"]), + Module: stringValue(values["module"]), + Provider: stringValue(values["provider"]), + Capabilities: stringSliceValue(values["capabilities"]), + Descriptors: contractCapabilityDescriptorsFromAny(values["capability_descriptors"]), + Health: stringValue(values["health"]), + MissingRequirements: stringSliceValue(values["missing_requirements"]), + } +} + +func providerCapabilitiesOutputFromMap(values map[string]any) *contracts.ProviderCapabilitiesOutput { + return &contracts.ProviderCapabilitiesOutput{ + Module: stringValue(values["module"]), + Provider: stringValue(values["provider"]), + Capabilities: stringSliceValue(values["capabilities"]), + Descriptors: contractCapabilityDescriptorsFromAny(values["capability_descriptors"]), + Health: stringValue(values["health"]), + MissingRequirements: stringSliceValue(values["missing_requirements"]), + } +} + +func authorizationDecisionOutputFromMap(values map[string]any) *contracts.AuthorizationDecisionOutput { + return &contracts.AuthorizationDecisionOutput{ + Allowed: boolValue(values["allowed"]), + Mode: contractAuthzMode(stringValue(values["mode"])), + Subject: stringValue(values["subject"]), + Context: stringValue(values["context"]), + Reason: stringValue(values["reason"]), + Explain: stringValue(values["explain"]), } } @@ -619,3 +707,115 @@ func stringSliceValue(value any) []string { return nil } } + +func contractCapabilityDescriptorsFromAny(value any) []*contracts.CapabilityDescriptor { + values, ok := value.([]any) + if !ok { + return nil + } + out := make([]*contracts.CapabilityDescriptor, 0, len(values)) + for _, value := range values { + descriptor := capabilityDescriptorFromMap(anyMapValue(value)) + if descriptor != nil { + out = append(out, descriptor) + } + } + return out +} + +func capabilityDescriptorFromMap(values map[string]any) *contracts.CapabilityDescriptor { + if values == nil { + return nil + } + return &contracts.CapabilityDescriptor{ + Mode: contractAuthzMode(stringValue(values["mode"])), + Operations: contractAuthzOperations(stringSliceValue(values["operations"])), + Configured: boolValue(values["configured"]), + Source: stringValue(values["source"]), + Health: stringValue(values["health"]), + UnsupportedReason: stringValue(values["unsupported_reason"]), + } +} + +func contractAuthzMode(mode string) contracts.AuthzMode { + switch AuthzCapability(mode) { + case CapabilityRBAC: + return contracts.AuthzMode_AUTHZ_MODE_RBAC + case CapabilityABAC: + return contracts.AuthzMode_AUTHZ_MODE_ABAC + case CapabilityReBAC: + return contracts.AuthzMode_AUTHZ_MODE_REBAC + case CapabilityACL: + return contracts.AuthzMode_AUTHZ_MODE_ACL + default: + return contracts.AuthzMode_AUTHZ_MODE_UNSPECIFIED + } +} + +func contractAuthzOperations(operations []string) []contracts.AuthzOperation { + out := make([]contracts.AuthzOperation, 0, len(operations)) + for _, operation := range operations { + switch AuthzOperation(operation) { + case OperationCheck: + out = append(out, contracts.AuthzOperation_AUTHZ_OPERATION_CHECK) + case OperationManageRoles: + out = append(out, contracts.AuthzOperation_AUTHZ_OPERATION_MANAGE_ROLES) + case OperationManagePolicies: + out = append(out, contracts.AuthzOperation_AUTHZ_OPERATION_MANAGE_POLICIES) + case OperationManageRelations: + out = append(out, contracts.AuthzOperation_AUTHZ_OPERATION_MANAGE_RELATIONS) + case OperationList: + out = append(out, contracts.AuthzOperation_AUTHZ_OPERATION_LIST) + } + } + return out +} + +func authzModeString(mode contracts.AuthzMode) string { + switch mode { + case contracts.AuthzMode_AUTHZ_MODE_RBAC: + return string(CapabilityRBAC) + case contracts.AuthzMode_AUTHZ_MODE_ABAC: + return string(CapabilityABAC) + case contracts.AuthzMode_AUTHZ_MODE_REBAC: + return string(CapabilityReBAC) + case contracts.AuthzMode_AUTHZ_MODE_ACL: + return string(CapabilityACL) + default: + return "" + } +} + +func capabilityRequirementsToAny(requirements []*contracts.CapabilityRequirement) []any { + out := make([]any, 0, len(requirements)) + for _, requirement := range requirements { + operations := make([]any, 0, len(requirement.GetOperations())) + for _, operation := range requirement.GetOperations() { + switch operation { + case contracts.AuthzOperation_AUTHZ_OPERATION_CHECK: + operations = append(operations, string(OperationCheck)) + case contracts.AuthzOperation_AUTHZ_OPERATION_MANAGE_ROLES: + operations = append(operations, string(OperationManageRoles)) + case contracts.AuthzOperation_AUTHZ_OPERATION_MANAGE_POLICIES: + operations = append(operations, string(OperationManagePolicies)) + case contracts.AuthzOperation_AUTHZ_OPERATION_MANAGE_RELATIONS: + operations = append(operations, string(OperationManageRelations)) + case contracts.AuthzOperation_AUTHZ_OPERATION_LIST: + operations = append(operations, string(OperationList)) + } + } + out = append(out, map[string]any{"mode": authzModeString(requirement.GetMode()), "operations": operations}) + } + return out +} + +func stringMapFromStruct(value *structpb.Struct) map[string]string { + if value == nil { + return nil + } + out := make(map[string]string, len(value.GetFields())) + for key, field := range value.GetFields() { + out[key] = fmt.Sprint(field.AsInterface()) + } + return out +} diff --git a/plugin.contracts.json b/plugin.contracts.json index b10bd93..882699b 100644 --- a/plugin.contracts.json +++ b/plugin.contracts.json @@ -13,6 +13,34 @@ "mode": "strict", "config": "workflow.plugins.authz.v1.PermitModuleConfig" }, + { + "kind": "module", + "type": "authz.keto", + "mode": "strict", + "config": "workflow.plugins.authz.v1.KetoModuleConfig" + }, + { + "kind": "module", + "type": "authz.scope_catalog", + "mode": "strict", + "config": "workflow.plugins.authz.v1.ScopeCatalogConfig" + }, + { + "kind": "step", + "type": "step.authz_check", + "mode": "strict", + "config": "workflow.plugins.authz.v1.AuthorizationDecisionConfig", + "input": "workflow.plugins.authz.v1.AuthorizationDecisionInput", + "output": "workflow.plugins.authz.v1.AuthorizationDecisionOutput" + }, + { + "kind": "step", + "type": "step.authz_require_capabilities", + "mode": "strict", + "config": "workflow.plugins.authz.v1.RequireCapabilitiesConfig", + "input": "workflow.plugins.authz.v1.RequireCapabilitiesInput", + "output": "workflow.plugins.authz.v1.ProviderCapabilitiesOutput" + }, { "kind": "step", "type": "step.authz_check_casbin", @@ -133,6 +161,190 @@ "input": "workflow.plugins.authz.v1.ListInput", "output": "workflow.plugins.authz.v1.GenericStepOutput" }, + { + "kind": "service_method", + "serviceName": "ScopeCatalog", + "method": "RegisterScopes", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RegisterScopesInput", + "output": "workflow.plugins.authz.v1.RegisterScopesOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeCatalog", + "method": "ListScopes", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ListScopesInput", + "output": "workflow.plugins.authz.v1.ListScopesOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeCatalog", + "method": "ResolveSubjectScopes", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ResolveSubjectScopesInput", + "output": "workflow.plugins.authz.v1.ResolveSubjectScopesOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeCatalog", + "method": "RegisterDeclarations", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RegisterDeclarationsInput", + "output": "workflow.plugins.authz.v1.RegisterDeclarationsOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeCatalog", + "method": "ListDeclarations", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ListDeclarationsInput", + "output": "workflow.plugins.authz.v1.ListDeclarationsOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeCatalog", + "method": "ResolveProjectionInputs", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ResolveProjectionInputsInput", + "output": "workflow.plugins.authz.v1.ResolveProjectionInputsOutput" + }, + { + "kind": "service_method", + "serviceName": "ProviderCapabilities", + "method": "GetCapabilities", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ProviderCapabilitiesInput", + "output": "workflow.plugins.authz.v1.ProviderCapabilitiesOutput" + }, + { + "kind": "service_method", + "serviceName": "ProviderCapabilities", + "method": "RequireCapabilities", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ProviderCapabilitiesInput", + "output": "workflow.plugins.authz.v1.ProviderCapabilitiesOutput" + }, + { + "kind": "service_method", + "serviceName": "AttributePolicyProvider", + "method": "DeclareAttributes", + "mode": "strict", + "input": "workflow.plugins.authz.v1.DeclareAttributesInput", + "output": "workflow.plugins.authz.v1.DeclareAttributesOutput" + }, + { + "kind": "service_method", + "serviceName": "AttributePolicyProvider", + "method": "UpsertAttributePolicy", + "mode": "strict", + "input": "workflow.plugins.authz.v1.UpsertAttributePolicyInput", + "output": "workflow.plugins.authz.v1.UpsertAttributePolicyOutput" + }, + { + "kind": "service_method", + "serviceName": "AttributePolicyProvider", + "method": "ListAttributePolicies", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ListAttributePoliciesInput", + "output": "workflow.plugins.authz.v1.ListAttributePoliciesOutput" + }, + { + "kind": "service_method", + "serviceName": "AttributePolicyProvider", + "method": "RemoveAttributePolicy", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RemoveAttributePolicyInput", + "output": "workflow.plugins.authz.v1.RemoveAttributePolicyOutput" + }, + { + "kind": "service_method", + "serviceName": "AttributePolicyProvider", + "method": "CheckAttributes", + "mode": "strict", + "input": "workflow.plugins.authz.v1.AttributeCheckInput", + "output": "workflow.plugins.authz.v1.AttributeCheckOutput" + }, + { + "kind": "service_method", + "serviceName": "RelationshipProvider", + "method": "UpsertRelationTuple", + "mode": "strict", + "input": "workflow.plugins.authz.v1.UpsertRelationTupleInput", + "output": "workflow.plugins.authz.v1.UpsertRelationTupleOutput" + }, + { + "kind": "service_method", + "serviceName": "RelationshipProvider", + "method": "ListRelationTuples", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ListRelationTuplesInput", + "output": "workflow.plugins.authz.v1.ListRelationTuplesOutput" + }, + { + "kind": "service_method", + "serviceName": "RelationshipProvider", + "method": "RemoveRelationTuple", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RemoveRelationTupleInput", + "output": "workflow.plugins.authz.v1.RemoveRelationTupleOutput" + }, + { + "kind": "service_method", + "serviceName": "RelationshipProvider", + "method": "CheckRelation", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RelationCheckInput", + "output": "workflow.plugins.authz.v1.RelationCheckOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeRoleProvider", + "method": "DeclareScopes", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RegisterScopesInput", + "output": "workflow.plugins.authz.v1.RegisterScopesOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeRoleProvider", + "method": "UpsertRole", + "mode": "strict", + "input": "workflow.plugins.authz.v1.UpsertRoleInput", + "output": "workflow.plugins.authz.v1.UpsertRoleOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeRoleProvider", + "method": "AssignRole", + "mode": "strict", + "input": "workflow.plugins.authz.v1.AssignRoleInput", + "output": "workflow.plugins.authz.v1.AssignRoleOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeRoleProvider", + "method": "ListAssignments", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ListRoleAssignmentsInput", + "output": "workflow.plugins.authz.v1.ListRoleAssignmentsOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeRoleProvider", + "method": "RemoveAssignment", + "mode": "strict", + "input": "workflow.plugins.authz.v1.RemoveRoleAssignmentInput", + "output": "workflow.plugins.authz.v1.RemoveRoleAssignmentOutput" + }, + { + "kind": "service_method", + "serviceName": "ScopeRoleProvider", + "method": "CheckScope", + "mode": "strict", + "input": "workflow.plugins.authz.v1.ScopeCheckInput", + "output": "workflow.plugins.authz.v1.ScopeCheckOutput" + }, { "kind": "step", "type": "step.permit_check",