Skip to content

Commit c683024

Browse files
samsternbergclaude
andcommitted
feat(auth): Add JSON object context support for Conditional Data Access
The ctx claim in bearer tokens and signed data tokens previously only accepted a String, which meant structured CEL expressions like request.context.role == 'admin' could not be satisfied. Add setCtx(Map<String, Object>) overloads to BearerToken and SignedDataTokens builders so the JWT ctx claim is serialized as a nested JSON object. Also add setContext(Map) and getContextAsObject() to Credentials for use with the high-level Skyflow client. All changes are backwards compatible — existing setCtx(String) and setContext(String) APIs are unchanged. Refs SK-2679 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9d69e23 commit c683024

13 files changed

Lines changed: 527 additions & 90 deletions

File tree

README.md

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2771,10 +2771,14 @@ public class BearerTokenGenerationExample {
27712771

27722772
## Generate bearer tokens with context
27732773

2774-
**Context-aware authorization** embeds context values into a bearer token during its generation and so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization. .
2774+
**Context-aware authorization** embeds context values into a bearer token during its generation and so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization.
27752775

27762776
A service account with the `context_id` identifier generates bearer tokens containing context information, represented as a JWT claim in a Skyflow-generated bearer token. Tokens generated from such service accounts include a `context_identifier` claim, are valid for 60 minutes, and can be used to make API calls to the Data and Management APIs, depending on the service account's permissions.
27772777

2778+
The context can be provided as a simple string or as a `Map<String, Object>` for structured context. Use a `Map` when your policies use Conditional Data Access with CEL expressions that reference nested context fields (e.g., `request.context.role == 'admin'`).
2779+
2780+
### String context
2781+
27782782
[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java):
27792783

27802784
```java
@@ -2783,60 +2787,44 @@ import com.skyflow.serviceaccount.util.BearerToken;
27832787

27842788
import java.io.File;
27852789

2786-
/**
2787-
* Example program to generate a Bearer Token using Skyflow's BearerToken utility.
2788-
* The token is generated using two approaches:
2789-
* 1. By providing the credentials.json file path.
2790-
* 2. By providing the contents of credentials.json as a string.
2791-
*/
2792-
public class BearerTokenGenerationWithContextExample {
2793-
public static void main(String[] args) {
2794-
// Variable to store the generated Bearer Token
2795-
String bearerToken = null;
2790+
// Generate Bearer Token with a simple string context
2791+
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
27962792

2797-
// Approach 1: Generate Bearer Token by specifying the path to the credentials.json file
2798-
try {
2799-
// Replace <YOUR_CREDENTIALS_FILE_PATH> with the full path to your credentials.json file
2800-
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
2793+
BearerToken token = BearerToken.builder()
2794+
.setCredentials(new File(filePath))
2795+
.setCtx("abc") // Simple string context
2796+
.build();
28012797

2802-
// Create a BearerToken object using the file path
2803-
BearerToken token = BearerToken.builder()
2804-
.setCredentials(new File(filePath)) // Set credentials using a File object
2805-
.setCtx("abc") // Set context string (example: "abc")
2806-
.build(); // Build the BearerToken object
2798+
String bearerToken = token.getBearerToken();
2799+
```
28072800

2808-
// Retrieve the Bearer Token as a string
2809-
bearerToken = token.getBearerToken();
2801+
### JSON object context (Conditional Data Access)
28102802

2811-
// Print the generated Bearer Token to the console
2812-
System.out.println(bearerToken);
2813-
} catch (SkyflowException e) {
2814-
// Handle exceptions specific to Skyflow operations
2815-
e.printStackTrace();
2816-
}
2803+
Skyflow's [Conditional Data Access](https://docs.skyflow.com/docs/governance/roles/conditional-data-access/overview) feature enables dynamic, context-aware access control by allowing roles to activate only when specific conditions are met at runtime. Conditions are defined using Common Expression Language (CEL) expressions that evaluate against `request.context`, `request.time`, and `request.originIP`.
28172804

