diff --git a/README.rst b/README.rst index f08d5a9..78b88f3 100644 --- a/README.rst +++ b/README.rst @@ -132,7 +132,7 @@ supported, with a simulated behaviour for server that do not support it. For the ``AUTHENTICATE`` command, supported mechanisms are ``DIGEST-MD5``, -``PLAIN``, ``LOGIN`` and ``OAUTHBEARER``. +``PLAIN``, ``LOGIN``, ``OAUTHBEARER`` and ``XOAUTH2``. Basic usage ^^^^^^^^^^^ diff --git a/sievelib/managesieve.py b/sievelib/managesieve.py index e9e2298..8ca5ba6 100644 --- a/sievelib/managesieve.py +++ b/sievelib/managesieve.py @@ -30,7 +30,7 @@ "VERSION", ] -SUPPORTED_AUTH_MECHS = ["DIGEST-MD5", "PLAIN", "LOGIN", "OAUTHBEARER"] +SUPPORTED_AUTH_MECHS = ["DIGEST-MD5", "PLAIN", "LOGIN", "OAUTHBEARER", "XOAUTH2"] class Error(Exception): @@ -419,6 +419,27 @@ def _oauthbearer_authentication( return True return False + def _xoauth2_authentication( + self, login: bytes, password: bytes, authz_id: bytes = b"" + ) -> bool: + """ + OAUTHBEARER authentication. + + :param login: username + :param password: access token + :return: True on success, False otherwise. + """ + if isinstance(login, str): + login = login.encode("utf-8") + if isinstance(password, str): + password = password.encode("utf-8") + token = b"user=" + login + b",\001auth=Bearer " + password + b"\001\001" + token = base64.b64encode(token) + code, data = self.__send_command("AUTHENTICATE", [b"XOAUTH2", token]) + if code == "OK": + return True + return False + def __authenticate( self, login: str, diff --git a/sievelib/tests/test_managesieve.py b/sievelib/tests/test_managesieve.py index 0232ab1..628fc9a 100644 --- a/sievelib/tests/test_managesieve.py +++ b/sievelib/tests/test_managesieve.py @@ -8,14 +8,14 @@ CAPABILITIES = ( b'"IMPLEMENTATION" "Example1 ManageSieved v001"\r\n' b'"VERSION" "1.0"\r\n' - b'"SASL" "PLAIN SCRAM-SHA-1 GSSAPI OAUTHBEARER"\r\n' + b'"SASL" "PLAIN SCRAM-SHA-1 GSSAPI OAUTHBEARER XOAUTH2"\r\n' b'"SIEVE" "fileinto vacation"\r\n' b'"STARTTLS"\r\n' ) CAPABILITIES_WITHOUT_VERSION = ( b'"IMPLEMENTATION" "Example1 ManageSieved v001"\r\n' - b'"SASL" "PLAIN SCRAM-SHA-1 GSSAPI OAUTHBEARER"\r\n' + b'"SASL" "PLAIN SCRAM-SHA-1 GSSAPI OAUTHBEARER XOAUTH2"\r\n' b'"SIEVE" "fileinto vacation"\r\n' b'"STARTTLS"\r\n' ) @@ -64,6 +64,11 @@ def test_auth_oauthbearer(self, mock_socket): mock_socket.return_value.recv.side_effect = (AUTHENTICATION,) self.assertTrue(self.client.connect("user", "token", authmech="OAUTHBEARER")) + def test_auth_xoauth2(self, mock_socket): + """Test XOAUTH2 mechanism.""" + mock_socket.return_value.recv.side_effect = (AUTHENTICATION,) + self.assertTrue(self.client.connect("user", "token", authmech="XOAUTH2")) + def test_capabilities(self, mock_socket): """Test capabilities command.""" self.authenticate(mock_socket)