예제 #1
0
    def __init__(self, *args, **kwargs):
        version = kwargs.pop('version', None)
        super(Protocol, self).__init__(*args, **kwargs)

        scheme = 'https' if self.has_ssl else 'http'
        self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server)
        self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server)
        self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server)

        # Autodetect authentication type if necessary
        # pylint: disable=access-member-before-definition
        if self.auth_type is None:
            self.auth_type = get_service_authtype(
                service_endpoint=self.service_endpoint,
                versions=API_VERSIONS,
                name=self.credentials.username)

        # Default to the auth type used by the service. We only need this if 'version' is None
        self.docs_auth_type = self.auth_type

        # Try to behave nicely with the Exchange server. We want to keep the connection open between requests.
        # We also want to re-use sessions, to avoid the NTLM auth handshake on every request.
        pool_size = self.pool_size or self.SESSION_POOLSIZE
        self._session_pool = LifoQueue(maxsize=pool_size)
        for _ in range(pool_size):
            self._session_pool.put(self.create_session(), block=False)

        if version:
            isinstance(version, Version)
            self.version = version
        else:
            # Version.guess() needs auth objects and a working session pool
            try:
                # Try to get the auth_type of 'types.xsd' so we can fetch it and look at the version contained there
                self.docs_auth_type = get_docs_authtype(
                    docs_url=self.types_url)
            except TransportError:
                pass
            self.version = Version.guess(self)

        # Used by services to process service requests that are able to run in parallel. Thread pool should be
        # larger than the connection pool so we have time to process data without idling the connection.
        # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would
        # leave open threads around to be garbage collected.
        if self.pool_size:
            thread_poolsize = 4 * self.pool_size
        else:
            thread_poolsize = 4 * self.SESSION_POOLSIZE
        self.thread_pool = ThreadPool(processes=thread_poolsize)
예제 #2
0
class AutodiscoverProtocol(BaseProtocol):
    # Protocol which implements the bare essentials for autodiscover
    TIMEOUT = 10  # Seconds

    def __init__(self, *args, **kwargs):
        super(AutodiscoverProtocol, self).__init__(*args, **kwargs)
        self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE)
        for _ in range(self.SESSION_POOLSIZE):
            self._session_pool.put(self.create_session(), block=False)

    def __str__(self):
        return '''\
Autodiscover endpoint: %s
Auth type: %s''' % (
            self.service_endpoint,
            self.auth_type,
        )
예제 #3
0
class AutodiscoverProtocol(BaseProtocol):
    # Protocol which implements the bare essentials for autodiscover
    TIMEOUT = 10  # Seconds

    def __init__(self, *args, **kwargs):
        super(AutodiscoverProtocol, self).__init__(*args, **kwargs)
        self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE)
        for _ in range(self.SESSION_POOLSIZE):
            self._session_pool.put(self.create_session(), block=False)

    def __str__(self):
        return '''\
Autodiscover endpoint: %s
Auth type: %s''' % (
            self.service_endpoint,
            self.auth_type,
        )
