Module exchangelib.autodiscover.properties

Expand source code
from ..errors import AutoDiscoverFailed, ErrorNonExistentMailbox
from ..fields import (
    BooleanField,
    BuildField,
    Choice,
    ChoiceField,
    EmailAddressField,
    EWSElementField,
    IntegerField,
    OnOffField,
    ProtocolListField,
    TextField,
)
from ..properties import EWSElement
from ..transport import BASIC, CBA, DEFAULT_ENCODING, GSSAPI, NOAUTH, NTLM, SSPI
from ..util import AUTODISCOVER_BASE_NS, AUTODISCOVER_REQUEST_NS
from ..util import AUTODISCOVER_RESPONSE_NS as RNS
from ..util import ParseError, add_xml_child, create_element, is_xml, to_xml, xml_to_str
from ..version import Version


class AutodiscoverBase(EWSElement):
    NAMESPACE = RNS


class User(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/user-pox"""

    ELEMENT_NAME = "User"

    display_name = TextField(field_uri="DisplayName", namespace=RNS)
    legacy_dn = TextField(field_uri="LegacyDN", namespace=RNS)
    deployment_id = TextField(field_uri="DeploymentId", namespace=RNS)  # GUID format
    autodiscover_smtp_address = EmailAddressField(field_uri="AutoDiscoverSMTPAddress", namespace=RNS)


class IntExtUrlBase(AutodiscoverBase):
    external_url = TextField(field_uri="ExternalUrl", namespace=RNS)
    internal_url = TextField(field_uri="InternalUrl", namespace=RNS)


class AddressBook(IntExtUrlBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/addressbook-pox"""

    ELEMENT_NAME = "AddressBook"


class MailStore(IntExtUrlBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailstore-pox"""

    ELEMENT_NAME = "MailStore"


class NetworkRequirements(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/networkrequirements-pox"""

    ELEMENT_NAME = "NetworkRequirements"

    ipv4_start = TextField(field_uri="IPv4Start", namespace=RNS)
    ipv4_end = TextField(field_uri="IPv4End", namespace=RNS)
    ipv6_start = TextField(field_uri="IPv6Start", namespace=RNS)
    ipv6_end = TextField(field_uri="IPv6End", namespace=RNS)


class SimpleProtocol(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/protocol-pox

    Used for the 'Internal' and 'External' elements that may contain a stripped-down version of the Protocol element.
    """

    ELEMENT_NAME = "Protocol"
    WEB = "WEB"
    EXCH = "EXCH"
    EXPR = "EXPR"
    EXHTTP = "EXHTTP"
    TYPES = (WEB, EXCH, EXPR, EXHTTP)

    type = ChoiceField(field_uri="Type", choices={Choice(c) for c in TYPES}, namespace=RNS)
    as_url = TextField(field_uri="ASUrl", namespace=RNS)


class IntExtBase(AutodiscoverBase):
    # TODO: 'OWAUrl' also has an AuthenticationMethod enum-style XML attribute with values:
    #  WindowsIntegrated, FBA, NTLM, Digest, Basic, LiveIdFba, OAuth
    owa_url = TextField(field_uri="OWAUrl", namespace=RNS)
    protocol = EWSElementField(value_cls=SimpleProtocol)


class Internal(IntExtBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/internal-pox"""

    ELEMENT_NAME = "Internal"


class External(IntExtBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/external-pox"""

    ELEMENT_NAME = "External"


class Protocol(SimpleProtocol):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/protocol-pox"""

    # Attribute 'Type' is ignored here. Has a name conflict with the child element and does not seem useful.
    version = TextField(field_uri="Version", is_attribute=True, namespace=RNS)
    internal = EWSElementField(value_cls=Internal)
    external = EWSElementField(value_cls=External)
    ttl = IntegerField(field_uri="TTL", namespace=RNS, default=1)  # TTL for this autodiscover response, in hours
    server = TextField(field_uri="Server", namespace=RNS)
    server_dn = TextField(field_uri="ServerDN", namespace=RNS)
    server_version = BuildField(field_uri="ServerVersion", namespace=RNS)
    mdb_dn = TextField(field_uri="MdbDN", namespace=RNS)
    public_folder_server = TextField(field_uri="PublicFolderServer", namespace=RNS)
    port = IntegerField(field_uri="Port", namespace=RNS, min=1, max=65535)
    directory_port = IntegerField(field_uri="DirectoryPort", namespace=RNS, min=1, max=65535)
    referral_port = IntegerField(field_uri="ReferralPort", namespace=RNS, min=1, max=65535)
    ews_url = TextField(field_uri="EwsUrl", namespace=RNS)
    emws_url = TextField(field_uri="EmwsUrl", namespace=RNS)
    sharing_url = TextField(field_uri="SharingUrl", namespace=RNS)
    ecp_url = TextField(field_uri="EcpUrl", namespace=RNS)
    ecp_url_um = TextField(field_uri="EcpUrl-um", namespace=RNS)
    ecp_url_aggr = TextField(field_uri="EcpUrl-aggr", namespace=RNS)
    ecp_url_mt = TextField(field_uri="EcpUrl-mt", namespace=RNS)
    ecp_url_ret = TextField(field_uri="EcpUrl-ret", namespace=RNS)
    ecp_url_sms = TextField(field_uri="EcpUrl-sms", namespace=RNS)
    ecp_url_publish = TextField(field_uri="EcpUrl-publish", namespace=RNS)
    ecp_url_photo = TextField(field_uri="EcpUrl-photo", namespace=RNS)
    ecp_url_tm = TextField(field_uri="EcpUrl-tm", namespace=RNS)
    ecp_url_tm_creating = TextField(field_uri="EcpUrl-tmCreating", namespace=RNS)
    ecp_url_tm_hiding = TextField(field_uri="EcpUrl-tmHiding", namespace=RNS)
    ecp_url_tm_editing = TextField(field_uri="EcpUrl-tmEditing", namespace=RNS)
    ecp_url_extinstall = TextField(field_uri="EcpUrl-extinstall", namespace=RNS)
    oof_url = TextField(field_uri="OOFUrl", namespace=RNS)
    oab_url = TextField(field_uri="OABUrl", namespace=RNS)
    um_url = TextField(field_uri="UMUrl", namespace=RNS)
    ews_partner_url = TextField(field_uri="EwsPartnerUrl", namespace=RNS)
    login_name = TextField(field_uri="LoginName", namespace=RNS)
    domain_required = OnOffField(field_uri="DomainRequired", namespace=RNS)
    domain_name = TextField(field_uri="DomainName", namespace=RNS)
    spa = OnOffField(field_uri="SPA", namespace=RNS, default=True)
    auth_package = ChoiceField(
        field_uri="AuthPackage",
        namespace=RNS,
        choices={Choice(c) for c in ("basic", "kerb", "kerbntlm", "ntlm", "certificate", "negotiate", "nego2")},
    )
    cert_principal_name = TextField(field_uri="CertPrincipalName", namespace=RNS)
    ssl = OnOffField(field_uri="SSL", namespace=RNS, default=True)
    auth_required = OnOffField(field_uri="AuthRequired", namespace=RNS, default=True)
    use_pop_path = OnOffField(field_uri="UsePOPAuth", namespace=RNS)
    smtp_last = OnOffField(field_uri="SMTPLast", namespace=RNS, default=False)
    network_requirements = EWSElementField(value_cls=NetworkRequirements)
    address_book = EWSElementField(value_cls=AddressBook)
    mail_store = EWSElementField(value_cls=MailStore)

    @property
    def auth_type(self):
        # Translates 'auth_package' value to our own 'auth_type' enum vals
        if not self.auth_required:
            return NOAUTH
        if not self.auth_package:
            return None
        return {
            # Missing in list are DIGEST and OAUTH2
            "basic": BASIC,
            "kerb": GSSAPI,
            "kerbntlm": NTLM,  # Means client can choose between NTLM and GSSAPI
            "ntlm": NTLM,
            "certificate": CBA,
            "negotiate": SSPI,  # Unsure about this one
            "nego2": GSSAPI,
            "anonymous": NOAUTH,  # Seen in some docs even though it's not mentioned in MSDN
        }.get(self.auth_package.lower())


class Error(EWSElement):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/error-pox"""

    ELEMENT_NAME = "Error"
    NAMESPACE = AUTODISCOVER_BASE_NS

    id = TextField(field_uri="Id", namespace=AUTODISCOVER_BASE_NS, is_attribute=True)
    time = TextField(field_uri="Time", namespace=AUTODISCOVER_BASE_NS, is_attribute=True)
    code = TextField(field_uri="ErrorCode", namespace=AUTODISCOVER_BASE_NS)
    message = TextField(field_uri="Message", namespace=AUTODISCOVER_BASE_NS)
    debug_data = TextField(field_uri="DebugData", namespace=AUTODISCOVER_BASE_NS)


class Account(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/account-pox"""

    ELEMENT_NAME = "Account"
    REDIRECT_URL = "redirectUrl"
    REDIRECT_ADDR = "redirectAddr"
    SETTINGS = "settings"
    ACTIONS = (REDIRECT_URL, REDIRECT_ADDR, SETTINGS)

    type = ChoiceField(field_uri="AccountType", namespace=RNS, choices={Choice("email")})
    action = ChoiceField(field_uri="Action", namespace=RNS, choices={Choice(p) for p in ACTIONS})
    microsoft_online = BooleanField(field_uri="MicrosoftOnline", namespace=RNS)
    redirect_url = TextField(field_uri="RedirectURL", namespace=RNS)
    redirect_address = EmailAddressField(field_uri="RedirectAddr", namespace=RNS)
    image = TextField(field_uri="Image", namespace=RNS)  # Path to image used for branding
    service_home = TextField(field_uri="ServiceHome", namespace=RNS)  # URL to website of ISP
    protocols = ProtocolListField()
    # 'SmtpAddress' is inside the 'PublicFolderInformation' element
    public_folder_smtp_address = TextField(field_uri="SmtpAddress", namespace=RNS)

    @classmethod
    def from_xml(cls, elem, account):
        kwargs = {}
        public_folder_information = elem.find(f"{{{cls.NAMESPACE}}}PublicFolderInformation")
        for f in cls.FIELDS:
            if f.name == "public_folder_smtp_address":
                if public_folder_information is None:
                    continue
                kwargs[f.name] = f.from_xml(elem=public_folder_information, account=account)
                continue
            kwargs[f.name] = f.from_xml(elem=elem, account=account)
        cls._clear(elem)
        return cls(**kwargs)


class Response(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/response-pox"""

    ELEMENT_NAME = "Response"

    user = EWSElementField(value_cls=User)
    account = EWSElementField(value_cls=Account)

    @property
    def redirect_address(self):
        try:
            if self.account.action != Account.REDIRECT_ADDR:
                return None
            return self.account.redirect_address
        except AttributeError:
            return None

    @property
    def redirect_url(self):
        try:
            if self.account.action != Account.REDIRECT_URL:
                return None
            return self.account.redirect_url
        except AttributeError:
            return None

    @property
    def autodiscover_smtp_address(self):
        # AutoDiscoverSMTPAddress might not be present in the XML. In this case, use the original email address.
        try:
            if self.account.action != Account.SETTINGS:
                return None
            return self.user.autodiscover_smtp_address
        except AttributeError:
            return None

    @property
    def version(self):
        # Get the server version. Not all protocol entries have a server version, so we cheat a bit and also look at the
        # other ones that point to the same endpoint.
        ews_url = self.protocol.ews_url
        for protocol in self.account.protocols:
            if not protocol.ews_url or not protocol.server_version:
                continue
            if protocol.ews_url.lower() == ews_url.lower():
                return Version(build=protocol.server_version)
        return None

    @property
    def protocol(self):
        """Return the protocol containing an EWS URL.

        A response may contain a number of possible protocol types. EXPR is meant for EWS. See
        https://techcommunity.microsoft.com/t5/blogs/blogarticleprintpage/blog-id/Exchange/article-id/16

        We allow fallback to EXCH if EXPR is not available, to support installations where EXPR is not available.

        Additionally, some responses may contain an EXPR with no EWS URL. In that case, return EXCH, if available.
        """
        protocols = {p.type: p for p in self.account.protocols if p.ews_url}
        if Protocol.EXPR in protocols:
            return protocols[Protocol.EXPR]
        if Protocol.EXCH in protocols:
            return protocols[Protocol.EXCH]
        raise ValueError(
            f"No EWS URL found in any of the available protocols: {[str(p) for p in self.account.protocols]}"
        )


class ErrorResponse(EWSElement):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/response-pox

    Like 'Response', but with a different namespace.
    """

    ELEMENT_NAME = "Response"
    NAMESPACE = AUTODISCOVER_BASE_NS

    error = EWSElementField(value_cls=Error)


class Autodiscover(EWSElement):
    ELEMENT_NAME = "Autodiscover"
    NAMESPACE = AUTODISCOVER_BASE_NS

    response = EWSElementField(value_cls=Response)
    error_response = EWSElementField(value_cls=ErrorResponse)

    @staticmethod
    def _clear(elem):
        # Parent implementation also clears the parent, but this element doesn't have one.
        elem.clear()

    @classmethod
    def from_bytes(cls, bytes_content):
        """Create an instance from response bytes. An Autodiscover request and response example is available at:
        https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/pox-autodiscover-response-for-exchange

        :param bytes_content:
        :return:
        """
        if not is_xml(bytes_content):
            raise ParseError(f"Response is not XML: {bytes_content}", "<not from file>", -1, 0)
        root = to_xml(bytes_content).getroot()  # May raise ParseError
        if root.tag != cls.response_tag():
            raise ParseError(f"Unknown root element in XML: {bytes_content}", "<not from file>", -1, 0)
        return cls.from_xml(elem=root, account=None)

    def raise_errors(self):
        if self.response is not None:
            return

        # Find an error message in the response and raise the relevant exception
        try:
            error_code = self.error_response.error.code
            message = self.error_response.error.message
            if message in ("The e-mail address cannot be found.", "The email address can't be found."):
                raise ErrorNonExistentMailbox("The SMTP address has no mailbox associated with it")
            raise AutoDiscoverFailed(f"Unknown error {error_code}: {message}")
        except AttributeError:
            raise AutoDiscoverFailed(f"Unknown autodiscover error response: {self.error_response}")

    @staticmethod
    def payload(email):
        # Builds a full Autodiscover XML request
        payload = create_element("Autodiscover", attrs=dict(xmlns=AUTODISCOVER_REQUEST_NS))
        request = create_element("Request")
        add_xml_child(request, "EMailAddress", email)
        add_xml_child(request, "AcceptableResponseSchema", RNS)
        payload.append(request)
        return xml_to_str(payload, encoding=DEFAULT_ENCODING, xml_declaration=True)

Classes

class Account (**kwargs)
Expand source code
class Account(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/account-pox"""

    ELEMENT_NAME = "Account"
    REDIRECT_URL = "redirectUrl"
    REDIRECT_ADDR = "redirectAddr"
    SETTINGS = "settings"
    ACTIONS = (REDIRECT_URL, REDIRECT_ADDR, SETTINGS)

    type = ChoiceField(field_uri="AccountType", namespace=RNS, choices={Choice("email")})
    action = ChoiceField(field_uri="Action", namespace=RNS, choices={Choice(p) for p in ACTIONS})
    microsoft_online = BooleanField(field_uri="MicrosoftOnline", namespace=RNS)
    redirect_url = TextField(field_uri="RedirectURL", namespace=RNS)
    redirect_address = EmailAddressField(field_uri="RedirectAddr", namespace=RNS)
    image = TextField(field_uri="Image", namespace=RNS)  # Path to image used for branding
    service_home = TextField(field_uri="ServiceHome", namespace=RNS)  # URL to website of ISP
    protocols = ProtocolListField()
    # 'SmtpAddress' is inside the 'PublicFolderInformation' element
    public_folder_smtp_address = TextField(field_uri="SmtpAddress", namespace=RNS)

    @classmethod
    def from_xml(cls, elem, account):
        kwargs = {}
        public_folder_information = elem.find(f"{{{cls.NAMESPACE}}}PublicFolderInformation")
        for f in cls.FIELDS:
            if f.name == "public_folder_smtp_address":
                if public_folder_information is None:
                    continue
                kwargs[f.name] = f.from_xml(elem=public_folder_information, account=account)
                continue
            kwargs[f.name] = f.from_xml(elem=elem, account=account)
        cls._clear(elem)
        return cls(**kwargs)

Ancestors

Class variables

var ACTIONS
var ELEMENT_NAME
var FIELDS
var REDIRECT_ADDR
var REDIRECT_URL
var SETTINGS

Static methods

def from_xml(elem, account)
Expand source code
@classmethod
def from_xml(cls, elem, account):
    kwargs = {}
    public_folder_information = elem.find(f"{{{cls.NAMESPACE}}}PublicFolderInformation")
    for f in cls.FIELDS:
        if f.name == "public_folder_smtp_address":
            if public_folder_information is None:
                continue
            kwargs[f.name] = f.from_xml(elem=public_folder_information, account=account)
            continue
        kwargs[f.name] = f.from_xml(elem=elem, account=account)
    cls._clear(elem)
    return cls(**kwargs)

Instance variables

var action
var image
var microsoft_online
var protocols
var public_folder_smtp_address
var redirect_address
var redirect_url
var service_home
var type

Inherited members

class AddressBook (**kwargs)
Expand source code
class AddressBook(IntExtUrlBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/addressbook-pox"""

    ELEMENT_NAME = "AddressBook"

Ancestors

Class variables

var ELEMENT_NAME

Inherited members

class Autodiscover (**kwargs)

Base class for all XML element implementations.

Expand source code
class Autodiscover(EWSElement):
    ELEMENT_NAME = "Autodiscover"
    NAMESPACE = AUTODISCOVER_BASE_NS

    response = EWSElementField(value_cls=Response)
    error_response = EWSElementField(value_cls=ErrorResponse)

    @staticmethod
    def _clear(elem):
        # Parent implementation also clears the parent, but this element doesn't have one.
        elem.clear()

    @classmethod
    def from_bytes(cls, bytes_content):
        """Create an instance from response bytes. An Autodiscover request and response example is available at:
        https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/pox-autodiscover-response-for-exchange

        :param bytes_content:
        :return:
        """
        if not is_xml(bytes_content):
            raise ParseError(f"Response is not XML: {bytes_content}", "<not from file>", -1, 0)
        root = to_xml(bytes_content).getroot()  # May raise ParseError
        if root.tag != cls.response_tag():
            raise ParseError(f"Unknown root element in XML: {bytes_content}", "<not from file>", -1, 0)
        return cls.from_xml(elem=root, account=None)

    def raise_errors(self):
        if self.response is not None:
            return

        # Find an error message in the response and raise the relevant exception
        try:
            error_code = self.error_response.error.code
            message = self.error_response.error.message
            if message in ("The e-mail address cannot be found.", "The email address can't be found."):
                raise ErrorNonExistentMailbox("The SMTP address has no mailbox associated with it")
            raise AutoDiscoverFailed(f"Unknown error {error_code}: {message}")
        except AttributeError:
            raise AutoDiscoverFailed(f"Unknown autodiscover error response: {self.error_response}")

    @staticmethod
    def payload(email):
        # Builds a full Autodiscover XML request
        payload = create_element("Autodiscover", attrs=dict(xmlns=AUTODISCOVER_REQUEST_NS))
        request = create_element("Request")
        add_xml_child(request, "EMailAddress", email)
        add_xml_child(request, "AcceptableResponseSchema", RNS)
        payload.append(request)
        return xml_to_str(payload, encoding=DEFAULT_ENCODING, xml_declaration=True)

Ancestors

Class variables

var ELEMENT_NAME
var FIELDS
var NAMESPACE

Static methods

def from_bytes(bytes_content)

Create an instance from response bytes. An Autodiscover request and response example is available at: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/pox-autodiscover-response-for-exchange

:param bytes_content: :return:

Expand source code
@classmethod
def from_bytes(cls, bytes_content):
    """Create an instance from response bytes. An Autodiscover request and response example is available at:
    https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/pox-autodiscover-response-for-exchange

    :param bytes_content:
    :return:
    """
    if not is_xml(bytes_content):
        raise ParseError(f"Response is not XML: {bytes_content}", "<not from file>", -1, 0)
    root = to_xml(bytes_content).getroot()  # May raise ParseError
    if root.tag != cls.response_tag():
        raise ParseError(f"Unknown root element in XML: {bytes_content}", "<not from file>", -1, 0)
    return cls.from_xml(elem=root, account=None)
def payload(email)
Expand source code
@staticmethod
def payload(email):
    # Builds a full Autodiscover XML request
    payload = create_element("Autodiscover", attrs=dict(xmlns=AUTODISCOVER_REQUEST_NS))
    request = create_element("Request")
    add_xml_child(request, "EMailAddress", email)
    add_xml_child(request, "AcceptableResponseSchema", RNS)
    payload.append(request)
    return xml_to_str(payload, encoding=DEFAULT_ENCODING, xml_declaration=True)

Instance variables

var error_response
var response

Methods

def raise_errors(self)
Expand source code
def raise_errors(self):
    if self.response is not None:
        return

    # Find an error message in the response and raise the relevant exception
    try:
        error_code = self.error_response.error.code
        message = self.error_response.error.message
        if message in ("The e-mail address cannot be found.", "The email address can't be found."):
            raise ErrorNonExistentMailbox("The SMTP address has no mailbox associated with it")
        raise AutoDiscoverFailed(f"Unknown error {error_code}: {message}")
    except AttributeError:
        raise AutoDiscoverFailed(f"Unknown autodiscover error response: {self.error_response}")

Inherited members

class AutodiscoverBase (**kwargs)

Base class for all XML element implementations.

Expand source code
class AutodiscoverBase(EWSElement):
    NAMESPACE = RNS

Ancestors

Subclasses

Class variables

var NAMESPACE

Inherited members

class Error (**kwargs)
Expand source code
class Error(EWSElement):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/error-pox"""

    ELEMENT_NAME = "Error"
    NAMESPACE = AUTODISCOVER_BASE_NS

    id = TextField(field_uri="Id", namespace=AUTODISCOVER_BASE_NS, is_attribute=True)
    time = TextField(field_uri="Time", namespace=AUTODISCOVER_BASE_NS, is_attribute=True)
    code = TextField(field_uri="ErrorCode", namespace=AUTODISCOVER_BASE_NS)
    message = TextField(field_uri="Message", namespace=AUTODISCOVER_BASE_NS)
    debug_data = TextField(field_uri="DebugData", namespace=AUTODISCOVER_BASE_NS)

Ancestors

Class variables

var ELEMENT_NAME
var FIELDS
var NAMESPACE

Instance variables

var code
var debug_data
var id
var message
var time

Inherited members

class ErrorResponse (**kwargs)
Expand source code
class ErrorResponse(EWSElement):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/response-pox

    Like 'Response', but with a different namespace.
    """

    ELEMENT_NAME = "Response"
    NAMESPACE = AUTODISCOVER_BASE_NS

    error = EWSElementField(value_cls=Error)

Ancestors

Class variables

var ELEMENT_NAME
var FIELDS
var NAMESPACE

Instance variables

var error

Inherited members

class External (**kwargs)
Expand source code
class External(IntExtBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/external-pox"""

    ELEMENT_NAME = "External"

Ancestors

Class variables

var ELEMENT_NAME

Inherited members

class IntExtBase (**kwargs)

Base class for all XML element implementations.

Expand source code
class IntExtBase(AutodiscoverBase):
    # TODO: 'OWAUrl' also has an AuthenticationMethod enum-style XML attribute with values:
    #  WindowsIntegrated, FBA, NTLM, Digest, Basic, LiveIdFba, OAuth
    owa_url = TextField(field_uri="OWAUrl", namespace=RNS)
    protocol = EWSElementField(value_cls=SimpleProtocol)

Ancestors

Subclasses

Class variables

var FIELDS

Instance variables

var owa_url
var protocol

Inherited members

class IntExtUrlBase (**kwargs)

Base class for all XML element implementations.

Expand source code
class IntExtUrlBase(AutodiscoverBase):
    external_url = TextField(field_uri="ExternalUrl", namespace=RNS)
    internal_url = TextField(field_uri="InternalUrl", namespace=RNS)

Ancestors

Subclasses

Class variables

var FIELDS

Instance variables

var external_url
var internal_url

Inherited members

class Internal (**kwargs)
Expand source code
class Internal(IntExtBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/internal-pox"""

    ELEMENT_NAME = "Internal"

Ancestors

Class variables

var ELEMENT_NAME

Inherited members

class MailStore (**kwargs)
Expand source code
class MailStore(IntExtUrlBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/mailstore-pox"""

    ELEMENT_NAME = "MailStore"

Ancestors

Class variables

var ELEMENT_NAME

Inherited members

class NetworkRequirements (**kwargs)
Expand source code
class NetworkRequirements(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/networkrequirements-pox"""

    ELEMENT_NAME = "NetworkRequirements"

    ipv4_start = TextField(field_uri="IPv4Start", namespace=RNS)
    ipv4_end = TextField(field_uri="IPv4End", namespace=RNS)
    ipv6_start = TextField(field_uri="IPv6Start", namespace=RNS)
    ipv6_end = TextField(field_uri="IPv6End", namespace=RNS)

Ancestors

Class variables

var ELEMENT_NAME
var FIELDS

Instance variables

var ipv4_end
var ipv4_start
var ipv6_end
var ipv6_start

Inherited members

class Protocol (**kwargs)
Expand source code
class Protocol(SimpleProtocol):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/protocol-pox"""

    # Attribute 'Type' is ignored here. Has a name conflict with the child element and does not seem useful.
    version = TextField(field_uri="Version", is_attribute=True, namespace=RNS)
    internal = EWSElementField(value_cls=Internal)
    external = EWSElementField(value_cls=External)
    ttl = IntegerField(field_uri="TTL", namespace=RNS, default=1)  # TTL for this autodiscover response, in hours
    server = TextField(field_uri="Server", namespace=RNS)
    server_dn = TextField(field_uri="ServerDN", namespace=RNS)
    server_version = BuildField(field_uri="ServerVersion", namespace=RNS)
    mdb_dn = TextField(field_uri="MdbDN", namespace=RNS)
    public_folder_server = TextField(field_uri="PublicFolderServer", namespace=RNS)
    port = IntegerField(field_uri="Port", namespace=RNS, min=1, max=65535)
    directory_port = IntegerField(field_uri="DirectoryPort", namespace=RNS, min=1, max=65535)
    referral_port = IntegerField(field_uri="ReferralPort", namespace=RNS, min=1, max=65535)
    ews_url = TextField(field_uri="EwsUrl", namespace=RNS)
    emws_url = TextField(field_uri="EmwsUrl", namespace=RNS)
    sharing_url = TextField(field_uri="SharingUrl", namespace=RNS)
    ecp_url = TextField(field_uri="EcpUrl", namespace=RNS)
    ecp_url_um = TextField(field_uri="EcpUrl-um", namespace=RNS)
    ecp_url_aggr = TextField(field_uri="EcpUrl-aggr", namespace=RNS)
    ecp_url_mt = TextField(field_uri="EcpUrl-mt", namespace=RNS)
    ecp_url_ret = TextField(field_uri="EcpUrl-ret", namespace=RNS)
    ecp_url_sms = TextField(field_uri="EcpUrl-sms", namespace=RNS)
    ecp_url_publish = TextField(field_uri="EcpUrl-publish", namespace=RNS)
    ecp_url_photo = TextField(field_uri="EcpUrl-photo", namespace=RNS)
    ecp_url_tm = TextField(field_uri="EcpUrl-tm", namespace=RNS)
    ecp_url_tm_creating = TextField(field_uri="EcpUrl-tmCreating", namespace=RNS)
    ecp_url_tm_hiding = TextField(field_uri="EcpUrl-tmHiding", namespace=RNS)
    ecp_url_tm_editing = TextField(field_uri="EcpUrl-tmEditing", namespace=RNS)
    ecp_url_extinstall = TextField(field_uri="EcpUrl-extinstall", namespace=RNS)
    oof_url = TextField(field_uri="OOFUrl", namespace=RNS)
    oab_url = TextField(field_uri="OABUrl", namespace=RNS)
    um_url = TextField(field_uri="UMUrl", namespace=RNS)
    ews_partner_url = TextField(field_uri="EwsPartnerUrl", namespace=RNS)
    login_name = TextField(field_uri="LoginName", namespace=RNS)
    domain_required = OnOffField(field_uri="DomainRequired", namespace=RNS)
    domain_name = TextField(field_uri="DomainName", namespace=RNS)
    spa = OnOffField(field_uri="SPA", namespace=RNS, default=True)
    auth_package = ChoiceField(
        field_uri="AuthPackage",
        namespace=RNS,
        choices={Choice(c) for c in ("basic", "kerb", "kerbntlm", "ntlm", "certificate", "negotiate", "nego2")},
    )
    cert_principal_name = TextField(field_uri="CertPrincipalName", namespace=RNS)
    ssl = OnOffField(field_uri="SSL", namespace=RNS, default=True)
    auth_required = OnOffField(field_uri="AuthRequired", namespace=RNS, default=True)
    use_pop_path = OnOffField(field_uri="UsePOPAuth", namespace=RNS)
    smtp_last = OnOffField(field_uri="SMTPLast", namespace=RNS, default=False)
    network_requirements = EWSElementField(value_cls=NetworkRequirements)
    address_book = EWSElementField(value_cls=AddressBook)
    mail_store = EWSElementField(value_cls=MailStore)

    @property
    def auth_type(self):
        # Translates 'auth_package' value to our own 'auth_type' enum vals
        if not self.auth_required:
            return NOAUTH
        if not self.auth_package:
            return None
        return {
            # Missing in list are DIGEST and OAUTH2
            "basic": BASIC,
            "kerb": GSSAPI,
            "kerbntlm": NTLM,  # Means client can choose between NTLM and GSSAPI
            "ntlm": NTLM,
            "certificate": CBA,
            "negotiate": SSPI,  # Unsure about this one
            "nego2": GSSAPI,
            "anonymous": NOAUTH,  # Seen in some docs even though it's not mentioned in MSDN
        }.get(self.auth_package.lower())

Ancestors

Class variables

var FIELDS

Instance variables

var address_book
var auth_package
var auth_required
var auth_type
Expand source code
@property
def auth_type(self):
    # Translates 'auth_package' value to our own 'auth_type' enum vals
    if not self.auth_required:
        return NOAUTH
    if not self.auth_package:
        return None
    return {
        # Missing in list are DIGEST and OAUTH2
        "basic": BASIC,
        "kerb": GSSAPI,
        "kerbntlm": NTLM,  # Means client can choose between NTLM and GSSAPI
        "ntlm": NTLM,
        "certificate": CBA,
        "negotiate": SSPI,  # Unsure about this one
        "nego2": GSSAPI,
        "anonymous": NOAUTH,  # Seen in some docs even though it's not mentioned in MSDN
    }.get(self.auth_package.lower())
var cert_principal_name
var directory_port
var domain_name
var domain_required
var ecp_url
var ecp_url_aggr
var ecp_url_extinstall
var ecp_url_mt
var ecp_url_photo
var ecp_url_publish
var ecp_url_ret
var ecp_url_sms
var ecp_url_tm
var ecp_url_tm_creating
var ecp_url_tm_editing
var ecp_url_tm_hiding
var ecp_url_um
var emws_url
var ews_partner_url
var ews_url
var external
var internal
var login_name
var mail_store
var mdb_dn
var network_requirements
var oab_url
var oof_url
var port
var public_folder_server
var referral_port
var server
var server_dn
var server_version
var sharing_url
var smtp_last
var spa
var ssl
var ttl
var um_url
var use_pop_path
var version

Inherited members

class Response (**kwargs)
Expand source code
class Response(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/response-pox"""

    ELEMENT_NAME = "Response"

    user = EWSElementField(value_cls=User)
    account = EWSElementField(value_cls=Account)

    @property
    def redirect_address(self):
        try:
            if self.account.action != Account.REDIRECT_ADDR:
                return None
            return self.account.redirect_address
        except AttributeError:
            return None

    @property
    def redirect_url(self):
        try:
            if self.account.action != Account.REDIRECT_URL:
                return None
            return self.account.redirect_url
        except AttributeError:
            return None

    @property
    def autodiscover_smtp_address(self):
        # AutoDiscoverSMTPAddress might not be present in the XML. In this case, use the original email address.
        try:
            if self.account.action != Account.SETTINGS:
                return None
            return self.user.autodiscover_smtp_address
        except AttributeError:
            return None

    @property
    def version(self):
        # Get the server version. Not all protocol entries have a server version, so we cheat a bit and also look at the
        # other ones that point to the same endpoint.
        ews_url = self.protocol.ews_url
        for protocol in self.account.protocols:
            if not protocol.ews_url or not protocol.server_version:
                continue
            if protocol.ews_url.lower() == ews_url.lower():
                return Version(build=protocol.server_version)
        return None

    @property
    def protocol(self):
        """Return the protocol containing an EWS URL.

        A response may contain a number of possible protocol types. EXPR is meant for EWS. See
        https://techcommunity.microsoft.com/t5/blogs/blogarticleprintpage/blog-id/Exchange/article-id/16

        We allow fallback to EXCH if EXPR is not available, to support installations where EXPR is not available.

        Additionally, some responses may contain an EXPR with no EWS URL. In that case, return EXCH, if available.
        """
        protocols = {p.type: p for p in self.account.protocols if p.ews_url}
        if Protocol.EXPR in protocols:
            return protocols[Protocol.EXPR]
        if Protocol.EXCH in protocols:
            return protocols[Protocol.EXCH]
        raise ValueError(
            f"No EWS URL found in any of the available protocols: {[str(p) for p in self.account.protocols]}"
        )

Ancestors

Class variables

var ELEMENT_NAME
var FIELDS

Instance variables

var account
var autodiscover_smtp_address
Expand source code
@property
def autodiscover_smtp_address(self):
    # AutoDiscoverSMTPAddress might not be present in the XML. In this case, use the original email address.
    try:
        if self.account.action != Account.SETTINGS:
            return None
        return self.user.autodiscover_smtp_address
    except AttributeError:
        return None
var protocol

Return the protocol containing an EWS URL.

A response may contain a number of possible protocol types. EXPR is meant for EWS. See https://techcommunity.microsoft.com/t5/blogs/blogarticleprintpage/blog-id/Exchange/article-id/16

We allow fallback to EXCH if EXPR is not available, to support installations where EXPR is not available.

Additionally, some responses may contain an EXPR with no EWS URL. In that case, return EXCH, if available.

Expand source code
@property
def protocol(self):
    """Return the protocol containing an EWS URL.

    A response may contain a number of possible protocol types. EXPR is meant for EWS. See
    https://techcommunity.microsoft.com/t5/blogs/blogarticleprintpage/blog-id/Exchange/article-id/16

    We allow fallback to EXCH if EXPR is not available, to support installations where EXPR is not available.

    Additionally, some responses may contain an EXPR with no EWS URL. In that case, return EXCH, if available.
    """
    protocols = {p.type: p for p in self.account.protocols if p.ews_url}
    if Protocol.EXPR in protocols:
        return protocols[Protocol.EXPR]
    if Protocol.EXCH in protocols:
        return protocols[Protocol.EXCH]
    raise ValueError(
        f"No EWS URL found in any of the available protocols: {[str(p) for p in self.account.protocols]}"
    )
var redirect_address
Expand source code
@property
def redirect_address(self):
    try:
        if self.account.action != Account.REDIRECT_ADDR:
            return None
        return self.account.redirect_address
    except AttributeError:
        return None
var redirect_url
Expand source code
@property
def redirect_url(self):
    try:
        if self.account.action != Account.REDIRECT_URL:
            return None
        return self.account.redirect_url
    except AttributeError:
        return None
var user
var version
Expand source code
@property
def version(self):
    # Get the server version. Not all protocol entries have a server version, so we cheat a bit and also look at the
    # other ones that point to the same endpoint.
    ews_url = self.protocol.ews_url
    for protocol in self.account.protocols:
        if not protocol.ews_url or not protocol.server_version:
            continue
        if protocol.ews_url.lower() == ews_url.lower():
            return Version(build=protocol.server_version)
    return None

Inherited members

class SimpleProtocol (**kwargs)

MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/protocol-pox

Used for the 'Internal' and 'External' elements that may contain a stripped-down version of the Protocol element.

Expand source code
class SimpleProtocol(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/protocol-pox

    Used for the 'Internal' and 'External' elements that may contain a stripped-down version of the Protocol element.
    """

    ELEMENT_NAME = "Protocol"
    WEB = "WEB"
    EXCH = "EXCH"
    EXPR = "EXPR"
    EXHTTP = "EXHTTP"
    TYPES = (WEB, EXCH, EXPR, EXHTTP)

    type = ChoiceField(field_uri="Type", choices={Choice(c) for c in TYPES}, namespace=RNS)
    as_url = TextField(field_uri="ASUrl", namespace=RNS)

Ancestors

Subclasses

Class variables

var ELEMENT_NAME
var EXCH
var EXHTTP
var EXPR
var FIELDS
var TYPES
var WEB

Instance variables

var as_url
var type

Inherited members

class User (**kwargs)
Expand source code
class User(AutodiscoverBase):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/user-pox"""

    ELEMENT_NAME = "User"

    display_name = TextField(field_uri="DisplayName", namespace=RNS)
    legacy_dn = TextField(field_uri="LegacyDN", namespace=RNS)
    deployment_id = TextField(field_uri="DeploymentId", namespace=RNS)  # GUID format
    autodiscover_smtp_address = EmailAddressField(field_uri="AutoDiscoverSMTPAddress", namespace=RNS)

Ancestors

Class variables

var ELEMENT_NAME
var FIELDS

Instance variables

var autodiscover_smtp_address
var deployment_id
var display_name
var legacy_dn

Inherited members