Skip to content

Commit e40c0af

Browse files
authored
Merge pull request #5 from TaskarCenterAtUW/authorizer_implementation
Authorizer Impementation
2 parents ae74d5f + 2b9fb6b commit e40c0af

10 files changed

Lines changed: 253 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change log
22

3+
### 0.0.8
4+
- Authorization methods included
5+
- Simulated and Hosted Authorizers implemented
6+
- Examples written for both
7+
- Readme updated appropriately.
8+
9+
310
### 0.0.7
411
- Fixed configuration issues
512
- Fixed all the review comments

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,85 @@ test_file = container.create_file('text.txt', 'text/plain')
188188
test_file.upload(file_like_io.read())
189189
```
190190

191+
## Authorization
192+
193+
Core offers a simple way of verifying the authorization of a user and their role.
194+
195+
Checking the permission involves three steps
196+
1. Preparing a permission request object
197+
2. Getting an authorizer object from core
198+
3. Requesting if the permission is valid/true
199+
200+
### Preparing the permission request
201+
202+
```python
203+
from python_ms_core.core.auth.models.permission_request import PermissionRequest
204+
205+
permission_request = PermissionRequest(
206+
user_id='<userID>',
207+
org_id='<orgID>',
208+
should_satisfy_all=False,
209+
permissions=['permission1', 'permission2']
210+
)
211+
212+
```
213+
214+
In the above example, `should_satisfy_all` helps in figuring out if all the permissions are needed or any one of the permission is sufficient.
215+
216+
### Getting the authorizer from core
217+
218+
Core exposes `get_authorizer` method with 2 parameters
219+
220+
1. `request_params` parameter which is instance of `PermissionRequest` class (Mandatory Parameter).
221+
2. `config` parameter (Optional)
222+
223+
There are two types of `Authorizer` objects in core.
224+
1. HostedAuthorizer: checks the permissions against a hosted API
225+
2. SimulatedAuthorizer: makes a simulated authorizer used for local/non-hosted environment.
226+
227+
The following code demonstrates getting the simulated and hosted authorizer
228+
```python
229+
from python_ms_core import Core
230+
core = Core()
231+
# HostedAuthorizer
232+
233+
hosted_authorizer = core.get_authorizer(config={'provider': 'Hosted', 'api_url': '<AUTH_API_URL>'})
234+
235+
simulated_authorizer = core.get_authorizer(config={'provider': 'Simulated'})
236+
237+
```
238+
In case `api_url` is not provided for `Hosted` auth provider, the core will pick it up from environment variable `AUTHURL`
239+
240+
#### Requesting if certain permission is valid:
241+
242+
Use the method `has_ermission(request_params)` to know if the permission request is valid/not.
243+
244+
```python
245+
246+
# Complete Example
247+
from python_ms_core import Core
248+
from python_ms_core.core.auth.models.permission_request import PermissionRequest
249+
250+
core = Core()
251+
252+
253+
permission_request = PermissionRequest(
254+
user_id='<userID>',
255+
org_id='<orgID>',
256+
should_satisfy_all=False,
257+
permissions=['permission1', 'permission2']
258+
)
259+
260+
# With Hosted provider
261+
auth_provider = core.get_authorizer(config={'provider': 'Hosted', 'api_url': '<AUTH_API_URL>'})
262+
response = auth_provider.has_permission(request_params=permission_request)
263+
# Response will be boolean
264+
265+
# With Simulated provider
266+
auth_provider = core.get_authorizer(config={'provider': 'Simulated'})
267+
response = auth_provider.has_permission(request_params=permission_request)
268+
# Response will be boolean
269+
```
270+
271+
#### How does Simulated authentication work?
272+
With simulated authentication, the method `has_permission` simply returns the value given in `should_satisfy_all` property in the permission request.

freeze_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
build_date = date.today().strftime('%Y-%m-%d')
1313

14-
version = '0.0.7'
14+
version = '0.0.8'
1515

1616
with open(version_file_path, 'w+') as version_file:
1717
version_file.write("version = '{}'\n".format(version))

src/example.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from python_ms_core import Core
1111
from python_ms_core.core.queue.models.queue_message import QueueMessage
12+
from python_ms_core.core.auth.models.permission_request import PermissionRequest
1213

1314
core = Core(config='Local')
1415
print('Hello')
@@ -74,6 +75,19 @@ def process(message):
7475
publish_messages(topic)
7576
time.sleep(2)
7677

77-
# logger = core.get_logger()
78-
# logger.record_metric(name='test', value='test')
78+
permission_params = PermissionRequest(
79+
user_id='7961d767-a352-464f-95b6-cd1c5189a93c',
80+
org_id='5e339544-3b12-40a5-8acd-78c66d1fa981',
81+
should_satisfy_all=False,
82+
permissions=['poc']
83+
)
84+
85+
try:
86+
auth = core.get_authorizer()
87+
resp = auth.has_permission(request_params=permission_params)
88+
print(resp)
89+
except Exception as e:
90+
print(f'Request failed with Code: {e.status_code}, Message: {e.message}')
91+
print()
92+
7993
os._exit(os.EX_OK)

src/python_ms_core/__init__.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
from .core.topic.local_topic import LocalTopic
66
from .core.storage.providers.azure.azure_storage_client import AzureStorageClient
77
from .core.storage.providers.local.local_storage_client import LocalStorageClient
8-
from .core.config.config import CoreConfig, LocalConfig
8+
from .core.auth.provider.hosted.hosted_authorizer import HostedAuthorizer
9+
from .core.auth.provider.simulated.simulated_authorizer import SimulatedAuthorizer
10+
from .core.config.config import CoreConfig, LocalConfig, AuthConfig
911

1012
LOCAL_ENV = 'LOCAL'
1113
AZURE_ENV = 'AZURE'
14+
HOSTED_ENV = 'HOSTED'
15+
SIMULATED_ENV = 'SIMULATED'
1216

1317

1418
class Core:
@@ -26,7 +30,7 @@ def get_logger(self):
2630
elif logger_config.provider.upper() == AZURE_ENV:
2731
return Logger(config=logger_config)
2832
else:
29-
logging.error(f'Unimplemented initialization for core {logger_config.provider}')
33+
logging.error(f'Failed to initialization core.get_logger for provider: {logger_config.provider}')
3034

3135
def get_topic(self, topic_name: str):
3236
topic_config = self.config.topic()
@@ -35,7 +39,7 @@ def get_topic(self, topic_name: str):
3539
elif topic_config.provider.upper() == AZURE_ENV:
3640
return Topic(config=topic_config, topic_name=topic_name)
3741
else:
38-
logging.error(f'Unimplemented initialization for core {topic_config.provider}')
42+
logging.error(f'Failed to initialization core.get_topic for provider: {topic_config.provider}')
3943

4044
def get_storage_client(self):
4145
storage_config = self.config.storage()
@@ -44,7 +48,21 @@ def get_storage_client(self):
4448
elif storage_config.provider.upper() == AZURE_ENV:
4549
return AzureStorageClient(storage_config)
4650
else:
47-
logging.error(f'Unimplemented initialization for core {storage_config.provider}')
51+
logging.error(f'Failed to initialization core.get_storage_client for provider: {storage_config.provider}')
52+
53+
def get_authorizer(self, config: dict = None):
54+
if config is None:
55+
auth_config = self.config.auth()
56+
else:
57+
provider = config.get('provider', None)
58+
api_url = config.get('api_url', None)
59+
auth_config = AuthConfig(provider=provider, auth_url=api_url)
60+
if auth_config.provider.upper() == SIMULATED_ENV:
61+
return SimulatedAuthorizer(config=auth_config)
62+
elif auth_config.provider.upper() == HOSTED_ENV:
63+
return HostedAuthorizer(config=auth_config)
64+
else:
65+
logging.error(f'Failed to initialization core.get_authorizer for provider: {auth_config.provider}')
4866

4967
def __check_health(self):
5068
print('\x1b[32m ------------------------- \x1b[0m')
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from abc import ABC, abstractmethod
2+
from ..models.permission_request import PermissionRequest
3+
4+
5+
class AuthorizerAbstract(ABC):
6+
7+
@abstractmethod
8+
def __init__(self, config=None): pass
9+
10+
@abstractmethod
11+
def has_permission(self, request_params: PermissionRequest): pass
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from typing import Optional
2+
from dataclasses import dataclass
3+
4+
5+
@dataclass
6+
class PermissionRequest:
7+
user_id: str
8+
org_id: str
9+
permissions: Optional[str] = None
10+
should_satisfy_all: bool = False
11+
12+
def __init__(self, user_id: str, org_id: str, permissions: Optional[str] = None, should_satisfy_all: bool = False):
13+
self._user_id = user_id
14+
self._org_id = org_id
15+
self._permissions = permissions
16+
self._should_satisfy_all = should_satisfy_all
17+
18+
@property
19+
def user_id(self):
20+
return self._user_id
21+
22+
@user_id.setter
23+
def user_id(self, value):
24+
self._user_id = value
25+
26+
@property
27+
def org_id(self):
28+
return self._org_id
29+
30+
@org_id.setter
31+
def org_id(self, value):
32+
self._org_id = value
33+
34+
@property
35+
def permissions(self):
36+
return self._permissions
37+
38+
@permissions.setter
39+
def permissions(self, value):
40+
self._permissions = value
41+
42+
@property
43+
def should_satisfy_all(self):
44+
return self._should_satisfy_all
45+
46+
@should_satisfy_all.setter
47+
def should_satisfy_all(self, value):
48+
self._should_satisfy_all = value
49+
50+
def get_search_params(self):
51+
affirmative = 'true' if self._should_satisfy_all else 'false'
52+
params = {
53+
'userId': self._user_id,
54+
'agencyId': self._org_id,
55+
'affirmative': affirmative
56+
}
57+
if self._permissions and len(self._permissions) > 0:
58+
params['roles'] = self._permissions
59+
return params
60+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import requests
2+
from requests.models import PreparedRequest
3+
from ...abstracts.authorizer_abstract import AuthorizerAbstract
4+
from ...models.permission_request import PermissionRequest
5+
from ....resource_errors import ExceptionHandler
6+
7+
8+
class HostedAuthorizer(AuthorizerAbstract):
9+
def __init__(self, config=None):
10+
self.config = config
11+
12+
@ExceptionHandler.decorated
13+
def has_permission(self, request_params: PermissionRequest):
14+
if request_params.permissions and len(request_params.permissions) > 0:
15+
url = self.config.auth_url or None
16+
if url:
17+
req = PreparedRequest()
18+
req.prepare_url(url, request_params.get_search_params())
19+
response = requests.get(req.url)
20+
return response.json()
21+
else:
22+
raise ValueError('No API url provided')
23+
else:
24+
raise ValueError('No roles provided')
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from ...abstracts.authorizer_abstract import AuthorizerAbstract
2+
from ...models.permission_request import PermissionRequest
3+
from ....resource_errors import ExceptionHandler
4+
5+
6+
class SimulatedAuthorizer(AuthorizerAbstract):
7+
def __init__(self, config=None):
8+
self.config = config
9+
10+
@ExceptionHandler.decorated
11+
def has_permission(self, request_params: PermissionRequest):
12+
return request_params.should_satisfy_all

src/python_ms_core/core/config/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,20 @@ def __init__(self, provider: str, con_string: str):
3030
self.connection_string = con_string
3131

3232

33+
class AuthConfig:
34+
def __init__(self, provider: str, auth_url: str = None):
35+
self.provider = provider
36+
self.auth_url = auth_url
37+
38+
3339
class CoreConfig:
3440
def __init__(self):
3541
self.provider = os.environ.get('PROVIDER', 'Azure')
3642
self.queue_connection = os.environ.get('QUEUECONNECTION', None)
3743
self.queue_name = os.environ.get('LOGGERQUEUE', 'tdei-ms-log')
3844
self.topic_connection = os.environ.get('QUEUECONNECTION', None)
3945
self.storage_connection = os.environ.get('STORAGECONNECTION', None)
46+
self.auth_url = os.environ.get('AUTHURL', None)
4047

4148
def logger(self):
4249
return LogerConfig(
@@ -64,6 +71,12 @@ def storage(self):
6471
con_string=self.storage_connection
6572
)
6673

74+
def auth(self):
75+
return AuthConfig(
76+
provider='Hosted',
77+
auth_url=self.auth_url
78+
)
79+
6780

6881
class LocalConfig:
6982
def __init__(self):
@@ -98,3 +111,8 @@ def storage(self):
98111
provider=self.provider,
99112
con_string=self.storage_connection
100113
)
114+
115+
def auth(self):
116+
return AuthConfig(
117+
provider='Simulated'
118+
)

0 commit comments

Comments
 (0)