Skip to content

Error 401 using certificate-based authentication (CBA) #1350

@darknetehf

Description

@darknetehf

Hello,

TL;DR: I cannot get exchangelib to work with CBA, although oauth2 works.

I am already using exchangelib with oauth2, but would like to use certificates instead of tokens, because tokens have a limited lifetime.
I have researched related issues: #1183 #1093 #751 and Google.

Working unit test with oauth2:

from exchangelib import IMPERSONATION, OAUTH2, Account, Configuration, OAuth2Credentials
from exchangelib.version import EXCHANGE_O365, Version

CLIENT_ID = "xxx"
CLIENT_SECRET = "xxx"
TENANT_ID = "xxx"
imap_host = "outlook.office365.com"
mailbox_user = "user@example.com"

# authenticate using OAUTH2
credentials = OAuth2Credentials(
    client_id=CLIENT_ID, tenant_id=TENANT_ID, client_secret=CLIENT_SECRET
)
# do not rely on autodiscover but specify explicit server name
config = Configuration(
    server=imap_host, credentials=credentials,
    version=Version(build=EXCHANGE_O365), auth_type=OAUTH2
)


def test_exchange_connection(logger):
    logger.info("Logging in to mailbox")

    # see: https://github.com/ecederstrand/exchangelib/issues/735
    # use IMPERSONATION instead of DELEGATE: "ExchangeImpersonation SOAP header must be present for this type of OAuth token"
    my_account = Account(
        primary_smtp_address=mailbox_user,
        config=config, autodiscover=False, access_type=IMPERSONATION
    )
    logger.info(f"my_account.root.tree(): {my_account.root.tree()}")
    assert my_account.root.child_folder_count > 0

Attempted unit test for CBA:

from exchangelib.protocol import BaseProtocol
from exchangelib import Account, Configuration, DELEGATE, IMPERSONATION, CBA, TLSClientAuth
from exchangelib.version import EXCHANGE_O365, Version

imap_host = "outlook.office365.com"
mailbox_user = "user@example.com"

TLSClientAuth.cert_file = "mycert.pem"
BaseProtocol.HTTP_ADAPTER_CLS = TLSClientAuth

config = Configuration(server=imap_host, auth_type=CBA, version=Version(build=EXCHANGE_O365))

def test_exchange_connection(logger):
    my_account = Account(
        primary_smtp_address=mailbox_user,
        config=config, autodiscover=False,
        access_type=DELEGATE)

    logger.info(f"my_account.root.child_folder_count: {dir(my_account.root.child_folder_count)}")

This results in a 401 error.

Relevant notes:

  • I am not using autodiscover
  • I already use certificates in Azure AD for MSAL with Python and that works
  • The Python code uses a PEM file that combines both private key and public certificate - when using only the private key I had a loop of SSL errors
  • I have tried DELEGATE and IMPERSONATION, not sure if that would make any difference

Is this the correct way or is there anything I should try?
Log below and thanks for looking.

============================= test session starts ==============================
platform linux -- Python 3.12.8, pytest-7.4.4, pluggy-1.5.0
configfile: pytest.ini

----------------------------- live log collection ------------------------------
2025-01-15 18:17:49 - DEBUG - spnego._gss - :55 - Python gssapi not available, cannot use any GSSAPIProxy protocols: No module named 'krb5'
collected 1 item

