Policies
Policies are JSON-defined permission grants attached to owners through roles. Reference for policy structure, JSON Schema validation, resources, conditions, and the permissions a statement can grant.
ODD Platform allows to manage access to resources by creating policies and attaching them to owners through roles.
Policies are described in JSON format and validated with JSON Schema.
Deleting a policy is blocked while a role is still bound to it. ODD refuses to delete a policy that any role still references — the attempt fails with "Policy is attached to a role." To delete a policy, first detach it from every role on the Roles tab; once no role references it, the deletion succeeds and the permissions it granted go with it. This mirrors the cascade-delete guards on namespaces and owners — see Permissions.
JSON policy structure
Each policy is represented by an array of statements and each statement defines a resource with optional conditions and permissions which will be allowed for given resource.
{
"statements": [
{
"resource": {
"type": "",
"conditions": {}
},
"permissions": []
},
{
"resource": {
"type": "",
"conditions": {}
},
"permissions": []
}
]
}Resource type
There are 3 possible types of policy resource:
DATA_ENTITY - Indicates, that current permissions are applied for data entity
TERM - Indicates, that current permissions are applied for dictionary term
MANAGEMENT - Indicates, that current permissions are general and work all over the platform
QUERY_EXAMPLE - Indicates, that the current permissions are applied for query examples
Each type can be combined only with associated permissions and conditions, e.g. if you describe statement for DATA_ENTITY type you can only use data entity's conditions and permissions.
Conditions
Conditions allow to specify the circumstances under which the policy grants permission.
This is an optional field and in case of absence, permissions will be applied to all resource type entries.
Conditions can't be applied to MANAGEMENT resource type
In ODD Platform we have pre-defined condition operators and fields, which can be used with these operators.
Condition operators
Currently we support next operators:
all- all conditions under this operator must be positiveany- at least one condition under this operation must be positiveeq- condition field must be equal to some valuenot_eq- condition field must not be equal to some valuematch- condition field must match some valuenot_match- condition field must not match some valueis- condition field must be truenot_is- condition field must be false
Condition fields
There are couple of pre-defined fields, which can be used in conditions. Each resource type has its own fields.
Data entity
dataEntity:oddrn- data entity's ODDRNdataEntity:internalName- data entity's business namedataEntity:externalName- data entity's ingested namedataEntity:type- data entity's type namedataEntity:class- data entity's class namedataEntity:datasource:oddrn- data entity's datasource ODDRNdataEntity:datasource:name- data entity's datasource namedataEntity:namespace:name- data entity's namespace namedataEntity:tag:name- data entity's tag namedataEntity:owner- data entity's ownerdataEntity:owner:title- data entity's owner title (see Title vocabulary caveat below)
Term
term:name- term's nameterm:namespace:name- term's namespace nameterm:tag:name- term's tag nameterm:owner- term's ownerterm:owner:title- term's owner title (see Title vocabulary caveat below)
Condition examples
User must be term's owner, term must be in Open Data Discovery namespace and have tag, which name equals to
Test.At least one of the conditions must be positive: User must be data entity's owner OR data entity shouldn't have tag
PII.
Permissions
Please check the Permissions section for all available permissions list.
Policy examples
Data entity policy with conditions
Policy allows to update business name, description and custom metadata if user is data entity's owner and this data entity is in Open Data Discovery namespace
Data entity policy without conditions
All actions are allowed for all data entities
Dictionary term policy with conditions
Policy allows to update term information and ownership if it has Customer tag
Management policy
Policy allows to manage datasources, collectors and namespaces
Combined policy
Policy allows to edit term information and permits all actions for data entities from Finance namespace.
Title vocabulary caveat for :owner:title conditions
:owner:title conditionsThe dataEntity:owner:title and term:owner:title condition fields evaluate against the platform's Title table — a free-text vocabulary populated by users at ownership-grant time, not a curated allowlist. Two operator-visible behaviours follow from this and apply to every Policy condition referencing those fields.
Title strings are case-sensitive, exact-match, and not normalised. The platform's TitleService.getOrCreate(name) accepts any string verbatim: no case-folding, no whitespace trimming, no slug-deduping, no @Pattern or @Size validation. The underlying title.name column has no CHECK constraint. Two users who type 'Data Steward', 'data steward', 'DATA STEWARD', ' Data Steward ', or 'data-steward' into the same role-attach form each accumulate as distinct rows in the Title table. A Policy condition dataEntity:owner:title == 'Data Steward' silently misses every other-casing variant — operators carrying the wrong-cased Title receive access-denied with no platform-visible diagnostic.
The Title table is also written through a side-channel: any caller with DATA_ENTITY_OWNERSHIP_CREATE or TERM_OWNERSHIP_CREATE can mint a new Title row by typing a never-before-seen string into an ownership-grant form. New Title strings become part of the Policy-condition vocabulary the moment they are written.
Mitigations operators can apply today (pending an upstream curated-Title-vocabulary fix): enumerate the variants explicitly with an any block of eq conditions — { "any": [ { "eq": { "dataEntity:owner:title": "Data Steward" } }, { "eq": { "dataEntity:owner:title": "data steward" } }, { "eq": { "dataEntity:owner:title": "DATA STEWARD" } } ] }. (There is no in operator — see Condition operators for the supported set; a condition using in is rejected by the policy JSON Schema, and the platform returns an error rather than saving the policy.) OR restrict the two *_OWNERSHIP_CREATE permissions to a vocabulary-steward role so the Title set stays bounded; OR deploy a periodic SQL job that consolidates obvious typo-variants.
There is no Management UI tab for Titles. The Management surface lists Namespaces, Datasources, Integrations, Collectors, Owners, Tags, Associations, Roles, and Policies — no Titles entry. Operators cannot browse, merge, or delete duplicate or typo'd Titles via the UI today. Title curation requires direct database access against the title table until a Titles management surface ships upstream.
Performance characteristics
Every authenticated request that reaches a permission-gated endpoint resolves the caller's Policies before the handler runs. The resolution is not cached at the request scope, the user scope, or any other scope — PolicyService.getCurrentUserPolicies is invoked from the platform's permission extractors on every authorized HTTP request and executes two JOIN roundtrips against PostgreSQL each time (a 5-table user→owner→user-owner-mapping chain to resolve the caller's roles, then a 2-table role→policies chain to fetch each role's policy set).
Throughput implication. For a platform receiving N authenticated requests per second, the authorization hot-path generates approximately 2 × N PostgreSQL queries per second over and above the application-logic queries served by the matching handler. Size the R2DBC connection pool (spring.r2dbc.pool.max-size) and the database CPU budget accordingly — a practical heuristic is to provision 4–5 concurrent JOIN slots per peak authenticated request per second, plus headroom for the application-logic queries each request also triggers.
Operator-tunable knob today: none. There is no in-process cache, no @Cacheable annotation, no operator setting to tune the cache TTL. An upstream request-scoped cache for getCurrentUserPolicies is tracked; until it ships, the per-request cost is the floor.
Last updated