What OAuth permissions needed for exchangelib?

%3CLINGO-SUB%20id%3D%22lingo-sub-2858179%22%20slang%3D%22en-US%22%3EWhat%20OAuth%20permissions%20needed%20for%20exchangelib%3F%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-2858179%22%20slang%3D%22en-US%22%3E%3CP%3EMy%20end%20goal%20is%20to%20have%20a%20script%20that%20moves%20a%20single%20users%20mail%20around%20(archiving%20stuff%20etc.).%20Right%20now%20I'm%20just%20trying%20to%20be%20able%20to%20look%20at%20the%20mail.%20I'm%20using%20a%20python%20library%20called%20%3CA%20href%3D%22https%3A%2F%2Fgithub.com%2Fecederstrand%2Fexchangelib%22%20target%3D%22_self%22%20rel%3D%22noopener%20noreferrer%22%3Eexchangelib%3C%2FA%3E.%20However%2C%20I%20can't%20seem%20to%20get%20the%20permissions%20right.%20Here's%20the%20code%20I'm%20using%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CDIV%20class%3D%22%22%3E%3CPRE%3E%3CSPAN%20class%3D%22%22%3Efrom%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eexchangelib%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eimport%3C%2FSPAN%3E%20(%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3EAccount%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3EConfiguration%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3EOAuth2Credentials%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3EDELEGATE%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3EOAUTH2%3C%2FSPAN%3E%2C%0A)%0A%3CSPAN%20class%3D%22%22%3Efrom%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eos%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eimport%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eenviron%3C%2FSPAN%3E%0A%0A%3CSPAN%20class%3D%22%22%3Eusername%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eenviron%3C%2FSPAN%3E%5B%3CSPAN%20class%3D%22%22%3E%22USERNAME%22%3C%2FSPAN%3E%5D%0A%3CSPAN%20class%3D%22%22%3Eclient_id%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eenviron%3C%2FSPAN%3E%5B%3CSPAN%20class%3D%22%22%3E%22CLIENT_ID%22%3C%2FSPAN%3E%5D%0A%3CSPAN%20class%3D%22%22%3Etenant_id%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eenviron%3C%2FSPAN%3E%5B%3CSPAN%20class%3D%22%22%3E%22TENANT_ID%22%3C%2FSPAN%3E%5D%0A%3CSPAN%20class%3D%22%22%3Esecret_value%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3Eenviron%3C%2FSPAN%3E%5B%3CSPAN%20class%3D%22%22%3E%22VALUE%22%3C%2FSPAN%3E%5D%0A%0A%3CSPAN%20class%3D%22%22%3Ecredentials%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3EOAuth2Credentials%3C%2FSPAN%3E(%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3Eclient_id%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3Eclient_id%3C%2FSPAN%3E%2C%20%3CSPAN%20class%3D%22%22%3Etenant_id%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3Etenant_id%3C%2FSPAN%3E%2C%20%3CSPAN%20class%3D%22%22%3Eclient_secret%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3Esecret_value%3C%2FSPAN%3E%0A)%0A%3CSPAN%20class%3D%22%22%3Econf%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3EConfiguration%3C%2FSPAN%3E(%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3Ecredentials%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3Ecredentials%3C%2FSPAN%3E%2C%20%3CSPAN%20class%3D%22%22%3Eserver%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%22outlook.office365.com%22%3C%2FSPAN%3E%2C%20%3CSPAN%20class%3D%22%22%3Eauth_type%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3EOAUTH2%3C%2FSPAN%3E%0A)%0A%3CSPAN%20class%3D%22%22%3Eaccount%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%20%3CSPAN%20class%3D%22%22%3EAccount%3C%2FSPAN%3E(%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3Eprimary_smtp_address%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3Eusername%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3Eautodiscover%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3EFalse%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3Econfig%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3Econf%3C%2FSPAN%3E%2C%0A%20%20%20%20%3CSPAN%20class%3D%22%22%3Eaccess_type%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3E%3D%3C%2FSPAN%3E%3CSPAN%20class%3D%22%22%3EDELEGATE%3C%2FSPAN%3E%2C%0A)%3C%2FPRE%3E%3CP%3EAnd%20here's%20what%20the%20permissions%20look%20like%20in%20AzureAD%3C%2FP%3E%3CP%3E%3CSPAN%20class%3D%22lia-inline-image-display-wrapper%20lia-image-align-inline%22%20image-alt%3D%22image161.png%22%20style%3D%22width%3A%20999px%3B%22%3E%3CIMG%20src%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fimage%2Fserverpage%2Fimage-id%2F318148iECBB1D9579628A50%2Fimage-size%2Flarge%3Fv%3Dv2%26amp%3Bpx%3D999%22%20role%3D%22button%22%20title%3D%22image161.png%22%20alt%3D%22image161.png%22%20%2F%3E%3C%2FSPAN%3E%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EAnd%20here's%20the%20error%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%3ETraceback%20(most%20recent%20call%20last)%3A%0A%20%20File%20%22test.py%22%2C%20line%2021%2C%20in%20%26lt%3Bmodule%26gt%3B%0A%20%20%20%20account%20%3D%20Account(%0A%20%20File%20%22...%2Faccount.py%22%2C%20line%20133%2C%20in%20__init__%0A%20%20%20%20self.version%20%3D%20self.protocol.version%0A%20%20File%20%22...%2Fprotocol.py%22%2C%20line%20470%2C%20in%20version%0A%20%20%20%20self.config.version%20%3D%20Version.guess(self%2C%20api_version_hint%3Dself._api_version_hint)%0A%20%20File%20%22...%2Fversion.py%22%2C%20line%20229%2C%20in%20guess%0A%20%20%20%20list(ResolveNames(protocol%3Dprotocol).call(unresolved_entries%3D%5Bname%5D))%0A%20%20File%20%22...%2Fservices%2Fresolve_names.py%22%2C%20line%2052%2C%20in%20_elems_to_objs%0A%20%20%20%20for%20elem%20in%20elems%3A%0A%20%20File%20%22...%2Fservices%2Fcommon.py%22%2C%20line%20212%2C%20in%20_chunked_get_elements%0A%20%20%20%20yield%20from%20self._get_elements(payload%3Dpayload_func(chunk%2C%20**kwargs))%0A%20%20File%20%22...%2Fservices%2Fcommon.py%22%2C%20line%20230%2C%20in%20_get_elements%0A%20%20%20%20yield%20from%20self._response_generator(payload%3Dpayload)%0A%20%20File%20%22...%2Fservices%2Fcommon.py%22%2C%20line%20196%2C%20in%20_response_generator%0A%20%20%20%20response%20%3D%20self._get_response_xml(payload%3Dpayload)%0A%20%20File%20%22...%2Fservices%2Fcommon.py%22%2C%20line%20310%2C%20in%20_get_response_xml%0A%20%20%20%20r%20%3D%20self._get_response(payload%3Dpayload%2C%20api_version%3Dapi_version)%0A%20%20File%20%22...%2Fservices%2Fcommon.py%22%2C%20line%20265%2C%20in%20_get_response%0A%20%20%20%20r%2C%20session%20%3D%20post_ratelimited(%0A%20%20File%20%22...%2Futil.py%22%2C%20line%20877%2C%20in%20post_ratelimited%0A%20%20%20%20protocol.retry_policy.raise_response_errors(r)%20%20%23%20Always%20raises%20an%20exception%0A%20%20File%20%22...%2Fprotocol.py%22%2C%20line%20689%2C%20in%20raise_response_errors%0A%20%20%20%20raise%20UnauthorizedError('Invalid%20credentials%20for%20%25s'%20%25%20response.url)%0Aexchangelib.errors.UnauthorizedError%3A%20Invalid%20credentials%20for%20https%3A%2F%2Foutlook.office365.com%2FEWS%2FExchange.asmx%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EBy%20the%20way%20I've%20looked%20into%20a%20bunch%20of%20different%20methods%20of%20moving%20email%20but%20I'm%20dealing%20with%20a%20few%20hundred%20thousand%20emails%20and%20nothing%20else%20will%20do%20it%20in%20a%20reasonable%20time%20(except%20IMAP%20but...%20its%20IMAP).%20Specifically%3A%3C%2FP%3E%3CUL%3E%3CLI%3EThe%20web%20interface%20doesn't%20allow%20selecting%20and%20moving%20more%20than%20like%20100%20emails%3C%2FLI%3E%3CLI%3EThe%20outlook%20desktop%20app%20wont%20move%20more%20than%20about%201000%20emails%20at%20a%20time%20without%20the%20move%20crashing.%3C%2FLI%3E%3CLI%3EFor%20some%20reason%20using%20the%20addon%20interface%20with%20C%23%20was%20also%20unstable%20(I%20got%20a%20test%20to%20complete%20once%20but%20it%20failed%20like%206%20times%20with%20no%20exceptions%20or%20anything)%3C%2FLI%3E%3CLI%3EThe%20powershell%20command%20line%20thing%20that%20you%20connect%20to%20with%20like%20Connect-ExchangeOnline%20doesn't%20allow%20you%20to%20move%20individual%20emails.%3C%2FLI%3E%3CLI%3EMicrosoft%20Graph%20rate%20limits%20you%20at%2010%2C000%20email%20moves%20per%20day.%3C%2FLI%3E%3C%2FUL%3E%3C%2FDIV%3E%3C%2FLINGO-BODY%3E%3CLINGO-LABS%20id%3D%22lingo-labs-2858179%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3EAuthentication%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EDeveloper%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EExchange%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3Eidentity%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E
Occasional Visitor