예제 #4
0
class Protocol(with_metaclass(CachingProtocol, BaseProtocol)):
    def __init__(self, *args, **kwargs):
        version = kwargs.pop('version', None)
        super(Protocol, self).__init__(*args, **kwargs)

        scheme = 'https' if self.has_ssl else 'http'
        self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server)
        self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server)
        self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server)

        # Autodetect authentication type if necessary
        # pylint: disable=access-member-before-definition
        if self.auth_type is None:
            self.auth_type = get_service_authtype(
                service_endpoint=self.service_endpoint,
                versions=API_VERSIONS,
                name=self.credentials.username)

        # Try to behave nicely with the Exchange server. We want to keep the connection open between requests.
        # We also want to re-use sessions, to avoid the NTLM auth handshake on every request.
        pool_size = self.pool_size or self.SESSION_POOLSIZE
        self._session_pool = LifoQueue(maxsize=pool_size)
        for _ in range(pool_size):
            self._session_pool.put(self.create_session(), block=False)

        if version:
            isinstance(version, Version)
            self.version = version
        else:
            # Version.guess() needs auth objects and a working session pool
            self.version = Version.guess(self)

        # Used by services to process service requests that are able to run in parallel. Thread pool should be
        # larger than the connection pool so we have time to process data without idling the connection.
        # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would
        # leave open threads around to be garbage collected.
        if self.pool_size:
            thread_poolsize = 4 * self.pool_size
        else:
            thread_poolsize = 4 * self.SESSION_POOLSIZE
        self.thread_pool = ThreadPool(processes=thread_poolsize)

    def get_timezones(self, timezones=None, return_full_timezone_data=False):
        """ Get timezone definitions from the server

        :param timezones: A list of EWSDateTime instances. If None, fetches all timezones from server
        :param return_full_timezone_data: If true, also returns periods and transitions
        :return: A list of (tz_id, name, periods, transitions) tuples
        """
        return GetServerTimeZones(protocol=self).call(
            timezones=timezones,
            return_full_timezone_data=return_full_timezone_data)

    def get_free_busy_info(self,
                           accounts,
                           start,
                           end,
                           merged_free_busy_interval=30,
                           requested_view='DetailedMerged'):
        """ Returns free/busy information for a list of accounts

        :param accounts: A list of (account, attendee_type, exclude_conflicts) tuples, where account is an Account
               object, attendee_type is a MailboxData.attendee_type choice, and exclude_conflicts is a boolean.
        :param start: The start datetime of the request
        :param end: The end datetime of the request
        :param merged_free_busy_interval: The interval, in minutes, of merged free/busy information
        :param requested_view: The type of information returned. Possible values are defined in the
               FreeBusyViewOptions.requested_view choices.
        :return: A generator of FreeBusyView objects
        """
        from .account import Account
        for account, attendee_type, exclude_conflicts in accounts:
            if not isinstance(account, Account):
                raise ValueError(
                    "'accounts' item %r must be an 'Account' instance" %
                    account)
            if attendee_type not in MailboxData.ATTENDEE_TYPES:
                raise ValueError("'accounts' item %r must be one of %s" %
                                 (attendee_type, MailboxData.ATTENDEE_TYPES))
            if not isinstance(exclude_conflicts, bool):
                raise ValueError(
                    "'accounts' item %r must be a 'bool' instance" %
                    exclude_conflicts)
        if start >= end:
            raise ValueError("'start' must be less than 'end' (%s -> %s)" %
                             (start, end))
        if not isinstance(merged_free_busy_interval, int):
            raise ValueError(
                "'merged_free_busy_interval' value %r must be an 'int'" %
                merged_free_busy_interval)
        if requested_view not in FreeBusyViewOptions.REQUESTED_VIEWS:
            raise ValueError(
                "'requested_view' value %r must be one of %s" %
                (requested_view, FreeBusyViewOptions.REQUESTED_VIEWS))
        _, _, periods, transitions, transitions_groups = list(
            self.get_timezones(timezones=[start.tzinfo],
                               return_full_timezone_data=True))[0]
        return GetUserAvailability(self).call(
            timezone=TimeZone.from_server_timezone(
                periods=periods,
                transitions=transitions,
                transitionsgroups=transitions_groups,
                for_year=start.year),
            mailbox_data=[
                MailboxData(email=account.primary_smtp_address,
                            attendee_type=attendee_type,
                            exclude_conflicts=exclude_conflicts)
                for account, attendee_type, exclude_conflicts in accounts
            ],
            free_busy_view_options=FreeBusyViewOptions(
                time_window=TimeWindow(start=start, end=end),
                merged_free_busy_interval=merged_free_busy_interval,
                requested_view=requested_view,
            ),
        )

    def get_roomlists(self):
        return GetRoomLists(protocol=self).call()

    def get_rooms(self, roomlist):
        from .properties import RoomList
        return GetRooms(protocol=self).call(roomlist=RoomList(
            email_address=roomlist))

    def resolve_names(self,
                      names,
                      return_full_contact_data=False,
                      search_scope=None,
                      shape=None):
        """ Resolve accounts on the server using partial account data, e.g. an email address or initials

        :param names: A list of identifiers to query
        :param return_full_contact_data: If True, returns full contact data
        :param search_scope: The scope to perform the search. Must be one of SEARCH_SCOPE_CHOICES
        :param shape:
        :return: A list of Mailbox items or, if return_full_contact_data is True, tuples of (Mailbox, Contact) items
        """
        from .items import SHAPE_CHOICES, SEARCH_SCOPE_CHOICES
        if search_scope:
            if search_scope not in SEARCH_SCOPE_CHOICES:
                raise ValueError("'search_scope' %s must be one if %s" %
                                 (search_scope, SEARCH_SCOPE_CHOICES))
        if shape:
            if shape not in SHAPE_CHOICES:
                raise ValueError("'shape' %s must be one if %s" %
                                 (shape, SHAPE_CHOICES))
        return list(
            ResolveNames(protocol=self).call(
                unresolved_entries=names,
                return_full_contact_data=return_full_contact_data,
                search_scope=search_scope,
                contact_data_shape=shape,
            ))

    def get_searchable_mailboxes(self,
                                 search_filter=None,
                                 expand_group_membership=False):
        """This method is only available to users who have been assigned the Discovery Management RBAC role. See
        https://technet.microsoft.com/en-us/library/jj200692(v=exchg.150).aspx.

        :param search_filter: Is set, must be a single email alias
        :param expand_group_membership: If True, returned distribution lists are expanded
        :return: a list of SearchableMailbox, FailedMailbox or Exception instances
        """
        return list(
            GetSearchableMailboxes(protocol=self).call(
                search_filter=search_filter,
                expand_group_membership=expand_group_membership,
            ))

    def __str__(self):
        return '''\
EWS url: %s
Product name: %s
EWS API version: %s
Build number: %s
EWS auth: %s''' % (
            self.service_endpoint,
            self.version.fullname,
            self.version.api_version,
            self.version.build,
            self.auth_type,
        )
