Skip to content

Commit 295deef

Browse files
committed
Current user access checks with logging
1 parent 2d326a6 commit 295deef

5 files changed

Lines changed: 128 additions & 2 deletions

File tree

documentation/index.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,21 @@ If you want to add a list of tags, you do it as follows:
10031003
The **User** class enables you to manage users, creating, deleting and updating (as for
10041004
other HDX objects) according to your permissions.
10051005

1006+
You can obtain the currently logged in user (which is based on the API token used in the
1007+
configuration):
1008+
1009+
user = User.get_current_user()
1010+
1011+
You can check that the current user has a particular permission to a specific
1012+
organization:
1013+
1014+
result = User.check_current_user_organization_access("hdx", "read")
1015+
1016+
For a general access check to use before running a script that creates or updates
1017+
datasets:
1018+
1019+
username = User.check_current_user_write_access("hdx")
1020+
10061021
You can email a user. First you need to set up an email server using a dictionary or
10071022
file:
10081023

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ rsa==4.9.1
258258
# via google-auth
259259
ruamel-yaml==0.18.10
260260
# via hdx-python-utilities
261-
setuptools==80.7.1
261+
setuptools==80.8.0
262262
# via ckanapi
263263
shellingham==1.5.4
264264
# via typer

src/hdx/data/hdxobject.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ def _write_to_hdx(
367367
self,
368368
action: str,
369369
data: Dict,
370-
id_field_name: str = None,
370+
id_field_name: Optional[str] = None,
371371
files_to_upload: Dict = {},
372372
) -> Union[Dict, List]:
373373
"""Creates or updates an HDX object in HDX and return HDX object metadata dict

src/hdx/data/user.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,20 @@ def email(
167167
**kwargs,
168168
)
169169

170+
@staticmethod
171+
def get_current_user(configuration: Optional[Configuration] = None) -> "User":
172+
"""Get current user (based on authorisation from API token)
173+
174+
Args:
175+
configuration (Optional[Configuration]): HDX configuration. Defaults to global configuration.
176+
177+
Returns:
178+
User: Current user
179+
"""
180+
user = User(configuration=configuration)
181+
user._save_to_hdx("show", {})
182+
return user
183+
170184
@staticmethod
171185
def get_all_users(
172186
configuration: Optional[Configuration] = None, **kwargs: Any
@@ -373,6 +387,32 @@ def check_current_user_organization_access(
373387
return True
374388
return False
375389

390+
@classmethod
391+
def check_current_user_write_access(
392+
cls, organization: str, permission: str = "create_dataset"
393+
) -> "User":
394+
"""Check logged in user has write access to a given organization. Raises
395+
PermissionError if teh user does not have access otherwise logs and returns the
396+
current username.
397+
398+
Args:
399+
organization (str): Organization id or name.
400+
permission (str): Permission to check for. Defaults to 'create_dataset'.
401+
402+
Returns:
403+
str: Username of current user
404+
"""
405+
current_user = cls.get_current_user()
406+
username = current_user["name"]
407+
if not cls.check_current_user_organization_access(organization, permission):
408+
raise PermissionError(
409+
f'Current user "{username}" does not have "{permission}" access to "{organization}" organization!'
410+
)
411+
logger.info(
412+
f'Current user "{username}" has "{permission}" access to "{organization}" organization'
413+
)
414+
return username
415+
376416
def get_token_list(self):
377417
"""Get API tokens for user.
378418

tests/hdx/data/test_user.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,25 @@ def post(url, data, headers, files, allow_redirects, auth=None):
263263

264264
Configuration.read().remoteckan().session = MockSession()
265265

266+
@pytest.fixture(scope="function")
267+
def show_current_user(self):
268+
class MockSession:
269+
@staticmethod
270+
def post(url, data, headers, files, allow_redirects, auth=None):
271+
if "show" not in url:
272+
return MockResponse(
273+
404,
274+
'{"success": false, "error": {"message": "TEST ERROR: Not show", "__type": "TEST ERROR: Not Show Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}',
275+
)
276+
result = json.dumps(resultdict)
277+
return MockResponse(
278+
200,
279+
'{"success": true, "result": %s, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}'
280+
% result,
281+
)
282+
283+
Configuration.read().remoteckan().session = MockSession()
284+
266285
@pytest.fixture(scope="function")
267286
def post_list(self):
268287
class MockSession:
@@ -304,6 +323,42 @@ def post(url, data, headers, files, allow_redirects, auth=None):
304323

305324
Configuration.read().remoteckan().session = MockSession()
306325

326+
@pytest.fixture(scope="function")
327+
def post_check_current_user_write_access(self):
328+
class MockSession:
329+
@staticmethod
330+
def post(url, data, headers, files, allow_redirects, auth=None):
331+
decodedata = data.decode("utf-8")
332+
datadict = json.loads(decodedata)
333+
if "user" in url:
334+
if "show" in url:
335+
result = json.dumps(resultdict)
336+
return MockResponse(
337+
200,
338+
'{"success": true, "result": %s, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}'
339+
% result,
340+
)
341+
elif "list" in url:
342+
return MockResponse(
343+
200,
344+
'{"success": true, "result": %s, "help": "http://test-data.humdata.org/api/3/action/help_show?name=organization_list"}'
345+
% json.dumps(orglist),
346+
)
347+
elif "organization" in url:
348+
if "show" in url:
349+
result = json.dumps(orgdict)
350+
if (
351+
datadict["id"] == "b67e6c74-c185-4f43-b561-0e114a736f19"
352+
or datadict["id"] == "TEST1"
353+
):
354+
return MockResponse(
355+
200,
356+
'{"success": true, "result": %s, "help": "http://test-data.humdata.org/api/3/action/help_show?name=user_show"}'
357+
% result,
358+
)
359+
360+
Configuration.read().remoteckan().session = MockSession()
361+
307362
@pytest.fixture(scope="function")
308363
def post_listorgs_invalid(self):
309364
class MockSession:
@@ -522,6 +577,10 @@ def test_update_json(self, configuration, static_json):
522577
assert user["name"] == "MyUser1"
523578
assert user["about"] == "other"
524579

580+
def test_get_current_user(self, configuration, show_current_user):
581+
user = User.get_current_user()
582+
assert user["name"] == "MyUser1"
583+
525584
def test_get_all_users(self, configuration, post_list, mocksmtp):
526585
users = User.get_all_users()
527586
assert len(users) == 2
@@ -636,6 +695,18 @@ def test_get_organizations_invalid_user(self, configuration, post_listorgs_inval
636695
assert user.get_organization_dicts() == []
637696
assert User.get_current_user_organization_dicts() == []
638697

698+
def test_check_current_user_write_access(
699+
self, configuration, post_check_current_user_write_access
700+
):
701+
username = User.check_current_user_write_access(
702+
"b67e6c74-c185-4f43-b561-0e114a736f19"
703+
)
704+
assert username == "MyUser1"
705+
username = User.check_current_user_write_access("acled")
706+
assert username == "MyUser1"
707+
with pytest.raises(PermissionError):
708+
User.check_current_user_write_access("lala")
709+
639710
def test_get_token_list(self, configuration, post_tokenlist):
640711
user = User.read_from_hdx("9f3e9973-7dbe-4c65-8820-f48578e3ffea")
641712
tokens = user.get_token_list()

0 commit comments

Comments
 (0)