Skip to content

Commit a1517f8

Browse files
authored
Add FGA APIs (#463)
* Add FGA APIs * README update * now with the missing fga files * formating I * typo fix * typo fix II * typo fix III
1 parent 8dee0de commit a1517f8

File tree

5 files changed

+404
-151
lines changed

5 files changed

+404
-151
lines changed

README.md

Lines changed: 43 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ These sections show how to use the SDK to perform permission and user management
7070
10. [Impersonate](#impersonate)
7171
11. [Embedded links](#embedded-links)
7272
12. [Audit](#audit)
73-
13. [Manage ReBAC Authz](#manage-rebac-authz)
73+
13. [Manage FGA (Fine-grained Authorization)](#manage-fga-fine-grained-authorization)
7474
14. [Manage Project](#manage-project)
7575
15. [Manage SSO Applications](#manage-sso-applications)
7676

@@ -1107,183 +1107,75 @@ await descopeClient.management.audit.create_event(
11071107
)
11081108
```
11091109

1110-
### Manage ReBAC Authz
1110+
### Manage FGA (Fine-grained Authorization)
11111111

11121112
Descope supports full relation based access control (ReBAC) using a zanzibar like schema and operations.
1113-
A schema is comprized of namespaces (entities like documents, folders, orgs, etc.) and each namespace has relation definitions to define relations.
1114-
Each relation definition can be simple (either you have it or not) or complex (union of nodes).
1113+
A schema is comprized of types (entities like documents, folders, orgs, etc.) and each type has relation definitions and permission to define relations to other types.
11151114

11161115
A simple example for a file system like schema would be:
11171116

11181117
```yaml
1119-
# Example schema for the authz tests
1120-
name: Files
1121-
namespaces:
1122-
- name: org
1123-
relationDefinitions:
1124-
- name: parent
1125-
- name: member
1126-
complexDefinition:
1127-
nType: union
1128-
children:
1129-
- nType: child
1130-
expression:
1131-
neType: self
1132-
- nType: child
1133-
expression:
1134-
neType: relationLeft
1135-
relationDefinition: parent
1136-
relationDefinitionNamespace: org
1137-
targetRelationDefinition: member
1138-
targetRelationDefinitionNamespace: org
1139-
- name: folder
1140-
relationDefinitions:
1141-
- name: parent
1142-
- name: owner
1143-
complexDefinition:
1144-
nType: union
1145-
children:
1146-
- nType: child
1147-
expression:
1148-
neType: self
1149-
- nType: child
1150-
expression:
1151-
neType: relationRight
1152-
relationDefinition: parent
1153-
relationDefinitionNamespace: folder
1154-
targetRelationDefinition: owner
1155-
targetRelationDefinitionNamespace: folder
1156-
- name: editor
1157-
complexDefinition:
1158-
nType: union
1159-
children:
1160-
- nType: child
1161-
expression:
1162-
neType: self
1163-
- nType: child
1164-
expression:
1165-
neType: relationRight
1166-
relationDefinition: parent
1167-
relationDefinitionNamespace: folder
1168-
targetRelationDefinition: editor
1169-
targetRelationDefinitionNamespace: folder
1170-
- nType: child
1171-
expression:
1172-
neType: targetSet
1173-
targetRelationDefinition: owner
1174-
targetRelationDefinitionNamespace: folder
1175-
- name: viewer
1176-
complexDefinition:
1177-
nType: union
1178-
children:
1179-
- nType: child
1180-
expression:
1181-
neType: self
1182-
- nType: child
1183-
expression:
1184-
neType: relationRight
1185-
relationDefinition: parent
1186-
relationDefinitionNamespace: folder
1187-
targetRelationDefinition: viewer
1188-
targetRelationDefinitionNamespace: folder
1189-
- nType: child
1190-
expression:
1191-
neType: targetSet
1192-
targetRelationDefinition: editor
1193-
targetRelationDefinitionNamespace: folder
1194-
- name: doc
1195-
relationDefinitions:
1196-
- name: parent
1197-
- name: owner
1198-
complexDefinition:
1199-
nType: union
1200-
children:
1201-
- nType: child
1202-
expression:
1203-
neType: self
1204-
- nType: child
1205-
expression:
1206-
neType: relationRight
1207-
relationDefinition: parent
1208-
relationDefinitionNamespace: doc
1209-
targetRelationDefinition: owner
1210-
targetRelationDefinitionNamespace: folder
1211-
- name: editor
1212-
complexDefinition:
1213-
nType: union
1214-
children:
1215-
- nType: child
1216-
expression:
1217-
neType: self
1218-
- nType: child
1219-
expression:
1220-
neType: relationRight
1221-
relationDefinition: parent
1222-
relationDefinitionNamespace: doc
1223-
targetRelationDefinition: editor
1224-
targetRelationDefinitionNamespace: folder
1225-
- nType: child
1226-
expression:
1227-
neType: targetSet
1228-
targetRelationDefinition: owner
1229-
targetRelationDefinitionNamespace: doc
1230-
- name: viewer
1231-
complexDefinition:
1232-
nType: union
1233-
children:
1234-
- nType: child
1235-
expression:
1236-
neType: self
1237-
- nType: child
1238-
expression:
1239-
neType: relationRight
1240-
relationDefinition: parent
1241-
relationDefinitionNamespace: doc
1242-
targetRelationDefinition: viewer
1243-
targetRelationDefinitionNamespace: folder
1244-
- nType: child
1245-
expression:
1246-
neType: targetSet
1247-
targetRelationDefinition: editor
1248-
targetRelationDefinitionNamespace: doc
1249-
```
1118+
model AuthZ 1.0
1119+
1120+
type user
1121+
1122+
type org
1123+
relation member: user
1124+
relation parent: org
1125+
1126+
type folder
1127+
relation parent: folder
1128+
relation owner: user | org#member
1129+
relation editor: user
1130+
relation viewer: user
1131+
1132+
permission can_create: owner | parent.owner
1133+
permission can_edit: editor | can_create
1134+
permission can_view: viewer | can_edit
1135+
1136+
type doc
1137+
relation parent: folder
1138+
relation owner: user | org#member
1139+
relation editor: user
1140+
relation viewer: user
1141+
1142+
permission can_create: owner | parent.owner
1143+
permission can_edit: editor | can_create
1144+
permission can_view: viewer | can_edit
1145+
```
12501146
12511147
Descope SDK allows you to fully manage the schema and relations as well as perform simple (and not so simple) checks regarding the existence of relations.
12521148
12531149
```python
1254-
# Load the existing schema
1255-
schema = descope_client.mgmt.authz.load_schema()
1256-
1257-
# Save schema and make sure to remove all namespaces not listed
1258-
descope_client.mgmt.authz.save_schema(schema, True)
1150+
# Save schema (where schema is an str as defined above)
1151+
descope_client.mgmt.fga.save_schema(schema)
12591152

12601153
# Create a relation between a resource and user
1261-
descope_client.mgmt.authz.create_relations(
1154+
descope_client.mgmt.fga.create_relations(
12621155
[
12631156
{
12641157
"resource": "some-doc",
1265-
"relationDefinition": "owner",
1266-
"namespace": "doc",
1158+
"resourceType": "doc",
1159+
"relation": "owner",
12671160
"target": "u1",
1161+
"targetType": "user",
12681162
}
12691163
]
12701164
)
12711165

1272-
# Check if target has the relevant relation
1273-
# The answer should be true because an owner is also a viewer
1274-
relations = descope_client.mgmt.authz.has_relations(
1166+
# Check if target has a relevant relation
1167+
# The answer should be true because an owner can also view
1168+
relations = descope_client.mgmt.fga.check(
12751169
[
12761170
{
12771171
"resource": "some-doc",
1278-
"relationDefinition": "viewer",
1279-
"namespace": "doc",
1172+
"resourceType": "doc",
1173+
"relation": "owner",
12801174
"target": "u1",
1175+
"targetType": "user",
12811176
}
12821177
]
12831178
)
1284-
1285-
# Get list of targets and resources changed since the given date.
1286-
res = descope_client.mgmt.authz.get_modified()
12871179
```
12881180

12891181
### Manage Project

descope/management/common.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ class MgmtV1:
131131
authz_re_target_with_relation = "/v1/mgmt/authz/re/targetwithrelation"
132132
authz_get_modified = "/v1/mgmt/authz/getmodified"
133133

134+
# FGA (new style Authz)
135+
fga_save_schema = "/v1/mgmt/fga/schema"
136+
fga_create_relations = "/v1/mgmt/fga/relations"
137+
fga_delete_relations = "/v1/mgmt/fga/relations/delete"
138+
fga_check = "/v1/mgmt/fga/check"
139+
134140
# Project
135141
project_update_name = "/v1/mgmt/project/update/name"
136142
project_update_tags = "/v1/mgmt/project/update/tags"

descope/management/fga.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
from datetime import datetime, timezone
2+
from typing import Any, List, Optional
3+
4+
from descope._auth_base import AuthBase
5+
from descope.management.common import MgmtV1
6+
7+
8+
class FGA(AuthBase):
9+
def save_schema(self, schema: str):
10+
"""
11+
Create or update an FGA schema.
12+
Args:
13+
schema (str): the schema in the AuthZ 1.0 DSL
14+
model AuthZ 1.0
15+
16+
type user
17+
18+
type org
19+
relation member: user
20+
relation parent: org
21+
22+
type folder
23+
relation parent: folder
24+
relation owner: user | org#member
25+
relation editor: user
26+
relation viewer: user
27+
28+
permission can_create: owner | parent.owner
29+
permission can_edit: editor | can_create
30+
permission can_view: viewer | can_edit
31+
32+
type doc
33+
relation parent: folder
34+
relation owner: user | org#member
35+
relation editor: user
36+
relation viewer: user
37+
38+
permission can_create: owner | parent.owner
39+
permission can_edit: editor | can_create
40+
permission can_view: viewer | can_edit
41+
Raise:
42+
AuthException: raised if saving fails
43+
"""
44+
self._auth.do_post(
45+
MgmtV1.fga_save_schema,
46+
{"dsl": schema},
47+
pswd=self._auth.management_key,
48+
)
49+
50+
def create_relations(
51+
self,
52+
relations: List[dict],
53+
):
54+
"""
55+
Create the given relations based on the existing schema
56+
Args:
57+
relations (List[dict]): the relations to create. Each in the following format:
58+
{
59+
"resource": "id of the resource that has the relation",
60+
"resourceType": "the type of the resource (namespace)",
61+
"relation": "the relation definition for the relation",
62+
"target": "the target that has the relation - usually users or other resources",
63+
"targetType": "the type of the target (namespace) - can also be group#member for target sets"
64+
}
65+
Raise:
66+
AuthException: raised if create relations fails
67+
"""
68+
self._auth.do_post(
69+
MgmtV1.fga_create_relations,
70+
{
71+
"tuples": relations,
72+
},
73+
pswd=self._auth.management_key,
74+
)
75+
76+
def delete_relations(
77+
self,
78+
relations: List[dict],
79+
):
80+
"""
81+
Delete the given relations based on the existing schema
82+
Args:
83+
relations (List[dict]): the relations to create. Each in the format as specified above for (create_relations)
84+
Raise:
85+
AuthException: raised if delete relations fails
86+
"""
87+
self._auth.do_post(
88+
MgmtV1.fga_delete_relations,
89+
{
90+
"tuples": relations,
91+
},
92+
pswd=self._auth.management_key,
93+
)
94+
95+
def check(
96+
self,
97+
relations: List[dict],
98+
) -> List[dict]:
99+
"""
100+
Queries the given relations to see if they exist returning true if they do
101+
Args:
102+
relations (List[dict]): List of relation queries each in the format of:
103+
{
104+
"resource": "id of the resource that has the relation",
105+
"resourceType": "the type of the resource (namespace)",
106+
"relation": "the relation definition for the relation",
107+
"target": "the target that has the relation - usually users or other resources",
108+
"targetType": "the type of the target (namespace)"
109+
}
110+
111+
Return value (List[dict]):
112+
Return List in the format
113+
[
114+
{
115+
"allowed": True|False
116+
"relation": {
117+
"resource": "id of the resource that has the relation",
118+
"resourceType": "the type of the resource (namespace)",
119+
"relation": "the relation definition for the relation",
120+
"target": "the target that has the relation - usually users or other resources",
121+
"targetType": "the type of the target (namespace)"
122+
}
123+
}
124+
]
125+
Raise:
126+
AuthException: raised if query fails
127+
"""
128+
response = self._auth.do_post(
129+
MgmtV1.fga_check,
130+
{
131+
"tuples": relations,
132+
},
133+
pswd=self._auth.management_key,
134+
)
135+
return list(
136+
map(
137+
lambda tuple: {"relation": tuple["tuple"], "allowed": tuple["allowed"]},
138+
response.json()["tuples"],
139+
)
140+
)

0 commit comments

Comments
 (0)