Skip to content

Commit d118cbf

Browse files
committed
Merge branch 'feature/permission-api'
2 parents b0a34ab + 9d95b9f commit d118cbf

5 files changed

Lines changed: 177 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## ?.?.?
2+
3+
* Added support for the new [permission API](https://github.com/oslokommune/okdata-permission-api).
4+
15
## 0.7.0
26

37
* `Dataset.update_dataset` now supports partial metadata updates when the

okdata/sdk/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"uploadUrl": "https://api.data-dev.oslo.systems/data-uploader",
2626
"statusApiUrl": "https://api.data-dev.oslo.systems/status-api/status",
2727
"simpleDatasetAuthorizerUrl": "https://api.data-dev.oslo.systems/simple-dataset-authorizer",
28+
"permissionApiUrl": "https://api.data-dev.oslo.systems/okdata-permission-api",
2829
}
2930
OKDATA_CONFIG["prod"] = {
3031
"client_id": None,
@@ -43,6 +44,7 @@
4344
"uploadUrl": "https://api.data.oslo.systems/data-uploader",
4445
"statusApiUrl": "https://api.data.oslo.systems/status-api/status",
4546
"simpleDatasetAuthorizerUrl": "https://api.data.oslo.systems/simple-dataset-authorizer",
47+
"permissionApiUrl": "https://api.data.oslo.systems/okdata-permission-api",
4648
}
4749

4850
OKDATA_DEFAULT_ENVIRONMENT = "prod"

okdata/sdk/permission/client.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import logging
2+
from urllib.parse import quote
3+
4+
# TODO: Polyfill. Replace with `from dataclasses import asdict` once support
5+
# for Python 3.6 is dropped.
6+
from okdata.sdk.permission.user_types import asdict
7+
from okdata.sdk import SDK
8+
9+
log = logging.getLogger()
10+
11+
12+
class PermissionClient(SDK):
13+
def __init__(self, config=None, auth=None, env=None):
14+
self.__name__ = "permission"
15+
super().__init__(config, auth, env)
16+
self.api_url = self.config.get("permissionApiUrl")
17+
18+
def update_permission(
19+
self, resource_name, scope, add_users=[], remove_users=[], retries=0
20+
):
21+
"""Grant or revoke permissions for a resource.
22+
23+
`resource_name` must be given on the form "namespace:type:id", while
24+
`scope` must be given on the form "namespace:type:permission".
25+
26+
`add_users` and `remove_users` are iterables containing the users to
27+
give access to and to revoke access from, respectively. The users
28+
should be instances of the user types defined in
29+
`okdata.sdk.permission.user_types`.
30+
31+
Usage example giving read access to the dataset "my-dataset" to the
32+
user "janedoe":
33+
34+
update_permission(
35+
"okdata:dataset:my-dataset",
36+
"okdata:dataset:read",
37+
add_users=[User("janedoe")],
38+
)
39+
"""
40+
url = "{}/permissions/{}".format(self.api_url, quote(resource_name))
41+
data = {
42+
"add_users": list(map(asdict, add_users)),
43+
"remove_users": list(map(asdict, remove_users)),
44+
"scope": scope,
45+
}
46+
log.info(f"SDK:Updating permissions for {resource_name} with {data}")
47+
return self.put(url, data, retries=retries).json()
48+
49+
def get_my_permissions(self, retries=0):
50+
"""Return a dictionary of permissions associated with the current user.
51+
52+
The dictionary is on the form:
53+
54+
{
55+
"resource-name-1": {"scopes": ["scope-1", "scope-2"]},
56+
"resource-name-2": ...
57+
}
58+
"""
59+
url = f"{self.api_url}/my_permissions"
60+
log.info(f"SDK:Listing permissions from: {url}")
61+
return self.get(url, retries=retries).json()
62+
63+
def get_permissions(self, resource_name, retries=0):
64+
"""Return a list of permissions associated with `resource_name`."""
65+
url = "{}/permissions/{}".format(self.api_url, quote(resource_name))
66+
log.info(f"SDK:Getting permissions for {resource_name} from: {url}")
67+
return self.get(url, retries=retries).json()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# TODO: Use these simplified dataclasses once support for Python 3.6 is
2+
# dropped. Meanwhile we'll use the "polyfill" classes defined below.
3+
#
4+
# from dataclasses import dataclass, field
5+
#
6+
# @dataclass
7+
# class Client:
8+
# user_id: str
9+
# user_type: str = field(default="client", init=False)
10+
#
11+
#
12+
# @dataclass
13+
# class Team:
14+
# user_id: str
15+
# user_type: str = field(default="team", init=False)
16+
#
17+
#
18+
# @dataclass
19+
# class User:
20+
# user_id: str
21+
# user_type: str = field(default="user", init=False)
22+
23+
24+
class Client:
25+
user_id: str
26+
user_type: str
27+
28+
def __init__(self, user_id):
29+
self.user_id = user_id
30+
self.user_type = "client"
31+
32+
33+
class Team:
34+
user_id: str
35+
user_type: str
36+
37+
def __init__(self, user_id):
38+
self.user_id = user_id
39+
self.user_type = "team"
40+
41+
42+
class User:
43+
user_id: str
44+
user_type: str
45+
46+
def __init__(self, user_id):
47+
self.user_id = user_id
48+
self.user_type = "user"
49+
50+
51+
def asdict(obj):
52+
return obj.__dict__

tests/permission/test_client.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import re
2+
import json
3+
4+
from okdata.sdk.permission.client import PermissionClient
5+
from okdata.sdk.permission.user_types import User
6+
7+
8+
def test_update_permission(requests_mock):
9+
client = PermissionClient()
10+
matcher = re.compile("permissions/okdata%3Adataset%3Afoo")
11+
res = {
12+
"resource_name": "okdata:dataset:foo",
13+
"description": "Allows reading okdata:dataset:foo",
14+
"scope": "okdata:dataset:read",
15+
"teams": [],
16+
"users": ["janedoe"],
17+
"clients": [],
18+
}
19+
requests_mock.register_uri("PUT", matcher, text=json.dumps(res), status_code=200)
20+
assert (
21+
client.update_permission(
22+
"okdata:dataset:foo", "okdata:dataset:read", add_users=[User("janedoe")]
23+
)
24+
== res
25+
)
26+
27+
28+
def test_get_my_permissions(requests_mock):
29+
client = PermissionClient()
30+
matcher = re.compile("my_permissions")
31+
res = {
32+
"okdata:dataset:foo": {"scopes": ["okdata:dataset:read"]},
33+
}
34+
requests_mock.register_uri("GET", matcher, text=json.dumps(res), status_code=200)
35+
assert client.get_my_permissions() == res
36+
37+
38+
def test_get_permissions(requests_mock):
39+
client = PermissionClient()
40+
matcher = re.compile("permissions/okdata%3Adataset%3Afoo")
41+
res = [
42+
{
43+
"resource_name": "okdata:dataset:foo",
44+
"description": "Allows reading okdata:dataset:foo",
45+
"scope": "okdata:dataset:read",
46+
"teams": [],
47+
"users": ["janedoe"],
48+
"clients": [],
49+
}
50+
]
51+
requests_mock.register_uri("GET", matcher, text=json.dumps(res), status_code=200)
52+
assert client.get_permissions("okdata:dataset:foo") == res

0 commit comments

Comments
 (0)