diff --git a/sdk/rapid/exceptions.py b/sdk/rapid/exceptions.py index ad10646..010932b 100644 --- a/sdk/rapid/exceptions.py +++ b/sdk/rapid/exceptions.py @@ -92,3 +92,9 @@ class InvalidDomainNameException(Exception): class DomainConflictException(Exception): pass + +class ClientDoesNotHaveUserAdminPermissionsException(Exception): + pass + +class ClientDoesNotHaveDataAdminPermissionsException(Exception): + pass \ No newline at end of file diff --git a/sdk/rapid/rapid.py b/sdk/rapid/rapid.py index c72ea13..b58223f 100644 --- a/sdk/rapid/rapid.py +++ b/sdk/rapid/rapid.py @@ -29,6 +29,8 @@ SubjectNotFoundException, InvalidDomainNameException, DomainConflictException, + ClientDoesNotHaveUserAdminPermissionsException, + ClientDoesNotHaveDataAdminPermissionsException ) @@ -441,3 +443,170 @@ def create_protected_domain(self, name: str): raise DomainConflictException(data["details"]) raise Exception("Failed to create protected domain") + + def create_user(self, user_name: str, user_email: str, user_permissions: list[str]): + """ + Creates a new user on the API with the specified permissions. + + Args: + user_name (str): The name of the user to create. + user_email (str): The email of the user to create. + user_permissions (list[str]): The permissions of the user to create. + + Raises: + rapid.exceptions.InvalidPermissionsException: If an error occurs while trying to create the user. + """ + url = f"{self.auth.url}/user" + response = requests.post( + url, + headers=self.generate_headers(), + data=json.dumps( + {"username": user_name, "email": user_email, "permissions": user_permissions} + ), + timeout=TIMEOUT_PERIOD, + ) + data = json.loads(response.content.decode("utf-8")) + if response.status_code == 201: + return data + elif response.status_code == 400: + if data["details"] == 'One or more of the provided permissions is invalid or duplicated': + raise InvalidPermissionsException( + "One or more of the provided permissions is invalid or duplicated" + ) + else: + raise SubjectAlreadyExistsException( + data["details"] + ) + elif response.status_code == 401: + raise ClientDoesNotHaveUserAdminPermissionsException( + data["details"] + ) + + raise Exception("Failed to create user") + + def delete_user(self, user_name: str, user_id: str): + """ + Deletes a client from the API based on their id + + Args: + client_id (str): The id of the client to delete. + + Raises: + rapid.exceptions.SubjectNotFoundException: If the client does not exist. + """ + url = f"{self.auth.url}/user" + response = requests.delete( + url, + headers=self.generate_headers(), + data=json.dumps( + {"username": user_name, "user_id": user_id} + ), + timeout=TIMEOUT_PERIOD, + ) + data = json.loads(response.content.decode("utf-8")) + if response.status_code == 200: + return data + elif response.status_code == 400: + raise SubjectNotFoundException( + f"Failed to delete user with id: {user_id}, ensure it exists." + ) + elif response.status_code == 401: + raise ClientDoesNotHaveUserAdminPermissionsException( + data["details"] + ) + + raise Exception("Failed to delete user") + + def list_subjects(self): + """ + List all current subjects within rAPId instance. + + Returns: + A JSON response of the API's response. + """ + response = requests.get( + f"{self.auth.url}/subjects", + headers=self.generate_headers(), + timeout=TIMEOUT_PERIOD, + ) + data = json.loads(response.content.decode("utf-8")) + if response.status_code == 200: + return data + elif response.status_code == 401: + raise ClientDoesNotHaveUserAdminPermissionsException( + data["details"] + ) + + raise Exception("Failed to list subjects") + + def list_layers(self): + """ + List all current layers within rAPId instance. + + Returns: + A JSON response of the API's response. + """ + response = requests.get( + f"{self.auth.url}/layers", + headers=self.generate_headers(), + timeout=TIMEOUT_PERIOD, + ) + data = json.loads(response.content.decode("utf-8")) + if response.status_code == 200: + return data + + raise Exception("Failed to list layers") + + def list_protected_domains(self): + """ + List all current protected domains within rAPId instance. + + Returns: + A JSON response of the API's response. + """ + response = requests.get( + f"{self.auth.url}/protected_domains", + headers=self.generate_headers(), + timeout=TIMEOUT_PERIOD, + ) + data = json.loads(response.content.decode("utf-8")) + if response.status_code == 200: + return data + elif response.status_code == 401: + raise ClientDoesNotHaveUserAdminPermissionsException( + data["details"] + ) + + raise Exception("Failed to list protected domains") + + def delete_dataset(self, layer: str, domain: str, dataset: str): + """ + Deletes a dataset from the API based on their id + + Args: + layer (str): The dataset layer to delete. + domain (str): The dataset domain to delete. + dataset (str): The dataset to delete. + + Raises: + rapid.exceptions.DatasetNotFoundException: If the dataset does not exist. + """ + url = f"{self.auth.url}/datasets/{layer}/{domain}/{dataset}" + response = requests.delete( + url, + headers=self.generate_headers(), + timeout=TIMEOUT_PERIOD, + ) + data = json.loads(response.content.decode("utf-8")) + if response.status_code == 202: + return data + elif response.status_code == 400: + raise DatasetNotFoundException( + f"Could not find dataset, {layer}/{domain}/{dataset} to delete", data + ) + elif response.status_code == 401: + raise ClientDoesNotHaveDataAdminPermissionsException( + data["details"] + ) + + raise Exception("Failed to delete dataset") diff --git a/sdk/tests/test_rapid.py b/sdk/tests/test_rapid.py index e95d336..7f80877 100644 --- a/sdk/tests/test_rapid.py +++ b/sdk/tests/test_rapid.py @@ -18,8 +18,11 @@ InvalidPermissionsException, SubjectNotFoundException, SubjectAlreadyExistsException, + DatasetNotFoundException, InvalidDomainNameException, DomainConflictException, + ClientDoesNotHaveUserAdminPermissionsException, + ClientDoesNotHaveDataAdminPermissionsException ) from .conftest import RAPID_URL, RAPID_TOKEN @@ -460,3 +463,146 @@ def test_create_protected_domain_success(self, requests_mock: Mocker, rapid: Rap ) res = rapid.create_protected_domain("dummy") assert res is None + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_create_user_success(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = { + "username": "user", + "email": "user", + "permissions": ["READ_ALL"], + "user_id": "xxx-yyy-zzz" + } + requests_mock.post(f"{RAPID_URL}/user", json=mocked_response, status_code=201) + res = rapid.create_user("user", "user@user.com", ["READ_ALL"]) + assert res == mocked_response + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_create_user_failure_subjectalreadyexists(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = {"details": "The user 'user' or email 'user@user.com' already exist"} + requests_mock.post(f"{RAPID_URL}/user", json=mocked_response, status_code=400) + with pytest.raises(SubjectAlreadyExistsException): + rapid.create_user("user", "user@user.com", ["READ_ALL"]) + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_create_user_failure_invalidpermissions(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = {"details": "One or more of the provided permissions is invalid or duplicated"} + requests_mock.post(f"{RAPID_URL}/user", json=mocked_response, status_code=400) + with pytest.raises(InvalidPermissionsException): + rapid.create_user("user", "user@user.com", ["READ_ALL"]) + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_create_user_failure_ClientDoesNotHaveUserAdminPermissions(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = {"details": "data"} + requests_mock.post(f"{RAPID_URL}/user", json=mocked_response, status_code=401) + with pytest.raises(ClientDoesNotHaveUserAdminPermissionsException): + rapid.create_user("user", "user@user.com", ["READ_ALL"]) + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_delete_user_success(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = { + "username": "user", + "user_id": "xxx-yyy-zzz" + } + requests_mock.delete( + f"{RAPID_URL}/user", json=mocked_response, status_code=200 + ) + res = rapid.delete_user("user", "xxx-yyy-zzz") + assert res == mocked_response + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_delete_user_failure_SubjectNotFound(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = {"data": "dummy"} + requests_mock.delete( + f"{RAPID_URL}/user", json=mocked_response, status_code=400 + ) + with pytest.raises(SubjectNotFoundException): + rapid.delete_user("user", "xxx-yyy-zzz") + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_delete_user_failure_ClientDoesNotHaveUserAdminPermissions(self, requests_mock: Mocker, rapid: Rapid): + mocked_response = {"details": "User xxx-yyy-zzz does not have permissions that grant access to the endpoint scopes []"} + requests_mock.delete( + f"{RAPID_URL}/user", json=mocked_response, status_code=401 + ) + with pytest.raises(ClientDoesNotHaveUserAdminPermissionsException): + rapid.delete_user("user", "xxx-yyy-zzz") + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_list_subjects_success(self, requests_mock: Mocker, rapid: Rapid): + expected = {"response": "dummy"} + requests_mock.get(f"{RAPID_URL}/subjects", json=expected) + + res = rapid.list_subjects() + assert res == expected + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_list_subjects_failure_ClientDoesNotHaveUserAdminPermissions(self, requests_mock: Mocker, rapid: Rapid): + expected = {"details": "User xxx-yyy-zzz does not have permissions that grant access to the endpoint scopes []"} + requests_mock.get(f"{RAPID_URL}/subjects", json=expected, status_code=401) + with pytest.raises(ClientDoesNotHaveUserAdminPermissionsException): + rapid.list_subjects() + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_list_layers(self, requests_mock: Mocker, rapid: Rapid): + expected = {"response": "dummy"} + requests_mock.get(f"{RAPID_URL}/layers", json=expected) + + res = rapid.list_layers() + assert res == expected + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_list_protected_domains(self, requests_mock: Mocker, rapid: Rapid): + expected = {"details": "User xxx-yyy-zzz does not have permissions that grant access to the endpoint scopes []"} + requests_mock.get(f"{RAPID_URL}/protected_domains", json=expected) + + res = rapid.list_protected_domains() + assert res == expected + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_list_protected_domains_failure_ClientDoesNotHaveUserAdminPermissions(self, requests_mock: Mocker, rapid: Rapid): + expected = {"details": "User xxx-yyy-zzz does not have permissions that grant access to the endpoint scopes []"} + requests_mock.get(f"{RAPID_URL}/protected_domains", json=expected, status_code=401) + with pytest.raises(ClientDoesNotHaveUserAdminPermissionsException): + rapid.list_protected_domains() + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_delete_dataset_success(self, requests_mock: Mocker, rapid: Rapid): + layer = "raw" + domain = "test_domain" + dataset = "test_dataset" + mocked_response = {'details': '{dataset} has been deleted.'} + requests_mock.delete( + f"{RAPID_URL}/datasets/{layer}/{domain}/{dataset}", + json=mocked_response, + status_code=202 + ) + res = rapid.delete_dataset(layer, domain, dataset) + assert res == mocked_response + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_delete_dataset_failure_DatasetNotFound(self, requests_mock: Mocker, rapid: Rapid): + layer = "raw" + domain = "test_domain" + dataset = "test_dataset" + mocked_response = {"response": "dummy"} + requests_mock.delete( + f"{RAPID_URL}/datasets/{layer}/{domain}/{dataset}", + json=mocked_response, + status_code=400 + ) + with pytest.raises(DatasetNotFoundException): + rapid.delete_dataset(layer, domain, dataset) + + @pytest.mark.usefixtures("requests_mock", "rapid") + def test_delete_dataset_failure_ClientDoesNotHaveDataAdminPermissions(self, requests_mock: Mocker, rapid: Rapid): + layer = "raw" + domain = "test_domain" + dataset = "test_dataset" + mocked_response = {'details': "User xxx-yyy-zzz does not have permissions that grant access to the endpoint scopes []"} + requests_mock.delete( + f"{RAPID_URL}/datasets/{layer}/{domain}/{dataset}", + json=mocked_response, + status_code=401 + ) + with pytest.raises(ClientDoesNotHaveDataAdminPermissionsException): + rapid.delete_dataset(layer, domain, dataset)