My end goal is to have a script that moves a single users mail around (archiving stuff etc.). Right now I'm just trying to be able to look at the mail. I'm using a python library called exchangelib. However, I can't seem to get the permissions right. Here's the code I'm using

 

from exchangelib import (
    Account,
    Configuration,
    OAuth2Credentials,
    DELEGATE,
    OAUTH2,
)
from os import environ

username = environ["USERNAME"]
client_id = environ["CLIENT_ID"]
tenant_id = environ["TENANT_ID"]
secret_value = environ["VALUE"]

credentials = OAuth2Credentials(
    client_id=client_id, tenant_id=tenant_id, client_secret=secret_value
)
conf = Configuration(
    credentials=credentials, server="outlook.office365.com", auth_type=OAUTH2
)
account = Account(
    primary_smtp_address=username,
    autodiscover=False,
    config=conf,
    access_type=DELEGATE,
)

And here's what the permissions look like in AzureAD

image161.png

 

And here's the error

 

Traceback (most recent call last):
  File "test.py", line 21, in <module>
    account = Account(
  File ".../account.py", line 133, in __init__
    self.version = self.protocol.version
  File ".../protocol.py", line 470, in version
    self.config.version = Version.guess(self, api_version_hint=self._api_version_hint)
  File ".../version.py", line 229, in guess
    list(ResolveNames(protocol=protocol).call(unresolved_entries=[name]))
  File ".../services/resolve_names.py", line 52, in _elems_to_objs
    for elem in elems:
  File ".../services/common.py", line 212, in _chunked_get_elements
    yield from self._get_elements(payload=payload_func(chunk, **kwargs))
  File ".../services/common.py", line 230, in _get_elements
    yield from self._response_generator(payload=payload)
  File ".../services/common.py", line 196, in _response_generator
    response = self._get_response_xml(payload=payload)
  File ".../services/common.py", line 310, in _get_response_xml
    r = self._get_response(payload=payload, api_version=api_version)
  File ".../services/common.py", line 265, in _get_response
    r, session = post_ratelimited(
  File ".../util.py", line 877, in post_ratelimited
    protocol.retry_policy.raise_response_errors(r)  # Always raises an exception
  File ".../protocol.py", line 689, in raise_response_errors
    raise UnauthorizedError('Invalid credentials for %s' % response.url)
exchangelib.errors.UnauthorizedError: Invalid credentials for https://outlook.office365.com/EWS/Exchange.asmx

 

By the way I've looked into a bunch of different methods of moving email but I'm dealing with a few hundred thousand emails and nothing else will do it in a reasonable time (except IMAP but... its IMAP). Specifically:

  • The web interface doesn't allow selecting and moving more than like 100 emails
  • The outlook desktop app wont move more than about 1000 emails at a time without the move crashing.
  • For some reason using the addon interface with C# was also unstable (I got a test to complete once but it failed like 6 times with no exceptions or anything)
  • The powershell command line thing that you connect to with like Connect-ExchangeOnline doesn't allow you to move individual emails.
  • Microsoft Graph rate limits you at 10,000 email moves per day.
1 Reply

@GarbledSpaceman 

 

I have exact the same problem ... help very appreciated