diff --git a/.github/workflows/markdownlint.yaml b/.github/workflows/markdownlint.yaml new file mode 100644 index 00000000..69b726b9 --- /dev/null +++ b/.github/workflows/markdownlint.yaml @@ -0,0 +1,30 @@ +name: Markdownlint + +on: + pull_request: + branches: + - main + paths: + - "**/*.md" + - "**/*.mdx" + - ".markdownlint.yaml" + +jobs: + markdownlint: + name: Markdownlint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Get changed markdown files + id: changed + run: | + files=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '\.(md|mdx)$' || true) + echo "files<> "$GITHUB_OUTPUT" + echo "$files" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ github.token }} + - uses: DavidAnson/markdownlint-cli2-action@v19 + if: steps.changed.outputs.files != '' + with: + globs: ${{ steps.changed.outputs.files }} diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..37a5698b --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,30 @@ +MD024: # no-duplicate-heading + siblings_only: true +MD033: # no-inline-html + allowed_elements: + - br + - summary + - details + - strong + - Tabs + - TabItem + # MDX components (imported JSX — markdownlint can't distinguish from HTML) + - AssertionExamples + - CodeBlock + - CreateAttribute + - CreateConditionSet + - CreateNamespace + - CreateSubjectMapping + - DecryptOptions + - EncryptOptions + - GetDecisionsExample + - JsAuthNote + - ListAttributes + - ListNamespaces + - ListSubjectMapping + - SdkVersion +MD010: # no-hard-tabs + code_blocks: false # Go code uses tabs +MD013: false # Line length +MD041: false # First line heading — MDX files start with imports/frontmatter +MD060: true # Table column style diff --git a/code_samples/tdf/decrypt_options.mdx b/code_samples/tdf/decrypt_options.mdx index d50ea098..61e67cc3 100644 --- a/code_samples/tdf/decrypt_options.mdx +++ b/code_samples/tdf/decrypt_options.mdx @@ -1,8 +1,6 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -## Decrypt Options - The following options can be passed to the decrypt call to control how the TDF is opened and validated. --- @@ -371,4 +369,3 @@ Not available in the JavaScript SDK. The manifest size limit is not configurable - diff --git a/code_samples/tdf/encrypt_options.mdx b/code_samples/tdf/encrypt_options.mdx index cbbffc39..52c3671b 100644 --- a/code_samples/tdf/encrypt_options.mdx +++ b/code_samples/tdf/encrypt_options.mdx @@ -1,8 +1,6 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -## Encrypt Options - The following options can be passed to the encrypt call to control how the TDF is constructed. --- @@ -513,7 +511,7 @@ const tdf = await client.createTDF({ -Signed assertions can be verified on decrypt using [Assertion Verification Keys](#assertion-verification-keys). +Signed assertions can be verified on decrypt using [Assertion Verification Keys](/sdks/tdf#assertion-verification-keys). --- diff --git a/docs/sdks/authentication.mdx b/docs/sdks/authentication.mdx index c9c32f35..d5b93149 100644 --- a/docs/sdks/authentication.mdx +++ b/docs/sdks/authentication.mdx @@ -3,10 +3,8 @@ sidebar_position: 2 title: Authentication --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Authentication +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; The SDKs authenticate with an [OIDC](https://openid.net/developers/how-connect-works/)-compatible identity provider (IdP) to obtain access tokens for the platform. The platform itself is a **resource server**, not an identity provider — you bring your own IdP (Keycloak is the reference implementation). @@ -41,7 +39,7 @@ import io.opentdf.platform.sdk.SDKBuilder; ```typescript -import { authTokenInterceptor, OpenTDF } from '@opentdf/sdk'; +import { authTokenInterceptor, OpenTDF } from "@opentdf/sdk"; ``` @@ -83,15 +81,23 @@ SDK sdk = new SDKBuilder() ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; const client = new OpenTDF({ - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'my-client-id', - clientSecret: 'my-client-secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "my-client-id", + clientSecret: "my-client-secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); ``` @@ -141,15 +147,23 @@ The Java SDK wraps the JWT as a `BearerAccessToken` and performs an RFC 8693 tok ```typescript -import { authTokenInterceptor, externalJwtTokenProvider, OpenTDF } from '@opentdf/sdk'; +import { + authTokenInterceptor, + externalJwtTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; const client = new OpenTDF({ - interceptors: [authTokenInterceptor(externalJwtTokenProvider({ - clientId: 'my-client-id', - externalJwt: 'eyJhbGciOi...', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + externalJwtTokenProvider({ + clientId: "my-client-id", + externalJwt: "eyJhbGciOi...", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); ``` @@ -165,17 +179,25 @@ Refresh token authentication is currently available in the JavaScript SDK only. ::: ```typescript -import { authTokenInterceptor, refreshTokenProvider, OpenTDF } from '@opentdf/sdk'; +import { + authTokenInterceptor, + refreshTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; // Use a refresh token obtained from a prior OIDC login flow. // The provider automatically exchanges it for access tokens and handles rotation. const client = new OpenTDF({ - interceptors: [authTokenInterceptor(refreshTokenProvider({ - clientId: 'my-app', - refreshToken: 'refresh-token-from-login-flow', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + refreshTokenProvider({ + clientId: "my-app", + refreshToken: "refresh-token-from-login-flow", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); ``` @@ -188,16 +210,18 @@ Access token authentication is currently available in the JavaScript SDK only. G ::: ```typescript -import { authTokenInterceptor, OpenTDF } from '@opentdf/sdk'; +import { authTokenInterceptor, OpenTDF } from "@opentdf/sdk"; // Pass any () => Promise function that returns a valid access token. // Example: read from your OIDC library's user manager. const client = new OpenTDF({ - interceptors: [authTokenInterceptor(async () => { - const user = await userManager.getUser(); - return user?.access_token ?? ''; - })], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor(async () => { + const user = await userManager.getUser(); + return user?.access_token ?? ""; + }), + ], + platformUrl: "http://localhost:8080", }); ``` @@ -269,7 +293,7 @@ For advanced integrations where the built-in auth methods don't fit, you can **b -**Option A: Standard OAuth2 token source** +### Option A: Standard OAuth2 token source ```go import "golang.org/x/oauth2" @@ -283,7 +307,7 @@ client, err := sdk.New("http://localhost:8080", ) ``` -**Option B: Implement the `AccessTokenSource` interface** +### Option B: Implement the `AccessTokenSource` interface For full control, implement the `auth.AccessTokenSource` interface: @@ -317,26 +341,26 @@ SDK sdk = new SDKBuilder() Write a custom interceptor for full control over request authentication. This is useful when your app already has its own auth system (e.g., an OIDC library like `oidc-client-ts`, Auth0, or a custom token store): ```typescript -import { type Interceptor } from '@connectrpc/connect'; -import { OpenTDF } from '@opentdf/sdk'; +import { type Interceptor } from "@connectrpc/connect"; +import { OpenTDF } from "@opentdf/sdk"; // Replace this with however your app obtains tokens — // e.g., from an OIDC library, auth context, or token store. async function getMyToken(): Promise { - // Example: read from your OIDC library's user manager - // const user = await userManager.getUser(); - // return user?.access_token ?? ''; - throw new Error('Implement getMyToken() for your auth system'); + // Example: read from your OIDC library's user manager + // const user = await userManager.getUser(); + // return user?.access_token ?? ''; + throw new Error("Implement getMyToken() for your auth system"); } const myAuthInterceptor: Interceptor = (next) => async (req) => { - req.header.set('Authorization', `Bearer ${await getMyToken()}`); - return next(req); + req.header.set("Authorization", `Bearer ${await getMyToken()}`); + return next(req); }; const client = new OpenTDF({ - interceptors: [myAuthInterceptor], - platformUrl: 'http://localhost:8080', + interceptors: [myAuthInterceptor], + platformUrl: "http://localhost:8080", }); ``` @@ -347,11 +371,11 @@ const client = new OpenTDF({ [DPoP (Demonstration of Proof-of-Possession)](https://datatracker.ietf.org/doc/html/rfc9449) binds access tokens to a cryptographic key pair, preventing token theft and replay attacks. The SDKs handle DPoP automatically in most cases. -| SDK | Default behavior | Customization | -|-----|-----------------|---------------| -| **Go** | DPoP key auto-generated | `sdk.WithSessionSignerRSA(key)` to provide your own RSA key | -| **Java** | Always on (RSA, auto-generated) | `SDKBuilder.srtSigner(signer)` for custom signing | -| **JavaScript** | Off by default with interceptors | Use `authTokenDPoPInterceptor()` to enable | +| SDK | Default behavior | Customization | +| -------------- | -------------------------------- | ----------------------------------------------------------- | +| **Go** | DPoP key auto-generated | `sdk.WithSessionSignerRSA(key)` to provide your own RSA key | +| **Java** | Always on (RSA, auto-generated) | `SDKBuilder.srtSigner(signer)` for custom signing | +| **JavaScript** | Off by default with interceptors | Use `authTokenDPoPInterceptor()` to enable | @@ -387,18 +411,18 @@ SDK sdk = new SDKBuilder() DPoP is off by default with interceptors. Use `authTokenDPoPInterceptor()` to enable it: ```typescript -import { authTokenDPoPInterceptor, OpenTDF } from '@opentdf/sdk'; +import { authTokenDPoPInterceptor, OpenTDF } from "@opentdf/sdk"; // Use any TokenProvider: refreshTokenProvider(), clientCredentialsTokenProvider(), // externalJwtTokenProvider(), or a custom () => Promise function. const dpopInterceptor = authTokenDPoPInterceptor({ - tokenProvider: getAccessToken, // your token provider here + tokenProvider: getAccessToken, // your token provider here }); const client = new OpenTDF({ - interceptors: [dpopInterceptor], - dpopKeys: dpopInterceptor.dpopKeys, - platformUrl: 'http://localhost:8080', + interceptors: [dpopInterceptor], + dpopKeys: dpopInterceptor.dpopKeys, + platformUrl: "http://localhost:8080", }); ``` @@ -421,18 +445,18 @@ The legacy `AuthProvider` pattern managed token lifecycle internally: ```typescript -import { AuthProviders, OpenTDF } from '@opentdf/sdk'; +import { AuthProviders, OpenTDF } from "@opentdf/sdk"; const authProvider = await AuthProviders.clientSecretAuthProvider({ - clientId: 'my-client-id', - clientSecret: 'my-client-secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - exchange: 'client', + clientId: "my-client-id", + clientSecret: "my-client-secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + exchange: "client", }); const client = new OpenTDF({ - authProvider, - platformUrl: 'http://localhost:8080', + authProvider, + platformUrl: "http://localhost:8080", }); await client.ready; ``` diff --git a/docs/sdks/authorization.mdx b/docs/sdks/authorization.mdx index 72640fb5..c6b17eb0 100644 --- a/docs/sdks/authorization.mdx +++ b/docs/sdks/authorization.mdx @@ -3,14 +3,12 @@ sidebar_position: 7 title: Authorization --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import JsAuthNote from '../../code_samples/js_auth_note.mdx' -import SdkVersion from '@site/src/components/SdkVersion'; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JsAuthNote from "../../code_samples/js_auth_note.mdx"; +import SdkVersion from "@site/src/components/SdkVersion"; -# Authorization - -The authorization system answers two questions: *"What can this entity access?"* ([GetEntitlements](#getentitlements)) and *"Can this entity access this specific resource?"* ([GetDecision](#getdecision)). For batch checks, use [GetDecisionBulk](#getdecisionbulk). +The authorization system answers two questions: _"What can this entity access?"_ ([GetEntitlements](#getentitlements)) and _"Can this entity access this specific resource?"_ ([GetDecision](#getdecision)). For batch checks, use [GetDecisionBulk](#getdecisionbulk). ## Setup @@ -63,15 +61,23 @@ SDK sdk = SDKBuilder.newBuilder() ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, +} from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; const platform = new PlatformClient({ - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); // All JavaScript snippets below use `platform`. @@ -91,13 +97,13 @@ Every authorization call requires an `EntityIdentifier` — the entity (user, se -| Helper | Description | -|--------|-------------| -| `authorizationv2.ForEmail(email)` | Identify by email address | -| `authorizationv2.ForClientID(clientID)` | Identify by client ID (service account / NPE) | -| `authorizationv2.ForUserName(username)` | Identify by username | -| `authorizationv2.ForToken(jwt)` | Resolve entity from a JWT token | -| `authorizationv2.WithRequestToken()` | Derive entity from the request's Authorization header | +| Helper | Description | +| --------------------------------------- | ----------------------------------------------------- | +| `authorizationv2.ForEmail(email)` | Identify by email address | +| `authorizationv2.ForClientID(clientID)` | Identify by client ID (service account / NPE) | +| `authorizationv2.ForUserName(username)` | Identify by username | +| `authorizationv2.ForToken(jwt)` | Resolve entity from a JWT token | +| `authorizationv2.WithRequestToken()` | Derive entity from the request's Authorization header | ```go import authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2" @@ -107,6 +113,7 @@ req := &authorizationv2.GetDecisionRequest{ // ... } ``` +
Without helpers (manual proto construction) @@ -142,12 +149,12 @@ req := &authorizationv2.GetDecisionRequest{ -| Helper | Description | -|--------|-------------| -| `EntityIdentifiers.forEmail(email)` | Identify by email address | +| Helper | Description | +| ----------------------------------------- | --------------------------------------------- | +| `EntityIdentifiers.forEmail(email)` | Identify by email address | | `EntityIdentifiers.forClientId(clientId)` | Identify by client ID (service account / NPE) | -| `EntityIdentifiers.forUserName(username)` | Identify by username | -| `EntityIdentifiers.forToken(jwt)` | Resolve entity from a JWT token | +| `EntityIdentifiers.forUserName(username)` | Identify by username | +| `EntityIdentifiers.forToken(jwt)` | Resolve entity from a JWT token | ```java import io.opentdf.platform.sdk.EntityIdentifiers; @@ -157,6 +164,7 @@ GetDecisionRequest request = GetDecisionRequest.newBuilder() // ... .build(); ``` +
Without helpers (manual proto construction) @@ -180,22 +188,23 @@ EntityIdentifier.newBuilder() -| Helper | Description | -|--------|-------------| -| `EntityIdentifiers.forEmail(email)` | Identify by email address | -| `EntityIdentifiers.forClientId(clientId)` | Identify by client ID (service account / NPE) | -| `EntityIdentifiers.forUserName(username)` | Identify by username | -| `EntityIdentifiers.forToken(jwt)` | Resolve entity from a JWT token | -| `EntityIdentifiers.withRequestToken()` | Derive entity from the request's Authorization header | +| Helper | Description | +| ----------------------------------------- | ----------------------------------------------------- | +| `EntityIdentifiers.forEmail(email)` | Identify by email address | +| `EntityIdentifiers.forClientId(clientId)` | Identify by client ID (service account / NPE) | +| `EntityIdentifiers.forUserName(username)` | Identify by username | +| `EntityIdentifiers.forToken(jwt)` | Resolve entity from a JWT token | +| `EntityIdentifiers.withRequestToken()` | Derive entity from the request's Authorization header | ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; +import { EntityIdentifiers } from "@opentdf/sdk"; const response = await platform.v2.authorization.getDecision({ - entityIdentifier: EntityIdentifiers.forEmail('alice@example.com'), + entityIdentifier: EntityIdentifiers.forEmail("alice@example.com"), // ... }); ``` +
Without helpers (manual object construction) @@ -227,15 +236,15 @@ const response = await platform.v2.authorization.getDecision({ **Supported entity types:** -| Type | Go | Java | JavaScript | -|------|-----|------|------------| -| Email | `ForEmail(email)` | `EntityIdentifiers.forEmail(email)` | `EntityIdentifiers.forEmail(email)` | -| Client ID | `ForClientID(id)` | `EntityIdentifiers.forClientId(id)` | `EntityIdentifiers.forClientId(id)` | -| Username | `ForUserName(name)` | `EntityIdentifiers.forUserName(name)` | `EntityIdentifiers.forUserName(name)` | -| JWT Token | `ForToken(jwt)` | `EntityIdentifiers.forToken(jwt)` | `EntityIdentifiers.forToken(jwt)` | -| Request Token | `WithRequestToken()` | — | `EntityIdentifiers.withRequestToken()` | -| Claims | — | manual proto construction | manual object construction | -| Registered Resource | — | manual proto construction | manual object construction | +| Type | Go | Java | JavaScript | +| ------------------- | -------------------- | ------------------------------------- | -------------------------------------- | +| Email | `ForEmail(email)` | `EntityIdentifiers.forEmail(email)` | `EntityIdentifiers.forEmail(email)` | +| Client ID | `ForClientID(id)` | `EntityIdentifiers.forClientId(id)` | `EntityIdentifiers.forClientId(id)` | +| Username | `ForUserName(name)` | `EntityIdentifiers.forUserName(name)` | `EntityIdentifiers.forUserName(name)` | +| JWT Token | `ForToken(jwt)` | `EntityIdentifiers.forToken(jwt)` | `EntityIdentifiers.forToken(jwt)` | +| Request Token | `WithRequestToken()` | — | `EntityIdentifiers.withRequestToken()` | +| Claims | — | manual proto construction | manual object construction | +| Registered Resource | — | manual proto construction | manual object construction | - **Claims** are used by the Entity Resolution Service (ERS) for custom claim-based entity resolution. - **Registered Resource** identifies an entity by a [registered resource](/components/policy/registered_resources) value FQN stored in platform policy, where the resource acts as a single entity for authorization decisions. @@ -246,7 +255,7 @@ const response = await platform.v2.authorization.getDecision({ Returns all attribute values an entity is entitled to access. Use this for building UIs that show available data, pre-filtering content, or understanding an entity's overall access scope. -**Signature** +### Signature @@ -272,14 +281,14 @@ await platform.v2.authorization.getEntitlements({ ... }) -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `entityIdentifier` | `EntityIdentifier` | Yes | The entity to query. Use [helpers](#entityidentifier) like `ForEmail(...)` (Go) or `EntityIdentifiers.forEmail(...)` (Java/JS). | -| `withComprehensiveHierarchy` | `bool` | No | When true, returns all entitled values for attributes with hierarchy rules, propagating down from the entitled value. | +| Parameter | Type | Required | Description | +| ---------------------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `entityIdentifier` | `EntityIdentifier` | Yes | The entity to query. Use [helpers](#entityidentifier) like `ForEmail(...)` (Go) or `EntityIdentifiers.forEmail(...)` (Java/JS). | +| `withComprehensiveHierarchy` | `bool` | No | When true, returns all entitled values for attributes with hierarchy rules, propagating down from the entitled value. | -**Example** +### Example @@ -400,34 +409,34 @@ for (EntityEntitlements entitlement : resp.getEntitlementsList()) { ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; +import { EntityIdentifiers } from "@opentdf/sdk"; const response = await platform.v2.authorization.getEntitlements({ - entityIdentifier: EntityIdentifiers.forEmail('bob@OrgA.com'), + entityIdentifier: EntityIdentifiers.forEmail("bob@OrgA.com"), }); for (const entitlement of response.entitlements) { - console.log('Entitled to:', entitlement.actionsPerAttributeValueFqn); + console.log("Entitled to:", entitlement.actionsPerAttributeValueFqn); } ``` To expand hierarchy rules: ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; +import { EntityIdentifiers } from "@opentdf/sdk"; const response = await platform.v2.authorization.getEntitlements({ - entityIdentifier: EntityIdentifiers.forEmail('user@company.com'), + entityIdentifier: EntityIdentifiers.forEmail("user@company.com"), withComprehensiveHierarchy: true, }); -console.log('Scoped entitlements:', response.entitlements); +console.log("Scoped entitlements:", response.entitlements); ``` -**Returns** +### Returns A list of [EntityEntitlements](#entityentitlements) objects, each containing a map of attribute value FQNs to the actions the entity can perform on them. @@ -437,7 +446,7 @@ A list of [EntityEntitlements](#entityentitlements) objects, each containing a m Returns a permit/deny decision for a specific entity + action + resource combination. This is the enforcement point in your application. -**Signature** +### Signature @@ -463,15 +472,15 @@ await platform.v2.authorization.getDecision({ ... }) -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `entityIdentifier` | `EntityIdentifier` | Yes | The entity requesting access. Use [helpers](#entityidentifier) like `ForEmail(...)` (Go) or `EntityIdentifiers.forEmail(...)` (Java/JS). | -| `action` | `Action` | Yes | The action being performed (e.g., `decrypt`, `read`). | -| `resource` | `Resource` | Yes | The resource being accessed, identified by attribute value FQNs. | +| Parameter | Type | Required | Description | +| ------------------ | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `entityIdentifier` | `EntityIdentifier` | Yes | The entity requesting access. Use [helpers](#entityidentifier) like `ForEmail(...)` (Go) or `EntityIdentifiers.forEmail(...)` (Java/JS). | +| `action` | `Action` | Yes | The action being performed (e.g., `decrypt`, `read`). | +| `resource` | `Resource` | Yes | The resource being accessed, identified by attribute value FQNs. | -**Example** +### Example @@ -641,19 +650,19 @@ if (decision.getDecision() == Decision.DECISION_PERMIT) { ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; -import { Decision } from '@opentdf/sdk/platform/authorization/v2/authorization_pb.js'; +import { EntityIdentifiers } from "@opentdf/sdk"; +import { Decision } from "@opentdf/sdk/platform/authorization/v2/authorization_pb.js"; const response = await platform.v2.authorization.getDecision({ - entityIdentifier: EntityIdentifiers.forEmail('user@company.com'), - action: { name: 'decrypt' }, + entityIdentifier: EntityIdentifiers.forEmail("user@company.com"), + action: { name: "decrypt" }, resource: { resource: { - case: 'attributeValues', + case: "attributeValues", value: { fqns: [ - 'https://company.com/attr/clearance/value/confidential', - 'https://company.com/attr/department/value/finance', + "https://company.com/attr/clearance/value/confidential", + "https://company.com/attr/department/value/finance", ], }, }, @@ -662,19 +671,19 @@ const response = await platform.v2.authorization.getDecision({ const decision = response.decision; if (decision?.decision === Decision.PERMIT) { - console.log('Access granted'); + console.log("Access granted"); if (decision.requiredObligations.length > 0) { - console.log('Required obligations:', decision.requiredObligations); + console.log("Required obligations:", decision.requiredObligations); } } else { - console.log('Access denied'); + console.log("Access denied"); } ``` -**Returns** +### Returns A [ResourceDecision](#resourcedecision) with a [Decision](#decision) (permit/deny) and any required [obligations](/components/policy/obligations) the consuming application must enforce. @@ -684,7 +693,7 @@ A [ResourceDecision](#resourcedecision) with a [Decision](#decision) (permit/den Evaluates multiple entity + action + resource combinations in a single call. Each request can include multiple resources, and the response indicates whether all resources were permitted plus per-resource decisions. -**Signature** +### Signature @@ -710,21 +719,21 @@ await platform.v2.authorization.getDecisionBulk({ ... }) -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `decisionRequests` | `[]GetDecisionMultiResourceRequest` | Yes | Each entry contains an entity, action, and one or more resources to evaluate. | +| Parameter | Type | Required | Description | +| ------------------ | ----------------------------------- | -------- | ----------------------------------------------------------------------------- | +| `decisionRequests` | `[]GetDecisionMultiResourceRequest` | Yes | Each entry contains an entity, action, and one or more resources to evaluate. | Each `GetDecisionMultiResourceRequest` contains: -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `entityIdentifier` | `EntityIdentifier` | Yes | The entity requesting access. | -| `action` | `Action` | Yes | The action being performed. | -| `resources` | `[]Resource` | Yes | Resources to evaluate, each with an `ephemeralId` for correlation. | +| Field | Type | Required | Description | +| ------------------ | ------------------ | -------- | ------------------------------------------------------------------ | +| `entityIdentifier` | `EntityIdentifier` | Yes | The entity requesting access. | +| `action` | `Action` | Yes | The action being performed. | +| `resources` | `[]Resource` | Yes | Resources to evaluate, each with an `ephemeralId` for correlation. | -**Example** +### Example @@ -873,7 +882,7 @@ for (GetDecisionMultiResourceResponse response : resp.getDecisionResponsesList()
V1 API (Legacy) -import GetDecisionsExample from '@site/code_samples/java/get-decisions.mdx'; +import GetDecisionsExample from "@site/code_samples/java/get-decisions.mdx"; @@ -883,26 +892,28 @@ import GetDecisionsExample from '@site/code_samples/java/get-decisions.mdx'; ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; +import { EntityIdentifiers } from "@opentdf/sdk"; const response = await platform.v2.authorization.getDecisionBulk({ decisionRequests: [ { - entityIdentifier: EntityIdentifiers.forEmail('user@company.com'), - action: { name: 'decrypt' }, + entityIdentifier: EntityIdentifiers.forEmail("user@company.com"), + action: { name: "decrypt" }, resources: [ { - ephemeralId: 'resource-1', + ephemeralId: "resource-1", resource: { - case: 'attributeValues', - value: { fqns: ['https://company.com/attr/class/value/public'] }, + case: "attributeValues", + value: { fqns: ["https://company.com/attr/class/value/public"] }, }, }, { - ephemeralId: 'resource-2', + ephemeralId: "resource-2", resource: { - case: 'attributeValues', - value: { fqns: ['https://company.com/attr/class/value/confidential'] }, + case: "attributeValues", + value: { + fqns: ["https://company.com/attr/class/value/confidential"], + }, }, }, ], @@ -912,11 +923,11 @@ const response = await platform.v2.authorization.getDecisionBulk({ for (const resp of response.decisionResponses) { if (resp.allPermitted !== undefined) { - console.log('All resources permitted:', resp.allPermitted.value); + console.log("All resources permitted:", resp.allPermitted.value); } for (const resourceDecision of resp.resourceDecisions) { console.log( - `Resource ${resourceDecision.ephemeralResourceId}: ${resourceDecision.decision}` + `Resource ${resourceDecision.ephemeralResourceId}: ${resourceDecision.decision}`, ); } } @@ -925,7 +936,7 @@ for (const resp of response.decisionResponses) { -**Returns** +### Returns A list of [GetDecisionMultiResourceResponse](#getdecisionmultiresourceresponse) objects, each containing an `allPermitted` flag and per-resource [ResourceDecision](#resourcedecision) entries. @@ -937,29 +948,29 @@ A list of [GetDecisionMultiResourceResponse](#getdecisionmultiresourceresponse) The authorization result. -| Value | Description | -|-------|-------------| +| Value | Description | +| ----------------- | ------------------------------------------------------------------------------------- | | `DECISION_PERMIT` | Access is granted. Check `requiredObligations` for any controls the PEP must enforce. | -| `DECISION_DENY` | Access is denied. | +| `DECISION_DENY` | Access is denied. | ### ResourceDecision The result for a single resource in a decision response. -| Field | Type | Description | -|-------|------|-------------| -| `ephemeralResourceId` | `string` | Correlates with the `ephemeralId` set on the request's [Resource](#resource). | -| `decision` | [`Decision`](#decision) | Permit or deny. | -| `requiredObligations` | `[]string` | Obligation value FQNs the PEP must fulfill (e.g., `https://example.com/obl/drm/value/watermarking`). Empty if none required. | +| Field | Type | Description | +| --------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `ephemeralResourceId` | `string` | Correlates with the `ephemeralId` set on the request's [Resource](#resource). | +| `decision` | [`Decision`](#decision) | Permit or deny. | +| `requiredObligations` | `[]string` | Obligation value FQNs the PEP must fulfill (e.g., `https://example.com/obl/drm/value/watermarking`). Empty if none required. | ### Resource Identifies the data being accessed. A resource can be specified in two ways: -| Field | Type | Description | -|-------|------|-------------| -| `ephemeralId` | `string` | An ID you assign for correlating with the response. Used in bulk requests. | -| `attributeValues.fqns` | `[]string` | Attribute value FQNs on the resource (1–20). Use this for TDF payloads or any resource identified by attribute values. | +| Field | Type | Description | +| ---------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `ephemeralId` | `string` | An ID you assign for correlating with the response. Used in bulk requests. | +| `attributeValues.fqns` | `[]string` | Attribute value FQNs on the resource (1–20). Use this for TDF payloads or any resource identified by attribute values. | | `registeredResourceValueFqn` | `string` (URI) | A [registered resource](/components/policy/registered_resources) value FQN stored in platform policy. Alternative to `attributeValues`. | ```go @@ -989,9 +1000,9 @@ Identifies the data being accessed. A resource can be specified in two ways: Returned by [GetEntitlements](#getentitlements). One per entity, mapping attribute value FQNs to the actions that entity can perform. -| Field | Type | Description | -|-------|------|-------------| -| `ephemeralId` | `string` | Correlates with the entity in the request. | +| Field | Type | Description | +| ----------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `ephemeralId` | `string` | Correlates with the entity in the request. | | `actionsPerAttributeValueFqn` | `map` | Keys are attribute value FQNs. Values are lists of [actions](/sdks/policy#action) the entity can perform on data carrying that attribute value. | ```go @@ -1014,10 +1025,10 @@ for (const e of resp.entitlements) { Returned by [GetDecisionBulk](#getdecisionbulk). One per entity+action combination in the request. -| Field | Type | Description | -|-------|------|-------------| -| `allPermitted` | `bool` | Convenience flag — `true` if every resource in this group was permitted. | -| `resourceDecisions` | [`[]ResourceDecision`](#resourcedecision) | Per-resource results with individual decisions and obligations. | +| Field | Type | Description | +| ------------------- | ----------------------------------------- | ------------------------------------------------------------------------ | +| `allPermitted` | `bool` | Convenience flag — `true` if every resource in this group was permitted. | +| `resourceDecisions` | [`[]ResourceDecision`](#resourcedecision) | Per-resource results with individual decisions and obligations. | --- diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx index f47ed8bd..f3d2fc43 100644 --- a/docs/sdks/discovery.mdx +++ b/docs/sdks/discovery.mdx @@ -3,12 +3,10 @@ sidebar_position: 8 title: Discovery --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import JsAuthNote from '../../code_samples/js_auth_note.mdx' -import SdkVersion from '@site/src/components/SdkVersion'; - -# Discovery +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JsAuthNote from "../../code_samples/js_auth_note.mdx"; +import SdkVersion from "@site/src/components/SdkVersion"; Before encrypting data with `CreateTDF`, it helps to verify that the attributes you intend to use actually exist on the platform. Without this step, encryption succeeds but decryption fails later with a cryptic "resource not found" error because the attribute was never created. @@ -65,14 +63,25 @@ SDK sdk = SDKBuilder.newBuilder() ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; - -const platformUrl = 'http://localhost:8080'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; + +const auth = { + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], +}; + +const platformUrl = "http://localhost:8080"; // All JavaScript snippets below use `auth` and `platformUrl`. // Standalone functions like listAttributes() take these as arguments. @@ -89,7 +98,7 @@ const platformUrl = 'http://localhost:8080'; Returns all active attributes on the platform. Use this to see what's available before choosing which attributes to apply to a TDF. -**Signature** +### Signature @@ -99,6 +108,7 @@ Returns all active attributes on the platform. Use this to see what's available ```go client.ListAttributes(ctx, ...namespaceFilter) ``` + @@ -108,6 +118,7 @@ client.ListAttributes(ctx, ...namespaceFilter) sdk.listAttributes() sdk.listAttributes(namespaceFilter) ``` + @@ -116,16 +127,17 @@ sdk.listAttributes(namespaceFilter) ```typescript await listAttributes(platformUrl, auth, namespaceFilter?) ``` + -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `namespaceFilter` | `string` | No | When provided, returns only attributes in this namespace (e.g., `"opentdf.io"`). | +| Parameter | Type | Required | Description | +| ----------------- | -------- | -------- | -------------------------------------------------------------------------------- | +| `namespaceFilter` | `string` | No | When provided, returns only attributes in this namespace (e.g., `"opentdf.io"`). | -**Example** +### Example @@ -174,28 +186,28 @@ List attrs = sdk.listAttributes("opentdf.io"); ```typescript -import { listAttributes } from '@opentdf/sdk'; +import { listAttributes } from "@opentdf/sdk"; const attrs = await listAttributes(platformUrl, auth); for (const a of attrs) { - console.log(a.fqn, a.rule); - for (const v of a.values) { - console.log(' value:', v.value); - } + console.log(a.fqn, a.rule); + for (const v of a.values) { + console.log(" value:", v.value); + } } ``` To filter by namespace: ```typescript -const attrs = await listAttributes(platformUrl, auth, 'opentdf.io'); +const attrs = await listAttributes(platformUrl, auth, "opentdf.io"); ``` -**Returns** +### Returns A list of [Attribute](/sdks/policy#attribute-object) objects, each with nested [values](/sdks/policy#attribute-value-object). Paginates automatically — you always get the full list back without managing page tokens. @@ -205,7 +217,7 @@ A list of [Attribute](/sdks/policy#attribute-object) objects, each with nested [ Reports whether an attribute definition exists on the platform. Returns `true`/`false` — useful for a quick existence check before adding values or constructing an access policy. -**Signature** +### Signature @@ -215,6 +227,7 @@ Reports whether an attribute definition exists on the platform. Returns `true`/` ```go client.AttributeExists(ctx, attributeFqn) ``` + @@ -223,24 +236,26 @@ client.AttributeExists(ctx, attributeFqn) ```java sdk.attributeExists(attributeFqn) ``` + ```typescript -await attributeExists(platformUrl, auth, attributeFqn) +await attributeExists(platformUrl, auth, attributeFqn); ``` + -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `attributeFqn` | `string` | Yes | An attribute-level FQN (no `/value/` segment): `https:///attr/` | +| Parameter | Type | Required | Description | +| -------------- | -------- | -------- | ------------------------------------------------------------------------------------------ | +| `attributeFqn` | `string` | Yes | An attribute-level FQN (no `/value/` segment): `https:///attr/` | -**Example** +### Example @@ -269,18 +284,22 @@ if (!exists) { ```typescript -import { attributeExists } from '@opentdf/sdk'; +import { attributeExists } from "@opentdf/sdk"; -const exists = await attributeExists(platformUrl, auth, 'https://opentdf.io/attr/department'); +const exists = await attributeExists( + platformUrl, + auth, + "https://opentdf.io/attr/department", +); if (!exists) { - console.log('attribute does not exist — create it before use'); + console.log("attribute does not exist — create it before use"); } ``` -**Returns** +### Returns `true` if the attribute exists, `false` otherwise. @@ -290,7 +309,7 @@ if (!exists) { Reports whether a specific attribute value FQN exists on the platform. Returns `true`/`false` — useful for spot-checking one pre-registered value. -**Signature** +### Signature @@ -300,6 +319,7 @@ Reports whether a specific attribute value FQN exists on the platform. Returns ` ```go client.AttributeValueExists(ctx, valueFqn) ``` + @@ -308,24 +328,26 @@ client.AttributeValueExists(ctx, valueFqn) ```java sdk.attributeValueExists(valueFqn) ``` + ```typescript -await attributeValueExists(platformUrl, auth, valueFqn) +await attributeValueExists(platformUrl, auth, valueFqn); ``` + -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `valueFqn` | `string` | Yes | A full attribute value FQN: `https:///attr//value/` | +| Parameter | Type | Required | Description | +| ---------- | -------- | -------- | ------------------------------------------------------------------------------------- | +| `valueFqn` | `string` | Yes | A full attribute value FQN: `https:///attr//value/` | -**Example** +### Example @@ -354,18 +376,22 @@ if (!exists) { ```typescript -import { attributeValueExists } from '@opentdf/sdk'; +import { attributeValueExists } from "@opentdf/sdk"; -const exists = await attributeValueExists(platformUrl, auth, 'https://opentdf.io/attr/department/value/finance'); +const exists = await attributeValueExists( + platformUrl, + auth, + "https://opentdf.io/attr/department/value/finance", +); if (!exists) { - console.log('value does not exist on this attribute'); + console.log("value does not exist on this attribute"); } ``` -**Returns** +### Returns `true` if the value exists, `false` otherwise. @@ -375,7 +401,7 @@ if (!exists) { Checks that a list of attribute value FQNs exist on the platform **before** calling `CreateTDF`. This catches misspellings and missing attributes at the point of use rather than at decryption time. -**Signature** +### Signature @@ -385,6 +411,7 @@ Checks that a list of attribute value FQNs exist on the platform **before** call ```go client.ValidateAttributes(ctx, fqns...) ``` + @@ -393,24 +420,26 @@ client.ValidateAttributes(ctx, fqns...) ```java sdk.validateAttributes(fqns) ``` + ```typescript -await validateAttributes(platformUrl, auth, fqns) +await validateAttributes(platformUrl, auth, fqns); ``` + -**Parameters** +### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `fqns` | `list of strings` | Yes | Attribute value FQNs to validate (up to 250 per call). | +| Parameter | Type | Required | Description | +| --------- | ----------------- | -------- | ------------------------------------------------------ | +| `fqns` | `list of strings` | Yes | Attribute value FQNs to validate (up to 250 per call). | -**Example** +### Example @@ -461,20 +490,20 @@ sdk.createTDF(inputStream, outputStream, config); ```typescript -import { validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; +import { validateAttributes, AttributeNotFoundError } from "@opentdf/sdk"; const fqns = [ - 'https://opentdf.io/attr/department/value/marketing', - 'https://opentdf.io/attr/clearance/value/executive', + "https://opentdf.io/attr/department/value/marketing", + "https://opentdf.io/attr/clearance/value/executive", ]; try { - await validateAttributes(platformUrl, auth, fqns); + await validateAttributes(platformUrl, auth, fqns); } catch (e) { - if (e instanceof AttributeNotFoundError) { - console.error('attribute validation failed:', e.message); - // e.message names the specific FQNs that are missing - } + if (e instanceof AttributeNotFoundError) { + console.error("attribute validation failed:", e.message); + // e.message names the specific FQNs that are missing + } } // Safe to encrypt — all attributes confirmed present @@ -483,7 +512,7 @@ try { -**Returns** +### Returns No return value on success. Throws/returns an error naming the specific FQNs that are missing. @@ -499,7 +528,7 @@ Accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, Returns the attribute value FQNs assigned to a specific entity (person or non-person). Use this to inspect what a user or service account has been granted — for example, to understand why someone can or cannot decrypt a TDF. -**Signature** +### Signature @@ -509,6 +538,7 @@ Returns the attribute value FQNs assigned to a specific entity (person or non-pe ```go client.GetEntityAttributes(ctx, entity) ``` + @@ -517,6 +547,7 @@ client.GetEntityAttributes(ctx, entity) ```java sdk.getEntityAttributes(entity) ``` + @@ -525,22 +556,23 @@ sdk.getEntityAttributes(entity) ```typescript await platform.v2.authorization.getEntitlements({ ... }) ``` + The JS SDK does not expose a standalone `getEntityAttributes` function. Use `getEntitlements` via `PlatformClient` in a Node.js server environment. -**Parameters** +### Parameters **Go/Java:** -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `entity` | `Entity` | Yes | The entity to look up. Supports email, username, client ID, and UUID. | +| Parameter | Type | Required | Description | +| --------- | -------- | -------- | --------------------------------------------------------------------- | +| `entity` | `Entity` | Yes | The entity to look up. Supports email, username, client ID, and UUID. | **JavaScript** uses `getEntitlements` instead (see [EntityIdentifier](/sdks/authorization#entityidentifier) for the request shape). -**Example** +### Example @@ -614,27 +646,30 @@ Entity.newBuilder().setId("e1").setUuid("550e8400-e29b-41d4-a716-446655440000"). ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; +import { EntityIdentifiers } from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; const platform = new PlatformClient({ ...auth, platformUrl }); const resp = await platform.v2.authorization.getEntitlements({ - entityIdentifier: EntityIdentifiers.forEmail('alice@example.com'), + entityIdentifier: EntityIdentifiers.forEmail("alice@example.com"), }); if (resp.entitlements.length > 0) { - console.log("alice's entitlements:", resp.entitlements[0].actionsPerAttributeValueFqn); + console.log( + "alice's entitlements:", + resp.entitlements[0].actionsPerAttributeValueFqn, + ); } ``` Other supported [entity identifier helpers](/sdks/authorization#entityidentifier): ```typescript -import { EntityIdentifiers } from '@opentdf/sdk'; +import { EntityIdentifiers } from "@opentdf/sdk"; -EntityIdentifiers.forUserName('alice') // By username -EntityIdentifiers.forClientId('my-service') // By client ID (NPE / service account) +EntityIdentifiers.forUserName("alice"); // By username +EntityIdentifiers.forClientId("my-service"); // By client ID (NPE / service account) ``` :::note @@ -644,7 +679,7 @@ The JavaScript SDK does not currently provide a `forUuid` helper. To use UUID-ba -**Returns** +### Returns **Go/Java:** A list of [attribute value](/sdks/policy#attribute-value-object) FQN strings that the entity is entitled to access. **JavaScript:** An [EntityEntitlements](/sdks/authorization#entityentitlements) response from `getEntitlements`, containing a map of FQNs to permitted actions. @@ -733,39 +768,49 @@ System.out.println("data encrypted successfully"); ```typescript -import { listAttributes, attributeExists, validateAttributes, AttributeNotFoundError, OpenTDF } from '@opentdf/sdk'; +import { + listAttributes, + attributeExists, + validateAttributes, + AttributeNotFoundError, + OpenTDF, +} from "@opentdf/sdk"; // 1. See what's available on the platform const attrs = await listAttributes(platformUrl, auth); console.log(`platform has ${attrs.length} active attributes`); // 2. Check a specific attribute exists before using it -const exists = await attributeExists(platformUrl, auth, 'https://opentdf.io/attr/department'); +const exists = await attributeExists( + platformUrl, + auth, + "https://opentdf.io/attr/department", +); if (!exists) { - console.error('attribute missing — create it first'); - process.exit(1); + console.error("attribute missing — create it first"); + process.exit(1); } // 3. Validate the specific values before encrypting -const required = ['https://opentdf.io/attr/department/value/marketing']; +const required = ["https://opentdf.io/attr/department/value/marketing"]; try { - await validateAttributes(platformUrl, auth, required); + await validateAttributes(platformUrl, auth, required); } catch (e) { - if (e instanceof AttributeNotFoundError) { - console.error('required attributes missing:', e.message); - process.exit(1); - } - throw e; + if (e instanceof AttributeNotFoundError) { + console.error("required attributes missing:", e.message); + process.exit(1); + } + throw e; } // 4. Encrypt with confidence const client = new OpenTDF({ ...auth, platformUrl }); const ciphertext = await client.createTDF({ - source: { type: 'stream', location: dataStream }, - attributes: required, - defaultKASEndpoint: platformUrl, + source: { type: "stream", location: dataStream }, + attributes: required, + defaultKASEndpoint: platformUrl, }); -console.log('data encrypted successfully'); +console.log("data encrypted successfully"); ``` diff --git a/docs/sdks/index.mdx b/docs/sdks/index.mdx index 5986ea5a..2bfb7a9d 100644 --- a/docs/sdks/index.mdx +++ b/docs/sdks/index.mdx @@ -3,17 +3,15 @@ title: SDK sidebar_label: SDK --- -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import CodeBlock from '@theme/CodeBlock' -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import CodeBlock from "@theme/CodeBlock"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; :::tip Get Started with the SDK Quickstart Ready to start building? Check out our **[SDK Quickstart Guide](/sdks/quickstart)** for step-by-step examples of encrypting and decrypting data with OpenTDF! ::: -# SDK - The OpenTDF platform provides Software Development Kits in the Go, Java, and JavaScript languages. The SDKs include guides and working examples for managing policy, creating Trusted Data Format (TDF) protected objects, and making authorization decisions within an application. Please refer to the [SDK Feature Matrix](../appendix/matrix.mdx#sdk) in the Appendix for the supported features in each SDK. @@ -37,10 +35,12 @@ go get github.com/opentdf/platform/sdk@latest -{` + {` io.opentdf.platform sdk-pom - `}{useDocusaurusContext().siteConfig.customFields.javaSdkVersion}{` + `} + {useDocusaurusContext().siteConfig.customFields.javaSdkVersion} + {` `} diff --git a/docs/sdks/obligations.mdx b/docs/sdks/obligations.mdx index 33df801d..b50fa78f 100644 --- a/docs/sdks/obligations.mdx +++ b/docs/sdks/obligations.mdx @@ -3,11 +3,9 @@ sidebar_position: 6 title: Obligations --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import SdkVersion from '@site/src/components/SdkVersion'; - -# Obligations +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import SdkVersion from "@site/src/components/SdkVersion"; An **obligation** is a PDP-to-PEP directive that accompanies an access decision: "permit, provided these controls are enforced." Obligations are scoped to a namespace and can carry multiple values and triggers. See [Obligations](/components/policy/obligations) for the policy concept. @@ -43,15 +41,23 @@ if err != nil { ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, +} from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; const platform = new PlatformClient({ - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); // All JavaScript snippets below use `platform`. @@ -65,10 +71,9 @@ const platform = new PlatformClient({ ## Obligation Definitions -
-List Obligations +### List Obligations -**Signature** +#### Signature @@ -87,16 +92,16 @@ await platform.v1.obligation.listObligations({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `namespaceId` | `string` (UUID) | No | Filter to obligations in this namespace. | -| `namespaceFqn` | `string` (URI) | No | Filter by namespace FQN. Alternative to `namespaceId`. | +| Parameter | Type | Required | Description | +| -------------- | --------------- | -------- | ------------------------------------------------------ | +| `namespaceId` | `string` (UUID) | No | Filter to obligations in this namespace. | +| `namespaceFqn` | `string` (URI) | No | Filter by namespace FQN. Alternative to `namespaceId`. | Without a filter, returns all obligations across all namespaces. -**Example** +#### Example @@ -140,23 +145,20 @@ To filter by namespace: ```typescript const resp = await platform.v1.obligation.listObligations({ - namespaceFqn: 'https://example.com', + namespaceFqn: "https://example.com", }); ``` -**Returns** +#### Returns A list of [Obligation objects](#obligation-object). -
- -
-Get an Obligation +### Get an Obligation -**Signature** +#### Signature @@ -175,16 +177,16 @@ await platform.v1.obligation.getObligation({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The obligation UUID. | -| `fqn` | `string` | The obligation FQN (e.g., `https://example.com/obl/drm`). | +| Parameter | Type | Description | +| --------- | --------------- | --------------------------------------------------------- | +| `id` | `string` (UUID) | The obligation UUID. | +| `fqn` | `string` | The obligation FQN (e.g., `https://example.com/obl/drm`). | -**Example** +#### Example @@ -208,7 +210,7 @@ log.Printf("Obligation ID: %s\n", resp.GetObligation().GetId()) ```typescript const resp = await platform.v1.obligation.getObligation({ - fqn: 'https://example.com/obl/drm', + fqn: "https://example.com/obl/drm", }); console.log(`Obligation ID: ${resp.obligation?.id}`); @@ -217,18 +219,15 @@ console.log(`Obligation ID: ${resp.obligation?.id}`); -**Returns** +#### Returns A single [Obligation object](#obligation-object). -
- -
-Get Obligations by FQNs +### Get Obligations by FQNs Batch-fetch multiple obligations by FQN in a single request. -**Signature** +#### Signature @@ -247,13 +246,13 @@ await platform.v1.obligation.getObligationsByFQNs({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `fqns` | `[]string` | Yes | Obligation FQNs to look up. | +| Parameter | Type | Required | Description | +| --------- | ---------- | -------- | --------------------------- | +| `fqns` | `[]string` | Yes | Obligation FQNs to look up. | -**Example** +#### Example @@ -282,10 +281,7 @@ for fqn, obl := range resp.GetFqnObligationMap() { ```typescript const resp = await platform.v1.obligation.getObligationsByFQNs({ - fqns: [ - 'https://example.com/obl/drm', - 'https://example.com/obl/audit', - ], + fqns: ["https://example.com/obl/drm", "https://example.com/obl/audit"], }); for (const [fqn, obl] of Object.entries(resp.fqnObligationMap)) { @@ -296,16 +292,13 @@ for (const [fqn, obl] of Object.entries(resp.fqnObligationMap)) { -**Returns** +#### Returns A map of FQN to [Obligation object](#obligation-object). -
- -
-Create an Obligation +### Create an Obligation -**Signature** +#### Signature @@ -324,16 +317,16 @@ await platform.v1.obligation.createObligation({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `namespaceId` | `string` (UUID) | Yes* | The parent namespace. Required unless `namespaceFqn` is provided. | -| `namespaceFqn` | `string` (URI) | Yes* | The parent namespace FQN. Alternative to `namespaceId`. | -| `name` | `string` | Yes | Obligation name (e.g., `drm`, `audit`). | -| `values` | `[]string` | No | Initial values to create with the obligation. Can also be added later via [Create Obligation Value](#create-obligation-value). | +| Parameter | Type | Required | Description | +| -------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `namespaceId` | `string` (UUID) | Yes\* | The parent namespace. Required unless `namespaceFqn` is provided. | +| `namespaceFqn` | `string` (URI) | Yes\* | The parent namespace FQN. Alternative to `namespaceId`. | +| `name` | `string` | Yes | Obligation name (e.g., `drm`, `audit`). | +| `values` | `[]string` | No | Initial values to create with the obligation. Can also be added later via [Create an Obligation Value](#create-an-obligation-value). | -**Example** +#### Example @@ -360,27 +353,26 @@ log.Printf("Created obligation: %s FQN: %s\n", ```typescript const resp = await platform.v1.obligation.createObligation({ - namespaceFqn: 'https://example.com', - name: 'drm', - values: ['watermarking', 'no-download'], + namespaceFqn: "https://example.com", + name: "drm", + values: ["watermarking", "no-download"], }); -console.log(`Created obligation: ${resp.obligation?.id} FQN: ${resp.obligation?.fqn}`); +console.log( + `Created obligation: ${resp.obligation?.id} FQN: ${resp.obligation?.fqn}`, +); ``` -**Returns** +#### Returns The created [Obligation object](#obligation-object). The resulting FQN follows the convention `/obl/` (e.g., `https://example.com/obl/drm`). -
- -
-Update an Obligation +### Update an Obligation -**Signature** +#### Signature @@ -399,14 +391,14 @@ await platform.v1.obligation.updateObligation({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The obligation ID to update. | -| `name` | `string` | No | New name. Only set fields are updated. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | -------------------------------------- | +| `id` | `string` (UUID) | Yes | The obligation ID to update. | +| `name` | `string` | No | New name. Only set fields are updated. | -**Example** +#### Example @@ -431,8 +423,8 @@ log.Printf("Updated obligation FQN: %s\n", resp.GetObligation().GetFqn()) ```typescript const resp = await platform.v1.obligation.updateObligation({ - id: '3f4a7c12-...', - name: 'digital-rights', + id: "3f4a7c12-...", + name: "digital-rights", }); console.log(`Updated obligation FQN: ${resp.obligation?.fqn}`); @@ -441,16 +433,13 @@ console.log(`Updated obligation FQN: ${resp.obligation?.fqn}`); -**Returns** +#### Returns The updated [Obligation object](#obligation-object). -
+### Delete an Obligation -
-Delete an Obligation - -**Signature** +#### Signature @@ -469,16 +458,16 @@ await platform.v1.obligation.deleteObligation({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The obligation UUID. | -| `fqn` | `string` | The obligation FQN. | +| Parameter | Type | Description | +| --------- | --------------- | -------------------- | +| `id` | `string` (UUID) | The obligation UUID. | +| `fqn` | `string` | The obligation FQN. | -**Example** +#### Example @@ -502,7 +491,7 @@ log.Printf("Deleted obligation: %s\n", resp.GetObligation().GetId()) ```typescript const resp = await platform.v1.obligation.deleteObligation({ - fqn: 'https://example.com/obl/drm', + fqn: "https://example.com/obl/drm", }); console.log(`Deleted obligation: ${resp.obligation?.id}`); @@ -511,22 +500,20 @@ console.log(`Deleted obligation: ${resp.obligation?.id}`); -**Returns** +#### Returns The deleted [Obligation object](#obligation-object). -
- ### Obligation Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `name` | `string` | The obligation name (e.g., `drm`, `audit`). | -| `fqn` | `string` | Fully qualified name, e.g., `https://example.com/obl/drm`. | -| `values` | [`[]ObligationValue`](#obligation-value-object) | The values defined under this obligation. | -| `namespace` | [`Namespace`](/sdks/policy#namespace-object) | The parent namespace. | -| `metadata` | [`Metadata`](/sdks/policy#metadata) | Optional [labels](/sdks/policy#metadata). | +| Field | Type | Description | +| ----------- | ----------------------------------------------- | ---------------------------------------------------------- | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `name` | `string` | The obligation name (e.g., `drm`, `audit`). | +| `fqn` | `string` | Fully qualified name, e.g., `https://example.com/obl/drm`. | +| `values` | [`[]ObligationValue`](#obligation-value-object) | The values defined under this obligation. | +| `namespace` | [`Namespace`](/sdks/policy#namespace-object) | The parent namespace. | +| `metadata` | [`Metadata`](/sdks/policy#metadata) | Optional [labels](/sdks/policy#metadata). | --- @@ -534,10 +521,9 @@ The deleted [Obligation object](#obligation-object). Each obligation can carry one or more values. Value FQNs follow the convention `/obl//value/` (e.g., `https://example.com/obl/drm/value/watermarking`). -
-Get an Obligation Value +### Get an Obligation Value -**Signature** +#### Signature @@ -556,16 +542,16 @@ await platform.v1.obligation.getObligationValue({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The value UUID. | -| `fqn` | `string` | The value FQN (e.g., `https://example.com/obl/drm/value/watermarking`). | +| Parameter | Type | Description | +| --------- | --------------- | ----------------------------------------------------------------------- | +| `id` | `string` (UUID) | The value UUID. | +| `fqn` | `string` | The value FQN (e.g., `https://example.com/obl/drm/value/watermarking`). | -**Example** +#### Example @@ -589,7 +575,7 @@ log.Printf("Value ID: %s\n", resp.GetValue().GetId()) ```typescript const resp = await platform.v1.obligation.getObligationValue({ - fqn: 'https://example.com/obl/drm/value/watermarking', + fqn: "https://example.com/obl/drm/value/watermarking", }); console.log(`Value ID: ${resp.value?.id}`); @@ -598,16 +584,13 @@ console.log(`Value ID: ${resp.value?.id}`); -**Returns** +#### Returns A single [Obligation Value object](#obligation-value-object). -
+### Get Obligation Values by FQNs -
-Get Obligation Values by FQNs - -**Signature** +#### Signature @@ -626,13 +609,13 @@ await platform.v1.obligation.getObligationValuesByFQNs({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `fqns` | `[]string` | Yes | Obligation value FQNs to look up. | +| Parameter | Type | Required | Description | +| --------- | ---------- | -------- | --------------------------------- | +| `fqns` | `[]string` | Yes | Obligation value FQNs to look up. | -**Example** +#### Example @@ -662,8 +645,8 @@ for fqn, val := range resp.GetFqnValueMap() { ```typescript const resp = await platform.v1.obligation.getObligationValuesByFQNs({ fqns: [ - 'https://example.com/obl/drm/value/watermarking', - 'https://example.com/obl/drm/value/no-download', + "https://example.com/obl/drm/value/watermarking", + "https://example.com/obl/drm/value/no-download", ], }); @@ -675,16 +658,13 @@ for (const [fqn, val] of Object.entries(resp.fqnValueMap)) { -**Returns** +#### Returns A map of FQN to [Obligation Value object](#obligation-value-object). -
- -
-Create an Obligation Value +### Create an Obligation Value -**Signature** +#### Signature @@ -703,15 +683,15 @@ await platform.v1.obligation.createObligationValue({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `obligationId` | `string` (UUID) | Yes* | The parent obligation. Required unless `obligationFqn` is provided. | -| `obligationFqn` | `string` | Yes* | The parent obligation FQN. Alternative to `obligationId`. | -| `value` | `string` | Yes | The value string (e.g., `watermarking`, `encrypt-at-rest`). | +| Parameter | Type | Required | Description | +| --------------- | --------------- | -------- | ------------------------------------------------------------------- | +| `obligationId` | `string` (UUID) | Yes\* | The parent obligation. Required unless `obligationFqn` is provided. | +| `obligationFqn` | `string` | Yes\* | The parent obligation FQN. Alternative to `obligationId`. | +| `value` | `string` | Yes | The value string (e.g., `watermarking`, `encrypt-at-rest`). | -**Example** +#### Example @@ -737,8 +717,8 @@ log.Printf("Created value: %s FQN: %s\n", ```typescript const resp = await platform.v1.obligation.createObligationValue({ - obligationFqn: 'https://example.com/obl/drm', - value: 'encrypt-at-rest', + obligationFqn: "https://example.com/obl/drm", + value: "encrypt-at-rest", }); console.log(`Created value: ${resp.value?.id} FQN: ${resp.value?.fqn}`); @@ -747,16 +727,13 @@ console.log(`Created value: ${resp.value?.id} FQN: ${resp.value?.fqn}`); -**Returns** +#### Returns The created [Obligation Value object](#obligation-value-object). -
- -
-Update an Obligation Value +### Update an Obligation Value -**Signature** +#### Signature @@ -775,14 +752,14 @@ await platform.v1.obligation.updateObligationValue({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The value ID to update. | -| `value` | `string` | No | New value string. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ----------------------- | +| `id` | `string` (UUID) | Yes | The value ID to update. | +| `value` | `string` | No | New value string. | -**Example** +#### Example @@ -807,8 +784,8 @@ log.Printf("Updated value FQN: %s\n", resp.GetValue().GetFqn()) ```typescript const resp = await platform.v1.obligation.updateObligationValue({ - id: '9a1b2c3d-...', - value: 'encrypt-at-rest-v2', + id: "9a1b2c3d-...", + value: "encrypt-at-rest-v2", }); console.log(`Updated value FQN: ${resp.value?.fqn}`); @@ -817,16 +794,13 @@ console.log(`Updated value FQN: ${resp.value?.fqn}`); -**Returns** +#### Returns The updated [Obligation Value object](#obligation-value-object). -
+### Delete an Obligation Value -
-Delete an Obligation Value - -**Signature** +#### Signature @@ -845,16 +819,16 @@ await platform.v1.obligation.deleteObligationValue({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The value UUID. | -| `fqn` | `string` | The value FQN. | +| Parameter | Type | Description | +| --------- | --------------- | --------------- | +| `id` | `string` (UUID) | The value UUID. | +| `fqn` | `string` | The value FQN. | -**Example** +#### Example @@ -878,7 +852,7 @@ log.Printf("Deleted value: %s\n", resp.GetValue().GetId()) ```typescript const resp = await platform.v1.obligation.deleteObligationValue({ - fqn: 'https://example.com/obl/drm/value/watermarking', + fqn: "https://example.com/obl/drm/value/watermarking", }); console.log(`Deleted value: ${resp.value?.id}`); @@ -887,22 +861,20 @@ console.log(`Deleted value: ${resp.value?.id}`); -**Returns** +#### Returns The deleted [Obligation Value object](#obligation-value-object). -
- ### Obligation Value Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `value` | `string` | The value string (e.g., `watermarking`, `no-download`). | -| `fqn` | `string` | Fully qualified name, e.g., `https://example.com/obl/drm/value/watermarking`. | -| `obligation` | [`Obligation`](#obligation-object) | The parent obligation. | -| `triggers` | [`[]ObligationTrigger`](#obligation-trigger-object) | Triggers associated with this value. | -| `metadata` | [`Metadata`](/sdks/policy#metadata) | Optional [labels](/sdks/policy#metadata). | +| Field | Type | Description | +| ------------ | --------------------------------------------------- | ----------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `value` | `string` | The value string (e.g., `watermarking`, `no-download`). | +| `fqn` | `string` | Fully qualified name, e.g., `https://example.com/obl/drm/value/watermarking`. | +| `obligation` | [`Obligation`](#obligation-object) | The parent obligation. | +| `triggers` | [`[]ObligationTrigger`](#obligation-trigger-object) | Triggers associated with this value. | +| `metadata` | [`Metadata`](/sdks/policy#metadata) | Optional [labels](/sdks/policy#metadata). | --- @@ -910,10 +882,9 @@ The deleted [Obligation Value object](#obligation-value-object). A trigger links an obligation value to a specific [action](/sdks/policy#action) + [attribute value](/sdks/policy#attribute-value-object) combination. When that action is performed on data carrying that attribute value, the obligation fires. -
-Add an Obligation Trigger +### Add an Obligation Trigger -**Signature** +#### Signature @@ -932,15 +903,15 @@ await platform.v1.obligation.addObligationTrigger({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `obligationValue` | `IdFqnIdentifier` | Yes | The obligation value to trigger. Provide `id` or `fqn`. | -| `action` | `IdNameIdentifier` | Yes | The action that fires this trigger. Provide `id` or `name` (e.g., `read`). | -| `attributeValue` | `IdFqnIdentifier` | Yes | The attribute value that must be present on the data. Provide `id` or `fqn`. | +| Parameter | Type | Required | Description | +| ----------------- | ------------------ | -------- | ---------------------------------------------------------------------------- | +| `obligationValue` | `IdFqnIdentifier` | Yes | The obligation value to trigger. Provide `id` or `fqn`. | +| `action` | `IdNameIdentifier` | Yes | The action that fires this trigger. Provide `id` or `name` (e.g., `read`). | +| `attributeValue` | `IdFqnIdentifier` | Yes | The attribute value that must be present on the data. Provide `id` or `fqn`. | -**Example** +#### Example @@ -975,9 +946,11 @@ log.Printf("Added trigger: %s\n", resp.GetTrigger().GetId()) ```typescript const resp = await platform.v1.obligation.addObligationTrigger({ - obligationValue: { fqn: 'https://example.com/obl/drm/value/watermarking' }, - action: { name: 'read' }, - attributeValue: { fqn: 'https://example.com/attr/classification/value/secret' }, + obligationValue: { fqn: "https://example.com/obl/drm/value/watermarking" }, + action: { name: "read" }, + attributeValue: { + fqn: "https://example.com/attr/classification/value/secret", + }, }); console.log(`Added trigger: ${resp.trigger?.id}`); @@ -986,16 +959,13 @@ console.log(`Added trigger: ${resp.trigger?.id}`); -**Returns** +#### Returns The created [Obligation Trigger object](#obligation-trigger-object). -
+### Get an Obligation Trigger -
-Get an Obligation Trigger - -**Signature** +#### Signature @@ -1005,16 +975,17 @@ The created [Obligation Trigger object](#obligation-trigger-object). ```go client.Obligations.GetObligationTrigger(ctx, &obligations.GetObligationTriggerRequest{...}) ``` + -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The trigger UUID. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ----------------- | +| `id` | `string` (UUID) | Yes | The trigger UUID. | -**Example** +#### Example @@ -1036,16 +1007,13 @@ log.Printf("Trigger ID: %s\n", resp.GetTrigger().GetId()) -**Returns** +#### Returns A single [Obligation Trigger object](#obligation-trigger-object). -
- -
-List Obligation Triggers +### List Obligation Triggers -**Signature** +#### Signature @@ -1064,13 +1032,13 @@ await platform.v1.obligation.listObligationTriggers({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `namespaceFqn` | `string` (URI) | No | Filter to triggers in this namespace. | +| Parameter | Type | Required | Description | +| -------------- | -------------- | -------- | ------------------------------------- | +| `namespaceFqn` | `string` (URI) | No | Filter to triggers in this namespace. | -**Example** +#### Example @@ -1096,7 +1064,7 @@ for _, trigger := range resp.GetTriggers() { ```typescript const resp = await platform.v1.obligation.listObligationTriggers({ - namespaceFqn: 'https://example.com', + namespaceFqn: "https://example.com", }); for (const trigger of resp.triggers) { @@ -1107,16 +1075,13 @@ for (const trigger of resp.triggers) { -**Returns** +#### Returns A list of [Obligation Trigger objects](#obligation-trigger-object). -
+### Remove an Obligation Trigger -
-Remove an Obligation Trigger - -**Signature** +#### Signature @@ -1135,13 +1100,13 @@ await platform.v1.obligation.removeObligationTrigger({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The trigger ID to remove. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ------------------------- | +| `id` | `string` (UUID) | Yes | The trigger ID to remove. | -**Example** +#### Example @@ -1165,7 +1130,7 @@ log.Printf("Removed trigger: %s\n", resp.GetTrigger().GetId()) ```typescript const resp = await platform.v1.obligation.removeObligationTrigger({ - id: '7e8f9a0b-...', + id: "7e8f9a0b-...", }); console.log(`Removed trigger: ${resp.trigger?.id}`); @@ -1174,18 +1139,16 @@ console.log(`Removed trigger: ${resp.trigger?.id}`); -**Returns** +#### Returns The removed [Obligation Trigger object](#obligation-trigger-object). -
- ### Obligation Trigger Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `obligationValue` | [`ObligationValue`](#obligation-value-object) | The obligation value this trigger fires for. | -| `action` | [`Action`](/sdks/policy#action) | The action that activates this trigger (e.g., `read`). | -| `attributeValue` | [`AttributeValue`](/sdks/policy#attribute-value-object) | The attribute value that must be present on the data. | -| `metadata` | [`Metadata`](/sdks/policy#metadata) | Optional [labels](/sdks/policy#metadata). | +| Field | Type | Description | +| ----------------- | ------------------------------------------------------- | ------------------------------------------------------ | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `obligationValue` | [`ObligationValue`](#obligation-value-object) | The obligation value this trigger fires for. | +| `action` | [`Action`](/sdks/policy#action) | The action that activates this trigger (e.g., `read`). | +| `attributeValue` | [`AttributeValue`](/sdks/policy#attribute-value-object) | The attribute value that must be present on the data. | +| `metadata` | [`Metadata`](/sdks/policy#metadata) | Optional [labels](/sdks/policy#metadata). | diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx index 6995b5cc..c6323eed 100644 --- a/docs/sdks/platform-client.mdx +++ b/docs/sdks/platform-client.mdx @@ -3,21 +3,19 @@ sidebar_position: 1 title: Overview --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import JsAuthNote from '../../code_samples/js_auth_note.mdx' - -# Overview +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JsAuthNote from "../../code_samples/js_auth_note.mdx"; Some SDK functionality — including policy management and authorization decisions — is provided through a **platform service client** rather than through the core SDK. This page explains the difference and when you'll use each. ## Core SDK vs. Platform Service Client -| | Core SDK | Platform Service Client | -|---|---|---| -| **What it does** | Wraps and unwraps TDF-protected data | Manages platform resources (policy, keys, authorization) | -| **Calls platform** | Some operations (e.g. key unwrap, service discovery) | Always — all methods are remote gRPC calls | -| **Examples** | `CreateTdf`, `LoadTdf` | `GetNamespace`, `GetDecision`, `ListKeyAccessServers` | +| | Core SDK | Platform Service Client | +| ------------------ | ---------------------------------------------------- | -------------------------------------------------------- | +| **What it does** | Wraps and unwraps TDF-protected data | Manages platform resources (policy, keys, authorization) | +| **Calls platform** | Some operations (e.g. key unwrap, service discovery) | Always — all methods are remote gRPC calls | +| **Examples** | `CreateTdf`, `LoadTdf` | `GetNamespace`, `GetDecision`, `ListKeyAccessServers` | This is the same pattern used by cloud provider SDKs — you instantiate a typed client once (analogous to `new S3Client()` in AWS), then call methods on it to manage remote resources. @@ -72,19 +70,30 @@ SDK sdk = SDKBuilder.newBuilder() ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; const auth = { - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'client-id', - clientSecret: 'client-secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "client-id", + clientSecret: "client-secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], }; -const client = new OpenTDF({ ...auth, platformUrl: 'http://localhost:8080' }); -const platform = new PlatformClient({ ...auth, platformUrl: 'http://localhost:8080' }); +const client = new OpenTDF({ ...auth, platformUrl: "http://localhost:8080" }); +const platform = new PlatformClient({ + ...auth, + platformUrl: "http://localhost:8080", +}); ``` @@ -123,10 +132,10 @@ v.getId(); // "a1b2c3d4-..." ```typescript -const resp = await platform.v1.attributes.getAttributeValue({ fqn: '...' }); -resp.value?.value; // "confidential" -resp.value?.fqn; // "https://example.com/attr/classification/value/confidential" -resp.value?.id; // "a1b2c3d4-..." +const resp = await platform.v1.attributes.getAttributeValue({ fqn: "..." }); +resp.value?.value; // "confidential" +resp.value?.fqn; // "https://example.com/attr/classification/value/confidential" +resp.value?.id; // "a1b2c3d4-..." ``` @@ -161,10 +170,12 @@ for (var v : resp.getValuesList()) { ```typescript -const resp = await platform.v1.attributes.listAttributeValues({ attributeId: '...' }); +const resp = await platform.v1.attributes.listAttributeValues({ + attributeId: "...", +}); for (const v of resp.values) { - v.value; // "confidential" - v.fqn; // "https://example.com/attr/classification/value/confidential" + v.value; // "confidential" + v.fqn; // "https://example.com/attr/classification/value/confidential" } ``` @@ -241,4 +252,3 @@ for (const e of resp.entitlements) { This pattern applies to all platform objects. Each SDK page includes type reference tables listing the available fields. - diff --git a/docs/sdks/policy.mdx b/docs/sdks/policy.mdx index a63b0c1b..d4aac557 100644 --- a/docs/sdks/policy.mdx +++ b/docs/sdks/policy.mdx @@ -3,18 +3,16 @@ sidebar_position: 5 title: Policy --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CreateNamespace from '../../code_samples/policy_code/create_namespace.mdx' -import ListNamespaces from '../../code_samples/policy_code/list_namespaces.mdx' -import CreateAttribute from '../../code_samples/policy_code/create_attribute.mdx' -import ListAttributes from '../../code_samples/policy_code/list_attributes.mdx' -import CreateConditionSet from '../../code_samples/policy_code/create_subject_condition_set.mdx' -import CreateSubjectMapping from '../../code_samples/policy_code/create_subject_mapping.mdx' -import ListSubjectMapping from '../../code_samples/policy_code/list_subject_mapping.mdx' -import JsAuthNote from '../../code_samples/js_auth_note.mdx' - -# Managing Policy +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import CreateNamespace from "../../code_samples/policy_code/create_namespace.mdx"; +import ListNamespaces from "../../code_samples/policy_code/list_namespaces.mdx"; +import CreateAttribute from "../../code_samples/policy_code/create_attribute.mdx"; +import ListAttributes from "../../code_samples/policy_code/list_attributes.mdx"; +import CreateConditionSet from "../../code_samples/policy_code/create_subject_condition_set.mdx"; +import CreateSubjectMapping from "../../code_samples/policy_code/create_subject_mapping.mdx"; +import ListSubjectMapping from "../../code_samples/policy_code/list_subject_mapping.mdx"; +import JsAuthNote from "../../code_samples/js_auth_note.mdx"; Policy is the set of rules that govern who can access data and under what conditions. It is made up of [**namespaces**](#namespaces), [**attributes**](#attributes), [**subject mappings**](#subject-mappings), [**subject condition sets**](#subject-condition-sets), and [**obligations**](/sdks/obligations). See [Policy](/components/policy) for the concept overview. The SDK provides CRUD access to these policy rules through remote gRPC calls powered by the [platform service client](/sdks/platform-client). @@ -68,15 +66,23 @@ SDK sdk = SDKBuilder.newBuilder() ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, +} from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; const platform = new PlatformClient({ - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); // All JavaScript snippets below use `platform`. @@ -100,17 +106,17 @@ All policy objects support optional metadata. When creating or updating a resour } ``` -`labels` is the only supported field. Other fields are silently ignored. When updating, use `metadataUpdateBehavior` to control whether labels are replaced or merged (see [Update a Namespace](#update-namespace) for an example). +`labels` is the only supported field. Other fields are silently ignored. When updating, use `metadataUpdateBehavior` to control whether labels are replaced or merged (see [Update a Namespace](#update-a-namespace) for an example). ### Pagination All `List` operations return a `pagination` object alongside the results: -| Field | Type | Description | -|-------|------|-------------| -| `currentOffset` | `int` | The offset that was requested. | -| `nextOffset` | `int` | The offset to use for the next page. Empty when no more results remain. | -| `total` | `int` | Total count of results across all pages. | +| Field | Type | Description | +| --------------- | ----- | ----------------------------------------------------------------------- | +| `currentOffset` | `int` | The offset that was requested. | +| `nextOffset` | `int` | The offset to use for the next page. Empty when no more results remain. | +| `total` | `int` | Total count of results across all pages. | Example response shape (Go): @@ -137,10 +143,9 @@ Namespaces are deactivated, not permanently deleted. There is no hard-delete ope -
-Get a Namespace +### Get a Namespace -**Signature** +#### Signature @@ -166,16 +171,16 @@ await platform.v1.namespace.getNamespace({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The namespace UUID. | -| `fqn` | `string` (URI) | The namespace FQN (e.g., `https://example.com`). | +| Parameter | Type | Description | +| --------- | --------------- | ------------------------------------------------ | +| `id` | `string` (UUID) | The namespace UUID. | +| `fqn` | `string` (URI) | The namespace FQN (e.g., `https://example.com`). | -**Example** +#### Example @@ -237,27 +242,26 @@ System.out.println("Namespace by FQN: " + respByFqn.getNamespace().getName()); ```typescript // Look up by UUID -let resp = await platform.v1.namespace.getNamespace({ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' }); +let resp = await platform.v1.namespace.getNamespace({ + id: "f47ac10b-58cc-4372-a567-0e02b2c3d479", +}); console.log(`Namespace: ${resp.namespace?.name} (ID: ${resp.namespace?.id})`); // Alternatively, look up by FQN -resp = await platform.v1.namespace.getNamespace({ fqn: 'https://example.com' }); +resp = await platform.v1.namespace.getNamespace({ fqn: "https://example.com" }); console.log(`Namespace by FQN: ${resp.namespace?.name}`); ``` -**Returns** +#### Returns A single [Namespace object](#namespace-object). Namespace names are unique — an FQN always resolves to exactly one namespace or an error. -
- -
-Update a Namespace +### Update a Namespace -**Signature** +#### Signature @@ -283,15 +287,15 @@ await platform.v1.namespace.updateNamespace({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The namespace ID to update. | -| `metadata` | [`Metadata`](#metadata) | No | Labels to attach, e.g., `{"labels": {"owner": "platform-team"}}`. | -| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` overwrites all existing labels. `METADATA_UPDATE_ENUM_EXTEND` merges new labels into existing ones. If omitted, defaults to `EXTEND`. | +| Parameter | Type | Required | Description | +| ------------------------ | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Yes | The namespace ID to update. | +| `metadata` | [`Metadata`](#metadata) | No | Labels to attach, e.g., `{"labels": {"owner": "platform-team"}}`. | +| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` overwrites all existing labels. `METADATA_UPDATE_ENUM_EXTEND` merges new labels into existing ones. If omitted, defaults to `EXTEND`. | -**Example** +#### Example @@ -348,9 +352,9 @@ System.out.println("Updated namespace ID: " + resp.getNamespace().getId()); ```typescript const resp = await platform.v1.namespace.updateNamespace({ - id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - metadata: { labels: { owner: 'platform-team', env: 'production' } }, - metadataUpdateBehavior: 'METADATA_UPDATE_ENUM_REPLACE', + id: "f47ac10b-58cc-4372-a567-0e02b2c3d479", + metadata: { labels: { owner: "platform-team", env: "production" } }, + metadataUpdateBehavior: "METADATA_UPDATE_ENUM_REPLACE", }); console.log(`Updated namespace ID: ${resp.namespace?.id}`); ``` @@ -358,16 +362,13 @@ console.log(`Updated namespace ID: ${resp.namespace?.id}`); -**Returns** +#### Returns The updated [Namespace object](#namespace-object). -
+### Deactivate a Namespace -
-Deactivate a Namespace - -**Signature** +#### Signature @@ -393,13 +394,13 @@ await platform.v1.namespace.deactivateNamespace({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The namespace ID to deactivate. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ------------------------------- | +| `id` | `string` (UUID) | Yes | The namespace ID to deactivate. | -**Example** +#### Example @@ -436,30 +437,30 @@ System.out.println("Namespace deactivated."); ```typescript -await platform.v1.namespace.deactivateNamespace({ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' }); -console.log('Namespace deactivated.'); +await platform.v1.namespace.deactivateNamespace({ + id: "f47ac10b-58cc-4372-a567-0e02b2c3d479", +}); +console.log("Namespace deactivated."); ``` -**Returns** +#### Returns Empty response. The namespace is soft-deleted and will no longer appear in `ListNamespaces` unless you filter by `INACTIVE` or `ANY` state. -
- ### Namespace Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `name` | `string` | The hostname used to scope attributes (e.g., `opentdf.io`). | -| `fqn` | `string` | Fully qualified name, e.g., `https://opentdf.io`. | -| `active` | `bool` | `true` until explicitly deactivated. | -| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | -| `createdAt` | `timestamp` | When the namespace was created. | -| `updatedAt` | `timestamp` | When the namespace was last modified. | +| Field | Type | Description | +| ----------- | ----------------------- | ----------------------------------------------------------- | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `name` | `string` | The hostname used to scope attributes (e.g., `opentdf.io`). | +| `fqn` | `string` | Fully qualified name, e.g., `https://opentdf.io`. | +| `active` | `bool` | `true` until explicitly deactivated. | +| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | +| `createdAt` | `timestamp` | When the namespace was created. | +| `updatedAt` | `timestamp` | When the namespace was last modified. | --- @@ -475,10 +476,9 @@ Attributes and attribute values are deactivated, not permanently deleted. -
-Get an Attribute +### Get an Attribute -**Signature** +#### Signature @@ -504,16 +504,16 @@ await platform.v1.attributes.getAttribute({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The attribute UUID. | -| `fqn` | `string` | The attribute FQN (e.g., `https://example.com/attr/classification`). | +| Parameter | Type | Description | +| --------- | --------------- | -------------------------------------------------------------------- | +| `id` | `string` (UUID) | The attribute UUID. | +| `fqn` | `string` | The attribute FQN (e.g., `https://example.com/attr/classification`). | -**Example** +#### Example @@ -562,9 +562,11 @@ for (var value : attr.getValuesList()) { ```typescript const resp = await platform.v1.attributes.getAttribute({ - fqn: 'https://example.com/attr/classification', + fqn: "https://example.com/attr/classification", }); -console.log(`Attribute: ${resp.attribute?.name}, Rule: ${resp.attribute?.rule}`); +console.log( + `Attribute: ${resp.attribute?.name}, Rule: ${resp.attribute?.rule}`, +); for (const v of resp.attribute?.values ?? []) { console.log(` Value: ${v.value} (ID: ${v.id})`); } @@ -573,16 +575,13 @@ for (const v of resp.attribute?.values ?? []) { -**Returns** +#### Returns A single [Attribute object](#attribute-object) with nested values. -
+### Update an Attribute -
-Update an Attribute - -**Signature** +#### Signature @@ -608,15 +607,15 @@ await platform.v1.attributes.updateAttribute({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The attribute ID to update. | -| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | -| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | +| Parameter | Type | Required | Description | +| ------------------------ | ----------------------- | -------- | -------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Yes | The attribute ID to update. | +| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | +| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | -**Example** +#### Example @@ -668,9 +667,9 @@ System.out.println("Updated attribute ID: " + resp.getAttribute().getId()); ```typescript const resp = await platform.v1.attributes.updateAttribute({ - id: 'a1b2c3d4-0000-0000-0000-000000000001', - metadata: { labels: { reviewed: 'true' } }, - metadataUpdateBehavior: 'METADATA_UPDATE_ENUM_EXTEND', + id: "a1b2c3d4-0000-0000-0000-000000000001", + metadata: { labels: { reviewed: "true" } }, + metadataUpdateBehavior: "METADATA_UPDATE_ENUM_EXTEND", }); console.log(`Updated attribute ID: ${resp.attribute?.id}`); ``` @@ -678,16 +677,13 @@ console.log(`Updated attribute ID: ${resp.attribute?.id}`); -**Returns** +#### Returns The updated [Attribute object](#attribute-object). -
- -
-Deactivate an Attribute +### Deactivate an Attribute -**Signature** +#### Signature @@ -713,13 +709,13 @@ await platform.v1.attributes.deactivateAttribute({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The attribute ID to deactivate. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ------------------------------- | +| `id` | `string` (UUID) | Yes | The attribute ID to deactivate. | -**Example** +#### Example @@ -758,7 +754,7 @@ System.out.println("Deactivated attribute: " + resp.getAttribute().getId()); ```typescript const resp = await platform.v1.attributes.deactivateAttribute({ - id: 'a1b2c3d4-0000-0000-0000-000000000001', + id: "a1b2c3d4-0000-0000-0000-000000000001", }); console.log(`Deactivated attribute: ${resp.attribute?.id}`); ``` @@ -766,20 +762,17 @@ console.log(`Deactivated attribute: ${resp.attribute?.id}`); -**Returns** +#### Returns Empty response. The attribute is soft-deleted and will no longer appear in `ListAttributes` unless you filter by `INACTIVE` or `ANY` state. -
- ### Attribute Values Attribute values are the individual members of an attribute (e.g., `secret`, `top-secret` under `classification`). They can be managed independently after the parent attribute is created. -
-Create an Attribute Value +### Create an Attribute Value -**Signature** +#### Signature @@ -805,15 +798,15 @@ await platform.v1.attributes.createAttributeValue({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `attributeId` | `string` (UUID) | Yes | The parent attribute ID. | -| `value` | `string` | Yes | The value string (e.g., `top-secret`). | -| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | +| Parameter | Type | Required | Description | +| ------------- | ----------------------- | -------- | -------------------------------------- | +| `attributeId` | `string` (UUID) | Yes | The parent attribute ID. | +| `value` | `string` | Yes | The value string (e.g., `top-secret`). | +| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | -**Example** +#### Example @@ -854,8 +847,8 @@ System.out.println("Created value: " + resp.getValue().getValue() + " (ID: " + r ```typescript const resp = await platform.v1.attributes.createAttributeValue({ - attributeId: 'a1b2c3d4-0000-0000-0000-000000000001', - value: 'top-secret', + attributeId: "a1b2c3d4-0000-0000-0000-000000000001", + value: "top-secret", }); console.log(`Created value: ${resp.value?.value} (ID: ${resp.value?.id})`); ``` @@ -863,16 +856,13 @@ console.log(`Created value: ${resp.value?.value} (ID: ${resp.value?.id})`); -**Returns** +#### Returns The created [Attribute Value object](#attribute-value-object). -
- -
-List Attribute Values +### List Attribute Values -**Signature** +#### Signature @@ -898,14 +888,14 @@ await platform.v1.attributes.listAttributeValues({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `attributeId` | `string` (UUID) | Yes | The parent attribute ID. | -| `state` | `string` | No | Filter by state: `ACTIVE_STATE_ENUM_ACTIVE` (default), `ACTIVE_STATE_ENUM_INACTIVE`, or `ACTIVE_STATE_ENUM_ANY`. | +| Parameter | Type | Required | Description | +| ------------- | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------- | +| `attributeId` | `string` (UUID) | Yes | The parent attribute ID. | +| `state` | `string` | No | Filter by state: `ACTIVE_STATE_ENUM_ACTIVE` (default), `ACTIVE_STATE_ENUM_INACTIVE`, or `ACTIVE_STATE_ENUM_ANY`. | -**Example** +#### Example @@ -948,7 +938,7 @@ for (var value : resp.getValuesList()) { ```typescript const resp = await platform.v1.attributes.listAttributeValues({ - attributeId: 'a1b2c3d4-0000-0000-0000-000000000001', + attributeId: "a1b2c3d4-0000-0000-0000-000000000001", }); for (const v of resp.values) { console.log(`Value: ${v.value} (ID: ${v.id})`); @@ -958,16 +948,13 @@ for (const v of resp.values) { -**Returns** +#### Returns A list of [Attribute Value objects](#attribute-value-object). -
- -
-Get an Attribute Value +### Get an Attribute Value -**Signature** +#### Signature @@ -993,16 +980,16 @@ await platform.v1.attributes.getAttributeValue({ ... }) -**Parameters** +#### Parameters Provide one of the following (exactly one is required): -| Parameter | Type | Description | -|-----------|------|-------------| -| `id` | `string` (UUID) | The value UUID. | -| `fqn` | `string` | The value FQN (e.g., `https://example.com/attr/classification/value/secret`). | +| Parameter | Type | Description | +| --------- | --------------- | ----------------------------------------------------------------------------- | +| `id` | `string` (UUID) | The value UUID. | +| `fqn` | `string` | The value FQN (e.g., `https://example.com/attr/classification/value/secret`). | -**Example** +#### Example @@ -1043,7 +1030,7 @@ System.out.println("Value: " + resp.getValue().getValue() + " (FQN: " + resp.get ```typescript const resp = await platform.v1.attributes.getAttributeValue({ - fqn: 'https://example.com/attr/classification/value/secret', + fqn: "https://example.com/attr/classification/value/secret", }); console.log(`Value: ${resp.value?.value} (FQN: ${resp.value?.fqn})`); ``` @@ -1051,18 +1038,15 @@ console.log(`Value: ${resp.value?.value} (FQN: ${resp.value?.fqn})`); -**Returns** +#### Returns A single [Attribute Value object](#attribute-value-object). -
- -
-Get Attribute Values by FQNs +### Get Attribute Values by FQNs Batch-fetch multiple attribute values by their FQNs in a single request. Returns a map keyed by FQN, each entry containing both the parent attribute definition and the specific value. -**Signature** +#### Signature @@ -1088,13 +1072,13 @@ await platform.v1.attributes.getAttributeValuesByFqns({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `fqns` | `[]string` | Yes | Attribute value FQNs to look up. | +| Parameter | Type | Required | Description | +| --------- | ---------- | -------- | -------------------------------- | +| `fqns` | `[]string` | Yes | Attribute value FQNs to look up. | -**Example** +#### Example @@ -1147,28 +1131,27 @@ resp.getFqnAttributeValuesMap().forEach((fqn, entry) -> ```typescript const resp = await platform.v1.attributes.getAttributeValuesByFqns({ fqns: [ - 'https://example.com/attr/classification/value/secret', - 'https://example.com/attr/department/value/engineering', + "https://example.com/attr/classification/value/secret", + "https://example.com/attr/department/value/engineering", ], }); for (const [fqn, entry] of Object.entries(resp.fqnAttributeValues)) { - console.log(`FQN: ${fqn} -> Attribute: ${entry.attribute?.name}, Value: ${entry.value?.value}`); + console.log( + `FQN: ${fqn} -> Attribute: ${entry.attribute?.name}, Value: ${entry.value?.value}`, + ); } ``` -**Returns** +#### Returns A map of FQN to `{attribute, value}` pairs. Each entry contains the parent [Attribute object](#attribute-object) and the specific [Attribute Value object](#attribute-value-object). -
- -
-Update an Attribute Value +### Update an Attribute Value -**Signature** +#### Signature @@ -1194,15 +1177,15 @@ await platform.v1.attributes.updateAttributeValue({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The value ID to update. | -| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | -| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | +| Parameter | Type | Required | Description | +| ------------------------ | ----------------------- | -------- | -------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Yes | The value ID to update. | +| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | +| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | -**Example** +#### Example @@ -1254,9 +1237,9 @@ System.out.println("Updated value ID: " + resp.getValue().getId()); ```typescript const resp = await platform.v1.attributes.updateAttributeValue({ - id: 'v1b2c3d4-0000-0000-0000-000000000001', - metadata: { labels: { deprecated: 'false' } }, - metadataUpdateBehavior: 'METADATA_UPDATE_ENUM_EXTEND', + id: "v1b2c3d4-0000-0000-0000-000000000001", + metadata: { labels: { deprecated: "false" } }, + metadataUpdateBehavior: "METADATA_UPDATE_ENUM_EXTEND", }); console.log(`Updated value ID: ${resp.value?.id}`); ``` @@ -1264,16 +1247,13 @@ console.log(`Updated value ID: ${resp.value?.id}`); -**Returns** +#### Returns The updated [Attribute Value object](#attribute-value-object). -
+### Deactivate an Attribute Value -
-Deactivate an Attribute Value - -**Signature** +#### Signature @@ -1299,13 +1279,13 @@ await platform.v1.attributes.deactivateAttributeValue({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The value ID to deactivate. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | --------------------------- | +| `id` | `string` (UUID) | Yes | The value ID to deactivate. | -**Example** +#### Example @@ -1344,7 +1324,7 @@ System.out.println("Deactivated value: " + resp.getValue().getId()); ```typescript const resp = await platform.v1.attributes.deactivateAttributeValue({ - id: 'v1b2c3d4-0000-0000-0000-000000000001', + id: "v1b2c3d4-0000-0000-0000-000000000001", }); console.log(`Deactivated value: ${resp.value?.id}`); ``` @@ -1352,39 +1332,37 @@ console.log(`Deactivated value: ${resp.value?.id}`); -**Returns** +#### Returns Empty response. The value is soft-deleted and will no longer appear in listings unless you filter by `INACTIVE` or `ANY` state. -
- ### Attribute Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `name` | `string` | The attribute name (e.g., `department`, `clearance`). | -| `fqn` | `string` | Fully qualified name, e.g., `https://opentdf.io/attr/department`. | -| `rule` | `string` | Access rule: `ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF`, `ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF`, or `ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY`. | -| `values` | `[]Value` | The [attribute values](#attribute-value-object) defined under this attribute. | -| `namespace` | `Namespace` | The parent [Namespace](#namespace-object). | -| `active` | `bool` | `true` until explicitly deactivated. | -| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | -| `createdAt` | `timestamp` | When the attribute was created. | -| `updatedAt` | `timestamp` | When the attribute was last modified. | +| Field | Type | Description | +| ----------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `name` | `string` | The attribute name (e.g., `department`, `clearance`). | +| `fqn` | `string` | Fully qualified name, e.g., `https://opentdf.io/attr/department`. | +| `rule` | `string` | Access rule: `ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF`, `ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF`, or `ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY`. | +| `values` | `[]Value` | The [attribute values](#attribute-value-object) defined under this attribute. | +| `namespace` | `Namespace` | The parent [Namespace](#namespace-object). | +| `active` | `bool` | `true` until explicitly deactivated. | +| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | +| `createdAt` | `timestamp` | When the attribute was created. | +| `updatedAt` | `timestamp` | When the attribute was last modified. | ### Attribute Value Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `value` | `string` | The value string (e.g., `finance`, `confidential`). | -| `fqn` | `string` | Fully qualified name, e.g., `https://opentdf.io/attr/department/value/finance`. | -| `attribute` | `Attribute` | The parent [Attribute](#attribute-object). | -| `active` | `bool` | `true` until explicitly deactivated. | -| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | -| `createdAt` | `timestamp` | When the value was created. | -| `updatedAt` | `timestamp` | When the value was last modified. | +| Field | Type | Description | +| ----------- | ----------------------- | ------------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `value` | `string` | The value string (e.g., `finance`, `confidential`). | +| `fqn` | `string` | Fully qualified name, e.g., `https://opentdf.io/attr/department/value/finance`. | +| `attribute` | `Attribute` | The parent [Attribute](#attribute-object). | +| `active` | `bool` | `true` until explicitly deactivated. | +| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | +| `createdAt` | `timestamp` | When the value was created. | +| `updatedAt` | `timestamp` | When the value was last modified. | --- @@ -1398,10 +1376,9 @@ Subject condition sets are permanently deleted (not deactivated). -
-List Subject Condition Sets +### List Subject Condition Sets -**Signature** +#### Signature @@ -1427,18 +1404,18 @@ await platform.v1.subjectMapping.listSubjectConditionSets({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `namespaceId` | `string` (UUID) | No | Filter to condition sets in this namespace. | -| `namespaceFqn` | `string` (URI) | No | Filter by namespace FQN (e.g., `https://example.com`). Alternative to `namespaceId`. | -| `pagination.limit` | `int` | No | Maximum results per page. | -| `pagination.offset` | `int` | No | Number of results to skip. | +| Parameter | Type | Required | Description | +| ------------------- | --------------- | -------- | ------------------------------------------------------------------------------------ | +| `namespaceId` | `string` (UUID) | No | Filter to condition sets in this namespace. | +| `namespaceFqn` | `string` (URI) | No | Filter by namespace FQN (e.g., `https://example.com`). Alternative to `namespaceId`. | +| `pagination.limit` | `int` | No | Maximum results per page. | +| `pagination.offset` | `int` | No | Number of results to skip. | Without a namespace filter, returns all subject condition sets across all namespaces. -**Example** +#### Example @@ -1484,16 +1461,13 @@ for (const scs of resp.subjectConditionSets) { -**Returns** +#### Returns A list of [Subject Condition Set objects](#subject-condition-set-object). Includes [pagination](#pagination) metadata. -
+### Get a Subject Condition Set -
-Get a Subject Condition Set - -**Signature** +#### Signature @@ -1519,13 +1493,13 @@ await platform.v1.subjectMapping.getSubjectConditionSet({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The subject condition set ID. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ----------------------------- | +| `id` | `string` (UUID) | Yes | The subject condition set ID. | -**Example** +#### Example @@ -1567,25 +1541,24 @@ System.out.println("Associated mappings: " + resp.getAssociatedSubjectMappingsLi ```typescript const resp = await platform.v1.subjectMapping.getSubjectConditionSet({ - id: 'a0b1c2d3-0000-0000-0000-000000000099', + id: "a0b1c2d3-0000-0000-0000-000000000099", }); console.log(`SCS ID: ${resp.subjectConditionSet?.id}`); -console.log(`Associated subject mappings: ${resp.associatedSubjectMappings.length}`); +console.log( + `Associated subject mappings: ${resp.associatedSubjectMappings.length}`, +); ``` -**Returns** +#### Returns The [Subject Condition Set object](#subject-condition-set-object), plus `associatedSubjectMappings` — all subject mappings currently referencing this condition set. -
+### Update a Subject Condition Set -
-Update a Subject Condition Set - -**Signature** +#### Signature @@ -1611,16 +1584,16 @@ await platform.v1.subjectMapping.updateSubjectConditionSet({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The subject condition set ID to update. | -| `subjectSets` | `[]SubjectSet` | No | Replaces the entire condition tree. Omit to update metadata only. | -| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | -| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | +| Parameter | Type | Required | Description | +| ------------------------ | ----------------------- | -------- | -------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Yes | The subject condition set ID to update. | +| `subjectSets` | `[]SubjectSet` | No | Replaces the entire condition tree. Omit to update metadata only. | +| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | +| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | -**Example** +#### Example @@ -1698,20 +1671,23 @@ System.out.println("Updated SCS ID: " + resp.getSubjectConditionSet().getId()); ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, +} from "@opentdf/sdk"; const resp = await platform.v1.subjectMapping.updateSubjectConditionSet({ - id: 'a0b1c2d3-0000-0000-0000-000000000099', + id: "a0b1c2d3-0000-0000-0000-000000000099", subjectSets: [ { conditionGroups: [ { - booleanOperator: 'CONDITION_BOOLEAN_TYPE_ENUM_AND', + booleanOperator: "CONDITION_BOOLEAN_TYPE_ENUM_AND", conditions: [ { - subjectExternalSelectorValue: '.clientId', - operator: 'SUBJECT_MAPPING_OPERATOR_ENUM_IN', - subjectExternalValues: ['my-service', 'my-other-service'], + subjectExternalSelectorValue: ".clientId", + operator: "SUBJECT_MAPPING_OPERATOR_ENUM_IN", + subjectExternalValues: ["my-service", "my-other-service"], }, ], }, @@ -1720,22 +1696,18 @@ const resp = await platform.v1.subjectMapping.updateSubjectConditionSet({ ], }); console.log(`Updated SCS ID: ${resp.subjectConditionSet?.id}`); - ``` -**Returns** +#### Returns The updated [Subject Condition Set object](#subject-condition-set-object). -
- -
-Delete a Subject Condition Set +### Delete a Subject Condition Set -**Signature** +#### Signature @@ -1761,13 +1733,13 @@ await platform.v1.subjectMapping.deleteSubjectConditionSet({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The subject condition set ID to delete. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | --------------------------------------- | +| `id` | `string` (UUID) | Yes | The subject condition set ID to delete. | -**Example** +#### Example @@ -1806,30 +1778,29 @@ System.out.println("Deleted SCS ID: " + resp.getSubjectConditionSet().getId()); ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, +} from "@opentdf/sdk"; const resp = await platform.v1.subjectMapping.deleteSubjectConditionSet({ - id: 'a0b1c2d3-0000-0000-0000-000000000099', + id: "a0b1c2d3-0000-0000-0000-000000000099", }); console.log(`Deleted SCS ID: ${resp.subjectConditionSet?.id}`); - ``` -**Returns** +#### Returns The deleted [Subject Condition Set object](#subject-condition-set-object). This is a hard delete — the object is permanently removed. -
- -
-Delete All Unmapped Subject Condition Sets +### Delete All Unmapped Subject Condition Sets Permanently deletes all subject condition sets that are not referenced by any subject mapping. Use this as a maintenance operation to clean up orphaned condition sets. -**Signature** +#### Signature @@ -1849,17 +1820,17 @@ sdk.getServices().subjectMappings().deleteAllUnmappedSubjectConditionSetsBlockin ```typescript -await platform.v1.subjectMapping.deleteAllUnmappedSubjectConditionSets({}) +await platform.v1.subjectMapping.deleteAllUnmappedSubjectConditionSets({}); ``` -**Parameters** +#### Parameters None. -**Example** +#### Example @@ -1893,38 +1864,41 @@ System.out.println("Deleted " + resp.getSubjectConditionSetsList().size() + " un ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk'; - -const resp = await platform.v1.subjectMapping.deleteAllUnmappedSubjectConditionSets({}); -console.log(`Deleted ${resp.subjectConditionSets.length} unmapped subject condition sets.`); - +import { + authTokenInterceptor, + clientCredentialsTokenProvider, +} from "@opentdf/sdk"; + +const resp = + await platform.v1.subjectMapping.deleteAllUnmappedSubjectConditionSets({}); +console.log( + `Deleted ${resp.subjectConditionSets.length} unmapped subject condition sets.`, +); ``` -**Returns** +#### Returns A list of all deleted [Subject Condition Set objects](#subject-condition-set-object). -
- ### Subject Condition Set Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `subjectSets` | `[]SubjectSet` | One or more [subject sets](#subjectset), evaluated with **AND** logic — all must match. | -| `namespace` | `Namespace` | The parent [Namespace](#namespace-object), if assigned. | -| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | -| `createdAt` | `timestamp` | When the condition set was created. | -| `updatedAt` | `timestamp` | When the condition set was last modified. | +| Field | Type | Description | +| ------------- | ----------------------- | --------------------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `subjectSets` | `[]SubjectSet` | One or more [subject sets](#subjectset), evaluated with **AND** logic — all must match. | +| `namespace` | `Namespace` | The parent [Namespace](#namespace-object), if assigned. | +| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | +| `createdAt` | `timestamp` | When the condition set was created. | +| `updatedAt` | `timestamp` | When the condition set was last modified. | ### Condition Structure A Subject Condition Set contains a tree of nested objects that define which entities match. Here's how they fit together: -``` +```text SubjectConditionSet └── subjectSets[] ← AND across sets └── conditionGroups[] ← AND across groups @@ -1939,33 +1913,33 @@ SubjectConditionSet ### SubjectSet -| Field | Type | Description | -|-------|------|-------------| +| Field | Type | Description | +| ----------------- | ------------------ | ----------------------------------------------------------------------------------- | | `conditionGroups` | `[]ConditionGroup` | One or more [condition groups](#conditiongroup). All groups must match (AND logic). | ### ConditionGroup -| Field | Type | Description | -|-------|------|-------------| -| `booleanOperator` | `string` | How to combine conditions: `CONDITION_BOOLEAN_TYPE_ENUM_AND` or `CONDITION_BOOLEAN_TYPE_ENUM_OR`. | -| `conditions` | `[]Condition` | One or more [conditions](#condition) to evaluate. | +| Field | Type | Description | +| ----------------- | ------------- | ------------------------------------------------------------------------------------------------- | +| `booleanOperator` | `string` | How to combine conditions: `CONDITION_BOOLEAN_TYPE_ENUM_AND` or `CONDITION_BOOLEAN_TYPE_ENUM_OR`. | +| `conditions` | `[]Condition` | One or more [conditions](#condition) to evaluate. | ### Condition A single claim match against an entity's token. -| Field | Type | Description | -|-------|------|-------------| -| `subjectExternalSelectorValue` | `string` | A selector path into the entity's flattened token claims (e.g., `.clientId`, `.realm_access.roles`). | -| `operator` | `string` | Comparison operator (see below). | -| `subjectExternalValues` | `[]string` | The values to compare against. | +| Field | Type | Description | +| ------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------- | +| `subjectExternalSelectorValue` | `string` | A selector path into the entity's flattened token claims (e.g., `.clientId`, `.realm_access.roles`). | +| `operator` | `string` | Comparison operator (see below). | +| `subjectExternalValues` | `[]string` | The values to compare against. | **Operators:** -| Operator | Description | -|----------|-------------| -| `SUBJECT_MAPPING_OPERATOR_ENUM_IN` | The claim value must exactly match one of the `subjectExternalValues`. | -| `SUBJECT_MAPPING_OPERATOR_ENUM_NOT_IN` | The claim value must not match any of the `subjectExternalValues`. | +| Operator | Description | +| ------------------------------------------- | ---------------------------------------------------------------------------------- | +| `SUBJECT_MAPPING_OPERATOR_ENUM_IN` | The claim value must exactly match one of the `subjectExternalValues`. | +| `SUBJECT_MAPPING_OPERATOR_ENUM_NOT_IN` | The claim value must not match any of the `subjectExternalValues`. | | `SUBJECT_MAPPING_OPERATOR_ENUM_IN_CONTAINS` | The claim value must contain (substring match) one of the `subjectExternalValues`. | **Example:** "Match entities whose `.clientId` is `my-service` AND whose `.realm_access.roles` contains `developer`": @@ -2000,16 +1974,16 @@ A single claim match against an entity's token. Used in [MatchSubjectMappings](#match-subject-mappings) to test which subject mappings match a given set of entity claims. -| Field | Type | Description | -|-------|------|-------------| +| Field | Type | Description | +| ----------------------- | -------- | ------------------------------------------------------------------------------------------ | | `externalSelectorValue` | `string` | A selector path into the entity's token claims (e.g., `.clientId`, `.realm_access.roles`). | -| `externalValue` | `string` | The claim value to test against (e.g., `my-service`, `developer`). | +| `externalValue` | `string` | The claim value to test against (e.g., `my-service`, `developer`). | --- ## Subject Mappings -A **subject mapping** links a subject condition set to a specific attribute value and a set of permitted actions (e.g., `DECRYPT`). It answers the question: *"which entities are allowed to do what with data carrying this attribute value?"* +A **subject mapping** links a subject condition set to a specific attribute value and a set of permitted actions (e.g., `DECRYPT`). It answers the question: _"which entities are allowed to do what with data carrying this attribute value?"_ :::note Hard-delete Subject mappings are permanently deleted (not deactivated). There is no restore operation. @@ -2019,10 +1993,9 @@ Subject mappings are permanently deleted (not deactivated). There is no restore -
-Get a Subject Mapping +### Get a Subject Mapping -**Signature** +#### Signature @@ -2048,13 +2021,13 @@ await platform.v1.subjectMapping.getSubjectMapping({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The subject mapping ID. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | ----------------------- | +| `id` | `string` (UUID) | Yes | The subject mapping ID. | -**Example** +#### Example @@ -2095,7 +2068,7 @@ System.out.println("Subject Mapping ID: " + resp.getSubjectMapping().getId()); ```typescript const resp = await platform.v1.subjectMapping.getSubjectMapping({ - id: '890b26db-4ee4-447f-ae8a-2862d922eeef', + id: "890b26db-4ee4-447f-ae8a-2862d922eeef", }); console.log(`Subject Mapping ID: ${resp.subjectMapping?.id}`); ``` @@ -2103,16 +2076,13 @@ console.log(`Subject Mapping ID: ${resp.subjectMapping?.id}`); -**Returns** +#### Returns A single [Subject Mapping object](#subject-mapping-object). -
- -
-Update a Subject Mapping +### Update a Subject Mapping -**Signature** +#### Signature @@ -2138,17 +2108,17 @@ await platform.v1.subjectMapping.updateSubjectMapping({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The subject mapping ID to update. | -| `actions` | [`[]Action`](#action) | No | Replaces the entire actions list. Omit to leave unchanged. | -| `subjectConditionSetId` | `string` (UUID) | No | Swap the linked condition set. | -| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | -| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | +| Parameter | Type | Required | Description | +| ------------------------ | ----------------------- | -------- | -------------------------------------------------------------------------- | +| `id` | `string` (UUID) | Yes | The subject mapping ID to update. | +| `actions` | [`[]Action`](#action) | No | Replaces the entire actions list. Omit to leave unchanged. | +| `subjectConditionSetId` | `string` (UUID) | No | Swap the linked condition set. | +| `metadata` | [`Metadata`](#metadata) | No | Optional [labels](#metadata). | +| `metadataUpdateBehavior` | `string` | No | `METADATA_UPDATE_ENUM_REPLACE` or `METADATA_UPDATE_ENUM_EXTEND` (default). | -**Example** +#### Example @@ -2203,8 +2173,8 @@ System.out.println("Updated subject mapping ID: " + resp.getSubjectMapping().get ```typescript const resp = await platform.v1.subjectMapping.updateSubjectMapping({ - id: '890b26db-4ee4-447f-ae8a-2862d922eeef', - actions: [{ standard: 'STANDARD_ACTION_DECRYPT' }], + id: "890b26db-4ee4-447f-ae8a-2862d922eeef", + actions: [{ standard: "STANDARD_ACTION_DECRYPT" }], }); console.log(`Updated subject mapping ID: ${resp.subjectMapping?.id}`); ``` @@ -2212,16 +2182,13 @@ console.log(`Updated subject mapping ID: ${resp.subjectMapping?.id}`); -**Returns** +#### Returns The updated [Subject Mapping object](#subject-mapping-object). -
- -
-Delete a Subject Mapping +### Delete a Subject Mapping -**Signature** +#### Signature @@ -2247,13 +2214,13 @@ await platform.v1.subjectMapping.deleteSubjectMapping({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `id` | `string` (UUID) | Yes | The subject mapping ID to delete. | +| Parameter | Type | Required | Description | +| --------- | --------------- | -------- | --------------------------------- | +| `id` | `string` (UUID) | Yes | The subject mapping ID to delete. | -**Example** +#### Example @@ -2292,7 +2259,7 @@ System.out.println("Deleted subject mapping ID: " + resp.getSubjectMapping().get ```typescript const resp = await platform.v1.subjectMapping.deleteSubjectMapping({ - id: '890b26db-4ee4-447f-ae8a-2862d922eeef', + id: "890b26db-4ee4-447f-ae8a-2862d922eeef", }); console.log(`Deleted subject mapping ID: ${resp.subjectMapping?.id}`); ``` @@ -2300,18 +2267,15 @@ console.log(`Deleted subject mapping ID: ${resp.subjectMapping?.id}`); -**Returns** +#### Returns The deleted [Subject Mapping object](#subject-mapping-object). This is a hard delete — the object is permanently removed. -
- -
-Match Subject Mappings +### Match Subject Mappings Given a list of subject properties (key-value pairs from an entity's token claims), returns all subject mappings whose condition sets match. Use this to determine which access grants apply to a given entity. -**Signature** +#### Signature @@ -2337,13 +2301,13 @@ await platform.v1.subjectMapping.matchSubjectMappings({ ... }) -**Parameters** +#### Parameters -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `subjectProperties` | `[]SubjectProperty` | Yes | Key-value pairs from an entity's claims. Each has `externalSelectorValue` (the claim path, e.g., `.clientId`) and `externalValue` (the claim value). | +| Parameter | Type | Required | Description | +| ------------------- | ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `subjectProperties` | `[]SubjectProperty` | Yes | Key-value pairs from an entity's claims. Each has `externalSelectorValue` (the claim path, e.g., `.clientId`) and `externalValue` (the claim value). | -**Example** +#### Example @@ -2401,8 +2365,11 @@ for (var sm : resp.getSubjectMappingsList()) { ```typescript const resp = await platform.v1.subjectMapping.matchSubjectMappings({ subjectProperties: [ - { externalSelectorValue: '.clientId', externalValue: 'my-service' }, - { externalSelectorValue: '.realm_access.roles', externalValue: 'developer' }, + { externalSelectorValue: ".clientId", externalValue: "my-service" }, + { + externalSelectorValue: ".realm_access.roles", + externalValue: "developer", + }, ], }); for (const sm of resp.subjectMappings) { @@ -2413,22 +2380,20 @@ for (const sm of resp.subjectMappings) { -**Returns** +#### Returns A list of [Subject Mapping objects](#subject-mapping-object) whose condition sets matched the provided subject properties. -
- ### Action An action represents what an entity is permitted to do with data. The platform ships with four **standard actions** that cannot be deleted: -| Standard Action | Description | -|-----------------|-------------| -| `read` | Read/decrypt data. Used in all TDF decrypt flows. | -| `create` | Create new data. | -| `update` | Update existing data. | -| `delete` | Delete data. | +| Standard Action | Description | +| --------------- | ------------------------------------------------- | +| `read` | Read/decrypt data. Used in all TDF decrypt flows. | +| `create` | Create new data. | +| `update` | Update existing data. | +| `delete` | Delete data. | You can also define **custom actions** (e.g., `download`, `queue-to-print`, `send_email`). Custom actions are globally unique, not namespaced, and are lowercased when stored. See [Actions](/components/policy/actions) for more. @@ -2446,18 +2411,20 @@ Action.newBuilder().setName("read").build(); ```typescript // JavaScript -{ name: 'read' } +{ + name: "read"; +} ``` ### Subject Mapping Object -| Field | Type | Description | -|-------|------|-------------| -| `id` | `string` (UUID) | Unique identifier, generated by the platform. | -| `attributeValue` | `Value` | The [Attribute Value](#attribute-value-object) this mapping grants access to. | -| `subjectConditionSet` | `SubjectConditionSet` | The [Subject Condition Set](#subject-condition-set-object) that determines which entities match. | -| `actions` | [`[]Action`](#action) | Permitted [actions](#action) (e.g., `read`, `create`). | -| `namespace` | `Namespace` | The parent [Namespace](#namespace-object), if assigned. | -| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | -| `createdAt` | `timestamp` | When the mapping was created. | -| `updatedAt` | `timestamp` | When the mapping was last modified. | +| Field | Type | Description | +| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------ | +| `id` | `string` (UUID) | Unique identifier, generated by the platform. | +| `attributeValue` | `Value` | The [Attribute Value](#attribute-value-object) this mapping grants access to. | +| `subjectConditionSet` | `SubjectConditionSet` | The [Subject Condition Set](#subject-condition-set-object) that determines which entities match. | +| `actions` | [`[]Action`](#action) | Permitted [actions](#action) (e.g., `read`, `create`). | +| `namespace` | `Namespace` | The parent [Namespace](#namespace-object), if assigned. | +| `metadata` | [`Metadata`](#metadata) | Optional [labels](#metadata). | +| `createdAt` | `timestamp` | When the mapping was created. | +| `updatedAt` | `timestamp` | When the mapping was last modified. | diff --git a/docs/sdks/quickstart/go.mdx b/docs/sdks/quickstart/go.mdx index 35860786..4a1bc3e9 100644 --- a/docs/sdks/quickstart/go.mdx +++ b/docs/sdks/quickstart/go.mdx @@ -3,8 +3,6 @@ sidebar_position: 1 title: Go --- -# Go SDK Quickstart - :::tip Back to SDK Quickstart This guide covers the **Go SDK** implementation. For other languages or general information, see the [SDK Quickstart](/sdks/quickstart) page. ::: @@ -18,6 +16,7 @@ This guide covers the **Go SDK** implementation. For other languages or general Before you begin, **make sure your OpenTDF platform is running!** Verify it's running: + ```bash curl -k https://platform.opentdf.local:8443/healthz ``` @@ -25,6 +24,7 @@ curl -k https://platform.opentdf.local:8443/healthz Should return: `{"status":"SERVING"}` If not running, start it: + ```bash cd ~/.opentdf/platform && docker compose up -d ``` @@ -49,6 +49,7 @@ go get github.com/opentdf/platform/sdk@latest ``` Expected output: + > ```console > go: downloading github.com/opentdf/platform/sdk v0.x.x > go: added github.com/opentdf/platform/sdk v0.x.x @@ -394,8 +395,6 @@ log.Printf("✅ Granted yourself access to department/marketing") Now you can decrypt the TDF you encrypted with the `marketing` attribute. 🎉 - - ### Save TDF to a File In production applications, you'll often need to persist encrypted TDFs to disk for storage, transmission, or archival. This allows you to: diff --git a/docs/sdks/quickstart/index.mdx b/docs/sdks/quickstart/index.mdx index a9a114a3..e46152a2 100644 --- a/docs/sdks/quickstart/index.mdx +++ b/docs/sdks/quickstart/index.mdx @@ -3,19 +3,18 @@ sidebar_position: 2 title: SDK Quickstart --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# SDK Quickstart +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; This guide shows you how to build a simple application using the OpenTDF SDKs to encrypt and decrypt data. You'll create a lightweight client that connects to your local OpenTDF platform and demonstrates the core SDK functionality. :::tip Prerequisites Complete the [Getting Started](/quickstart) guide first to set up your local OpenTDF platform. This quickstart assumes: + - Platform running at `https://platform.opentdf.local:8443` - Keycloak running at `https://keycloak.opentdf.local:9443` - Client credentials: `opentdf` / `secret` -::: + ::: :::note Time Commitment This quickstart takes **15-20 minutes** to complete. @@ -24,6 +23,7 @@ This quickstart takes **15-20 minutes** to complete. ## What You'll Build {#what-youll-build} A simple application that: + 1. Connects to your local OpenTDF platform 2. Authenticates using client credentials 3. Encrypts sensitive data into a TDF file @@ -33,61 +33,16 @@ A simple application that: OpenTDF provides native SDKs in three languages. Choose your language to get started with complete, working examples: -
-
-
-
-

Go SDK

-
-
-

Native Go library with excellent performance for backend services and CLI tools.

-
- -
-
- -
-
-
-

Java SDK

-
-
-

Full-featured Java library for enterprise applications and Android development.

-
- -
-
- -
-
-
-

JavaScript/TypeScript SDK

-
-
-

Modern SDK for Node.js and browser applications with TypeScript support.

-
- -
-
-
+- **[Go SDK](/sdks/quickstart/go)** — Native Go library with excellent performance for backend services and CLI tools. +- **[Java SDK](/sdks/quickstart/java)** — Full-featured Java library for enterprise applications and Android development. +- **[JavaScript/TypeScript SDK](/sdks/quickstart/javascript)** — Modern SDK for Node.js and browser applications with TypeScript support. ## Key Concepts {#key-concepts} ### Client Credentials Authentication All three SDKs use **client credentials** to authenticate with the platform. This is a machine-to-machine authentication flow suitable for: + - Backend services - CLI tools - Server-side applications @@ -98,6 +53,7 @@ In production, you would create separate client IDs for each service with approp ### TDF Format The SDKs create **TDF (Trusted Data Format)** files, which contain: + - **Encrypted payload**: Your data, encrypted with a symmetric key - **Manifest**: Metadata including policy attributes and Key Access Server information - **Policy**: Access control rules (which attributes are required to decrypt) @@ -114,17 +70,16 @@ The SDK examples use different settings for local development with self-signed c **Never use these settings in production.** In production: + - Use HTTPS connections with valid TLS certificates - Remove all insecure connection flags - Configure proper certificate validation -::: + ::: ## What's Next? {#whats-next} Having issues? See the **[SDK Troubleshooting](/sdks/troubleshooting)** guide for solutions to common problems. - - Now that you have basic SDK integration working, explore: - **[Creating TDFs](/sdks/tdf)**: Learn about TDF options, attributes, and policies diff --git a/docs/sdks/quickstart/java.mdx b/docs/sdks/quickstart/java.mdx index a1a3413d..9cfb9f89 100644 --- a/docs/sdks/quickstart/java.mdx +++ b/docs/sdks/quickstart/java.mdx @@ -3,8 +3,6 @@ sidebar_position: 2 title: Java --- -# Java SDK Quickstart - :::tip Back to SDK Quickstart This guide covers the **Java SDK** implementation. For other languages or general information, see the [SDK Quickstart](/sdks/quickstart) page. ::: @@ -19,6 +17,7 @@ This guide covers the **Java SDK** implementation. For other languages or genera Before you begin, **make sure your OpenTDF platform is running!** Verify it's running: + ```bash curl -k https://platform.opentdf.local:8443/healthz ``` @@ -26,6 +25,7 @@ curl -k https://platform.opentdf.local:8443/healthz Should return: `{"status":"SERVING"}` If not running, start it: + ```bash cd ~/.opentdf/platform && docker compose up -d ``` @@ -273,7 +273,6 @@ If you want to follow along, you can add the code to your quickstart file as we For additional policy management examples including managing attributes, namespaces, subject mappings, and more, see the [Policy SDK Guide](/sdks/policy). ::: - ### Create a New Attribute Value First, let's create a new attribute value that we can use for access control. If you followed the [Quickstart setup guide](/quickstart), you'll already have an attribute "department" with two values: finance and engineering. Let's add "marketing" to those values: @@ -545,7 +544,6 @@ System.out.println("✅ Granted yourself access to department/marketing"); Now you can decrypt the TDF you encrypted with the `marketing` attribute. 🎉 - ### Save TDF to a File In production applications, you'll often need to persist encrypted TDFs to disk for storage, transmission, or archival. This allows you to: diff --git a/docs/sdks/quickstart/javascript.mdx b/docs/sdks/quickstart/javascript.mdx index 1643e304..6651c22b 100644 --- a/docs/sdks/quickstart/javascript.mdx +++ b/docs/sdks/quickstart/javascript.mdx @@ -3,8 +3,6 @@ sidebar_position: 3 title: JavaScript/TypeScript --- -# JavaScript/TypeScript SDK Quickstart - :::tip Back to SDK Quickstart This guide covers the **JavaScript/TypeScript SDK** implementation. For other languages or general information, see the [SDK Quickstart](/sdks/quickstart) page. ::: @@ -18,6 +16,7 @@ This guide covers the **JavaScript/TypeScript SDK** implementation. For other la Before you begin, **make sure your OpenTDF platform is running!** Verify it's running: + ```bash curl -k https://platform.opentdf.local:8443/healthz ``` @@ -25,6 +24,7 @@ curl -k https://platform.opentdf.local:8443/healthz Should return: `{"status":"SERVING"}` If not running, start it: + ```bash cd ~/.opentdf/platform && docker compose up -d ``` @@ -59,6 +59,7 @@ npm install @opentdf/sdk ``` Expected output: + > ```console > added XX packages in Xs > ``` @@ -74,56 +75,64 @@ This quickstart uses `clientCredentialsTokenProvider` with the pre-configured Ke Create a file named `index.mjs`: ```javascript title="index.mjs" -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; async function main() { - console.log('🚀 Starting OpenTDF SDK Quickstart...'); + console.log("🚀 Starting OpenTDF SDK Quickstart..."); - const platformUrl = 'https://platform.opentdf.local:8443'; - const oidcOrigin = 'https://keycloak.opentdf.local:9443/auth/realms/opentdf'; + const platformUrl = "https://platform.opentdf.local:8443"; + const oidcOrigin = "https://keycloak.opentdf.local:9443/auth/realms/opentdf"; console.log(`📡 Connecting to platform: ${platformUrl}`); // For this quickstart we use client credentials (server-side only). // In a browser app, use refreshTokenProvider() with your OIDC login flow instead. const tokenProvider = clientCredentialsTokenProvider({ - clientId: 'opentdf', - clientSecret: 'secret', + clientId: "opentdf", + clientSecret: "secret", oidcOrigin, }); // Create a new OpenTDF client - console.log('🔐 Initializing client with auth interceptor...'); - const client = new OpenTDF({ interceptors: [authTokenInterceptor(tokenProvider)], platformUrl }); - console.log('✅ SDK client initialized successfully'); + console.log("🔐 Initializing client with auth interceptor..."); + const client = new OpenTDF({ + interceptors: [authTokenInterceptor(tokenProvider)], + platformUrl, + }); + console.log("✅ SDK client initialized successfully"); // Encrypt data - console.log('\n📝 Encrypting sensitive data...'); - const sensitiveData = 'Hello from the OpenTDF JS SDK! This data is encrypted.'; + console.log("\n📝 Encrypting sensitive data..."); + const sensitiveData = + "Hello from the OpenTDF JS SDK! This data is encrypted."; const encoder = new TextEncoder(); - console.log('🔒 Creating TDF...'); + console.log("🔒 Creating TDF..."); const tdfStream = await client.createTDF({ - source: { type: 'buffer', location: encoder.encode(sensitiveData) }, + source: { type: "buffer", location: encoder.encode(sensitiveData) }, // defaultKASEndpoint specifies the Key Access Service (KAS) endpoint // KAS manages encryption keys and enforces access policies defaultKASEndpoint: `${platformUrl}/kas`, }); const encrypted = new Uint8Array(await new Response(tdfStream).arrayBuffer()); - console.log('✅ Data successfully encrypted'); + console.log("✅ Data successfully encrypted"); console.log(`📊 Encrypted TDF size: ${encrypted.length} bytes`); // Decrypt data - console.log('\n🔓 Decrypting TDF...'); + console.log("\n🔓 Decrypting TDF..."); const decryptedStream = await client.read({ - source: { type: 'buffer', location: encrypted }, + source: { type: "buffer", location: encrypted }, }); const decryptedText = await new Response(decryptedStream).text(); - console.log('✅ Data successfully decrypted'); + console.log("✅ Data successfully decrypted"); console.log(`📤 Decrypted content:\n\n${decryptedText}\n`); - console.log('\n🎉 Quickstart complete!'); + console.log("\n🎉 Quickstart complete!"); } main().catch(console.error); @@ -190,19 +199,29 @@ For additional policy management examples including managing attributes, namespa Let's work with the "department" attribute and add a "marketing" value. If you completed the [Quickstart setup guide](/quickstart), the attribute may already exist with finance and engineering values. If not, we'll create it: ```javascript -import { authTokenInterceptor, clientCredentialsTokenProvider, attributeExists, attributeValueExists, OpenTDF } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; -import { AttributeRuleTypeEnum } from '@opentdf/sdk/platform/policy/objects_pb.js'; - -const platformUrl = 'https://platform.opentdf.local:8443'; -const oidcOrigin = 'https://keycloak.opentdf.local:9443/auth/realms/opentdf'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + attributeExists, + attributeValueExists, + OpenTDF, +} from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; +import { AttributeRuleTypeEnum } from "@opentdf/sdk/platform/policy/objects_pb.js"; + +const platformUrl = "https://platform.opentdf.local:8443"; +const oidcOrigin = "https://keycloak.opentdf.local:9443/auth/realms/opentdf"; // Client credentials for this quickstart only — use refreshTokenProvider() in browser apps -const interceptors = [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', - clientSecret: 'secret', - oidcOrigin, -}))]; +const interceptors = [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin, + }), + ), +]; const auth = { interceptors }; const client = new OpenTDF({ ...auth, platformUrl }); const platform = new PlatformClient({ ...auth, platformUrl }); @@ -211,15 +230,15 @@ const platform = new PlatformClient({ ...auth, platformUrl }); let namespaceId; try { const nsResp = await platform.v1.namespace.createNamespace({ - name: 'opentdf.io', + name: "opentdf.io", }); namespaceId = nsResp.namespace?.id; console.log(`✅ Created namespace: ${namespaceId}`); } catch (err) { - if (err.message?.includes('already_exists')) { + if (err.message?.includes("already_exists")) { // Namespace exists — fetch it directly by FQN const getNsResp = await platform.v1.namespace.getNamespace({ - identifier: { case: 'fqn', value: 'https://opentdf.io' }, + identifier: { case: "fqn", value: "https://opentdf.io" }, }); namespaceId = getNsResp.namespace?.id; console.log(`✅ Using existing namespace: ${namespaceId}`); @@ -229,32 +248,32 @@ try { } // Create the department attribute if it doesn't exist -const deptFqn = 'https://opentdf.io/attr/department'; +const deptFqn = "https://opentdf.io/attr/department"; if (!(await attributeExists(platformUrl, auth, deptFqn))) { await platform.v1.attributes.createAttribute({ namespaceId, - name: 'department', + name: "department", rule: AttributeRuleTypeEnum.ANY_OF, - values: ['marketing'], + values: ["marketing"], }); - console.log('✅ Created department attribute with marketing value'); + console.log("✅ Created department attribute with marketing value"); } else { - console.log('✅ Using existing department attribute'); + console.log("✅ Using existing department attribute"); } // Ensure the marketing value exists on the attribute const marketingFqn = `${deptFqn}/value/marketing`; const listResp = await platform.v1.attributes.listAttributes({}); const attribute = listResp.attributes.find( - (attr) => attr.name === 'department' && attr.namespace?.id === namespaceId + (attr) => attr.name === "department" && attr.namespace?.id === namespaceId, ); if (!(await attributeValueExists(platformUrl, auth, marketingFqn))) { await platform.v1.attributes.createAttributeValue({ attributeId: attribute?.id, - value: 'marketing', + value: "marketing", }); - console.log('✅ Added marketing value to department attribute'); + console.log("✅ Added marketing value to department attribute"); } console.log(`Full attribute FQN: ${marketingFqn}`); @@ -270,11 +289,11 @@ Now that you've created the attribute, update your `createTDF` call to include t ```javascript const tdfStream = await client.createTDF({ - source: { type: 'buffer', location: encoder.encode(sensitiveData) }, + source: { type: "buffer", location: encoder.encode(sensitiveData) }, // defaultKASEndpoint specifies the Key Access Service (KAS) endpoint // KAS manages encryption keys and enforces access policies defaultKASEndpoint: `${platformUrl}/kas`, - attributes: ['https://opentdf.io/attr/department/value/marketing'], + attributes: ["https://opentdf.io/attr/department/value/marketing"], }); ``` @@ -296,43 +315,47 @@ Learn more about actions in the [Actions documentation](/components/policy/actio import { ConditionBooleanTypeEnum, SubjectMappingOperatorEnum, -} from '@opentdf/sdk/platform/policy/objects_pb.js'; +} from "@opentdf/sdk/platform/policy/objects_pb.js"; // Get the attribute value ID for "marketing" -const marketingValue = attribute?.values?.find((v) => v.value === 'marketing'); +const marketingValue = attribute?.values?.find((v) => v.value === "marketing"); const attributeValueId = marketingValue?.id; // Subject condition sets use a nested structure: sets → groups → conditions. // This one matches any entity whose .clientId claim includes 'opentdf'. const scsResp = await platform.v1.subjectMapping.createSubjectConditionSet({ subjectConditionSet: { - subjectSets: [{ - conditionGroups: [{ - booleanOperator: ConditionBooleanTypeEnum.AND, - conditions: [{ - subjectExternalSelectorValue: '.clientId', - operator: SubjectMappingOperatorEnum.IN, - subjectExternalValues: ['opentdf'], - }], - }], - }], + subjectSets: [ + { + conditionGroups: [ + { + booleanOperator: ConditionBooleanTypeEnum.AND, + conditions: [ + { + subjectExternalSelectorValue: ".clientId", + operator: SubjectMappingOperatorEnum.IN, + subjectExternalValues: ["opentdf"], + }, + ], + }, + ], + }, + ], }, }); // Create the subject mapping to grant yourself the entitlement await platform.v1.subjectMapping.createSubjectMapping({ attributeValueId, - actions: [{ name: 'read' }], + actions: [{ name: "read" }], existingSubjectConditionSetId: scsResp.subjectConditionSet?.id, }); -console.log('✅ Granted yourself access to department/marketing'); +console.log("✅ Granted yourself access to department/marketing"); ``` Now you can decrypt the TDF you encrypted with the `marketing` attribute. 🎉 - - ### Save TDF to a File In production applications, you'll often need to persist encrypted TDFs to disk for storage, transmission, or archival. This allows you to: @@ -342,15 +365,15 @@ In production applications, you'll often need to persist encrypted TDFs to disk - **Archive encrypted data**: Store TDFs in backup systems or long-term storage with their access policies intact ```javascript -import fs from 'node:fs'; +import fs from "node:fs"; // After encryption -fs.writeFileSync('encrypted.tdf', encrypted); +fs.writeFileSync("encrypted.tdf", encrypted); // Later, load from file -const tdfData = fs.readFileSync('encrypted.tdf'); +const tdfData = fs.readFileSync("encrypted.tdf"); const decryptedStream = await client.read({ - source: { type: 'buffer', location: new Uint8Array(tdfData) }, + source: { type: "buffer", location: new Uint8Array(tdfData) }, }); ``` @@ -359,19 +382,19 @@ const decryptedStream = await client.read({ For large files, use streams instead of loading the entire file into memory: ```javascript -import fs from 'node:fs'; +import fs from "node:fs"; // Encrypt a large file using a stream source -const inputStream = fs.createReadStream('large-file.pdf'); +const inputStream = fs.createReadStream("large-file.pdf"); const tdfStream = await client.createTDF({ - source: { type: 'stream', location: inputStream }, + source: { type: "stream", location: inputStream }, // defaultKASEndpoint specifies the Key Access Service (KAS) endpoint // KAS manages encryption keys and enforces access policies defaultKASEndpoint: `${platformUrl}/kas`, }); // Pipe encrypted output to a file -const outputStream = fs.createWriteStream('large-file.pdf.tdf'); +const outputStream = fs.createWriteStream("large-file.pdf.tdf"); for await (const chunk of tdfStream) { outputStream.write(chunk); } @@ -386,25 +409,35 @@ For reference, here's a complete example showing all the pieces together: Create Attribute, Encrypt, Grant Access, Decrypt, Save File ```javascript title="index.mjs" -import fs from 'node:fs'; -import { authTokenInterceptor, clientCredentialsTokenProvider, attributeExists, attributeValueExists, OpenTDF } from '@opentdf/sdk'; -import { PlatformClient } from '@opentdf/sdk/platform'; +import fs from "node:fs"; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + attributeExists, + attributeValueExists, + OpenTDF, +} from "@opentdf/sdk"; +import { PlatformClient } from "@opentdf/sdk/platform"; import { AttributeRuleTypeEnum, ConditionBooleanTypeEnum, SubjectMappingOperatorEnum, -} from '@opentdf/sdk/platform/policy/objects_pb.js'; +} from "@opentdf/sdk/platform/policy/objects_pb.js"; async function main() { - const platformUrl = 'https://platform.opentdf.local:8443'; - const oidcOrigin = 'https://keycloak.opentdf.local:9443/auth/realms/opentdf'; + const platformUrl = "https://platform.opentdf.local:8443"; + const oidcOrigin = "https://keycloak.opentdf.local:9443/auth/realms/opentdf"; // Client credentials for this quickstart only — use refreshTokenProvider() in browser apps - const interceptors = [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', - clientSecret: 'secret', - oidcOrigin, - }))]; + const interceptors = [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin, + }), + ), + ]; const auth = { interceptors }; const client = new OpenTDF({ ...auth, platformUrl }); const platform = new PlatformClient({ ...auth, platformUrl }); @@ -413,14 +446,14 @@ async function main() { let namespaceId; try { const nsResp = await platform.v1.namespace.createNamespace({ - name: 'opentdf.io', + name: "opentdf.io", }); namespaceId = nsResp.namespace?.id; console.log(`✅ Created namespace: ${namespaceId}`); } catch (err) { - if (err.message?.includes('already_exists')) { + if (err.message?.includes("already_exists")) { const getNsResp = await platform.v1.namespace.getNamespace({ - identifier: { case: 'fqn', value: 'https://opentdf.io' }, + identifier: { case: "fqn", value: "https://opentdf.io" }, }); namespaceId = getNsResp.namespace?.id; console.log(`✅ Using existing namespace: ${namespaceId}`); @@ -430,17 +463,17 @@ async function main() { } // 2. Create the department attribute if it doesn't exist - const deptFqn = 'https://opentdf.io/attr/department'; + const deptFqn = "https://opentdf.io/attr/department"; if (!(await attributeExists(platformUrl, auth, deptFqn))) { await platform.v1.attributes.createAttribute({ namespaceId, - name: 'department', + name: "department", rule: AttributeRuleTypeEnum.ANY_OF, - values: ['marketing'], + values: ["marketing"], }); - console.log('✅ Created department attribute with marketing value'); + console.log("✅ Created department attribute with marketing value"); } else { - console.log('✅ Using existing department attribute'); + console.log("✅ Using existing department attribute"); } // Ensure the marketing value exists on the attribute @@ -448,88 +481,95 @@ async function main() { if (!(await attributeValueExists(platformUrl, auth, marketingFqn))) { const listResp = await platform.v1.attributes.listAttributes({}); const attribute = listResp.attributes.find( - (attr) => attr.name === 'department' && attr.namespace?.id === namespaceId + (attr) => + attr.name === "department" && attr.namespace?.id === namespaceId, ); await platform.v1.attributes.createAttributeValue({ attributeId: attribute?.id, - value: 'marketing', + value: "marketing", }); - console.log('✅ Added marketing value to department attribute'); + console.log("✅ Added marketing value to department attribute"); } // 3. Encrypt data with the marketing attribute - const plaintext = 'Sensitive marketing campaign data'; + const plaintext = "Sensitive marketing campaign data"; const encoder = new TextEncoder(); // defaultKASEndpoint specifies the Key Access Service (KAS) endpoint // KAS manages encryption keys and enforces access policies const tdfStream = await client.createTDF({ - source: { type: 'buffer', location: encoder.encode(plaintext) }, + source: { type: "buffer", location: encoder.encode(plaintext) }, defaultKASEndpoint: `${platformUrl}/kas`, - attributes: ['https://opentdf.io/attr/department/value/marketing'], + attributes: ["https://opentdf.io/attr/department/value/marketing"], }); const encrypted = new Uint8Array(await new Response(tdfStream).arrayBuffer()); - console.log('✅ Data encrypted with marketing attribute'); + console.log("✅ Data encrypted with marketing attribute"); // 4. Save TDF to file - fs.writeFileSync('encrypted.tdf', encrypted); - console.log('✅ TDF saved to encrypted.tdf'); + fs.writeFileSync("encrypted.tdf", encrypted); + console.log("✅ TDF saved to encrypted.tdf"); // 5. Grant yourself access to the marketing attribute // Fetch the attribute to get the marketing value ID for the subject mapping const attrList = await platform.v1.attributes.listAttributes({}); const deptAttr = attrList.attributes.find( - (attr) => attr.name === 'department' && attr.namespace?.id === namespaceId + (attr) => attr.name === "department" && attr.namespace?.id === namespaceId, ); - const marketingValue = deptAttr?.values?.find((v) => v.value === 'marketing'); + const marketingValue = deptAttr?.values?.find((v) => v.value === "marketing"); if (!marketingValue?.id) { - throw new Error('Marketing value not found in department attribute'); + throw new Error("Marketing value not found in department attribute"); } // Subject condition sets use a nested structure: sets → groups → conditions. // This one matches any entity whose .clientId claim includes 'opentdf'. const scsResp = await platform.v1.subjectMapping.createSubjectConditionSet({ subjectConditionSet: { - subjectSets: [{ - conditionGroups: [{ - booleanOperator: ConditionBooleanTypeEnum.AND, - conditions: [{ - subjectExternalSelectorValue: '.clientId', - operator: SubjectMappingOperatorEnum.IN, - subjectExternalValues: ['opentdf'], - }], - }], - }], + subjectSets: [ + { + conditionGroups: [ + { + booleanOperator: ConditionBooleanTypeEnum.AND, + conditions: [ + { + subjectExternalSelectorValue: ".clientId", + operator: SubjectMappingOperatorEnum.IN, + subjectExternalValues: ["opentdf"], + }, + ], + }, + ], + }, + ], }, }); try { await platform.v1.subjectMapping.createSubjectMapping({ attributeValueId: marketingValue.id, - actions: [{ name: 'read' }], + actions: [{ name: "read" }], existingSubjectConditionSetId: scsResp.subjectConditionSet?.id, }); - console.log('✅ Granted yourself access to department/marketing'); + console.log("✅ Granted yourself access to department/marketing"); } catch (err) { - if (err.message?.includes('already_exists')) { - console.log('✅ Subject mapping already exists for department/marketing'); + if (err.message?.includes("already_exists")) { + console.log("✅ Subject mapping already exists for department/marketing"); } else { throw err; } } // 6. Load TDF from file - const tdfData = fs.readFileSync('encrypted.tdf'); - console.log('✅ TDF loaded from encrypted.tdf'); + const tdfData = fs.readFileSync("encrypted.tdf"); + console.log("✅ TDF loaded from encrypted.tdf"); // 7. Decrypt the data const decryptedStream = await client.read({ - source: { type: 'buffer', location: new Uint8Array(tdfData) }, + source: { type: "buffer", location: new Uint8Array(tdfData) }, }); const decryptedText = await new Response(decryptedStream).text(); - console.log('✅ Data successfully decrypted'); + console.log("✅ Data successfully decrypted"); console.log(`📤 Decrypted content: ${decryptedText}`); } diff --git a/docs/sdks/tdf.mdx b/docs/sdks/tdf.mdx index 0364aa94..db97107c 100644 --- a/docs/sdks/tdf.mdx +++ b/docs/sdks/tdf.mdx @@ -3,14 +3,12 @@ sidebar_position: 4 title: TDF --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import EncryptOptions from '../../code_samples/tdf/encrypt_options.mdx' -import DecryptOptions from '../../code_samples/tdf/decrypt_options.mdx' -import AssertionExamples from '../../code_samples/tdf/assertion_examples.mdx' -import JsAuthNote from '../../code_samples/js_auth_note.mdx' - -# TDF +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import EncryptOptions from "../../code_samples/tdf/encrypt_options.mdx"; +import DecryptOptions from "../../code_samples/tdf/decrypt_options.mdx"; +import AssertionExamples from "../../code_samples/tdf/assertion_examples.mdx"; +import JsAuthNote from "../../code_samples/js_auth_note.mdx"; A **TDF (Trusted Data Format)** wraps a payload in a cryptographic envelope that binds access control policy directly to the data. The TDF contains an encrypted payload, a manifest describing the policy and key access configuration, and optional assertions. @@ -124,28 +122,36 @@ try (FileChannel ch = FileChannel.open(tmp, StandardOpenOption.READ)) { ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; const client = new OpenTDF({ - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'client-id', - clientSecret: 'client-secret', - oidcOrigin: 'https://platform.example.com/auth/realms/opentdf', - }))], - platformUrl: 'https://platform.example.com', + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "client-id", + clientSecret: "client-secret", + oidcOrigin: "https://platform.example.com/auth/realms/opentdf", + }), + ), + ], + platformUrl: "https://platform.example.com", }); // Encrypt const enc = new TextEncoder(); const tdfStream = await client.createTDF({ - source: { type: 'buffer', location: enc.encode('hello, world') }, - defaultKASEndpoint: 'https://platform.example.com/kas', + source: { type: "buffer", location: enc.encode("hello, world") }, + defaultKASEndpoint: "https://platform.example.com/kas", }); // Decrypt const encrypted = new Uint8Array(await new Response(tdfStream).arrayBuffer()); const stream = await client.read({ - source: { type: 'buffer', location: encrypted }, + source: { type: "buffer", location: encrypted }, }); console.log(await new Response(stream).text()); // "hello, world" ``` @@ -155,11 +161,13 @@ console.log(await new Response(stream).text()); // "hello, world" --- -## CreateTDF +## Manage TDFs + +### CreateTDF Encrypts a plaintext payload and writes a TDF to the provided output destination. -**Signature** +#### Signature @@ -186,37 +194,37 @@ async createTDF(options: CreateTDFOptions): Promise -**Parameters** +#### Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| `writer` | `io.Writer` | Where the encrypted TDF bytes are written. | -| `reader` | `io.ReadSeeker` | The plaintext data to encrypt. | -| `opts` | `...TDFOption` | Encryption options. At minimum, a KAS endpoint must be specified (e.g., `sdk.WithKasInformation()`). See [Encrypt Options](#encrypt-options). | +| Parameter | Type | Description | +| --------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `writer` | `io.Writer` | Where the encrypted TDF bytes are written. | +| `reader` | `io.ReadSeeker` | The plaintext data to encrypt. | +| `opts` | `...TDFOption` | Encryption options. At minimum, a KAS endpoint must be specified (e.g., `sdk.WithKasInformation()`). See [Encrypt Options](#encrypt-options). | -| Parameter | Type | Description | -|-----------|------|-------------| -| `plaintext` | `InputStream` | The plaintext data to encrypt. | -| `out` | `OutputStream` | Where the encrypted TDF bytes are written. | -| `config` | `Config.TDFConfig` | Encryption options. At minimum, a KAS endpoint must be specified. See [Encrypt Options](#encrypt-options). | +| Parameter | Type | Description | +| ----------- | ------------------ | ---------------------------------------------------------------------------------------------------------- | +| `plaintext` | `InputStream` | The plaintext data to encrypt. | +| `out` | `OutputStream` | Where the encrypted TDF bytes are written. | +| `config` | `Config.TDFConfig` | Encryption options. At minimum, a KAS endpoint must be specified. See [Encrypt Options](#encrypt-options). | -| Parameter | Type | Description | -|-----------|------|-------------| +| Parameter | Type | Description | +| --------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `options` | `CreateTDFOptions` | A single options object containing the source, KAS endpoint, attributes, and other settings. See [Encrypt Options](#encrypt-options). | -**Example** +#### Example @@ -264,8 +272,11 @@ Manifest manifest = sdk.createTDF(in, out, config); ```typescript const tdf = await client.createTDF({ - source: { type: 'buffer', location: new TextEncoder().encode('Sensitive data') }, - defaultKASEndpoint: 'https://kas.example.com', + source: { + type: "buffer", + location: new TextEncoder().encode("Sensitive data"), + }, + defaultKASEndpoint: "https://kas.example.com", }); const encrypted = await new Response(tdf).bytes(); ``` @@ -275,7 +286,7 @@ const encrypted = await new Response(tdf).bytes(); See [Encrypt Options](#encrypt-options) for the full list of configuration options. -**Returns** +#### Returns @@ -295,24 +306,24 @@ See [Encrypt Options](#encrypt-options) for the full list of configuration optio -**Errors** +#### Errors -| Error | Cause | -|-------|-------| -| KAS unreachable | The KAS could not be contacted to wrap the encryption key. | +| Error | Cause | +| ----------------- | --------------------------------------------------------------------------- | +| KAS unreachable | The KAS could not be contacted to wrap the encryption key. | | No KAS configured | No KAS was provided and autoconfigure is disabled or could not resolve one. | -| Write failure | An I/O error occurred writing to the output destination. | +| Write failure | An I/O error occurred writing to the output destination. | -| Exception | Cause | -|-----------|-------| +| Exception | Cause | +| -------------- | ----------------------------------------------------------------------------------------------- | | `SDKException` | KAS unreachable, policy invalid, or the SDK could not resolve a KAS from the attribute service. | -| `IOException` | I/O failure reading from the input stream or writing to the output stream. | +| `IOException` | I/O failure reading from the input stream or writing to the output stream. | @@ -324,11 +335,11 @@ Rejects with `Error` if the KAS is unreachable, authentication fails, or the sou --- -## LoadTDF +### LoadTDF Opens an encrypted TDF and returns a [TDF reader](#tdf-reader) that provides access to the plaintext payload and manifest data. -**Signature** +#### Signature @@ -358,35 +369,35 @@ open(options: ReadOptions): TDFReader -**Parameters** +#### Parameters -| Parameter | Type | Description | -|-----------|------|-------------| -| `reader` | `io.ReadSeeker` | The encrypted TDF to open. | -| `opts` | `...TDFReaderOption` | Optional decryption settings. See [Decrypt Options](#decrypt-options). | +| Parameter | Type | Description | +| --------- | -------------------- | ---------------------------------------------------------------------- | +| `reader` | `io.ReadSeeker` | The encrypted TDF to open. | +| `opts` | `...TDFReaderOption` | Optional decryption settings. See [Decrypt Options](#decrypt-options). | -| Parameter | Type | Description | -|-----------|------|-------------| -| `channel` | `SeekableByteChannel` | The encrypted TDF to open. | -| `config` | `Config.TDFReaderConfig` | Decryption settings. Use `Config.newTDFReaderConfig()` for defaults. See [Decrypt Options](#decrypt-options). | +| Parameter | Type | Description | +| --------- | ------------------------ | ------------------------------------------------------------------------------------------------------------- | +| `channel` | `SeekableByteChannel` | The encrypted TDF to open. | +| `config` | `Config.TDFReaderConfig` | Decryption settings. Use `Config.newTDFReaderConfig()` for defaults. See [Decrypt Options](#decrypt-options). | -| Parameter | Type | Description | -|-----------|------|-------------| +| Parameter | Type | Description | +| --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `options` | `ReadOptions` | A single options object. `source` accepts a `buffer` (`Uint8Array`) or `stream` (`ReadableStream`). See [Decrypt Options](#decrypt-options). | -**Example** +#### Example @@ -434,7 +445,7 @@ try (var channel = FileChannel.open(tdfPath, StandardOpenOption.READ)) { ```typescript const stream = await client.read({ - source: { type: 'buffer', location: encryptedBytes }, + source: { type: "buffer", location: encryptedBytes }, }); const text = await new Response(stream).text(); console.log(text); @@ -447,7 +458,7 @@ See [Decrypt Options](#decrypt-options) for the full list of configuration optio See [TDF Reader](#tdf-reader) for all methods on the reader object. See [PolicyObject](#policyobject) and [Manifest Object](#manifest-object) for the types returned by reader methods. -**Returns** +#### Returns @@ -468,17 +479,17 @@ See [PolicyObject](#policyobject) and [Manifest Object](#manifest-object) for th -**Errors** +#### Errors -| Error | Cause | -|-------|-------| -| Invalid TDF | The input is not a valid TDF. Use [`IsValidTdf`](#isvalidtdf) to pre-validate. | -| KAS unreachable | The KAS referenced in the manifest cannot be contacted. | -| Access denied | The caller's credentials do not satisfy the TDF policy. | -| KAS not allowlisted | The TDF references a KAS endpoint not on the allowlist. | +| Error | Cause | +| ------------------- | ------------------------------------------------------------------------------ | +| Invalid TDF | The input is not a valid TDF. Use [`IsValidTdf`](#isvalidtdf) to pre-validate. | +| KAS unreachable | The KAS referenced in the manifest cannot be contacted. | +| Access denied | The caller's credentials do not satisfy the TDF policy. | +| KAS not allowlisted | The TDF references a KAS endpoint not on the allowlist. | @@ -495,11 +506,11 @@ Rejects with `Error` if the TDF is invalid, the KAS is unreachable, access is de --- -## IsValidTdf +### IsValidTdf Checks whether a byte stream contains a valid TDF without decrypting it. Specifically, it validates the ZIP container structure and parses the embedded manifest JSON against the TDF schema. No key access, HMAC verification, or payload decryption is performed. The stream position is restored after the check. -**Signature** +#### Signature @@ -520,13 +531,13 @@ static boolean SDK.isTDF(SeekableByteChannel channel) -**Parameters** +#### Parameters -| Parameter | Required | Description | -|-----------|----------|-------------| +| Parameter | Required | Description | +| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `reader` / `channel` | Required | The data to inspect. The stream position is reset after the check, so the same reader can be passed directly to `LoadTDF`. | -**Example** +#### Example @@ -579,7 +590,7 @@ try (var channel = FileChannel.open(tdfPath, StandardOpenOption.READ)) { -**Returns** +#### Returns @@ -594,53 +605,53 @@ try (var channel = FileChannel.open(tdfPath, StandardOpenOption.READ)) { -**Errors** +#### Errors A non-nil error (Go) or `IOException` (Java) indicates an I/O failure reading the stream — not that the TDF is invalid. An invalid TDF returns `false` with no error. --- -## BulkDecrypt +### BulkDecrypt Decrypts multiple TDFs in a single operation, batching KAS key rewrap requests to reduce round-trip overhead. :::tip Why use BulkDecrypt? -Calling `LoadTDF` in a loop issues a separate KAS rewrap request per TDF. `BulkDecrypt` batches all rewrap requests by KAS endpoint so that *N* TDFs targeting the same KAS require only one round-trip instead of *N*. Use it whenever you need to decrypt more than a handful of TDFs. +Calling `LoadTDF` in a loop issues a separate KAS rewrap request per TDF. `BulkDecrypt` batches all rewrap requests by KAS endpoint so that _N_ TDFs targeting the same KAS require only one round-trip instead of _N_. Use it whenever you need to decrypt more than a handful of TDFs. ::: -**Signature** +#### Signature ```go func (s SDK) BulkDecrypt(ctx context.Context, opts ...BulkDecryptOption) error func (s SDK) PrepareBulkDecrypt(ctx context.Context, opts ...BulkDecryptOption) (*BulkDecryptPrepared, error) ``` -**Parameters** +#### Parameters -| Parameter | Required | Description | -|-----------|----------|-------------| -| `ctx` | Required | Context for the operation, used for cancellation and deadlines. | -| `opts` | Required | At minimum, `sdk.WithTDFs(tdfs...)` must be provided. | +| Parameter | Required | Description | +| --------- | -------- | --------------------------------------------------------------- | +| `ctx` | Required | Context for the operation, used for cancellation and deadlines. | +| `opts` | Required | At minimum, `sdk.WithTDFs(tdfs...)` must be provided. | `sdk.WithTDFs` accepts one or more `*sdk.BulkTDF` structs: -| Field | Type | Description | -|-------|------|-------------| -| `Reader` | `io.ReadSeeker` | Source of the encrypted TDF. | -| `Writer` | `io.Writer` | Destination for the decrypted plaintext. | +| Field | Type | Description | +| ---------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `Reader` | `io.ReadSeeker` | Source of the encrypted TDF. | +| `Writer` | `io.Writer` | Destination for the decrypted plaintext. | | `TriggeredObligations` | `RequiredObligations` | Populated after rewrap with obligation FQNs required by this TDF. See [Obligations in bulk decrypt](#obligations-in-bulk-decrypt). | -**Options** +#### Options -| Option | Description | -|--------|-------------| -| `WithTDFs(tdfs...)` | **(Required)** The TDFs to decrypt. | +| Option | Description | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `WithTDFs(tdfs...)` | **(Required)** The TDFs to decrypt. | | `WithBulkKasAllowlist([]string{...})` | Restrict which KAS endpoints may be contacted. Only listed URLs are allowed; requests to unlisted KAS endpoints fail with an error per TDF. If omitted and a KAS registry is configured, the SDK uses the registry as the allowlist. | -| `WithBulkIgnoreAllowlist(true)` | Bypass KAS allowlist checks entirely. A warning is logged for each KAS URL contacted. Use only for testing or fully trusted environments. | -| `WithTDFType(tdfType)` | Specify the TDF format explicitly. Currently only `sdk.Standard` (TDF3 / ZIP-based) is supported. If omitted, the SDK auto-detects the format from each reader. | -| `WithTDF3DecryptOptions(opts...)` | Pass additional reader options (e.g., session key, assertion verification) through to each TDF3 decryptor. These are the same options accepted by `LoadTDF`. | +| `WithBulkIgnoreAllowlist(true)` | Bypass KAS allowlist checks entirely. A warning is logged for each KAS URL contacted. Use only for testing or fully trusted environments. | +| `WithTDFType(tdfType)` | Specify the TDF format explicitly. Currently only `sdk.Standard` (TDF3 / ZIP-based) is supported. If omitted, the SDK auto-detects the format from each reader. | +| `WithTDF3DecryptOptions(opts...)` | Pass additional reader options (e.g., session key, assertion verification) through to each TDF3 decryptor. These are the same options accepted by `LoadTDF`. | -**Example** +#### Example ```go import ( @@ -696,12 +707,12 @@ if err != nil { err = prepared.BulkDecrypt(ctx) ``` -**Returns** +#### Returns - `BulkDecrypt` returns `error`. If individual TDFs fail, extract per-TDF errors using `sdk.FromBulkErrors(err)`. - `PrepareBulkDecrypt` returns `(*BulkDecryptPrepared, error)`. Call `prepared.BulkDecrypt(ctx)` to execute after inspecting the prepared request. -**Errors** +#### Errors `BulkDecrypt` does **not** fail fast — it attempts every TDF and collects individual failures into a single `BulkErrors` value. Use `sdk.FromBulkErrors(err)` to extract the underlying `[]error` slice: @@ -796,7 +807,9 @@ String metadata = reader.getMetadata(); `.metadata` on the `DecoratedStream` returned by `client.read()`: ```typescript -const stream = await client.read({ source: { type: 'buffer', location: encryptedBytes } }); +const stream = await client.read({ + source: { type: "buffer", location: encryptedBytes }, +}); const metadata = await stream.metadata; ``` @@ -944,7 +957,7 @@ Obligations are PDP-to-PEP directives embedded in a TDF that specify additional Returns the obligation FQNs that the platform requires the consuming application to enforce before granting access to this TDF's payload. If obligations have not yet been fetched, calling this method triggers a rewrap request to the KAS (without decrypting the payload). Subsequent calls return the cached result. -**Signature** +#### Signature @@ -979,7 +992,7 @@ type RequiredObligations = { -**Example** +#### Example @@ -1012,14 +1025,23 @@ for _, fqn := range obligations.FQNs { ```typescript -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; +import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, +} from "@opentdf/sdk"; const client = new OpenTDF({ - interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))], - platformUrl: 'http://localhost:8080', + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + platformUrl: "http://localhost:8080", }); const reader = client.open({ source: ciphertextSource }); @@ -1088,9 +1110,7 @@ reader, err := client.LoadTDF( ```typescript const reader = client.open({ source: ciphertextSource, - fulfillableObligationFQNs: [ - 'https://example.com/obl/drm/value/watermarking', - ], + fulfillableObligationFQNs: ["https://example.com/obl/drm/value/watermarking"], }); ``` @@ -1133,8 +1153,8 @@ for _, fqn := range obligations.FQNs { const reader = client.open({ source: ciphertextSource, fulfillableObligationFQNs: [ - 'https://example.com/obl/drm/value/watermarking', - 'https://example.com/obl/drm/value/no-download', + "https://example.com/obl/drm/value/watermarking", + "https://example.com/obl/drm/value/no-download", ], }); @@ -1219,34 +1239,34 @@ Every assertion is bound to the TDF's [data encryption key (DEK)](/spec/protocol ### Assertion Types -| Constant | Value | Purpose | -|----------|-------|---------| +| Constant | Value | Purpose | +| ------------------- | ------------ | ----------------------------------------------------------------------------------------------------- | | `HandlingAssertion` | `"handling"` | Data handling and processing instructions — e.g., retention policies, access restrictions, DLP rules. | -| `BaseAssertion` | `"other"` | General-purpose metadata — e.g., audit labels, content identifiers, provenance records. | +| `BaseAssertion` | `"other"` | General-purpose metadata — e.g., audit labels, content identifiers, provenance records. | ### Scopes -| Constant | Value | Description | -|----------|-------|-------------| -| `TrustedDataObjScope` | `"tdo"` | Assertion applies to the entire TDF object. | -| `PayloadScope` | `"payload"` | Assertion applies only to the encrypted payload. | +| Constant | Value | Description | +| --------------------- | ----------- | ------------------------------------------------ | +| `TrustedDataObjScope` | `"tdo"` | Assertion applies to the entire TDF object. | +| `PayloadScope` | `"payload"` | Assertion applies only to the encrypted payload. | ### AppliesToState -| Constant | Value | When to process | -|----------|-------|-----------------| -| `Encrypted` | `"encrypted"` | Process **before** decryption — useful for access-control checks, audit logging, or routing decisions that don't require seeing the plaintext. | -| `Unencrypted` | `"unencrypted"` | Process **after** decryption — useful for content filtering, retention enforcement, or any logic that inspects the plaintext. | +| Constant | Value | When to process | +| ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `Encrypted` | `"encrypted"` | Process **before** decryption — useful for access-control checks, audit logging, or routing decisions that don't require seeing the plaintext. | +| `Unencrypted` | `"unencrypted"` | Process **after** decryption — useful for content filtering, retention enforcement, or any logic that inspects the plaintext. | ### Statement Each assertion carries a `Statement` with three fields: -| Field | Description | -|-------|-------------| +| Field | Description | +| -------- | --------------------------------------------------------------------- | | `Format` | Payload encoding format (e.g., `"application/json"`, `"text/plain"`). | -| `Schema` | Schema identifier for the value (e.g., `"retention-policy-v1"`). | -| `Value` | The assertion payload as a string — typically a JSON object. | +| `Schema` | Schema identifier for the value (e.g., `"retention-policy-v1"`). | +| `Value` | The assertion payload as a string — typically a JSON object. | ### Signing and Verification @@ -1254,10 +1274,10 @@ By default, every assertion is bound to the TDF using the DEK with HMAC-SHA256 ( To enable **independent verification** (where a verifier doesn't need the DEK), sign the assertion with an RSA private key (`RS256`) at encrypt time and provide the corresponding public key at decrypt time. -| Algorithm | Constant | Key type | Use case | -|-----------|----------|----------|----------| -| HMAC-SHA256 | `AssertionKeyAlgHS256` | Symmetric (byte slice / DEK) | Default binding — tamper detection for anyone who can decrypt. | -| RSA-SHA256 | `AssertionKeyAlgRS256` | RSA key pair or `crypto.Signer` | Independent verification — any holder of the public key can verify. | +| Algorithm | Constant | Key type | Use case | +| ----------- | ---------------------- | ------------------------------- | ------------------------------------------------------------------- | +| HMAC-SHA256 | `AssertionKeyAlgHS256` | Symmetric (byte slice / DEK) | Default binding — tamper detection for anyone who can decrypt. | +| RSA-SHA256 | `AssertionKeyAlgRS256` | RSA key pair or `crypto.Signer` | Independent verification — any holder of the public key can verify. | @@ -1273,17 +1293,17 @@ The following types are returned by or passed to the methods above. `KASInfo` is the input type passed to `WithKasInformation` (Go) or used to build a `Config.KASInfo` (Java). It identifies a KAS endpoint and the key configuration to use when wrapping the data encryption key. -**Fields** +#### Fields -| Field | Go | Java | Required | Description | -|-------|-----|------|----------|-------------| -| URL | `URL string` | `String URL` | Required | The KAS endpoint URL. | -| Algorithm | `Algorithm string` | `String Algorithm` | Optional | Wrapping key algorithm (e.g. `"ec:secp256r1"`). Defaults to `"rsa:2048"` if empty. | -| KID | `KID string` | `String KID` | Optional | Key identifier on the KAS, used when the KAS hosts multiple keys. | -| PublicKey | `PublicKey string` | `String PublicKey` | Optional | PEM-encoded public key. If empty, the SDK fetches it from the KAS. | -| Default | `Default bool` | — | Optional | If `true`, this KAS is used as the default for encrypt calls when no KAS is explicitly specified. | +| Field | Go | Java | Required | Description | +| --------- | ------------------ | ------------------ | -------- | ------------------------------------------------------------------------------------------------- | +| URL | `URL string` | `String URL` | Required | The KAS endpoint URL. | +| Algorithm | `Algorithm string` | `String Algorithm` | Optional | Wrapping key algorithm (e.g. `"ec:secp256r1"`). Defaults to `"rsa:2048"` if empty. | +| KID | `KID string` | `String KID` | Optional | Key identifier on the KAS, used when the KAS hosts multiple keys. | +| PublicKey | `PublicKey string` | `String PublicKey` | Optional | PEM-encoded public key. If empty, the SDK fetches it from the KAS. | +| Default | `Default bool` | — | Optional | If `true`, this KAS is used as the default for encrypt calls when no KAS is explicitly specified. | -**Example** +#### Example @@ -1327,15 +1347,15 @@ Config.TDFConfig config = Config.newTDFConfig( `PolicyObject` is returned by [`tdfReader.Policy()`](#policy) (Go) and `reader.readPolicyObject()` (Java). It contains the decoded access control policy embedded in the TDF — including the UUID, the list of data attribute FQNs, and the dissemination list. -**Fields** +#### Fields -| Field | Go | Java | Description | -|-------|-----|------|-------------| -| UUID | `UUID string` | `String uuid` | Unique identifier for this policy. | +| Field | Go | Java | Description | +| ------------------- | ------------------- | ----------------------- | ---------------------------------------------------------------------------------------------- | +| UUID | `UUID string` | `String uuid` | Unique identifier for this policy. | | Body.DataAttributes | `[]attributeObject` | `List` | Data attribute FQNs governing access. Each entry has an `Attribute` (FQN string) and `KasURL`. | -| Body.Dissem | `[]string` | `List` | Dissemination list — entity identifiers explicitly granted access. | +| Body.Dissem | `[]string` | `List` | Dissemination list — entity identifiers explicitly granted access. | -**Example** +#### Example @@ -1388,8 +1408,8 @@ The policy is embedded in the [manifest](#manifest-object). Use `reader.manifest ```typescript const manifest = await reader.manifest(); const policy = JSON.parse(atob(manifest.encryptionInformation.policy)); -console.log('Policy UUID:', policy.uuid); -console.log('Attributes:', policy.body.dataAttributes); +console.log("Policy UUID:", policy.uuid); +console.log("Attributes:", policy.body.dataAttributes); ``` @@ -1401,41 +1421,41 @@ console.log('Attributes:', policy.body.dataAttributes); The `Manifest` type is returned by [`CreateTDF`](#createtdf) and accessible via [`tdfReader.Manifest()`](#manifest). It describes the full TDF structure — encryption method, key access configuration, payload metadata, and any attached assertions — without requiring decryption. -**Top-Level Fields** +#### Top-Level Fields -| Field | Type | Description | -|-------|------|-------------| -| `TDFVersion` | `string` | TDF spec version (e.g. `"4.3.0"`). Serialized as `schemaVersion` in the manifest JSON. | -| `EncryptionInformation` | `EncryptionInformation` | Encryption method, key access objects, and integrity information. | -| `Payload` | `Payload` | Metadata about the encrypted payload. | -| `Assertions` | `[]Assertion` | Cryptographically bound or signed statements attached to the TDF. Empty if none were added at encrypt time. | +| Field | Type | Description | +| ----------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- | +| `TDFVersion` | `string` | TDF spec version (e.g. `"4.3.0"`). Serialized as `schemaVersion` in the manifest JSON. | +| `EncryptionInformation` | `EncryptionInformation` | Encryption method, key access objects, and integrity information. | +| `Payload` | `Payload` | Metadata about the encrypted payload. | +| `Assertions` | `[]Assertion` | Cryptographically bound or signed statements attached to the TDF. Empty if none were added at encrypt time. | -**EncryptionInformation Fields** +#### EncryptionInformation Fields -| Field | Type | Description | -|-------|------|-------------| -| `KeyAccessObjs` | `[]KeyAccess` | One entry per KAS holding a key grant. Split TDFs have multiple entries. | -| `Method.Algorithm` | `string` | Encryption algorithm (e.g. `"AES-256-GCM"`). | -| `Policy` | `string` | Base64-encoded policy object. Use [`tdfReader.Policy()`](#policy) to decode. | +| Field | Type | Description | +| ------------------ | ------------- | ---------------------------------------------------------------------------- | +| `KeyAccessObjs` | `[]KeyAccess` | One entry per KAS holding a key grant. Split TDFs have multiple entries. | +| `Method.Algorithm` | `string` | Encryption algorithm (e.g. `"AES-256-GCM"`). | +| `Policy` | `string` | Base64-encoded policy object. Use [`tdfReader.Policy()`](#policy) to decode. | -**KeyAccess Fields** +#### KeyAccess Fields -| Field | Type | Description | -|-------|------|-------------| -| `KasURL` | `string` | URL of the KAS holding the key grant. | -| `KeyType` | `string` | Key access type (`"wrapped"` or `"remote"`). | +| Field | Type | Description | +| --------- | -------- | ------------------------------------------------------------------------------------------------------------------ | +| `KasURL` | `string` | URL of the KAS holding the key grant. | +| `KeyType` | `string` | Key access type (`"wrapped"` or `"remote"`). | | `SplitID` | `string` | Key split identifier. Entries sharing the same ID share a key segment; different IDs represent independent splits. | -| `KID` | `string` | Key identifier on the KAS, if set. | +| `KID` | `string` | Key identifier on the KAS, if set. | -**Payload Fields** +#### Payload Fields -| Field | Type | Description | -|-------|------|-------------| -| `MimeType` | `string` | MIME type of the plaintext payload. Empty string if not set at encrypt time. | -| `IsEncrypted` | `bool` | Always `true` in a valid TDF. | -| `Protocol` | `string` | Protocol identifier (e.g. `"zip"`). | +| Field | Type | Description | +| ------------- | -------- | ---------------------------------------------------------------------------- | +| `MimeType` | `string` | MIME type of the plaintext payload. Empty string if not set at encrypt time. | +| `IsEncrypted` | `bool` | Always `true` in a valid TDF. | +| `Protocol` | `string` | Protocol identifier (e.g. `"zip"`). | -**Example** +#### Example ```go import ( @@ -1464,16 +1484,16 @@ for _, a := range manifest.Assertions { `AssertionConfig` is the input type passed to `WithAssertions` when creating a TDF. It defines the assertion content, classification, and optional signing key. -**Fields** +#### Fields -| Field | Go | Java | Required | Description | -|-------|-----|------|----------|-------------| -| ID | `ID string` | `String id` | Yes | Unique identifier for the assertion. Used to look up verification keys on decrypt. | -| Type | `Type AssertionType` | `Type type` | Yes | `HandlingAssertion` (`"handling"`) or `BaseAssertion` (`"other"`). | -| Scope | `Scope Scope` | `Scope scope` | Yes | `TrustedDataObjScope` (`"tdo"`) or `PayloadScope` (`"payload"`). | -| AppliesToState | `AppliesToState AppliesToState` | `AppliesToState appliesToState` | Yes | `Encrypted` or `Unencrypted`. | -| Statement | `Statement Statement` | `Statement statement` | No | The assertion content. See [Statement (type)](#statement-type). | -| SigningKey | `SigningKey AssertionKey` | `AssertionKey signingKey` | No | Custom signing key. If empty, the assertion is bound with the DEK using HS256. See [AssertionKey](#assertionkey). | +| Field | Go | Java | Required | Description | +| -------------- | ------------------------------- | ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | +| ID | `ID string` | `String id` | Yes | Unique identifier for the assertion. Used to look up verification keys on decrypt. | +| Type | `Type AssertionType` | `Type type` | Yes | `HandlingAssertion` (`"handling"`) or `BaseAssertion` (`"other"`). | +| Scope | `Scope Scope` | `Scope scope` | Yes | `TrustedDataObjScope` (`"tdo"`) or `PayloadScope` (`"payload"`). | +| AppliesToState | `AppliesToState AppliesToState` | `AppliesToState appliesToState` | Yes | `Encrypted` or `Unencrypted`. | +| Statement | `Statement Statement` | `Statement statement` | No | The assertion content. See [Statement (type)](#statement-type). | +| SigningKey | `SigningKey AssertionKey` | `AssertionKey signingKey` | No | Custom signing key. If empty, the assertion is bound with the DEK using HS256. See [AssertionKey](#assertionkey). | --- @@ -1481,38 +1501,38 @@ for _, a := range manifest.Assertions { `Assertion` is the signed form stored in the TDF [manifest](#manifest-object). Accessible via `tdfReader.Manifest().Assertions` after decryption. -**Fields** +#### Fields -| Field | Type | Description | -|-------|------|-------------| -| `ID` | `string` | Assertion identifier (matches the `AssertionConfig.ID` used at encrypt time). | -| `Type` | `AssertionType` | `"handling"` or `"other"`. | -| `Scope` | `Scope` | `"tdo"` or `"payload"`. | -| `AppliesToState` | `AppliesToState` | `"encrypted"` or `"unencrypted"`. | -| `Statement` | `Statement` | The assertion content. | -| `Binding` | `Binding` | Cryptographic binding — `Method` (`"jws"`) and `Signature` (JWS token). | +| Field | Type | Description | +| ---------------- | ---------------- | ----------------------------------------------------------------------------- | +| `ID` | `string` | Assertion identifier (matches the `AssertionConfig.ID` used at encrypt time). | +| `Type` | `AssertionType` | `"handling"` or `"other"`. | +| `Scope` | `Scope` | `"tdo"` or `"payload"`. | +| `AppliesToState` | `AppliesToState` | `"encrypted"` or `"unencrypted"`. | +| `Statement` | `Statement` | The assertion content. | +| `Binding` | `Binding` | Cryptographic binding — `Method` (`"jws"`) and `Signature` (JWS token). | --- ### Statement (type) -**Fields** +#### Fields -| Field | Type | Description | -|-------|------|-------------| -| `Format` | `string` | Payload encoding format (e.g., `"application/json"`, `"text/plain"`). | -| `Schema` | `string` | Schema identifier for the value (e.g., `"retention-policy-v1"`). | -| `Value` | `string` | The assertion payload — typically a JSON object serialized as a string. | +| Field | Type | Description | +| -------- | -------- | ----------------------------------------------------------------------- | +| `Format` | `string` | Payload encoding format (e.g., `"application/json"`, `"text/plain"`). | +| `Schema` | `string` | Schema identifier for the value (e.g., `"retention-policy-v1"`). | +| `Value` | `string` | The assertion payload — typically a JSON object serialized as a string. | --- ### AssertionKey -**Fields** +#### Fields -| Field | Type | Description | -|-------|------|-------------| -| `Alg` | `AssertionKeyAlg` | `AssertionKeyAlgRS256` (`"RS256"`) for RSA-SHA256, or `AssertionKeyAlgHS256` (`"HS256"`) for HMAC-SHA256. | +| Field | Type | Description | +| ----- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Alg` | `AssertionKeyAlg` | `AssertionKeyAlgRS256` (`"RS256"`) for RSA-SHA256, or `AssertionKeyAlgHS256` (`"HS256"`) for HMAC-SHA256. | | `Key` | `interface{}` / `Object` | For RS256: an RSA private key (encrypt) or public key (verify). For HS256: a byte slice / shared secret. Supports `crypto.Signer` for hardware-backed keys. | --- @@ -1521,19 +1541,23 @@ for _, a := range manifest.Assertions { Passed to `WithAssertionVerificationKeys` when loading a TDF. Maps assertion IDs to the public keys used to verify their signatures. -**Fields** +#### Fields -| Field | Type | Description | -|-------|------|-------------| -| `DefaultKey` | `AssertionKey` | Fallback key used when no ID-specific key is found. | -| `Keys` | `map[string]AssertionKey` | Map of assertion ID to verification key. | +| Field | Type | Description | +| ------------ | ------------------------- | --------------------------------------------------- | +| `DefaultKey` | `AssertionKey` | Fallback key used when no ID-specific key is found. | +| `Keys` | `map[string]AssertionKey` | Map of assertion ID to verification key. | --- +## Encrypt Options + --- +## Decrypt Options + --- @@ -1554,7 +1578,7 @@ The experimental `tdf.NewWriter` API uses a segment-based model where you write TDFs produced by `NewWriter` are fully compatible with the standard [`LoadTDF`](#loadtdf). -**Import** +### Import @@ -1576,7 +1600,7 @@ Not available in the JavaScript SDK. -**Basic usage** +### Basic usage @@ -1644,7 +1668,7 @@ Not available in the JavaScript SDK. -**Out-of-order segments** +### Out-of-order segments Segments are written by index and assembled in index order at `Finalize` time regardless of write order: @@ -1691,19 +1715,19 @@ Not available in the JavaScript SDK. -**Key options** +### Key options -| Option | Description | -|--------|-------------| -| `tdf.WithIntegrityAlgorithm(algorithm)` | Root integrity algorithm. Can be `tdf.HS256` (default) or `tdf.GMAC`. | +| Option | Description | +| --------------------------------------------- | --------------------------------------------------------------------- | +| `tdf.WithIntegrityAlgorithm(algorithm)` | Root integrity algorithm. Can be `tdf.HS256` (default) or `tdf.GMAC`. | | `tdf.WithSegmentIntegrityAlgorithm(tdf.GMAC)` | Per-segment hash algorithm — `GMAC` is faster for many small segments | -| `tdf.WithAttributeValues(values)` | Data attribute `*policy.Value` slice for ABAC | -| `tdf.WithDefaultKAS(kasKey)` | Default KAS for key wrapping (Finalize option) | -| `tdf.WithEncryptedMetadata(string)` | Metadata encrypted inside the key access objects | -| `tdf.WithPayloadMimeType(string)` | MIME type of the payload content | -| `tdf.WithSegments([]int)` | Restrict Finalize to a specific contiguous segment prefix | +| `tdf.WithAttributeValues(values)` | Data attribute `*policy.Value` slice for ABAC | +| `tdf.WithDefaultKAS(kasKey)` | Default KAS for key wrapping (Finalize option) | +| `tdf.WithEncryptedMetadata(string)` | Metadata encrypted inside the key access objects | +| `tdf.WithPayloadMimeType(string)` | MIME type of the payload content | +| `tdf.WithSegments([]int)` | Restrict Finalize to a specific contiguous segment prefix | -**Inspecting the manifest before finalization** +### Inspecting the manifest before finalization `GetManifest` returns a snapshot of the TDF manifest at any point during the writer's lifecycle: @@ -1743,30 +1767,30 @@ Not available in the JavaScript SDK. -**Result types** +### Result types ### SegmentResult Returned by each `WriteSegment` call. -| Field | Type | Description | -|-------|------|-------------| -| `TDFData` | `io.Reader` | Reader for the encrypted segment bytes (nonce + ciphertext + ZIP structures). Stream or store this before calling `Finalize`. | -| `Index` | `int` | The segment index that was written. | -| `Hash` | `string` | Base64-encoded integrity hash for this segment. | -| `PlaintextSize` | `int64` | Size of the original (unencrypted) data in bytes. | -| `EncryptedSize` | `int64` | Size of the encrypted data in bytes. | +| Field | Type | Description | +| --------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `TDFData` | `io.Reader` | Reader for the encrypted segment bytes (nonce + ciphertext + ZIP structures). Stream or store this before calling `Finalize`. | +| `Index` | `int` | The segment index that was written. | +| `Hash` | `string` | Base64-encoded integrity hash for this segment. | +| `PlaintextSize` | `int64` | Size of the original (unencrypted) data in bytes. | +| `EncryptedSize` | `int64` | Size of the encrypted data in bytes. | ### FinalizeResult Returned by `Finalize`. -| Field | Type | Description | -|-------|------|-------------| -| `Data` | `[]byte` | Manifest JSON and ZIP footer bytes. Does **not** contain the encrypted payload — segment data is returned by each `WriteSegment` call via `TDFData`. | -| `Manifest` | `*Manifest` | The complete [TDF manifest](#manifest-object) including key access objects, integrity information, and assertions. | -| `TotalSegments` | `int` | Number of segments written. | -| `TotalSize` | `int64` | Total plaintext bytes across all segments. | -| `EncryptedSize` | `int64` | Total encrypted bytes across all segments. | +| Field | Type | Description | +| --------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Data` | `[]byte` | Manifest JSON and ZIP footer bytes. Does **not** contain the encrypted payload — segment data is returned by each `WriteSegment` call via `TDFData`. | +| `Manifest` | `*Manifest` | The complete [TDF manifest](#manifest-object) including key access objects, integrity information, and assertions. | +| `TotalSegments` | `int` | Number of segments written. | +| `TotalSize` | `int64` | Total plaintext bytes across all segments. | +| `EncryptedSize` | `int64` | Total encrypted bytes across all segments. | For the full API reference, see the [pkg.go.dev documentation](https://pkg.go.dev/github.com/opentdf/platform/sdk/experimental/tdf). diff --git a/docs/sdks/troubleshooting.mdx b/docs/sdks/troubleshooting.mdx index 75a2d9f4..eea9ace8 100644 --- a/docs/sdks/troubleshooting.mdx +++ b/docs/sdks/troubleshooting.mdx @@ -3,11 +3,9 @@ sidebar_position: 9 title: Troubleshooting --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import JsAuthNote from '../../code_samples/js_auth_note.mdx' - -# SDK Troubleshooting +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JsAuthNote from "../../code_samples/js_auth_note.mdx"; This guide covers common issues when working with the OpenTDF SDKs and how to resolve them. @@ -20,11 +18,13 @@ This section covers issues when **using the SDKs in your applications**. For pla **Error**: `rpc error: code = Unauthenticated` or `401 Unauthorized` **Solution**: Verify your credentials are correct: + ```bash otdfctl auth client-credentials ``` Check that your identity provider is accessible: + ```bash curl https:/// ``` @@ -46,18 +46,31 @@ curl https:/// **Solution**: 1. **Re-authenticate** (CLI): + ```bash otdfctl auth client-credentials ``` 2. **Use a built-in token provider**: The SDK's token providers handle caching and auto-refresh automatically: - ```js - import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk'; - const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', - }))] }; + ```js + import { + authTokenInterceptor, + clientCredentialsTokenProvider, + OpenTDF, + } from "@opentdf/sdk"; + + const auth = { + interceptors: [ + authTokenInterceptor( + clientCredentialsTokenProvider({ + clientId: "opentdf", + clientSecret: "secret", + oidcOrigin: "http://localhost:8080/auth/realms/opentdf", + }), + ), + ], + }; const client = new OpenTDF({ ...auth, platformUrl }); ``` @@ -95,6 +108,7 @@ curl https:/// 2. **Restart your browser and terminal** after trusting the certificate — applications must reload their certificate stores to pick up the change. 3. **Skip certificate verification** (quick workaround for CLI only, not recommended for production): + ```bash export OTDFCTL_TLS_NO_VERIFY=true ``` @@ -104,6 +118,7 @@ curl https:/// **Error**: `connection refused` or `platform unreachable` **Solution**: Verify your platform is running: + ```bash curl https:///healthz ``` @@ -120,12 +135,12 @@ On Linux, `"server misbehaving"` is a DNS resolution failure. It can be triggere **Common scenarios:** -| Scenario | Symptom | Fix | -|---|---|---| -| Input file not readable by process | Encryption fails with a cryptic error | `chmod 644 ` or run as the correct user | -| Output directory not writable | TDF write fails | `chmod 755 ` or check directory ownership | -| `/etc/resolv.conf` or `/etc/hosts` not readable | `"server misbehaving"` on any gRPC call | `sudo chmod 644 /etc/resolv.conf /etc/hosts` | -| Docker volume ownership mismatch | Operations fail with cryptic errors | `sudo chown -R $USER:$USER ~/.opentdf/` | +| Scenario | Symptom | Fix | +| ----------------------------------------------- | --------------------------------------- | ----------------------------------------------------- | +| Input file not readable by process | Encryption fails with a cryptic error | `chmod 644 ` or run as the correct user | +| Output directory not writable | TDF write fails | `chmod 755 ` or check directory ownership | +| `/etc/resolv.conf` or `/etc/hosts` not readable | `"server misbehaving"` on any gRPC call | `sudo chmod 644 /etc/resolv.conf /etc/hosts` | +| Docker volume ownership mismatch | Operations fail with cryptic errors | `sudo chown -R $USER:$USER ~/.opentdf/` | **Diagnostic:** @@ -149,17 +164,20 @@ For platform-level permission issues (Docker volumes, install directory), see [M ## Import Errors **Go**: `package github.com/opentdf/platform/sdk is not in GOROOT` + ```bash go mod tidy go get github.com/opentdf/platform/sdk@latest ``` **Java**: `package io.opentdf.platform.sdk does not exist` + ```bash mvn clean install -U ``` **JavaScript**: `Cannot find module '@opentdf/client'` + ```bash npm install @opentdf/sdk ``` @@ -171,13 +189,14 @@ npm install @opentdf/sdk -``` +```text reader.WriteTo failed: splitKey.unable to reconstruct split key: map[{https:// }:tdf: rewrap request 403 kao unwrap failed for split {https:// }: permission_denied: request error rpc error: code = PermissionDenied desc = forbidden] ``` **What to Look For**: The meaningful information is buried in the error. Look for: + - `PermissionDenied` or `forbidden` - `rewrap request 403` - `permission_denied: request error` @@ -185,13 +204,14 @@ rpc error: code = PermissionDenied desc = forbidden] -``` +```text io.opentdf.platform.sdk.SDK$SplitKeyException: splitKey.unable to reconstruct split key: {KeySplitStep{kas='https:///kas', splitID=''}=io.opentdf.platform.sdk.SDKException: error unwrapping key} error unwrapping key ``` **What to Look For**: The meaningful information is: + - `splitKey.unable to reconstruct split key` - `error unwrapping key` - `SplitKeyException` @@ -199,12 +219,13 @@ error unwrapping key -``` +```text Error: splitKey.unable to reconstruct split key error unwrapping key ``` **What to Look For**: The meaningful information is: + - `unable to reconstruct split key` - `error unwrapping key` @@ -264,15 +285,15 @@ The Go SDK v0.16.0 changed how KAS (Key Access Server) errors are classified. If 1. **Integrity failure (tamper)** — the TDF's policy binding or encrypted key has been modified 2. **Misconfiguration** — the request was malformed, used an unsupported key type, or the client lacks permission -**What changed**: Prior to v0.16.0, *all* KAS 400-level errors were classified as `ErrTampered`. This made `errors.Is(err, sdk.ErrTampered)` unreliable — it returned `true` even for configuration errors. Starting in v0.16.0, the SDK distinguishes between tamper and misconfiguration: +**What changed**: Prior to v0.16.0, _all_ KAS 400-level errors were classified as `ErrTampered`. This made `errors.Is(err, sdk.ErrTampered)` unreliable — it returned `true` even for configuration errors. Starting in v0.16.0, the SDK distinguishes between tamper and misconfiguration: -| Error source | SDK sentinel | `errors.Is(err, sdk.ErrTampered)`? | -|---|---|---| -| Policy binding mismatch | `sdk.ErrRewrapBadRequest` | Yes | -| DEK decryption failure | `sdk.ErrRewrapBadRequest` | Yes | -| Corrupted policy body | `sdk.ErrRewrapBadRequest` | Yes | -| Misconfiguration (e.g., wrong key type) | `sdk.ErrKASRequestError` | No | -| Access denied (403) | `sdk.ErrRewrapForbidden` | No | +| Error source | SDK sentinel | `errors.Is(err, sdk.ErrTampered)`? | +| --------------------------------------- | ------------------------- | ---------------------------------- | +| Policy binding mismatch | `sdk.ErrRewrapBadRequest` | Yes | +| DEK decryption failure | `sdk.ErrRewrapBadRequest` | Yes | +| Corrupted policy body | `sdk.ErrRewrapBadRequest` | Yes | +| Misconfiguration (e.g., wrong key type) | `sdk.ErrKASRequestError` | No | +| Access denied (403) | `sdk.ErrRewrapForbidden` | No | **Migration**: If your code catches all KAS errors via `ErrTampered`, add a check for `ErrKASRequestError`: @@ -325,7 +346,8 @@ For more detail on configuring subject mappings, see [Policy Management](/sdks/p **Error**: `already_exists: resource unique field violation` **Example**: -``` + +```text 2026/02/02 15:45:14 Failed to create namespace: already_exists: resource unique field violation exit status 1 ```