tests/test_exchange_cba.py::test_exchange_connection 
-------------------------------- live log call ---------------------------------
2025-01-15 18:17:49 - DEBUG - tzlocal - _get_localzone_name:123 - /etc/localtime found
2025-01-15 18:17:49 - DEBUG - tzlocal - _get_localzone_name:139 - 1 found:
 {'/etc/localtime is a symlink to': 'Iceland'}
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - __call__:408 - Waiting for _protocol_cache_lock
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - __call__:421 - Protocol __call__ cache miss. Adding key '('https://outlook.office365.com/EWS/Exchange.asmx', None)'
2025-01-15 18:17:49 - DEBUG - exchangelib.account - __init__:206 - Added account: user@example.com
2025-01-15 18:17:49 - INFO - tests.conftest - test_exchange_connection:29 - my_account: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_consume_item_service', 'access_type', 'ad_response', 'admin_audit_logs', 'affinity_cookie', 'archive_deleted_items', 'archive_inbox', 'archive_msg_folder_root', 'archive_recoverable_items_deletions', 'archive_recoverable_items_purges', 'archive_recoverable_items_root', 'archive_recoverable_items_versions', 'archive_root', 'bulk_archive', 'bulk_copy', 'bulk_create', 'bulk_delete', 'bulk_mark_as_junk', 'bulk_move', 'bulk_send', 'bulk_update', 'calendar', 'conflicts', 'contacts', 'conversation_history', 'create_rule', 'default_timezone', 'delegates', 'delete_rule', 'directory', 'domain', 'drafts', 'export', 'favorites', 'fetch', 'fetch_personas', 'fullname', 'identity', 'im_contact_list', 'inbox', 'journal', 'junk', 'local_failures', 'locale', 'mail_tips', 'msg_folder_root', 'my_contacts', 'notes', 'oof_settings', 'outbox', 'people_connect', 'primary_smtp_address', 'protocol', 'public_folders_root', 'pull_subscription', 'push_subscription', 'quick_contacts', 'recipient_cache', 'recoverable_items_deletions', 'recoverable_items_purges', 'recoverable_items_root', 'recoverable_items_versions', 'root', 'rules', 'search_folders', 'sent', 'server_failures', 'set_rule', 'streaming_subscription', 'subscribe_to_pull', 'subscribe_to_push', 'subscribe_to_streaming', 'sync_issues', 'tasks', 'todo_search', 'trash', 'unsubscribe', 'upload', 'version', 'voice_mail']
2025-01-15 18:17:49 - DEBUG - exchangelib.services.common - _chunked_get_elements:278 - Processing chunk 1 containing 1 items
2025-01-15 18:17:49 - DEBUG - exchangelib.services.common - _get_response_xml:393 - Calling service GetFolder
2025-01-15 18:17:49 - DEBUG - exchangelib.services.common - _get_response_xml:395 - Trying API version Exchange2016
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - increase_poolsize:206 - Server outlook.office365.com: Increasing session pool size from 0 to 1
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - create_session:330 - Server outlook.office365.com: Created session 84582
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - get_session:249 - Server outlook.office365.com: Waiting for session
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - get_session:255 - Server outlook.office365.com: Got session 84582
2025-01-15 18:17:49 - DEBUG - exchangelib.util - post_ratelimited:811 - Session 84582 thread 140214710770560 timeout 120: POST'ing to https://outlook.office365.com/EWS/Exchange.asmx after 0s sleep
2025-01-15 18:17:49 - DEBUG - urllib3.connectionpool - _new_conn:1049 - Starting new HTTPS connection (1): outlook.office365.com:443
2025-01-15 18:17:50 - DEBUG - urllib3.connectionpool - _make_request:544 - https://outlook.office365.com:443 "POST /EWS/Exchange.asmx HTTP/1.1" 401 0
2025-01-15 18:17:50 - DEBUG - exchangelib.util - post_ratelimited:862 - Timeout: 120
Session: 84582
Thread: 140214710770560
Auth type: None
URL: https://outlook.office365.com/EWS/Exchange.asmx
HTTP adapter: 
Streaming: False
Response time: 0.23992178899788996
Status code: 401
Request headers: {'User-Agent': 'exchangelib/5.5.0 (python-requests/2.32.3)', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Type': 'text/xml; charset=utf-8', 'X-AnchorMailbox': 'user@example.com', 'Content-Length': '1214'}
Response headers: {'Content-Length': '0', 'Server': 'Microsoft-HTTPAPI/2.0', 'X-BEServer': 'AS8PR08MB6357', 'X-NanoProxy': '1,1', 'Request-Id': '3d6befc1-e20e-e7b9-1ac1-75aae8bdd77b', 'X-CalculatedFETarget': 'AS9PR07CU001.internal.outlook.com', 'X-BeSku': 'WCS6', 'X-BackEndHttpStatus': '401,401', 'BasicChallengeAdded': 'True', 'MS-CV': 'we9rPQ7iuecawXWq6L3Xew.1.1', 'X-CalculatedBETarget': 'AS8PR08MB6357.eurprd08.prod.outlook.com', 'X-DiagInfo': 'AS8PR08MB6357', 'X-FEEFZInfo': 'DHR', 'X-UserType': 'Business', 'X-FEProxyInfo': 'AS9PR07CA0001', 'X-FEServer': 'DUZPR01CA0137', 'X-Proxy-BackendServerStatus': '401', 'X-Proxy-RoutingCorrectness': '1', 'X-RUM-NotUpdateQueriedPath': '1', 'X-RUM-NotUpdateQueriedDbCopy': '1', 'X-RUM-Validated': '1', 'X-FirstHopCafeEFZ': 'DUB', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'Set-Cookie': 'exchangecookie=a7a24814fef441248023763bc5787ec0; expires=Thu, 15-Jan-2026 18:17:50 GMT; path=/; secure; HttpOnly', 'WWW-Authenticate': 'Basic Realm=""', 'Date': 'Wed, 15 Jan 2025 18:17:49 GMT'}
2025-01-15 18:17:50 - DEBUG - exchangelib.util.xml - post_ratelimited:863 - Request XML: b'\nIdOnlyuser@example.comSMTPMailbox'
Response XML: b''
2025-01-15 18:17:50 - DEBUG - exchangelib.protocol - retire_session:279 - Server outlook.office365.com: Retiring session 84582
2025-01-15 18:17:50 - DEBUG - exchangelib.protocol - create_session:330 - Server outlook.office365.com: Created session 79172
2025-01-15 18:17:50 - DEBUG - exchangelib.protocol - release_session:261 - Server outlook.office365.com: Releasing session 79172
FAILED                                                                   [100%]

=================================== FAILURES ===================================
___________________________ test_exchange_connection ___________________________

self = 
obj = 
cls = 

    def __get__(self, obj, cls):
        if obj is None:
            return self
    
        obj_dict = obj.__dict__
        name = self.func.__name__
        with self.lock:
            try:
                # check if the value was computed before the lock was acquired
>               return obj_dict[name]
E               KeyError: 'root'

../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/cached_property.py:63: KeyError

During handling of the above exception, another exception occurred:

logger = 

    def test_exchange_connection(logger):
        my_account = Account(
            primary_smtp_address=mailbox_user,
            config=config, autodiscover=False,
            access_type=DELEGATE)
        logger.info(f"my_account: {dir(my_account)}")
>       logger.info(f"my_account.root.child_folder_count: {dir(my_account.root.child_folder_count)}")

tests/test_exchange_cba.py:30: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/cached_property.py:67: in __get__
    return obj_dict.setdefault(name, self.func(obj))
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/account.py:350: in root
    return Root.get_distinguished(account=self)
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/folders/roots.py:145: in get_distinguished
    return cls._get_distinguished(
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/folders/base.py:226: in _get_distinguished
    return cls.resolve(account=folder.account, folder=folder)
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/folders/base.py:530: in resolve
    folders = list(FolderCollection(account=account, folders=[folder]).resolve())
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/folders/collections.py:335: in resolve
    yield from self.__class__(account=self.account, folders=resolveable_folders).get_folders(
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/folders/collections.py:403: in get_folders
    yield from GetFolder(account=self.account).call(
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/services/get_folder.py:51: in _elems_to_objs
    for folder, elem in zip(self.folders, elems):
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/services/common.py:279: in _chunked_get_elements
    yield from self._get_elements(payload=payload_func(chunk, **kwargs))
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/services/common.py:300: in _get_elements
    yield from self._response_generator(payload=payload)
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/services/common.py:263: in _response_generator
    response = self._get_response_xml(payload=payload)
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/services/common.py:396: in _get_response_xml
    r = self._get_response(payload=payload, api_version=api_version)
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/services/common.py:347: in _get_response
    r, session = post_ratelimited(
../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/util.py:866: in post_ratelimited
    protocol.retry_policy.raise_response_errors(r)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = 
response = 

    def raise_response_errors(self, response):
        if response.status_code == 200:
            # Response is OK
            return
        if response.status_code == 500 and response.content and is_xml(response.content):
            # Some genius at Microsoft thinks it's OK to send a valid SOAP response as an HTTP 500
            log.debug("Got status code %s but trying to parse content anyway", response.status_code)
            return
        cas_error = response.headers.get("X-CasErrorCode")
        if cas_error:
            if cas_error.startswith("CAS error:"):
                # Remove unnecessary text
                cas_error = cas_error.split(":", 1)[1].strip()
            raise CASError(cas_error=cas_error, response=response)
        if response.status_code == 500 and (
            b"The specified server version is invalid" in response.content
            or b"ErrorInvalidSchemaVersionForMailboxVersion" in response.content
        ):
            # Another way of communicating invalid schema versions
            raise ErrorInvalidSchemaVersionForMailboxVersion("Invalid server version")
        if response.headers.get("connection") == "close":
            # Connection closed. OK to retry.
            raise ErrorServerBusy("Caused by closed connection")
        if (
            response.status_code == 302
            and response.headers.get("location", "").lower()
            == "/ews/genericerrorpage.htm?aspxerrorpath=/ews/exchange.asmx"
        ):
            # Redirect to genericerrorpage.htm is ridiculous behaviour for random outages. OK to retry.
            #
            # Redirect to '/internalsite/internalerror.asp' or '/internalsite/initparams.aspx' is caused by e.g. TLS
            # certificate f*ckups on the Exchange server. We should not retry those.
            raise ErrorInternalServerTransientError(f"Caused by HTTP 302 redirect to {response.headers['location']}")
        if response.status_code in (301, 302):
            try:
                redirect_url = get_redirect_url(response=response, allow_relative=False)
            except RelativeRedirect as e:
                log.debug("Redirect not allowed but we were relative redirected (%s -> %s)", response.url, e.value)
                raise RedirectError(url=e.value)
            log.debug("Redirect not allowed but we were redirected ( (%s -> %s)", response.url, redirect_url)
            raise RedirectError(url=redirect_url)
        if b"The referenced account is currently locked out" in response.content:
            raise UnauthorizedError("The referenced account is currently locked out")
        if response.status_code == 401 and self.fail_fast:
            # This is a login failure
>           raise UnauthorizedError(f"Invalid credentials for {response.url}")
E           exchangelib.errors.UnauthorizedError: Invalid credentials for https://outlook.office365.com/EWS/Exchange.asmx

../../.cache/pypoetry/virtualenvs/previsions-yjiFWp2F-py3.12/lib/python3.12/site-packages/exchangelib/protocol.py:727: UnauthorizedError
------------------------------ Captured log call -------------------------------
2025-01-15 18:17:49 - DEBUG - tzlocal - _get_localzone_name:123 - /etc/localtime found
2025-01-15 18:17:49 - DEBUG - tzlocal - _get_localzone_name:139 - 1 found:
 {'/etc/localtime is a symlink to': 'Iceland'}
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - __call__:408 - Waiting for _protocol_cache_lock
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - __call__:421 - Protocol __call__ cache miss. Adding key '('https://outlook.office365.com/EWS/Exchange.asmx', None)'
2025-01-15 18:17:49 - DEBUG - exchangelib.account - __init__:206 - Added account: user@example.com
2025-01-15 18:17:49 - INFO - tests.conftest - test_exchange_connection:29 - my_account: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_consume_item_service', 'access_type', 'ad_response', 'admin_audit_logs', 'affinity_cookie', 'archive_deleted_items', 'archive_inbox', 'archive_msg_folder_root', 'archive_recoverable_items_deletions', 'archive_recoverable_items_purges', 'archive_recoverable_items_root', 'archive_recoverable_items_versions', 'archive_root', 'bulk_archive', 'bulk_copy', 'bulk_create', 'bulk_delete', 'bulk_mark_as_junk', 'bulk_move', 'bulk_send', 'bulk_update', 'calendar', 'conflicts', 'contacts', 'conversation_history', 'create_rule', 'default_timezone', 'delegates', 'delete_rule', 'directory', 'domain', 'drafts', 'export', 'favorites', 'fetch', 'fetch_personas', 'fullname', 'identity', 'im_contact_list', 'inbox', 'journal', 'junk', 'local_failures', 'locale', 'mail_tips', 'msg_folder_root', 'my_contacts', 'notes', 'oof_settings', 'outbox', 'people_connect', 'primary_smtp_address', 'protocol', 'public_folders_root', 'pull_subscription', 'push_subscription', 'quick_contacts', 'recipient_cache', 'recoverable_items_deletions', 'recoverable_items_purges', 'recoverable_items_root', 'recoverable_items_versions', 'root', 'rules', 'search_folders', 'sent', 'server_failures', 'set_rule', 'streaming_subscription', 'subscribe_to_pull', 'subscribe_to_push', 'subscribe_to_streaming', 'sync_issues', 'tasks', 'todo_search', 'trash', 'unsubscribe', 'upload', 'version', 'voice_mail']
2025-01-15 18:17:49 - DEBUG - exchangelib.services.common - _chunked_get_elements:278 - Processing chunk 1 containing 1 items
2025-01-15 18:17:49 - DEBUG - exchangelib.services.common - _get_response_xml:393 - Calling service GetFolder
2025-01-15 18:17:49 - DEBUG - exchangelib.services.common - _get_response_xml:395 - Trying API version Exchange2016
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - increase_poolsize:206 - Server outlook.office365.com: Increasing session pool size from 0 to 1
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - create_session:330 - Server outlook.office365.com: Created session 84582
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - get_session:249 - Server outlook.office365.com: Waiting for session
2025-01-15 18:17:49 - DEBUG - exchangelib.protocol - get_session:255 - Server outlook.office365.com: Got session 84582
2025-01-15 18:17:49 - DEBUG - exchangelib.util - post_ratelimited:811 - Session 84582 thread 140214710770560 timeout 120: POST'ing to https://outlook.office365.com/EWS/Exchange.asmx after 0s sleep
2025-01-15 18:17:49 - DEBUG - urllib3.connectionpool - _new_conn:1049 - Starting new HTTPS connection (1): outlook.office365.com:443
2025-01-15 18:17:50 - DEBUG - urllib3.connectionpool - _make_request:544 - https://outlook.office365.com:443 "POST /EWS/Exchange.asmx HTTP/1.1" 401 0
2025-01-15 18:17:50 - DEBUG - exchangelib.util - post_ratelimited:862 - Timeout: 120
Session: 84582
Thread: 140214710770560
Auth type: None
URL: https://outlook.office365.com/EWS/Exchange.asmx
HTTP adapter: 
Streaming: False
Response time: 0.23992178899788996
Status code: 401
Request headers: {'User-Agent': 'exchangelib/5.5.0 (python-requests/2.32.3)', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Type': 'text/xml; charset=utf-8', 'X-AnchorMailbox': 'user@example.com', 'Content-Length': '1214'}
Response headers: {'Content-Length': '0', 'Server': 'Microsoft-HTTPAPI/2.0', 'X-BEServer': 'AS8PR08MB6357', 'X-NanoProxy': '1,1', 'Request-Id': '3d6befc1-e20e-e7b9-1ac1-75aae8bdd77b', 'X-CalculatedFETarget': 'AS9PR07CU001.internal.outlook.com', 'X-BeSku': 'WCS6', 'X-BackEndHttpStatus': '401,401', 'BasicChallengeAdded': 'True', 'MS-CV': 'we9rPQ7iuecawXWq6L3Xew.1.1', 'X-CalculatedBETarget': 'AS8PR08MB6357.eurprd08.prod.outlook.com', 'X-DiagInfo': 'AS8PR08MB6357', 'X-FEEFZInfo': 'DHR', 'X-UserType': 'Business', 'X-FEProxyInfo': 'AS9PR07CA0001', 'X-FEServer': 'DUZPR01CA0137', 'X-Proxy-BackendServerStatus': '401', 'X-Proxy-RoutingCorrectness': '1', 'X-RUM-NotUpdateQueriedPath': '1', 'X-RUM-NotUpdateQueriedDbCopy': '1', 'X-RUM-Validated': '1', 'X-FirstHopCafeEFZ': 'DUB', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'Set-Cookie': 'exchangecookie=a7a24814fef441248023763bc5787ec0; expires=Thu, 15-Jan-2026 18:17:50 GMT; path=/; secure; HttpOnly', 'WWW-Authenticate': 'Basic Realm=""', 'Date': 'Wed, 15 Jan 2025 18:17:49 GMT'}
2025-01-15 18:17:50 - DEBUG - exchangelib.util.xml - post_ratelimited:863 - Request XML: b'\nIdOnlyuser@example.comSMTPMailbox'
Response XML: b''
2025-01-15 18:17:50 - DEBUG - exchangelib.protocol - retire_session:279 - Server outlook.office365.com: Retiring session 84582
2025-01-15 18:17:50 - DEBUG - exchangelib.protocol - create_session:330 - Server outlook.office365.com: Created session 79172
2025-01-15 18:17:50 - DEBUG - exchangelib.protocol - release_session:261 - Server outlook.office365.com: Releasing session 79172
=========================== short test summary info ============================
FAILED tests/test_exchange_cba.py::test_exchange_connection - exchangelib.err...
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
============================== 1 failed in 0.78s ===============================

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions