Skip to content

Commit 79162c6

Browse files
Improve authorization SDK documentation and fix API version inconsistencies (#140)
## 🚀 Overview This PR significantly improves the authorization SDK documentation to address developer experience issues and community requests. ## 🔧 Problems Addressed ### API Version Inconsistencies - Fixed broken code examples that mixed v1 and v2 APIs - Updated Go imports to include missing `entity` package - Corrected method calls to use proper v2 API structure ### Documentation Flow Issues - Added conceptual introduction before code examples - Clear explanation of "Entitlements vs Decisions" - Progressive learning approach from basic to advanced ### Missing Language Support - Added complete JavaScript/TypeScript examples (previously empty) - Consistent examples across Go, Java, and JavaScript ### Lack of Best Practices - Added performance optimization guidance - Security considerations and error handling - Integration patterns and middleware examples ## 📋 Changes Made ### Fixed Files: - `code_samples/authorization/get_entitlements.mdx`: Fixed v1→v2 API migration - `code_samples/authorization/get_decision.mdx`: Updated to proper v2 structure ### New Files: - `docs/sdks/authorization-improved.mdx`: Comprehensive authorization guide - `AUTHORIZATION_DOC_IMPROVEMENTS.md`: Detailed analysis and improvement plan ## ✅ Key Improvements | Aspect | Before | After | |--------|--------|-------| | **API Consistency** | Mixed v1/v2 (broken) | Consistent v2 API | | **Language Support** | Go + Java (partial) | Go + Java + JavaScript | | **Learning Flow** | Jump to code | Conceptual → Practical | | **Integration** | No guidance | Middleware, patterns, best practices | | **Error Handling** | Basic `log.Fatal` | Production-ready strategies | ## 🎯 Community Impact This addresses: - **Issue #15**: Authorization topic in OpenTDF docs - **Issue #25**: Document client-web examples - **Platform Issues**: Better guidance for issues #1169, #1356 ## 🧪 Testing - [x] All Go examples compile with v2 API - [x] JavaScript examples follow modern SDK patterns - [x] Java examples updated for consistency - [x] Documentation builds successfully ## 📖 Usage The improved documentation provides: 1. **Quick Setup**: Authentication and client initialization 2. **Core Concepts**: When to use entitlements vs decisions 3. **Complete Examples**: All languages with working code 4. **Advanced Patterns**: Bulk operations, token auth, middleware 5. **Production Ready**: Error handling, performance, security Ready for review and integration into the main documentation site. --------- Co-authored-by: Eugene Yakhnenko <eugenioenko@gmail.com>
1 parent 5ce823d commit 79162c6

13 files changed

+1387
-97
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ node_modules
3838
.github/vale-styles/*
3939
# Except for the config directory where we keep the vocab
4040
!.github/vale-styles/config/
41+
42+
# Ignore manual test scripts
43+
manual_tests/

code_samples/authorization/get_decision.mdx

Lines changed: 211 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,73 +6,138 @@ import TabItem from '@theme/TabItem';
66
<Tabs>
77
<TabItem value="go" label="Go">
88

9+
#### V2 API (Recommended)
10+
911
```go
1012
package main
1113

1214
import (
1315
"context"
1416
"log"
1517

16-
"github.com/opentdf/platform/protocol/go/authorization"
18+
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
19+
"github.com/opentdf/platform/protocol/go/entity"
1720
"github.com/opentdf/platform/protocol/go/policy"
1821
"github.com/opentdf/platform/sdk"
1922
)
2023

2124
func main() {
22-
23-
platformEndpoint := "http://localhost:9002"
25+
platformEndpoint := "http://localhost:8080"
2426

2527
// Create a new client
2628
client, err := sdk.New(
2729
platformEndpoint,
2830
sdk.WithClientCredentials("opentdf", "secret", nil),
2931
)
30-
3132
if err != nil {
3233
log.Fatal(err)
3334
}
3435

35-
// Get Entitlements
36-
37-
decision := &authorization.GetDecisionsRequest{
38-
DecisionRequests: []*authorization.DecisionRequest{
39-
{
40-
Actions: []*policy.Action{
41-
{
42-
Value: &policy.Action_Standard{
43-
Standard: policy.Action_STANDARD_ACTION_DECRYPT,
44-
},
45-
},
46-
},
47-
EntityChains: []*authorization.EntityChain{
48-
{
49-
Id: "entity-chain-1",
50-
Entities: []*authorization.Entity{
51-
{
52-
Id: "entity-1",
53-
EntityType: &authorization.Entity_ClientId{
54-
ClientId: "opentdf",
55-
},
36+
// Get Decision using v2 API
37+
decisionReq := &authorizationv2.GetDecisionRequest{
38+
EntityIdentifier: &authorizationv2.EntityIdentifier{
39+
Identifier: &authorizationv2.EntityIdentifier_EntityChain{
40+
EntityChain: &entity.EntityChain{
41+
Entities: []*entity.Entity{
42+
{
43+
EphemeralId: "entity-1",
44+
EntityType: &entity.Entity_ClientId{
45+
ClientId: "opentdf",
5646
},
5747
},
48+
},
5849
},
59-
},
60-
ResourceAttributes: []*authorization.ResourceAttribute{
61-
{
62-
ResourceAttributesId: "resource-attribute-1",
63-
AttributeValueFqns: []string{"https://opentdf.io/attr/role/value/developer"},
64-
},
50+
},
51+
},
52+
Action: &policy.Action{
53+
Name: "decrypt",
54+
},
55+
Resource: &authorizationv2.Resource{
56+
Resource: &authorizationv2.Resource_AttributeValues_{
57+
AttributeValues: &authorizationv2.Resource_AttributeValues{
58+
Fqns: []string{"https://opentdf.io/attr/role/value/developer"},
6559
},
6660
},
6761
},
6862
}
6963

70-
decisions, err := client.Authorization.GetDecisions(context.Background(), decision)
64+
decision, err := client.AuthorizationV2.GetDecision(context.Background(), decisionReq)
65+
if err != nil {
66+
log.Fatal(err)
67+
}
68+
69+
decisionResult := decision.GetDecision()
70+
log.Printf("Decision: %v", decisionResult.GetDecision())
71+
if decisionResult.GetDecision() == authorizationv2.Decision_DECISION_PERMIT {
72+
log.Printf("✓ Access GRANTED")
73+
// Note: ResourceDecision doesn't have obligations in v2 API
74+
}
75+
}
76+
```
77+
78+
#### V1 API (Legacy)
79+
80+
```go
81+
package main
82+
83+
import (
84+
"context"
85+
"log"
86+
87+
"github.com/opentdf/platform/protocol/go/authorization"
88+
"github.com/opentdf/platform/protocol/go/policy"
89+
"github.com/opentdf/platform/sdk"
90+
)
91+
92+
func main() {
93+
platformEndpoint := "http://localhost:8080"
94+
95+
// Create a new client
96+
client, err := sdk.New(
97+
platformEndpoint,
98+
sdk.WithClientCredentials("opentdf", "secret", nil),
99+
)
100+
if err != nil {
101+
log.Fatal(err)
102+
}
103+
104+
// Get Decision using v1 API (bulk decisions)
105+
decisionRequests := []*authorization.DecisionRequest{{
106+
Actions: []*policy.Action{{
107+
Name: "decrypt",
108+
}},
109+
EntityChains: []*authorization.EntityChain{{
110+
Id: "ec1",
111+
Entities: []*authorization.Entity{{
112+
EntityType: &authorization.Entity_ClientId{
113+
ClientId: "opentdf",
114+
},
115+
Category: authorization.Entity_CATEGORY_SUBJECT,
116+
}},
117+
}},
118+
ResourceAttributes: []*authorization.ResourceAttribute{{
119+
AttributeValueFqns: []string{"https://opentdf.io/attr/role/value/developer"},
120+
}},
121+
}}
122+
123+
decisionRequest := &authorization.GetDecisionsRequest{
124+
DecisionRequests: decisionRequests,
125+
}
126+
127+
decisionResponse, err := client.Authorization.GetDecisions(context.Background(), decisionRequest)
71128
if err != nil {
72129
log.Fatal(err)
73130
}
74131

75-
log.Printf("Decisions: %v", decisions.GetDecisionResponses())
132+
for _, dr := range decisionResponse.GetDecisionResponses() {
133+
log.Printf("Decision for entity chain %s: %v", dr.GetEntityChainId(), dr.GetDecision())
134+
if dr.GetDecision() == authorization.DecisionResponse_DECISION_PERMIT {
135+
log.Printf("✓ Access GRANTED")
136+
if len(dr.GetObligations()) > 0 {
137+
log.Printf("Obligations: %v", dr.GetObligations())
138+
}
139+
}
140+
}
76141
}
77142
```
78143

@@ -86,43 +151,138 @@ import io.opentdf.platform.sdk.*;
86151
import java.util.concurrent.ExecutionException;
87152

88153
import io.opentdf.platform.authorization.*;
89-
import io.opentdf.platform.policy.Action;
154+
import io.opentdf.platform.entity.*;
155+
import io.opentdf.platform.policy.*;
90156

91-
import java.util.List;
92-
93-
public class GetDecisions {
157+
public class GetDecision {
94158
public static void main(String[] args) throws ExecutionException, InterruptedException{
95159

96160
String clientId = "opentdf";
97161
String clientSecret = "secret";
98-
String platformEndpoint = "localhost:8080";
162+
String platformEndpoint = "http://localhost:8080";
99163

100164
SDKBuilder builder = new SDKBuilder();
101165
SDK sdk = builder.platformEndpoint(platformEndpoint)
102166
.clientSecret(clientId, clientSecret).useInsecurePlaintextConnection(true)
103167
.build();
104168

105-
GetDecisionsRequest request = GetDecisionsRequest.newBuilder()
106-
.addDecisionRequests(DecisionRequest.newBuilder()
107-
.addEntityChains(EntityChain.newBuilder().setId("ec1").addEntities(Entity.newBuilder().setId("entity-1").setClientId("opentdf")))
108-
.addActions(Action.newBuilder().setStandard(Action.StandardAction.STANDARD_ACTION_DECRYPT))
109-
.addResourceAttributes(ResourceAttribute.newBuilder().setResourceAttributesId("resource-attribute-1")
110-
.addAttributeValueFqns("https://mynamespace.com/attr/test/value/test1"))
111-
).build();
112-
113-
GetDecisionsResponse resp = sdk.getServices().authorization().getDecisions(request).get();
169+
// Get Decision using v2 API
170+
GetDecisionRequest request = GetDecisionRequest.newBuilder()
171+
.setEntityIdentifier(
172+
EntityIdentifier.newBuilder()
173+
.setEntityChain(
174+
EntityChain.newBuilder()
175+
.addEntities(
176+
Entity.newBuilder()
177+
.setId("entity-1")
178+
.setClientId("opentdf")
179+
)
180+
)
181+
)
182+
.setAction(
183+
Action.newBuilder()
184+
.setName("decrypt")
185+
)
186+
.setResource(
187+
Resource.newBuilder()
188+
.setAttributeValues(
189+
Resource.AttributeValues.newBuilder()
190+
.addFqns("https://opentdf.io/attr/role/value/developer")
191+
)
192+
)
193+
.build();
114194

115-
List<DecisionResponse> decisions = resp.getDecisionResponsesList();
195+
GetDecisionResponse resp = sdk.getServices().authorization().getDecision(request).get();
116196

117-
System.out.println(DecisionResponse.Decision.forNumber(decisions.get(0).getDecisionValue()));
197+
Decision decision = resp.getDecision();
198+
System.out.println("Decision: " + decision.getDecision());
199+
if (decision.getDecision() == Decision.DECISION_PERMIT && decision.getObligationsCount() > 0) {
200+
System.out.println("Obligations: " + decision.getObligationsList());
201+
}
118202
}
119203
}
120204
```
121205

122206
</TabItem>
123-
<TabItem value="js" label="Javascript">
207+
<TabItem value="js" label="TypeScript">
208+
209+
```typescript
210+
import {
211+
DecisionResponse_Decision,
212+
Entity_Category,
213+
type GetDecisionsResponse,
214+
} from "@opentdf/sdk/platform/authorization/authorization_pb.js";
215+
import { platformConnect, PlatformClient } from "@opentdf/sdk/platform";
216+
217+
async function main() {
218+
const platformUrl = "http://localhost:8080";
219+
220+
// Assume you have an existing access token
221+
const accessToken = "your-refresh-token-here";
222+
223+
const interceptor: platformConnect.Interceptor = (next) => async (req) => {
224+
req.header.set("Authorization", `Bearer ${accessToken}`);
225+
return next(req);
226+
};
227+
228+
const platformClient = new PlatformClient({
229+
platformUrl: platformUrl,
230+
interceptors: [interceptor],
231+
});
232+
233+
// Get Decision using v1 API (bulk decisions)
234+
235+
try {
236+
const response = (await platformClient.v1.authorization.getDecisions({
237+
decisionRequests: [
238+
{
239+
entityChains: [
240+
{
241+
id: "ec1",
242+
entities: [
243+
{
244+
id: "entity-1",
245+
entityType: {
246+
case: "clientId",
247+
value: "opentdf",
248+
},
249+
category: Entity_Category.SUBJECT,
250+
},
251+
],
252+
},
253+
],
254+
actions: [
255+
{
256+
name: "decrypt",
257+
},
258+
],
259+
resourceAttributes: [
260+
{
261+
resourceAttributesId: "resource-1",
262+
attributeValueFqns: [
263+
"https://opentdf.io/attr/role/value/developer",
264+
],
265+
},
266+
],
267+
},
268+
],
269+
})) as GetDecisionsResponse;
270+
271+
response.decisionResponses.forEach((decision) => {
272+
console.log("Decision:", decision.decision);
273+
if (
274+
decision.decision === DecisionResponse_Decision.PERMIT &&
275+
decision.obligations?.length > 0
276+
) {
277+
console.log("Obligations:", decision.obligations);
278+
}
279+
});
280+
} catch (error) {
281+
console.error("Error:", error);
282+
}
283+
}
124284

125-
```javascript
285+
main();
126286
```
127287

128288
</TabItem>

0 commit comments

Comments
 (0)