diff --git a/docs/exchangelib/autodiscover/index.html b/docs/exchangelib/autodiscover/index.html index e6e83246..03e865b6 100644 --- a/docs/exchangelib/autodiscover/index.html +++ b/docs/exchangelib/autodiscover/index.html @@ -45,10 +45,10 @@

Module exchangelib.autodiscover

"AutodiscoverCache", "AutodiscoverProtocol", "Autodiscovery", - "discover", "autodiscover_cache", - "close_connections", "clear_cache", + "close_connections", + "discover", ] diff --git a/docs/exchangelib/configuration.html b/docs/exchangelib/configuration.html index 48c91443..d524188d 100644 --- a/docs/exchangelib/configuration.html +++ b/docs/exchangelib/configuration.html @@ -30,7 +30,7 @@

Module exchangelib.configuration

from cached_property import threaded_cached_property -from .credentials import BaseCredentials, BaseOAuth2Credentials +from .credentials import BaseCredentials, BaseOAuth2Credentials, O365InteractiveCredentials from .errors import InvalidEnumValue, InvalidTypeError from .protocol import FailFast, RetryPolicy from .transport import AUTH_TYPE_MAP, CREDENTIALS_REQUIRED, OAUTH2 @@ -123,7 +123,15 @@

Module exchangelib.configuration

f"{k}={getattr(self, k)!r}" for k in ("credentials", "service_endpoint", "auth_type", "version", "retry_policy") ) - return f"{self.__class__.__name__}({args_str})" + return f"{self.__class__.__name__}({args_str})" + + +class O365InteractiveConfiguration(Configuration): + SERVER = "outlook.office365.com" + + def __init__(self, client_id, username): + credentials = O365InteractiveCredentials(client_id=client_id, username=username) + super().__init__(server=self.SERVER, auth_type=OAUTH2, credentials=credentials)
@@ -248,6 +256,10 @@

Classes

) return f"{self.__class__.__name__}({args_str})" +

Subclasses

+

Instance variables

var credentials
@@ -288,6 +300,53 @@

Instance variables

+
+class O365InteractiveConfiguration +(client_id, username) +
+
+

Contains information needed to create an authenticated connection to an EWS endpoint.

+

The 'credentials' argument contains the credentials needed to authenticate with the server. Multiple credentials +implementations are available in 'exchangelib.credentials'.

+

config = Configuration(credentials=Credentials('john@example.com', 'MY_SECRET'), …)

+

The 'server' and 'service_endpoint' arguments are mutually exclusive. The former must contain only a domain name, +the latter a full URL:

+
config = Configuration(server='example.com', ...)
+config = Configuration(service_endpoint='https://mail.example.com/EWS/Exchange.asmx', ...)
+
+

If you know which authentication type the server uses, you add that as a hint in 'auth_type'. Likewise, you can +add the server version as a hint. This allows to skip the auth type and version guessing routines:

+
config = Configuration(auth_type=NTLM, ...)
+config = Configuration(version=Version(build=Build(15, 1, 2, 3)), ...)
+
+

You can use 'retry_policy' to define a custom retry policy for handling server connection failures:

+
config = Configuration(retry_policy=FaultTolerance(max_wait=3600), ...)
+
+

'max_connections' defines the max number of connections allowed for this server. This may be restricted by +policies on the Exchange server.

+
+ +Expand source code + +
class O365InteractiveConfiguration(Configuration):
+    SERVER = "outlook.office365.com"
+
+    def __init__(self, client_id, username):
+        credentials = O365InteractiveCredentials(client_id=client_id, username=username)
+        super().__init__(server=self.SERVER, auth_type=OAUTH2, credentials=credentials)
+
+

Ancestors

+ +

Class variables

+
+
var SERVER
+
+
+
+
+
@@ -311,6 +370,12 @@

