diff --git a/docs/sdks/discovery.mdx b/docs/sdks/discovery.mdx index ec7a2c4b..04ba3ba3 100644 --- a/docs/sdks/discovery.mdx +++ b/docs/sdks/discovery.mdx @@ -13,6 +13,57 @@ Before encrypting data with `CreateTDF`, it helps to verify that the attributes The Go, Java, and JavaScript SDKs provide five methods to discover and validate attributes without manual platform queries. +## Setup + +All examples on this page assume you have created a platform client. See [Authentication](/sdks/authentication) for full details including DPoP key binding. + + + + +```go +client, err := sdk.New("http://localhost:8080", + sdk.WithClientCredentials("opentdf", "secret", nil), +) +if err != nil { + log.Fatal(err) +} + +// All Go snippets below use `client` and `context.Background()`. +``` + + + + +```java +SDK sdk = SDKBuilder.newBuilder() + .platformEndpoint("http://localhost:8080") + .clientSecret("opentdf", "secret") + .useInsecurePlaintextConnection(true) + .build(); + +// All Java snippets below use `sdk`. +``` + + + + +```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'; + +// All JavaScript snippets below use `auth` and `platformUrl`. +// Standalone functions like listAttributes() take these as arguments. +``` + + + + --- @@ -21,6 +72,41 @@ The Go, Java, and JavaScript SDKs provide five methods to discover and validate Returns all active attributes on the platform. Use this to see what's available before choosing which attributes to apply to a TDF. +**Signature** + + + + +```go +client.ListAttributes(ctx, ...namespaceFilter) +``` + + + + +```java +sdk.listAttributes() +sdk.listAttributes(namespaceFilter) +``` + + + + +```typescript +await listAttributes(platformUrl, auth, namespaceFilter?) +``` + + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `namespaceFilter` | `string` | No | When provided, returns only attributes in this namespace (e.g., `"opentdf.io"`). | + +**Example** + @@ -67,13 +153,8 @@ List attrs = sdk.listAttributes("opentdf.io"); -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, listAttributes } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { listAttributes } from '@opentdf/sdk'; const attrs = await listAttributes(platformUrl, auth); @@ -87,27 +168,57 @@ for (const a of attrs) { To filter by namespace: -```ts +```typescript const attrs = await listAttributes(platformUrl, auth, 'opentdf.io'); ``` -`listAttributes` / `ListAttributes` paginates automatically — you always get the full list back without needing to manage page tokens. +**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. --- ## AttributeExists -Reports whether an attribute definition exists on the platform. Returns `true`/`false` — useful for a quick existence check before adding values to an attribute or constructing an access policy. +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. -`attributeFqn` should be an **attribute-level** FQN (no `/value/` segment): +**Signature** + + + +```go +client.AttributeExists(ctx, attributeFqn) ``` -https:///attr/ + + + + +```java +sdk.attributeExists(attributeFqn) ``` + + + +```typescript +await attributeExists(platformUrl, auth, attributeFqn) +``` + + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `attributeFqn` | `string` | Yes | An attribute-level FQN (no `/value/` segment): `https:///attr/` | + +**Example** + @@ -134,13 +245,8 @@ if (!exists) { -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, attributeExists } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { attributeExists } from '@opentdf/sdk'; const exists = await attributeExists(platformUrl, auth, 'https://opentdf.io/attr/department'); if (!exists) { @@ -151,18 +257,50 @@ if (!exists) { +**Returns** + +`true` if the attribute exists, `false` otherwise. + --- ## AttributeValueExists Reports whether a specific attribute value FQN exists on the platform. Returns `true`/`false` — useful for spot-checking one pre-registered value. -`valueFqn` should be a **full attribute value** FQN (with `/value/` segment): +**Signature** + + + + +```go +client.AttributeValueExists(ctx, valueFqn) +``` + + + +```java +sdk.attributeValueExists(valueFqn) ``` -https:///attr//value/ + + + + +```typescript +await attributeValueExists(platformUrl, auth, valueFqn) ``` + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `valueFqn` | `string` | Yes | A full attribute value FQN: `https:///attr//value/` | + +**Example** + @@ -189,13 +327,8 @@ if (!exists) { -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, attributeValueExists } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { attributeValueExists } from '@opentdf/sdk'; const exists = await attributeValueExists(platformUrl, auth, 'https://opentdf.io/attr/department/value/finance'); if (!exists) { @@ -206,12 +339,50 @@ if (!exists) { +**Returns** + +`true` if the value exists, `false` otherwise. + --- ## ValidateAttributes 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** + + + + +```go +client.ValidateAttributes(ctx, fqns...) +``` + + + + +```java +sdk.validateAttributes(fqns) +``` + + + + +```typescript +await validateAttributes(platformUrl, auth, fqns) +``` + + + + +**Parameters** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `fqns` | `list of strings` | Yes | Attribute value FQNs to validate (up to 250 per call). | + +**Example** + @@ -223,8 +394,7 @@ fqns := []string{ if err := client.ValidateAttributes(ctx, fqns...); err != nil { log.Fatalf("attribute validation failed: %v", err) - // err will name the specific FQNs that are missing, e.g.: - // "attribute not found: https://opentdf.io/attr/clearance/value/executive" + // err will name the specific FQNs that are missing } // Safe to encrypt — all attributes confirmed present @@ -247,8 +417,7 @@ try { sdk.validateAttributes(fqns); } catch (SDK.AttributeNotFoundException e) { System.err.println("attribute validation failed: " + e.getMessage()); - // getMessage() names the specific FQNs that are missing, e.g.: - // "attribute not found: https://opentdf.io/attr/clearance/value/executive" + // getMessage() names the specific FQNs that are missing } // Safe to encrypt — all attributes confirmed present @@ -262,13 +431,8 @@ sdk.createTDF(inputStream, outputStream, config); -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; - -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: 'http://localhost:8080/auth/realms/opentdf', -}))] }; +```typescript +import { validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; const fqns = [ 'https://opentdf.io/attr/department/value/marketing', @@ -280,8 +444,7 @@ try { } catch (e) { if (e instanceof AttributeNotFoundError) { console.error('attribute validation failed:', e.message); - // e.message names the specific FQNs that are missing, e.g.: - // "attribute not found: https://opentdf.io/attr/clearance/value/executive" + // e.message names the specific FQNs that are missing } } @@ -291,17 +454,61 @@ try { -**Why this matters**: `CreateTDF` succeeds even when the attribute doesn't exist on the platform (it embeds the FQN string in the TDF policy). The failure only surfaces later when a decryption client attempts to resolve the attribute through the KAS and gets a `not_found` response. `ValidateAttributes` makes that failure happen immediately, at the point of encryption, with a clear message. +**Returns** + +No return value on success. Throws/returns an error naming the specific FQNs that are missing. + +**Why this matters**: `CreateTDF` succeeds even when the attribute doesn't exist on the platform — it embeds the FQN string in the TDF policy. The failure only surfaces later when a decryption client attempts to resolve the attribute through the KAS. `ValidateAttributes` makes that failure happen immediately, with a clear message. :::tip -`validateAttributes` / `ValidateAttributes` accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, batch your FQNs across multiple calls. +Accepts up to 250 FQNs per call, matching the platform's limit. For larger sets, batch across multiple calls. ::: --- ## GetEntityAttributes -Returns the attribute value FQNs assigned to a specific entity (person entity or non-person entity). Use this to inspect what a user, service account, or other principal has been granted — for example, to understand why someone can or cannot decrypt a TDF. +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** + + + + +```go +client.GetEntityAttributes(ctx, entity) +``` + + + + +```java +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** + +**Go/Java:** + +| 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** @@ -374,14 +581,11 @@ Entity.newBuilder().setId("e1").setUuid("550e8400-e29b-41d4-a716-446655440000"). -The JavaScript SDK does not expose a `getEntityAttributes` convenience function because `GetEntitlements` is a server-side/PEP operation that should not be called from browser contexts. In a Node.js server environment, use the `PlatformClient` directly: - -```ts +```typescript import { PlatformClient } from '@opentdf/sdk/platform'; const platform = new PlatformClient({ ...auth, platformUrl }); -// By email address const resp = await platform.v2.authorization.getEntitlements({ entityIdentifier: { identifier: { @@ -406,7 +610,7 @@ if (resp.entitlements.length > 0) { Other supported entity types (placed inside the `entities` array): -```ts +```typescript // By username { ephemeralId: 'e1', entityType: { case: 'userName', value: 'alice' } } @@ -417,13 +621,17 @@ Other supported entity types (placed inside the `entities` array): +**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. + :::warning Authorization required -Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error. Contact your platform administrator to confirm your service account has the necessary policy access. +Querying another entity's attributes requires your caller to have appropriate platform permissions on the `GetEntitlements` endpoint. Without this, the call will return a `permission_denied` error. ::: --- -## Putting It Together +## Typical Workflow A common pattern is to validate attributes at application startup and surface helpful errors before any encryption happens: @@ -431,100 +639,78 @@ A common pattern is to validate attributes at application startup and surface he ```go -func main() { - client, err := sdk.New(platformEndpoint, - sdk.WithClientCredentials("opentdf", "secret", nil), - sdk.WithInsecureSkipVerifyConn(), - ) - if err != nil { - log.Fatalf("sdk init failed: %v", err) - } - - // 1. See what's available on the platform - attrs, err := client.ListAttributes(ctx) - if err != nil { - log.Fatalf("could not reach platform: %v", err) - } - log.Printf("platform has %d active attributes", len(attrs)) +// 1. See what's available on the platform +attrs, err := client.ListAttributes(ctx) +if err != nil { + log.Fatalf("could not reach platform: %v", err) +} +log.Printf("platform has %d active attributes", len(attrs)) - // 2. Check a specific attribute exists before using it - exists, err := client.AttributeExists(ctx, "https://opentdf.io/attr/department") - if err != nil { - log.Fatalf("service error: %v", err) - } - if !exists { - log.Fatalf("attribute missing — create it first") - } +// 2. Check a specific attribute exists before using it +exists, err := client.AttributeExists(ctx, "https://opentdf.io/attr/department") +if err != nil { + log.Fatalf("service error: %v", err) +} +if !exists { + log.Fatalf("attribute missing — create it first") +} - // 3. Validate the specific values before encrypting - required := []string{ - "https://opentdf.io/attr/department/value/marketing", - } - if err := client.ValidateAttributes(ctx, required...); err != nil { - log.Fatalf("required attributes missing — create them first: %v", err) - } +// 3. Validate the specific values before encrypting +required := []string{ + "https://opentdf.io/attr/department/value/marketing", +} +if err := client.ValidateAttributes(ctx, required...); err != nil { + log.Fatalf("required attributes missing: %v", err) +} - // 4. Encrypt with confidence - _, err = client.CreateTDF(encryptedBuffer, dataReader, - sdk.WithDataAttributes(required...), - sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}), - ) - if err != nil { - log.Fatalf("encryption failed: %v", err) - } - log.Println("data encrypted successfully") +// 4. Encrypt with confidence +_, err = client.CreateTDF(encryptedBuffer, dataReader, + sdk.WithDataAttributes(required...), + sdk.WithKasInformation(sdk.KASInfo{URL: platformEndpoint}), +) +if err != nil { + log.Fatalf("encryption failed: %v", err) } +log.Println("data encrypted successfully") ``` ```java -try (SDK sdk = new SDKBuilder() - .platformEndpoint(platformEndpoint) - .clientSecret("opentdf", "secret") - .build()) { - - // 1. See what's available on the platform - List attrs = sdk.listAttributes(); - System.out.printf("platform has %d active attributes%n", attrs.size()); - - // 2. Check a specific attribute exists before using it - if (!sdk.attributeExists("https://opentdf.io/attr/department")) { - System.err.println("attribute missing — create it first"); - return; - } +// 1. See what's available on the platform +List attrs = sdk.listAttributes(); +System.out.printf("platform has %d active attributes%n", attrs.size()); - // 3. Validate the specific values before encrypting - List required = List.of("https://opentdf.io/attr/department/value/marketing"); - try { - sdk.validateAttributes(required); - } catch (SDK.AttributeNotFoundException e) { - System.err.println("required attributes missing — create them first: " + e.getMessage()); - return; - } +// 2. Check a specific attribute exists before using it +if (!sdk.attributeExists("https://opentdf.io/attr/department")) { + System.err.println("attribute missing — create it first"); + return; +} - // 4. Encrypt with confidence - Config.TDFConfig config = Config.newTDFConfig( - Config.withDataAttributes(required), - Config.withKasInformation(new Config.KASInfo(platformEndpoint, null)) - ); - sdk.createTDF(inputStream, outputStream, config); - System.out.println("data encrypted successfully"); +// 3. Validate the specific values before encrypting +List required = List.of("https://opentdf.io/attr/department/value/marketing"); +try { + sdk.validateAttributes(required); +} catch (SDK.AttributeNotFoundException e) { + System.err.println("required attributes missing: " + e.getMessage()); + return; } + +// 4. Encrypt with confidence +Config.TDFConfig config = Config.newTDFConfig( + Config.withDataAttributes(required), + Config.withKasInformation(new Config.KASInfo(platformEndpoint, null)) +); +sdk.createTDF(inputStream, outputStream, config); +System.out.println("data encrypted successfully"); ``` -```ts -import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF, listAttributes, attributeExists, validateAttributes, AttributeNotFoundError } from '@opentdf/sdk'; - -// Define auth once — reuse for standalone functions and the OpenTDF client -const auth = { interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({ - clientId: 'opentdf', clientSecret: 'secret', - oidcOrigin: oidcEndpoint, -}))] }; +```typescript +import { listAttributes, attributeExists, validateAttributes, AttributeNotFoundError, OpenTDF } from '@opentdf/sdk'; // 1. See what's available on the platform const attrs = await listAttributes(platformUrl, auth); @@ -543,7 +729,7 @@ try { await validateAttributes(platformUrl, auth, required); } catch (e) { if (e instanceof AttributeNotFoundError) { - console.error('required attributes missing — create them first:', e.message); + console.error('required attributes missing:', e.message); process.exit(1); } throw e; diff --git a/docs/sdks/platform-client.mdx b/docs/sdks/platform-client.mdx index cfeaf4f7..6995b5cc 100644 --- a/docs/sdks/platform-client.mdx +++ b/docs/sdks/platform-client.mdx @@ -1,13 +1,13 @@ --- sidebar_position: 1 -title: Architecture +title: Overview --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import JsAuthNote from '../../code_samples/js_auth_note.mdx' -# Architecture +# Overview 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. @@ -89,3 +89,156 @@ const platform = new PlatformClient({ ...auth, platformUrl: 'http://localhost:80 + +## Response Objects + +All platform API calls return protobuf response objects. The way you access fields differs by language: **Go** uses generated getter methods prefixed with `Get`, **Java** uses standard getters, and **JavaScript** uses direct property access with optional chaining. + +**GetAttributeValue** — accessing a policy attribute value: + + + + +```go +resp, err := client.Attributes.GetAttributeValue(ctx, req) +v := resp.GetValue() +v.GetValue() // "confidential" +v.GetFqn() // "https://example.com/attr/classification/value/confidential" +v.GetId() // "a1b2c3d4-..." +``` + + + + +```java +var resp = sdk.getServices().attributes() + .getAttributeValueBlocking(req, Collections.emptyMap()).execute(); +var v = resp.getValue(); +v.getValue(); // "confidential" +v.getFqn(); // "https://example.com/attr/classification/value/confidential" +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-..." +``` + + + + +**ListAttributeValues** — iterating over a list response: + + + + +```go +resp, err := client.Attributes.ListAttributeValues(ctx, req) +for _, v := range resp.GetValues() { + v.GetValue() // "confidential" + v.GetFqn() // "https://example.com/attr/classification/value/confidential" +} +``` + + + + +```java +var resp = sdk.getServices().attributes() + .listAttributeValuesBlocking(req, Collections.emptyMap()).execute(); +for (var v : resp.getValuesList()) { + v.getValue(); // "confidential" + v.getFqn(); // "https://example.com/attr/classification/value/confidential" +} +``` + + + + +```typescript +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" +} +``` + + + + +**GetDecision** — accessing an authorization decision: + + + + +```go +resp, err := client.AuthorizationV2.GetDecision(ctx, req) +d := resp.GetDecision() +d.GetDecision() // DECISION_PERMIT or DECISION_DENY +d.GetRequiredObligations() // []string of obligation value FQNs +``` + + + + +```java +var resp = sdk.getServices().authorization().getDecision(req).get(); +var d = resp.getDecision(); +d.getDecision(); // DECISION_PERMIT or DECISION_DENY +d.getRequiredObligationsList(); // List of obligation value FQNs +``` + + + + +```typescript +const resp = await platform.v2.authorization.getDecision({ ... }); +resp.decision?.decision; // Decision.PERMIT or Decision.DENY +resp.decision?.requiredObligations; // string[] of obligation value FQNs +``` + + + + +**GetEntitlements** — accessing entity entitlements: + + + + +```go +resp, err := client.AuthorizationV2.GetEntitlements(ctx, req) +for _, e := range resp.GetEntitlements() { + e.GetActionsPerAttributeValueFqn() // map of FQN → permitted actions +} +``` + + + + +```java +var resp = sdk.getServices().authorization().getEntitlements(req).get(); +for (var e : resp.getEntitlementsList()) { + e.getActionsPerAttributeValueFqnMap(); // Map +} +``` + + + + +```typescript +const resp = await platform.v2.authorization.getEntitlements({ ... }); +for (const e of resp.entitlements) { + e.actionsPerAttributeValueFqn; // Record +} +``` + + + + +This pattern applies to all platform objects. Each SDK page includes type reference tables listing the available fields. +