diff --git a/selfprivacy_api/graphql/common_types/user.py b/selfprivacy_api/graphql/common_types/user.py index 2135be2..f401c82 100644 --- a/selfprivacy_api/graphql/common_types/user.py +++ b/selfprivacy_api/graphql/common_types/user.py @@ -36,7 +36,10 @@ def get_user_by_username(username: str) -> typing.Optional[User]: with ReadUserData() as data: if username == "root": - if data["ssh"]["rootKeys"] not in data: + if "ssh" not in data: + data["ssh"] = [] + + elif data["ssh"].get("rootKeys") is None: data["ssh"]["rootKeys"] = [] return User( @@ -54,6 +57,9 @@ def get_user_by_username(username: str) -> typing.Optional[User]: ssh_keys=data["sshKeys"], ) else: + if "users" not in data: + data["users"] = [] + for user in data["users"]: if user["username"] == username: if "sshKeys" not in user: diff --git a/selfprivacy_api/graphql/mutations/ssh_mutations.py b/selfprivacy_api/graphql/mutations/ssh_mutations.py index 8303ddd..ce658e1 100644 --- a/selfprivacy_api/graphql/mutations/ssh_mutations.py +++ b/selfprivacy_api/graphql/mutations/ssh_mutations.py @@ -6,15 +6,12 @@ import strawberry from selfprivacy_api.graphql import IsAuthenticated from selfprivacy_api.graphql.common_types.user import ( - User, UserMutationReturn, - UserType, get_user_by_username, ) from selfprivacy_api.utils import ( WriteUserData, - ReadUserData, validate_ssh_public_key, ) diff --git a/selfprivacy_api/graphql/mutations/system_mutations.py b/selfprivacy_api/graphql/mutations/system_mutations.py index d080af3..517a697 100644 --- a/selfprivacy_api/graphql/mutations/system_mutations.py +++ b/selfprivacy_api/graphql/mutations/system_mutations.py @@ -7,7 +7,7 @@ from selfprivacy_api.graphql import IsAuthenticated from selfprivacy_api.graphql.mutations.mutation_interface import ( MutationReturnInterface, ) -from selfprivacy_api.utils import ReadUserData, WriteUserData +from selfprivacy_api.utils import WriteUserData @strawberry.type diff --git a/selfprivacy_api/graphql/mutations/users_mutations.py b/selfprivacy_api/graphql/mutations/users_mutations.py index 06622e0..75ed0c3 100644 --- a/selfprivacy_api/graphql/mutations/users_mutations.py +++ b/selfprivacy_api/graphql/mutations/users_mutations.py @@ -9,7 +9,7 @@ from selfprivacy_api.graphql.common_types.user import ( get_user_by_username, ) from selfprivacy_api.graphql.mutations.mutation_interface import ( - MutationReturnInterface, + GenericMutationReturn, ) from selfprivacy_api.utils import ( WriteUserData, @@ -35,6 +35,15 @@ class UserMutations: def create_user(self, user: UserMutationInput) -> UserMutationReturn: """Create a new user""" + # Check if password is null or none + if user.password is None or user.password == "": + return UserMutationReturn( + success=False, + message="Password is none or null", + code=400, + user=None, + ) + hashed_password = hash_password(user.password) # Check if username is forbidden @@ -87,6 +96,9 @@ class UserMutations: ) with WriteUserData() as data: + if "users" not in data: + data["users"] = [] + data["users"].append( { "username": user.username, @@ -101,11 +113,11 @@ class UserMutations: ) @strawberry.mutation(permission_classes=[IsAuthenticated]) - def delete_user(self, username: str) -> MutationReturnInterface: + def delete_user(self, username: str) -> GenericMutationReturn: with WriteUserData() as data: if username == data["username"] or username == "root": - return MutationReturnInterface( + return GenericMutationReturn( success=False, message="Cannot delete main or root user", code=400, @@ -117,13 +129,13 @@ class UserMutations: data["users"].remove(data_user) break else: - return MutationReturnInterface( + return GenericMutationReturn( success=False, message="User does not exist", code=404, ) - return MutationReturnInterface( + return GenericMutationReturn( success=True, message="User was deleted", code=200, diff --git a/tests/test_graphql/test_system.py b/tests/test_graphql/test_system.py index bba6918..476846a 100644 --- a/tests/test_graphql/test_system.py +++ b/tests/test_graphql/test_system.py @@ -54,7 +54,7 @@ class ProcessMock: self.args = args self.kwargs = kwargs - def communicate(): # pylint: disable=no-method-argument + def communicate(): # pylint: disable=no-method-argument return (b"", None) returncode = 0 @@ -63,7 +63,7 @@ class ProcessMock: class BrokenServiceMock(ProcessMock): """Mock subprocess.Popen for broken service""" - def communicate(): # pylint: disable=no-method-argument + def communicate(): # pylint: disable=no-method-argument return (b"Testing error", None) returncode = 3 diff --git a/tests/test_graphql/test_users.py b/tests/test_graphql/test_users.py index 5b19890..744a94e 100644 --- a/tests/test_graphql/test_users.py +++ b/tests/test_graphql/test_users.py @@ -96,7 +96,7 @@ class ProcessMock: self.args = args self.kwargs = kwargs - def communicate(): # pylint: disable=no-method-argument + def communicate(): # pylint: disable=no-method-argument return (b"NEW_HASHED", None) returncode = 0 @@ -235,7 +235,7 @@ def test_graphql_get_some_user(authorized_client, some_users, mock_subprocess_po assert response.json["data"]["users"]["getUser"]["username"] == "user2" assert response.json["data"]["users"]["getUser"]["sshKeys"] == [] -# fail + def test_graphql_get_root_user(authorized_client, some_users, mock_subprocess_popen): response = authorized_client.get( "/graphql", @@ -251,8 +251,9 @@ def test_graphql_get_root_user(authorized_client, some_users, mock_subprocess_po assert len(response.json["data"]["users"]["getUser"]) == 2 assert response.json["data"]["users"]["getUser"]["username"] == "root" - assert response.json["data"]["users"]["getUser"]["sshKeys"] == [] - + assert response.json["data"]["users"]["getUser"]["sshKeys"] == [ + "ssh-ed25519 KEY test@pc" + ] def test_graphql_get_main_user(authorized_client, one_user, mock_subprocess_popen): @@ -270,7 +271,25 @@ def test_graphql_get_main_user(authorized_client, one_user, mock_subprocess_pope assert len(response.json["data"]["users"]["getUser"]) == 2 assert response.json["data"]["users"]["getUser"]["username"] == "tester" - assert response.json["data"]["users"]["getUser"]["sshKeys"] == ["ssh-rsa KEY test@pc"] + assert response.json["data"]["users"]["getUser"]["sshKeys"] == [ + "ssh-rsa KEY test@pc" + ] + + +def test_graphql_get_404user(authorized_client, one_user, mock_subprocess_popen): + response = authorized_client.get( + "/graphql", + json={ + "query": API_GET_USERS, + "variables": { + "username": "tyler_durden", + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["users"]["getUser"] == None API_CHANGE_USERS_MUTATION = """ @@ -279,7 +298,10 @@ mutation createUser($user: UserMutationInput!) { success message code - user + user { + username + sshKeys + } } } """ @@ -292,7 +314,7 @@ def test_graphql_add_user_unauthorize(client, one_user, mock_subprocess_popen): "query": API_CHANGE_USERS_MUTATION, "variables": { "user": { - "username": "user1", + "username": "user2", "password": "12345678", }, }, @@ -302,7 +324,131 @@ def test_graphql_add_user_unauthorize(client, one_user, mock_subprocess_popen): assert response.json.get("data") is None -def test_graphql_add_usere(authorized_client, one_user, mock_subprocess_popen): +def test_graphql_add_user(authorized_client, one_user, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": "user2", + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 201 + assert response.json["data"]["createUser"]["success"] is True + + assert response.json["data"]["createUser"]["user"]["username"] == "user2" + assert response.json["data"]["createUser"]["user"]["sshKeys"] == [] + + +def test_graphql_add_undefined_settings( + authorized_client, undefined_settings, mock_subprocess_popen +): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": "user2", + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 201 + assert response.json["data"]["createUser"]["success"] is True + + assert response.json["data"]["createUser"]["user"]["username"] == "user2" + assert response.json["data"]["createUser"]["user"]["sshKeys"] == [] + + +def test_graphql_add_without_password( + authorized_client, one_user, mock_subprocess_popen +): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": "user2", + "password": "", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 400 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +def test_graphql_add_without_both(authorized_client, one_user, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": "", + "password": "", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 400 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +@pytest.mark.parametrize("username", invalid_usernames) +def test_graphql_add_system_username( + authorized_client, one_user, mock_subprocess_popen, username +): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": username, + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 409 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +def test_graphql_add_existing_user(authorized_client, one_user, mock_subprocess_popen): response = authorized_client.post( "/graphql", json={ @@ -318,6 +464,259 @@ def test_graphql_add_usere(authorized_client, one_user, mock_subprocess_popen): assert response.status_code == 200 assert response.json.get("data") is not None - # assert response.json["data"][""]["message"] is not None - # assert response.json["data"][""]["code"] == 200 - # assert response.json["data"][""]["success"] is True + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 409 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +def test_graphql_add_main_user(authorized_client, one_user, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": "tester", + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 409 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +def test_graphql_add_long_username(authorized_client, one_user, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": "a" * 32, + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 400 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +@pytest.mark.parametrize("username", ["", "1", "фыр", "user1@", "^-^"]) +def test_graphql_add_invalid_username( + authorized_client, one_user, mock_subprocess_popen, username +): + response = authorized_client.post( + "/graphql", + json={ + "query": API_CHANGE_USERS_MUTATION, + "variables": { + "user": { + "username": username, + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["createUser"]["message"] is not None + assert response.json["data"]["createUser"]["code"] == 400 + assert response.json["data"]["createUser"]["success"] is False + + assert response.json["data"]["createUser"]["user"] == None + + +API_DELETE_USERS_MUTATION = """ +mutation deleteUser($username: String!) { + deleteUser(username: $username) { + success + message + code + } +} +""" + + +def test_graphql_delete_user_unauthorized(client, some_users, mock_subprocess_popen): + response = client.post( + "/graphql", + json={ + "query": API_DELETE_USERS_MUTATION, + "variables": {"username": "user1"}, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is None + + +def test_graphql_delete_user(authorized_client, some_users, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_DELETE_USERS_MUTATION, + "variables": {"username": "user1"}, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["deleteUser"]["code"] == 200 + assert response.json["data"]["deleteUser"]["message"] is not None + assert response.json["data"]["deleteUser"]["success"] is True + + +@pytest.mark.parametrize("username", ["", "def"]) +def test_graphql_delete_404users( + authorized_client, some_users, mock_subprocess_popen, username +): + response = authorized_client.post( + "/graphql", + json={ + "query": API_DELETE_USERS_MUTATION, + "variables": {"username": username}, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["deleteUser"]["code"] == 404 + assert response.json["data"]["deleteUser"]["message"] is not None + assert response.json["data"]["deleteUser"]["success"] is False + + +@pytest.mark.parametrize("username", invalid_usernames) +def test_graphql_delete_system_users( + authorized_client, some_users, mock_subprocess_popen, username +): + response = authorized_client.post( + "/graphql", + json={ + "query": API_DELETE_USERS_MUTATION, + "variables": {"username": username}, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert ( + response.json["data"]["deleteUser"]["code"] == 404 + or response.json["data"]["deleteUser"]["code"] == 400 + ) + assert response.json["data"]["deleteUser"]["message"] is not None + assert response.json["data"]["deleteUser"]["success"] is False + + +def test_graphql_delete_main_user(authorized_client, some_users, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_DELETE_USERS_MUTATION, + "variables": {"username": "tester"}, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["deleteUser"]["code"] == 400 + assert response.json["data"]["deleteUser"]["message"] is not None + assert response.json["data"]["deleteUser"]["success"] is False + + +API_UPDATE_USERS_MUTATION = """ +mutation updateUser($user: UserMutationInput!) { + updateUser(user: $user) { + success + message + code + user { + username + sshKeys + } + } +} +""" + + +def test_graphql_update_user_unauthorized(client, some_users, mock_subprocess_popen): + response = client.post( + "/graphql", + json={ + "query": API_UPDATE_USERS_MUTATION, + "variables": { + "user": { + "username": "user1", + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is None + + +def test_graphql_update_user(authorized_client, some_users, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_UPDATE_USERS_MUTATION, + "variables": { + "user": { + "username": "user1", + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["updateUser"]["code"] == 200 + assert response.json["data"]["updateUser"]["message"] is not None + assert response.json["data"]["updateUser"]["success"] is True + + assert response.json["data"]["updateUser"]["user"]["username"] == "user1" + assert response.json["data"]["updateUser"]["user"]["sshKeys"] == [ + "ssh-rsa KEY user1@pc" + ] + assert mock_subprocess_popen.call_count == 1 + + +def test_graphql_update_404user(authorized_client, some_users, mock_subprocess_popen): + response = authorized_client.post( + "/graphql", + json={ + "query": API_UPDATE_USERS_MUTATION, + "variables": { + "user": { + "username": "user666", + "password": "12345678", + }, + }, + }, + ) + assert response.status_code == 200 + assert response.json.get("data") is not None + + assert response.json["data"]["updateUser"]["code"] == 404 + assert response.json["data"]["updateUser"]["message"] is not None + assert response.json["data"]["updateUser"]["success"] is False + + assert response.json["data"]["updateUser"]["user"] == None + assert mock_subprocess_popen.call_count == 1