server +
  • +

    O365InteractiveConfiguration

    + +
  • diff --git a/docs/exchangelib/credentials.html b/docs/exchangelib/credentials.html index 71e9e0d9..a6df53cb 100644 --- a/docs/exchangelib/credentials.html +++ b/docs/exchangelib/credentials.html @@ -339,7 +339,20 @@

    Module exchangelib.credentials

    else ("[authorization_code]" if self.authorization_code is not None else None) ) description = " ".join(filter(None, [client_id, credential])) - return description or "[underspecified credentials]" + return description or "[underspecified credentials]" + + +class O365InteractiveCredentials(OAuth2AuthorizationCodeCredentials): + AUTHORITY = "https://login.microsoftonline.com/organizations" + SCOPE = ["EWS.AccessAsUser.All"] + + def __init__(self, client_id, username): + import msal + + app = msal.PublicClientApplication(client_id=client_id, authority=self.AUTHORITY) + print("A local browser window will be open for you to sign in. CTRL+C to cancel.") + access_token = app.acquire_token_interactive(self.SCOPE, login_hint=username) + super().__init__(access_token=access_token)
    @@ -802,6 +815,76 @@

    Class variables

    +
    +class O365InteractiveCredentials +(client_id, username) +
    +
    +

    Login info for OAuth 2.0 authentication using the authorization code grant type. This can be used in one of +several ways: +* Given an authorization code, client ID, and client secret, fetch a token ourselves and refresh it as needed if +supplied with a refresh token. +* Given an existing access token, client ID, and client secret, use the access token until it expires and then +refresh it as needed. +* Given only an existing access token, use it until it expires. This can be used to let the calling application +refresh tokens itself by subclassing and implementing refresh().

    +

    Unlike the base (client credentials) grant, authorization code credentials don't require a Microsoft tenant ID +because each access token (and the authorization code used to get the access token) is restricted to a single +tenant.

    +

    :param authorization_code: Code obtained when authorizing the application to access an account. In combination +with client_id and client_secret, will be used to obtain an access token.

    +
    + +Expand source code + +
    class O365InteractiveCredentials(OAuth2AuthorizationCodeCredentials):
    +    AUTHORITY = "https://login.microsoftonline.com/organizations"
    +    SCOPE = ["EWS.AccessAsUser.All"]
    +
    +    def __init__(self, client_id, username):
    +        import msal
    +
    +        app = msal.PublicClientApplication(client_id=client_id, authority=self.AUTHORITY)
    +        print("A local browser window will be open for you to sign in. CTRL+C to cancel.")
    +        access_token = app.acquire_token_interactive(self.SCOPE, login_hint=username)
    +        super().__init__(access_token=access_token)
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var AUTHORITY
    +
    +
    +
    +
    var SCOPE
    +
    +
    +
    +
    +

    Inherited members

    + +
    class OAuth2AuthorizationCodeCredentials (authorization_code=None, **kwargs) @@ -887,6 +970,10 @@

    Ancestors

  • BaseOAuth2Credentials
  • BaseCredentials
  • +

    Subclasses

    +

    Inherited members

    -

    string -> datetime from datetime.isoformat() output

    +

    string -> datetime from a string in most ISO 8601 formats

    Expand source code diff --git a/docs/exchangelib/folders/base.html b/docs/exchangelib/folders/base.html index 8c39d417..32a5ba63 100644 --- a/docs/exchangelib/folders/base.html +++ b/docs/exchangelib/folders/base.html @@ -68,7 +68,7 @@

    Module exchangelib.folders.base

    ) from ..queryset import DoesNotExist, SearchableMixIn from ..util import TNS, is_iterable, require_id -from ..version import EXCHANGE_2007_SP1, EXCHANGE_2010, SupportedVersionClassMixIn +from ..version import EXCHANGE_2007_SP1, EXCHANGE_2010, EXCHANGE_2016, SupportedVersionClassMixIn from .collections import FolderCollection, PullSubscription, PushSubscription, StreamingSubscription, SyncCompleted from .queryset import DEEP as DEEP_FOLDERS from .queryset import MISSING_FOLDER_ERRORS @@ -105,7 +105,9 @@

    Module exchangelib.folders.base

    ID_ELEMENT_CLS = FolderId _id = IdElementField(field_uri="folder:FolderId", value_cls=ID_ELEMENT_CLS) - _distinguished_id = IdElementField(field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId) + _distinguished_id = IdElementField( + field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId, supported_from=EXCHANGE_2016 + ) parent_folder_id = EWSElementField(field_uri="folder:ParentFolderId", value_cls=ParentFolderId, is_read_only=True) folder_class = CharField(field_uri="folder:FolderClass", is_required_after_save=True) name = CharField(field_uri="folder:DisplayName") @@ -972,6 +974,10 @@

    Module exchangelib.folders.base

    ) if folder_cls == Folder: log.debug("Fallback to class Folder (folder_class %s, name %s)", folder.folder_class, folder.name) + # Some servers return folders in a FindFolder result that have a DistinguishedFolderId element that the same + # server cannot handle in a GetFolder request. Only set the DistinguishedFolderId field if we recognize the ID. + if folder._distinguished_id and not folder_cls.DISTINGUISHED_FOLDER_ID: + folder._distinguished_id = None return folder_cls(root=root, **{f.name: getattr(folder, f.name) for f in folder.FIELDS})
    @@ -1014,7 +1020,9 @@

    Classes

    ID_ELEMENT_CLS = FolderId _id = IdElementField(field_uri="folder:FolderId", value_cls=ID_ELEMENT_CLS) - _distinguished_id = IdElementField(field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId) + _distinguished_id = IdElementField( + field_uri="folder:DistinguishedFolderId", value_cls=DistinguishedFolderId, supported_from=EXCHANGE_2016 + ) parent_folder_id = EWSElementField(field_uri="folder:ParentFolderId", value_cls=ParentFolderId, is_read_only=True) folder_class = CharField(field_uri="folder:FolderClass", is_required_after_save=True) name = CharField(field_uri="folder:DisplayName") @@ -3101,6 +3109,10 @@

    Inherited members

    ) if folder_cls == Folder: log.debug("Fallback to class Folder (folder_class %s, name %s)", folder.folder_class, folder.name) + # Some servers return folders in a FindFolder result that have a DistinguishedFolderId element that the same + # server cannot handle in a GetFolder request. Only set the DistinguishedFolderId field if we recognize the ID. + if folder._distinguished_id and not folder_cls.DISTINGUISHED_FOLDER_ID: + folder._distinguished_id = None return folder_cls(root=root, **{f.name: getattr(folder, f.name) for f in folder.FIELDS})

    Ancestors

    @@ -3181,6 +3193,10 @@

    Static methods

    ) if folder_cls == Folder: log.debug("Fallback to class Folder (folder_class %s, name %s)", folder.folder_class, folder.name) + # Some servers return folders in a FindFolder result that have a DistinguishedFolderId element that the same + # server cannot handle in a GetFolder request. Only set the DistinguishedFolderId field if we recognize the ID. + if folder._distinguished_id and not folder_cls.DISTINGUISHED_FOLDER_ID: + folder._distinguished_id = None return folder_cls(root=root, **{f.name: getattr(folder, f.name) for f in folder.FIELDS}) diff --git a/docs/exchangelib/folders/index.html b/docs/exchangelib/folders/index.html index 556e1f7c..982b1759 100644 --- a/docs/exchangelib/folders/index.html +++ b/docs/exchangelib/folders/index.html @@ -49,6 +49,7 @@

    Module exchangelib.folders

    Birthdays, Calendar, CalendarLogging, + CalendarSearchCache, CommonViews, Companies, Conflicts, @@ -96,6 +97,7 @@

    Module exchangelib.folders

    PdpProfileV2Secured, PeopleCentricConversationBuddies, PeopleConnect, + PersonMetadata, QedcDefaultRetention, QedcLongRetention, QedcMediumRetention, @@ -162,6 +164,7 @@

    Module exchangelib.folders

    "Birthdays", "Calendar", "CalendarLogging", + "CalendarSearchCache", "CommonViews", "Companies", "Conflicts", @@ -217,13 +220,14 @@

    Module exchangelib.folders

    "PdpProfileV2Secured", "PeopleCentricConversationBuddies", "PeopleConnect", + "PersonMetadata", + "PublicFoldersRoot", "QedcDefaultRetention", - "QedcMediumRetention", "QedcLongRetention", + "QedcMediumRetention", "QedcShortRetention", "QuarantinedEmail", "QuarantinedEmailDefaultCategory", - "PublicFoldersRoot", "QuickContacts", "RSSFeeds", "RecipientCache", @@ -245,8 +249,8 @@

    Module exchangelib.folders

    "ServerFailures", "SharePointNotifications", "Sharing", - "Shortcuts", "ShortNotes", + "Shortcuts", "Signal", "SingleFolderQuerySet", "SkypeTeamsMessages", @@ -390,7 +394,8 @@

    Inherited members

    class AllCategorizedItems(WellknownFolder):
         DISTINGUISHED_FOLDER_ID = "allcategorizeditems"
    -    CONTAINER_CLASS = "IPF.Note"
    + CONTAINER_CLASS = "IPF.Note" + supported_from = EXCHANGE_O365

    Ancestors