Skip to content

Commit

Permalink
Add FGA APIs (#463)
Browse files Browse the repository at this point in the history
* Add FGA APIs

* README update

* now with the missing fga files

* formating I

* typo fix

* typo fix II

* typo fix III
  • Loading branch information
slavikm authored Dec 8, 2024
1 parent 8dee0de commit a1517f8
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 151 deletions.
194 changes: 43 additions & 151 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ These sections show how to use the SDK to perform permission and user management
10. [Impersonate](#impersonate)
11. [Embedded links](#embedded-links)
12. [Audit](#audit)
13. [Manage ReBAC Authz](#manage-rebac-authz)
13. [Manage FGA (Fine-grained Authorization)](#manage-fga-fine-grained-authorization)
14. [Manage Project](#manage-project)
15. [Manage SSO Applications](#manage-sso-applications)

Expand Down Expand Up @@ -1107,183 +1107,75 @@ await descopeClient.management.audit.create_event(
)
```

### Manage ReBAC Authz
### Manage FGA (Fine-grained Authorization)

Descope supports full relation based access control (ReBAC) using a zanzibar like schema and operations.
A schema is comprized of namespaces (entities like documents, folders, orgs, etc.) and each namespace has relation definitions to define relations.
Each relation definition can be simple (either you have it or not) or complex (union of nodes).
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.

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

```yaml
# Example schema for the authz tests
name: Files
namespaces:
- name: org
relationDefinitions:
- name: parent
- name: member
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationLeft
relationDefinition: parent
relationDefinitionNamespace: org
targetRelationDefinition: member
targetRelationDefinitionNamespace: org
- name: folder
relationDefinitions:
- name: parent
- name: owner
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationRight
relationDefinition: parent
relationDefinitionNamespace: folder
targetRelationDefinition: owner
targetRelationDefinitionNamespace: folder
- name: editor
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationRight
relationDefinition: parent
relationDefinitionNamespace: folder
targetRelationDefinition: editor
targetRelationDefinitionNamespace: folder
- nType: child
expression:
neType: targetSet
targetRelationDefinition: owner
targetRelationDefinitionNamespace: folder
- name: viewer
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationRight
relationDefinition: parent
relationDefinitionNamespace: folder
targetRelationDefinition: viewer
targetRelationDefinitionNamespace: folder
- nType: child
expression:
neType: targetSet
targetRelationDefinition: editor
targetRelationDefinitionNamespace: folder
- name: doc
relationDefinitions:
- name: parent
- name: owner
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationRight
relationDefinition: parent
relationDefinitionNamespace: doc
targetRelationDefinition: owner
targetRelationDefinitionNamespace: folder
- name: editor
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationRight
relationDefinition: parent
relationDefinitionNamespace: doc
targetRelationDefinition: editor
targetRelationDefinitionNamespace: folder
- nType: child
expression:
neType: targetSet
targetRelationDefinition: owner
targetRelationDefinitionNamespace: doc
- name: viewer
complexDefinition:
nType: union
children:
- nType: child
expression:
neType: self
- nType: child
expression:
neType: relationRight
relationDefinition: parent
relationDefinitionNamespace: doc
targetRelationDefinition: viewer
targetRelationDefinitionNamespace: folder
- nType: child
expression:
neType: targetSet
targetRelationDefinition: editor
targetRelationDefinitionNamespace: doc
```
model AuthZ 1.0

type user

type org
relation member: user
relation parent: org

type folder
relation parent: folder
relation owner: user | org#member
relation editor: user
relation viewer: user

permission can_create: owner | parent.owner
permission can_edit: editor | can_create
permission can_view: viewer | can_edit

type doc
relation parent: folder
relation owner: user | org#member
relation editor: user
relation viewer: user

permission can_create: owner | parent.owner
permission can_edit: editor | can_create
permission can_view: viewer | can_edit
```
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.
```python
# Load the existing schema
schema = descope_client.mgmt.authz.load_schema()

# Save schema and make sure to remove all namespaces not listed
descope_client.mgmt.authz.save_schema(schema, True)
# Save schema (where schema is an str as defined above)
descope_client.mgmt.fga.save_schema(schema)

# Create a relation between a resource and user
descope_client.mgmt.authz.create_relations(
descope_client.mgmt.fga.create_relations(
[
{
"resource": "some-doc",
"relationDefinition": "owner",
"namespace": "doc",
"resourceType": "doc",
"relation": "owner",
"target": "u1",
"targetType": "user",
}
]
)

# Check if target has the relevant relation
# The answer should be true because an owner is also a viewer
relations = descope_client.mgmt.authz.has_relations(
# Check if target has a relevant relation
# The answer should be true because an owner can also view
relations = descope_client.mgmt.fga.check(
[
{
"resource": "some-doc",
"relationDefinition": "viewer",
"namespace": "doc",
"resourceType": "doc",
"relation": "owner",
"target": "u1",
"targetType": "user",
}
]
)

# Get list of targets and resources changed since the given date.
res = descope_client.mgmt.authz.get_modified()
```

### Manage Project
Expand Down
6 changes: 6 additions & 0 deletions descope/management/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ class MgmtV1:
authz_re_target_with_relation = "/v1/mgmt/authz/re/targetwithrelation"
authz_get_modified = "/v1/mgmt/authz/getmodified"

# FGA (new style Authz)
fga_save_schema = "/v1/mgmt/fga/schema"
fga_create_relations = "/v1/mgmt/fga/relations"
fga_delete_relations = "/v1/mgmt/fga/relations/delete"
fga_check = "/v1/mgmt/fga/check"

# Project
project_update_name = "/v1/mgmt/project/update/name"
project_update_tags = "/v1/mgmt/project/update/tags"
Expand Down
140 changes: 140 additions & 0 deletions descope/management/fga.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from datetime import datetime, timezone
from typing import Any, List, Optional

from descope._auth_base import AuthBase
from descope.management.common import MgmtV1


class FGA(AuthBase):
def save_schema(self, schema: str):
"""
Create or update an FGA schema.
Args:
schema (str): the schema in the AuthZ 1.0 DSL
model AuthZ 1.0
type user
type org
relation member: user
relation parent: org
type folder
relation parent: folder
relation owner: user | org#member
relation editor: user
relation viewer: user
permission can_create: owner | parent.owner
permission can_edit: editor | can_create
permission can_view: viewer | can_edit
type doc
relation parent: folder
relation owner: user | org#member
relation editor: user
relation viewer: user
permission can_create: owner | parent.owner
permission can_edit: editor | can_create
permission can_view: viewer | can_edit
Raise:
AuthException: raised if saving fails
"""
self._auth.do_post(
MgmtV1.fga_save_schema,
{"dsl": schema},
pswd=self._auth.management_key,
)

def create_relations(
self,
relations: List[dict],
):
"""
Create the given relations based on the existing schema
Args:
relations (List[dict]): the relations to create. Each in the following format:
{
"resource": "id of the resource that has the relation",
"resourceType": "the type of the resource (namespace)",
"relation": "the relation definition for the relation",
"target": "the target that has the relation - usually users or other resources",
"targetType": "the type of the target (namespace) - can also be group#member for target sets"
}
Raise:
AuthException: raised if create relations fails
"""
self._auth.do_post(
MgmtV1.fga_create_relations,
{
"tuples": relations,
},
pswd=self._auth.management_key,
)

def delete_relations(
self,
relations: List[dict],
):
"""
Delete the given relations based on the existing schema
Args:
relations (List[dict]): the relations to create. Each in the format as specified above for (create_relations)
Raise:
AuthException: raised if delete relations fails
"""
self._auth.do_post(
MgmtV1.fga_delete_relations,
{
"tuples": relations,
},
pswd=self._auth.management_key,
)

def check(
self,
relations: List[dict],
) -> List[dict]:
"""
Queries the given relations to see if they exist returning true if they do
Args:
relations (List[dict]): List of relation queries each in the format of:
{
"resource": "id of the resource that has the relation",
"resourceType": "the type of the resource (namespace)",
"relation": "the relation definition for the relation",
"target": "the target that has the relation - usually users or other resources",
"targetType": "the type of the target (namespace)"
}
Return value (List[dict]):
Return List in the format
[
{
"allowed": True|False
"relation": {
"resource": "id of the resource that has the relation",
"resourceType": "the type of the resource (namespace)",
"relation": "the relation definition for the relation",
"target": "the target that has the relation - usually users or other resources",
"targetType": "the type of the target (namespace)"
}
}
]
Raise:
AuthException: raised if query fails
"""
response = self._auth.do_post(
MgmtV1.fga_check,
{
"tuples": relations,
},
pswd=self._auth.management_key,
)
return list(
map(
lambda tuple: {"relation": tuple["tuple"], "allowed": tuple["allowed"]},
response.json()["tuples"],
)
)
Loading

0 comments on commit a1517f8

Please sign in to comment.