예제 #5
0
 def _create_session_pool(self):
     # Create a pool to reuse sessions containing connections to the server
     session_pool = LifoQueue(maxsize=self._session_pool_size)
     for _ in range(self._session_pool_size):
         session_pool.put(self.create_session(), block=False)
     return session_pool
예제 #6
0
class Protocol(with_metaclass(CachingProtocol, BaseProtocol)):
    def __init__(self, *args, **kwargs):
        version = kwargs.pop('version', None)
        super(Protocol, self).__init__(*args, **kwargs)

        scheme = 'https' if self.has_ssl else 'http'
        self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server)
        self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server)
        self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server)

        # Autodetect authentication type if necessary
        if self.auth_type is None:
            self.auth_type = get_service_authtype(
                service_endpoint=self.service_endpoint,
                versions=API_VERSIONS,
                name=self.credentials.username)

        # Default to the auth type used by the service. We only need this if 'version' is None
        self.docs_auth_type = self.auth_type

        # Try to behave nicely with the Exchange server. We want to keep the connection open between requests.
        # We also want to re-use sessions, to avoid the NTLM auth handshake on every request.
        self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE)
        for _ in range(self.SESSION_POOLSIZE):
            self._session_pool.put(self.create_session(), block=False)

        if version:
            isinstance(version, Version)
            self.version = version
        else:
            # Version.guess() needs auth objects and a working session pool
            try:
                # Try to get the auth_type of 'types.xsd' so we can fetch it and look at the version contained there
                self.docs_auth_type = get_docs_authtype(
                    docs_url=self.types_url)
            except TransportError:
                pass
            self.version = Version.guess(self)

        # Used by services to process service requests that are able to run in parallel. Thread pool should be
        # larger than the connection pool so we have time to process data without idling the connection.
        # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would
        # leave open threads around to be garbage collected.
        thread_poolsize = 4 * self.SESSION_POOLSIZE
        self.thread_pool = ThreadPool(processes=thread_poolsize)

    def get_timezones(self, timezones=None, return_full_timezone_data=False):
        """ Get timezone definitions from the server

        :param timezones: A list of EWSDateTime instances. If None, fetches all timezones from server
        :param return_full_timezone_data: If true, also returns periods and transitions
        :return: A list of (tz_id, name, periods, transitions) tuples
        """
        return GetServerTimeZones(protocol=self).call(
            timezones=timezones,
            return_full_timezone_data=return_full_timezone_data)

    def get_free_busy_info(self,
                           accounts,
                           start,
                           end,
                           merged_free_busy_interval=30,
                           requested_view='DetailedMerged'):
        """ Returns free/busy information for a list of accounts

        :param accounts: A list of (account, attendee_type, exclude_conflicts) tuples, where account is an Account
               object, attendee_type is a MailboxData.attendee_type choice, and exclude_conflicts is a boolean.
        :param start: The start datetime of the request
        :param end: The end datetime of the request
        :param merged_free_busy_interval: The interval, in minutes, of merged free/busy information
        :param requested_view: The type of information returned. Possible values are defined in the
               FreeBusyViewOptions.requested_view choices.
        :return: A generator of FreeBusyView objects
        """
        from .account import Account
        attendee_type_choices = {
            c.value
            for c in MailboxData.get_field_by_fieldname(
                'attendee_type').choices
        }
        for account, attendee_type, exclude_conflicts in accounts:
            if not isinstance(account, Account):
                raise ValueError(
                    "'accounts' item %r must be an 'Account' instance" %
                    account)
            if attendee_type not in attendee_type_choices:
                raise ValueError("'accounts' item %r must be one of %s" %
                                 (attendee_type, attendee_type_choices))
            if not isinstance(exclude_conflicts, bool):
                raise ValueError(
                    "'accounts' item %r must be a 'bool' instance" %
                    exclude_conflicts)
        if start >= end:
            raise ValueError("'start' must be less than 'end' (%s -> %s)" %
                             (start, end))
        if not isinstance(merged_free_busy_interval, int):
            raise ValueError(
                "'merged_free_busy_interval' value %r must be an 'int'" %
                merged_free_busy_interval)
        requested_view_choices = {
            c.value
            for c in FreeBusyViewOptions.get_field_by_fieldname(
                'requested_view').choices
        }
        if requested_view not in requested_view_choices:
            raise ValueError("'requested_view' value %r must be one of %s" %
                             (requested_view, requested_view_choices))
        tz = start.tzinfo  # The timezone of the start and end dates
        for_year = start.year
        _, _, periods, transitions, transitions_groups = list(
            self.get_timezones(timezones=[tz],
                               return_full_timezone_data=True))[0]
        timezone = TimeZone.from_server_timezone(periods,
                                                 transitions,
                                                 transitions_groups,
                                                 for_year=for_year)
        mailbox_data = list(
            MailboxData(email=account.primary_smtp_address,
                        attendee_type=attendee_type,
                        exclude_conflicts=exclude_conflicts)
            for account, attendee_type, exclude_conflicts in accounts)
        return GetUserAvailability(self).call(
            timezone=timezone,
            mailbox_data=mailbox_data,
            free_busy_view_options=FreeBusyViewOptions(
                time_window=TimeWindow(start=start, end=end),
                merged_free_busy_interval=merged_free_busy_interval,
                requested_view=requested_view,
            ),
        )

    def get_roomlists(self):
        return GetRoomLists(protocol=self).call()

    def get_rooms(self, roomlist):
        from .properties import RoomList
        return GetRooms(protocol=self).call(roomlist=RoomList(
            email_address=roomlist))

    def resolve_names(self,
                      names,
                      return_full_contact_data=False,
                      search_scope=None,
                      shape=None):
        from .items import SHAPE_CHOICES, SEARCH_SCOPE_CHOICES
        if search_scope:
            if search_scope not in SEARCH_SCOPE_CHOICES:
                raise ValueError("'search_scope' %s must be one if %s" %
                                 (search_scope, SEARCH_SCOPE_CHOICES))
        if shape:
            if shape not in AUTH_TYPE_MAP:
                raise ValueError("'shape' %s must be one if %s" %
                                 (shape, SHAPE_CHOICES))
        return list(
            ResolveNames(protocol=self).call(
                unresolved_entries=names,
                return_full_contact_data=return_full_contact_data,
                search_scope=search_scope,
                contact_data_shape=shape,
            ))

    def get_searchable_mailboxes(self,
                                 search_filter=None,
                                 expand_group_membership=False):
        return GetSearchableMailboxes(protocol=self).call(
            search_filter=search_filter,
            expand_group_membership=expand_group_membership,
        )

    def __str__(self):
        return '''\
EWS url: %s
Product name: %s
EWS API version: %s
Build number: %s
EWS auth: %s
XSD auth: %s''' % (
            self.service_endpoint,
            self.version.fullname,
            self.version.api_version,
            self.version.build,
            self.auth_type,
            self.docs_auth_type,
        )