2818-
// Approach 2: Generate Bearer Token by specifying the contents of credentials.json as a string
2819-
try {
2820-
// Replace <YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING> with the actual contents of your credentials.json file
2821-
String fileContents = "<YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING>";
2805+
To satisfy context-based conditions, pass a `Map<String, Object>` to `setCtx()`. The map is embedded as a nested JSON object in the JWT `ctx` claim, allowing CEL expressions to reference individual fields.
28222806

2823-
// Create a BearerToken object using the file contents as a string
2824-
BearerToken token = BearerToken.builder()
2825-
.setCredentials(fileContents) // Set credentials using a string representation of the file
2826-
.setCtx("abc") // Set context string (example: "abc")
2827-
.build(); // Build the BearerToken object
2807+
```java
2808+
import com.skyflow.errors.SkyflowException;
2809+
import com.skyflow.serviceaccount.util.BearerToken;
28282810

2829-
// Retrieve the Bearer Token as a string
2830-
bearerToken = token.getBearerToken();
2811+
import java.io.File;
2812+
import java.util.HashMap;
2813+
import java.util.Map;
28312814

2832-
// Print the generated Bearer Token to the console
2833-
System.out.println(bearerToken);
2834-
} catch (SkyflowException e) {
2835-
// Handle exceptions specific to Skyflow operations
2836-
e.printStackTrace();
2837-
}
2838-
}
2839-
}
2815+
// Create a context map matching your Conditional Data Access policy
2816+
// For example, if your policy condition is:
2817+
// request.context.role == 'admin' && request.context.project_id == 'proj_123'
2818+
Map<String, Object> context = new HashMap<>();
2819+
context.put("role", "admin");
2820+
context.put("project_id", "proj_123");
2821+
2822+
BearerToken token = BearerToken.builder()
2823+
.setCredentials(new File("<YOUR_CREDENTIALS_FILE_PATH>"))
2824+
.setCtx(context) // JSON object context for Conditional Data Access
2825+
.build();
2826+
2827+
String bearerToken = token.getBearerToken();
28402828
```
28412829

28422830
## Generate scoped bearer tokens
@@ -2903,6 +2891,8 @@ with the private key of the service account credentials, which adds an additiona
29032891
be detokenized by passing the signed data token and a bearer token generated from service account credentials. The
29042892
service account must have appropriate permissions and context to detokenize the signed data tokens.
29052893

2894+
Like bearer tokens, the context can be provided as a simple string or as a `Map<String, Object>` for Conditional Data Access.
2895+
29062896
[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java):
29072897

29082898
```java
@@ -2912,12 +2902,15 @@ import com.skyflow.serviceaccount.util.SignedDataTokens;
29122902

29132903
import java.io.File;
29142904
import java.util.ArrayList;
2905+
import java.util.HashMap;
29152906
import java.util.List;
2907+
import java.util.Map;
29162908

29172909
public class SignedTokenGenerationExample {
29182910
public static void main(String[] args) {
29192911
List<SignedDataTokenResponse> signedTokenValues;
2920-
// Generate Signed data token with context by specifying credentials.json file path
2912+
2913+
// Generate Signed data token with string context
29212914
try {
29222915
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
29232916
String context = "abc";
@@ -2935,15 +2928,17 @@ public class SignedTokenGenerationExample {
29352928
e.printStackTrace();
29362929
}
29372930

2938-
// Generate Signed data token with context by specifying credentials.json as string
2931+
// Generate Signed data token with JSON object context for Conditional Data Access
29392932
try {
2940-
String fileContents = "<YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING>";
2941-
String context = "abc";
2933+
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
2934+
Map<String, Object> context = new HashMap<>();
2935+
context.put("role", "admin");
2936+
context.put("project_id", "proj_123");
29422937
ArrayList<String> dataTokens = new ArrayList<>();
29432938
dataTokens.add("YOUR_DATA_TOKEN_1");
29442939
SignedDataTokens signedToken = SignedDataTokens.builder()
2945-
.setCredentials(fileContents)
2946-
.setCtx(context)
2940+
.setCredentials(new File(filePath))
2941+
.setCtx(context) // JSON object context
29472942
.setTimeToLive(30) // in seconds
29482943
.setDataTokens(dataTokens)
29492944
.build();

samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
import com.skyflow.serviceaccount.util.BearerToken;
55

66
import java.io.File;
7+
import java.util.HashMap;
8+
import java.util.Map;
79

810
/**
911
* Example program to generate a Bearer Token using Skyflow's BearerToken utility.
10-
* The token is generated using two approaches:
11-
* 1. By providing the credentials.json file path.
12-
* 2. By providing the contents of credentials.json as a string.
12+
* The token is generated using three approaches:
13+
* 1. By providing the credentials.json file path with a string context.
14+
* 2. By providing the contents of credentials.json as a string with a string context.
15+
* 3. By providing a JSON object context for Conditional Data Access.
1316
*/
1417
public class BearerTokenGenerationWithContextExample {
1518
public static void main(String[] args) {
@@ -57,5 +60,33 @@ public static void main(String[] args) {
5760
// Handle exceptions specific to Skyflow operations
5861
e.printStackTrace();
5962
}
63+
64+
// Approach 3: Generate Bearer Token with a JSON object context for Conditional Data Access
65+
// Use this approach when your Skyflow policy uses CEL expressions that reference nested
66+
// context fields, such as: request.context.role == 'admin'
67+
try {
68+
// Replace <YOUR_CREDENTIALS_FILE_PATH> with the full path to your credentials.json file
69+
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
70+
71+
// Create a context map with key-value pairs matching your Conditional Data Access policy
72+
Map<String, Object> context = new HashMap<>();
73+
context.put("role", "admin"); // Evaluated as request.context.role
74+
context.put("project_id", "proj_123"); // Evaluated as request.context.project_id
75+
76+
// Create a BearerToken object with the JSON object context
77+
BearerToken token = BearerToken.builder()
78+
.setCredentials(new File(filePath)) // Set credentials using a File object
79+
.setCtx(context) // Set context as a JSON object
80+
.build(); // Build the BearerToken object
81+
82+
// Retrieve the Bearer Token as a string
83+
bearerToken = token.getBearerToken();
84+
85+
// Print the generated Bearer Token to the console
86+
System.out.println(bearerToken);
87+
} catch (SkyflowException e) {
88+
// Handle exceptions specific to Skyflow operations
89+
e.printStackTrace();
90+
}
6091
}
6192
}

samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66

77
import java.io.File;
88
import java.util.ArrayList;
9+
import java.util.HashMap;
910
import java.util.List;
11+
import java.util.Map;
1012

1113
/**
12-
* This example demonstrates how to generate Signed Data Tokens using two methods:
13-
* 1. Specifying the path to a credentials JSON file.
14-
* 2. Providing the credentials JSON as a string.
14+
* This example demonstrates how to generate Signed Data Tokens using three methods:
15+
* 1. Specifying the path to a credentials JSON file with a string context.
16+
* 2. Providing the credentials JSON as a string with a string context.
17+
* 3. Using a JSON object context for Conditional Data Access.
1518
* <p>
1619
* Signed data tokens are used to verify and securely transmit data with a specified context and TTL.
1720
*/
@@ -70,5 +73,36 @@ public static void main(String[] args) {
7073
System.out.println("Error occurred while generating signed tokens using credentials string:");
7174
e.printStackTrace();
7275
}
76+
77+
// Example 3: Generate Signed Data Token with a JSON object context for Conditional Data Access
78+
// Use this approach when your Skyflow policy uses CEL expressions that reference nested
79+
// context fields, such as: request.context.role == 'admin'
80+
try {
81+
// Step 1: Specify the path to the service account credentials JSON file
82+
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>"; // Replace with the actual file path
83+
84+
// Step 2: Create a context map with key-value pairs matching your Conditional Data Access policy
85+
Map<String, Object> context = new HashMap<>();
86+
context.put("role", "admin"); // Evaluated as request.context.role
87+
context.put("project_id", "proj_123"); // Evaluated as request.context.project_id
88+
89+
ArrayList<String> dataTokens = new ArrayList<>();
90+
dataTokens.add("YOUR_DATA_TOKEN_1"); // Replace with your actual data token(s)
91+
92+
// Step 3: Build the SignedDataTokens object with the JSON object context
93+
SignedDataTokens signedToken = SignedDataTokens.builder()
94+
.setCredentials(new File(filePath)) // Provide the credentials file
95+
.setCtx(context) // Set context as a JSON object
96+
.setTimeToLive(30) // Set the TTL (in seconds)
97+
.setDataTokens(dataTokens) // Set the data tokens to sign
98+
.build();
99+
100+
// Step 4: Retrieve and print the signed data tokens
101+
signedTokenValues = signedToken.getSignedDataTokens();
102+
System.out.println("Signed Tokens (using JSON object context): " + signedTokenValues);
103+
} catch (SkyflowException e) {
104+
System.out.println("Error occurred while generating signed tokens with JSON object context:");
105+
e.printStackTrace();
106+
}
73107
}
74108
}

src/main/java/com/skyflow/config/Credentials.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.skyflow.config;
22

33
import java.util.ArrayList;
4+
import java.util.Map;
45

56
public class Credentials {
67
private String path;
78
private ArrayList<String> roles;
8-
private String context;
9+
private Object context;
910
private String credentialsString;
1011
private String token;
1112
private String apiKey;
@@ -33,13 +34,21 @@ public void setRoles(ArrayList<String> roles) {
3334
}
3435

3536
public String getContext() {
37+
return context instanceof String ? (String) context : null;
38+
}
39+
40+
public Object getContextAsObject() {
3641
return context;
3742
}
3843

3944
public void setContext(String context) {
4045
this.context = context;
4146
}
4247

48+
public void setContext(Map<String, Object> context) {
49+
this.context = context;
50+
}
51+
4352
public String getCredentialsString() {
4453
return credentialsString;
4554
}

src/main/java/com/skyflow/errors/ErrorMessage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public enum ErrorMessage {
3434
EmptyRoles("%s0 Initialization failed. Invalid roles. Specify at least one role."),
3535
EmptyRoleInRoles("%s0 Initialization failed. Invalid role. Specify a valid role."),
3636
EmptyContext("%s0 Initialization failed. Invalid context. Specify a valid context."),
37+
InvalidCtxType("%s0 Initialization failed. Invalid context type. Context must be a string or a map."),
38+
InvalidCtxMapKey("%s0 Initialization failed. Invalid context map key '%s1'. Context map keys must contain only alphanumeric characters and underscores."),
3739

3840
// Bearer token generation
3941
FileNotFound("%s0 Initialization failed. Credential file not found at %s1. Verify the file path."),

src/main/java/com/skyflow/logs/ErrorLogs.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public enum ErrorLogs {
2525
EMPTY_ROLES("Invalid credentials. Roles can not be empty."),
2626
EMPTY_OR_NULL_ROLE_IN_ROLES("Invalid credentials. Role can not be null or empty in roles at index %s1."),
2727
EMPTY_OR_NULL_CONTEXT("Invalid credentials. Context can not be empty."),
28+
INVALID_CTX_TYPE("Invalid credentials. Context must be a string or a map."),
29+
INVALID_CTX_MAP_KEY("Invalid credentials. Context map key '%s1' is invalid. Keys must match ^[a-zA-Z0-9_]+$."),
2830

2931
// Bearer token generation
3032
INVALID_BEARER_TOKEN("Bearer token is invalid or expired."),

0 commit comments

Comments
 (0)