diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index 01efefa7e..53b00e277 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -72,6 +72,11 @@ Activate/Deactivate a user:: user.activate() user.deactivate() +Ban/Unban a user:: + + user.ban() + user.unban() + Follow/Unfollow a user:: user.follow() diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 3da399c54..251ef7b25 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -186,6 +186,14 @@ class GitlabActivateError(GitlabOperationError): pass +class GitlabBanError(GitlabOperationError): + pass + + +class GitlabUnbanError(GitlabOperationError): + pass + + class GitlabSubscribeError(GitlabOperationError): pass diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index acd3b2f76..9e76bd5d0 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -283,6 +283,48 @@ def activate(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: self._attrs["state"] = "active" return server_data + @cli.register_custom_action("User") + @exc.on_http_error(exc.GitlabBanError) + def ban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + """Ban the user. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabBanError: If the user could not be banned + + Returns: + Whether the user has been banned + """ + path = f"/users/{self.encoded_id}/ban" + server_data = self.manager.gitlab.http_post(path, **kwargs) + if server_data: + self._attrs["state"] = "banned" + return server_data + + @cli.register_custom_action("User") + @exc.on_http_error(exc.GitlabUnbanError) + def unban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: + """Unban the user. + + Args: + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabUnbanError: If the user could not be unbanned + + Returns: + Whether the user has been unbanned + """ + path = f"/users/{self.encoded_id}/unban" + server_data = self.manager.gitlab.http_post(path, **kwargs) + if server_data: + self._attrs["state"] = "active" + return server_data + class UserManager(CRUDMixin, RESTManager): _path = "/users" diff --git a/tests/functional/api/test_users.py b/tests/functional/api/test_users.py index 0c5803408..5629063a7 100644 --- a/tests/functional/api/test_users.py +++ b/tests/functional/api/test_users.py @@ -37,6 +37,16 @@ def test_block_user(gl, user): assert user in users +def test_ban_user(gl, user): + user.ban() + retrieved_user = gl.users.get(user.id) + assert retrieved_user.state == "banned" + + user.unban() + retrieved_user = gl.users.get(user.id) + assert retrieved_user.state == "active" + + def test_delete_user(gl, wait_for_sidekiq): new_user = gl.users.create( { diff --git a/tests/unit/objects/test_users.py b/tests/unit/objects/test_users.py index a2ea5dec9..1c8959375 100644 --- a/tests/unit/objects/test_users.py +++ b/tests/unit/objects/test_users.py @@ -80,6 +80,32 @@ def resp_activate(): yield rsps +@pytest.fixture +def resp_ban(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/users/1/ban", + json={}, + content_type="application/json", + status=201, + ) + yield rsps + + +@pytest.fixture +def resp_unban(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/users/1/unban", + json={}, + content_type="application/json", + status=201, + ) + yield rsps + + @pytest.fixture def resp_get_user_status(): content = { @@ -216,6 +242,14 @@ def test_user_activate_deactivate(user, resp_activate): user.deactivate() +def test_user_ban(user, resp_ban): + user.ban() + + +def test_user_unban(user, resp_unban): + user.unban() + + def test_delete_user_identity(user, resp_delete_user_identity): user.identityproviders.delete("test_provider")