예제 #7
0
 def __init__(self, *args, **kwargs):
     super(AutodiscoverProtocol, self).__init__(*args, **kwargs)
     self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE)
     for _ in range(self.SESSION_POOLSIZE):
         self._session_pool.put(self.create_session(), block=False)
예제 #8
0
class Protocol(with_metaclass(CachingProtocol, BaseProtocol)):
    def __init__(self, *args, **kwargs):
        version = kwargs.pop('version', None)
        super(Protocol, self).__init__(*args, **kwargs)

        scheme = 'https' if self.has_ssl else 'http'
        self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server)
        self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server)
        self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server)

        # Autodetect authentication type if necessary
        if self.auth_type is None:
            self.auth_type = get_service_authtype(
                service_endpoint=self.service_endpoint,
                versions=API_VERSIONS,
                name=self.credentials.username)

        # Default to the auth type used by the service. We only need this if 'version' is None
        self.docs_auth_type = self.auth_type

        # Try to behave nicely with the Exchange server. We want to keep the connection open between requests.
        # We also want to re-use sessions, to avoid the NTLM auth handshake on every request.
        self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE)
        for _ in range(self.SESSION_POOLSIZE):
            self._session_pool.put(self.create_session(), block=False)

        if version:
            isinstance(version, Version)
            self.version = version
        else:
            # Version.guess() needs auth objects and a working session pool
            try:
                # Try to get the auth_type of 'types.xsd' so we can fetch it and look at the version contained there
                self.docs_auth_type = get_docs_authtype(
                    docs_url=self.types_url)
            except TransportError:
                pass
            self.version = Version.guess(self)

        # Used by services to process service requests that are able to run in parallel. Thread pool should be
        # larger than the connection pool so we have time to process data without idling the connection.
        # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would
        # leave open threads around to be garbage collected.
        thread_poolsize = 4 * self.SESSION_POOLSIZE
        self.thread_pool = ThreadPool(processes=thread_poolsize)

    def get_timezones(self):
        return GetServerTimeZones(protocol=self).call()

    def get_roomlists(self):
        return GetRoomLists(protocol=self).call()

    def get_rooms(self, roomlist):
        from .properties import RoomList
        return GetRooms(protocol=self).call(roomlist=RoomList(
            email_address=roomlist))

    def resolve_names(self,
                      names,
                      return_full_contact_data=False,
                      search_scope=None,
                      shape=None):
        from .items import SHAPE_CHOICES, SEARCH_SCOPE_CHOICES
        if search_scope:
            assert search_scope in SEARCH_SCOPE_CHOICES
        if shape:
            assert shape in SHAPE_CHOICES
        return list(
            ResolveNames(protocol=self).call(
                unresolved_entries=names,
                return_full_contact_data=return_full_contact_data,
                search_scope=search_scope,
                contact_data_shape=shape,
            ))

    def __str__(self):
        return '''\
EWS url: %s
Product name: %s
EWS API version: %s
Build number: %s
EWS auth: %s
XSD auth: %s''' % (
            self.service_endpoint,
            self.version.fullname,
            self.version.api_version,
            self.version.build,
            self.auth_type,
            self.docs_auth_type,
        )
예제 #9
0
 def _create_session_pool(self):
     # Create a pool to reuse sessions containing connections to the server
     session_pool = LifoQueue(maxsize=self._session_pool_size)
     for _ in range(self._session_pool_size):
         session_pool.put(self.create_session(), block=False)
     return session_pool
예제 #10
0
 def __init__(self, *args, **kwargs):
     super(AutodiscoverProtocol, self).__init__(*args, **kwargs)
     self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE)
     for _ in range(self.SESSION_POOLSIZE):
         self._session_pool.put(self.create_session(), block=False)