New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OAuth2Credentials requires SOAP impersonation headers unconditionally #735
Comments
Additionally - I've tried a separate way. Because I read in the comments of Configuration object class "If you want to use autodiscover, don't use a Configuration object." which seems necessary for setting the OAUTH2 flag because the only place to use it is in the Configuration class. So to use Auth2.0 is it necessary to turn off auto discover? When I replace the bottom half of my code (Below credential definition) credentials = OAuth2Credentials(
client_id=client_id,
client_secret=client_secret,
tenant_id=tenant_id
)
config = Configuration(
credentials=credentials,
service_endpoint="https://outlook.office365.com/EWS/Exchange.asmx",
auth_type=OAUTH2
)
account = Account(primary_smtp_address=address, config=config)
for item in account.inbox.all():
print("got item ", item.sender) I get a different error.
Not even sure how to set this SOAP header. For additional reference - I tried these exact same credentials with an C# example in a different library so I think my account is setup correctly for exchange services. |
OAuth2 hasn't been tested with autodiscover, I think. This is contrib code, so I'm not very familiar with how OAuth works with EWS myself. The error you are getting in non-autodiscover mode can probably be solved by adding |
Has there been any progress on this? I am getting the SOAP error as well. I am using the Here is the code that I am using: client_id = 'XXXXX'
client_secret = 'XXXXX'
tenant_id = 'XXXXX'
address = 'XXXXX@XXXXX.com'
logging.basicConfig(level=logging.DEBUG, handlers=[PrettyXmlHandler()])
oauth_creds = OAuth2Credentials(client_id=client_id,
client_secret=client_secret,
tenant_id=tenant_id)
config = Configuration( credentials=oauth_creds,
service_endpoint='https://outlook.office365.com/EWS/Exchange.asmx',
auth_type=OAUTH2)
account = Account( primary_smtp_address=address,
autodiscover=False,
config=config,
access_type=IMPERSONATION)
for item in account.inbox.all():
print("got item ", item.sender) And this is the error I am getting:
|
Can you enable debug logging and post the contents of the SOAP header in the request that triggers this error response? See https://github.com/ecederstrand/exchangelib#troubleshooting on enabling logging. |
Also, this could be related to #743 if the version of Exchange expects an |
I think that this is the SOAP header... Got a little lost in the logs! Let me know if you need anything else!
|
Ok. I think this happens during version guessing. The last stack trace you posted doesn't contain enough steps to confirm this. Can you post a full stack trace - a trace that starts in your own code? If this is indeed raised during version guessing, it's a bug in exchangelib. It's not going to be easy to fix. The root problem is that some services, e.g. Anyway, I think you can work around this by supplying a from exchangelib.version import Version, EXCHANGE_O365
config = Configuration(
...,
version=Version(build=EXCHANGE_O365),
) All services accessible via |
Adding the above Version code was able to fix the error I was having! Thanks so much!! |
…l SOAP impersonation headers. Add an optional 'identity' arg for OAuth2Credentials for cases where we need to pass identity information in SOAP headers but we don't have an account to get it from. Refs #735
In the referenced commit, I added a new optional form exchangelib import Identity
OAuth2Credentials(
client_id=client_id,
client_secret=client_secret,
tenant_id=tenant_id,
identity=Identity(primary_smtp_address=..., smtp_address=..., upn=..., sid=...),
) With this, you can specify either the SMTP address, UPN or SID of the account that the OAuth credentials were created for. This information will then go in the SOAP headers, which should fix the |
Closing. Feel free to reopen if you still have issues with this. |
@ecederstrand I'm trying to connect email with this library using OAuth2 on Microsoft Exchange. It works properly when we pass the appropriate valid primary_smtp_address (existing email), but it does not return any response or error when we pass an invalid email address. import time
from exchangelib import Account, OAuth2Credentials, Configuration, OAUTH2, Identity
client_id='client_id'
client_secret='client_secret'
tenant_id='tenant_id'
user='email_id'
credentials = OAuth2Credentials(
client_id=client_id,
client_secret=client_secret,
tenant_id=tenant_id,
identity=Identity(smtp_address=user)
)
print(credentials)
config = Configuration(
credentials=credentials,
auth_type=OAUTH2,
server="outlook.office365.com",
)
print(config)
current_time = time.time()
print('current time: ',current_time)
account = Account(
primary_smtp_address=user,
config=config,
autodiscover=False
)
print('Executed Time: ',time.time() - current_time)
print(account.root.all().count())
folder_name = getattr(account, 'inbox')
print(folder_name.all().count())
for item in folder_name.all().filter(**{'subject__icontains': 'Test2'})[:100]:
print(item.subject) |
What is the output of the print statements? Or do you get an exception? If you enable debug output (see https://ecederstrand.github.io/exchangelib/#troubleshooting) then you can see the entire interaction between the server and the client. That should help you see what is happening in the failing case. |
@ecederstrand, There is not returning any exception. After printing the above output, the script is not returning any response/error, it's going in a waiting state. After enabling the debug log output is:
|
Ok, in this case it seems we go into an infinite loop trying to guess the server version when the SMTP address is wrong. That's a bug. I'll try to recreate this on my test server and find a solution. |
@ecederstrand python sample script as below: from exchangelib import Account, Configuration, Credentials, OAuth2Credentials, Identity, OAUTH2
from urllib.parse import urlparse
from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter
import requests_oauthlib
client_id='client_id'
client_secret='client_secret'
tenant_id='tenant_id'
username='email_id'
host='outlook.office365.com'
auth_type='Modern Auth'
email='email_id'
access_type='impersonation'
verify_ssl=False
auth_method='Modern Auth'
password='*************'
def get_credentials(auth_type, username, password, email, client_id, client_secret, tenant_id):
if auth_type == OAUTH2:
credentials = OAuth2Credentials(
client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, identity=Identity(primary_smtp_address=email)
)
else:
credentials = Credentials(username=username, password=password)
return credentials
def get_exchange_client(email, credentials, server, access_type, auth_type, use_autodiscover=False, verify_ssl=False):
try:
access_type = access_type if access_type else 'delegate'
parse_object = urlparse(server)
if parse_object.scheme:
server = parse_object.netloc
if not verify_ssl:
BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter
if use_autodiscover:
return Account(primary_smtp_address=email, credentials=credentials,
autodiscover=use_autodiscover, access_type=access_type.lower())
else:
config = Configuration(server=server, credentials=credentials, auth_type=auth_type)
return Account(primary_smtp_address=email, config=config, access_type=access_type.lower())
except Exception as err:
print(err)
auth_type = OAUTH2 if auth_method == 'Modern Auth' else None
credentials = get_credentials(auth_type, username, password, email, client_id, client_secret, tenant_id)
client = get_exchange_client(email, credentials, host, access_type, auth_type, verify_ssl=verify_ssl)
print(client)
print(client.protocol.credentials.access_token)
import requests, xmltodict
service_url='https://outlook.office365.com/EWS/Exchange.asmx'
mailbox='user@domain.onmicrosoft.com'
request_body="""<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2016" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" soap:mustUnderstand="0" />
<t:ExchangeImpersonation>
<t:ConnectingSID>
<t:SmtpAddress>user@domain.onmicrosoft.com</t:SmtpAddress>
</t:ConnectingSID>
</t:ExchangeImpersonation>
</soap:Header>
<soap:Body>
<m:Subscribe>
<m:StreamingSubscriptionRequest>
<t:FolderIds>
<t:DistinguishedFolderId Id="inbox" />
</t:FolderIds>
<t:EventTypes>
<t:EventType>NewMailEvent</t:EventType>
<t:EventType>ModifiedEvent</t:EventType>
<t:EventType>MovedEvent</t:EventType>
</t:EventTypes>
</m:StreamingSubscriptionRequest>
</m:Subscribe>
</soap:Body>
</soap:Envelope>"""
headers = {'content-type': 'text/xml'}
response = requests.post(service_url, auth=requests_oauthlib.OAuth2(client_id=client_id,token=client.protocol.credentials.access_token), data=request_body, headers=headers, verify=False, timeout=90)
if response.ok:
try:
json_response = xmltodict.parse(response.text)
id = json_response['s:Envelope']['s:Body']['m:SubscribeResponse']['m:ResponseMessages'][
'm:SubscribeResponseMessage']['m:SubscriptionId']
if id:
print("Notification service successfully subscribe")
print("subscription id :{}".format(id))
body="""<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2016" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" soap:mustUnderstand="0" />
</soap:Header>
<soap:Body>
<m:GetStreamingEvents>
<m:SubscriptionIds>
<t:SubscriptionId>{id}</t:SubscriptionId>
</m:SubscriptionIds>
<m:ConnectionTimeout>1</m:ConnectionTimeout>
</m:GetStreamingEvents>
</soap:Body>
</soap:Envelope>""".format(id=id)
response2 = requests.post(service_url, auth=requests_oauthlib.OAuth2(client_id=client_id, token=client.protocol.credentials.access_token), data=body,
headers=headers, verify=False, timeout=90)
if response2.ok:
print('Response2 --->',response2.text)
else:
print("Error in unsubscribe notification service")
except Exception as e:
print(e)
else:
print("Error in subscribe notification service")
Response for a script as below:
InsecureRequestWarning,
EXCHANGE CLIENT: user@domain.onmicrosoft.com
{'token_type': 'Bearer', 'expires_in': 3599, 'ext_expires_in': 3599, 'access_token': '********************', 'expires_at': 1644941194.524418}
Notification service successfully subscribe
subscription id : '****************'
Error in unsubscribe notification service--->>>> <?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode xmlns:a="http://schemas.microsoft.com/exchange/services/2006/types">a:ErrorInvalidExchangeImpersonationHeaderData</faultcode><faultstring xml:lang="en-US">ExchangeImpersonation SOAP header must be present for this type of OAuth token.</faultstring><detail><e:ResponseCode xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">ErrorInvalidExchangeImpersonationHeaderData</e:ResponseCode><e:Message xmlns:e="http://schemas.microsoft.com/exchange/services/2006/errors">ExchangeImpersonation SOAP header must be present for this type of OAuth token.</e:Message></detail></s:Fault></s:Body></s:Envelope>
|
The above gives me the following error. Stack trace: Traceback (most recent call last): |
That response from the server contains the actual issue: "The token contains not enough scope to make this call.". You need to create an OAuth token with sufficient permissions to use EWS. |
I'm trying to connect to an email using Microsoft exchange with this library. Here is the code I'm using :
When I put in my credentials and run this program I get the following error:
Somehow exchangelib is telling me I'm not setting OAuth but if you look in my Configuration setup I am setting it there. Am I doing something wrong here?
Tested this on both Windows 10 and Amazon Linux AMI 2018.03 (rhel fedora) using Python 3.8.1, and Exchangelib 3.1.1
The text was updated successfully, but these errors were encountered: