freakboy3742 / pyxero

Python API for accessing the REST API of the Xero accounting tool.
BSD 3-Clause "New" or "Revised" License
280 stars 209 forks source link

XeroForbidden: FilesManager submits incorrect request for OAuth2 #308

Closed tarihala closed 1 year ago

tarihala commented 3 years ago

Summary of error

When using OAuth2, FilesManager methods, such as upload_file, return a XeroForbidden error, with status code 403 and message "AuthenticationUnsuccessful".

Error details

Using django 2.2.8 locally (together with django-sslserver==0.22, in order to be able to test OAuth2 over https as Xero requires), I successfully set up the credentials (credentials in code below) and am able to successfully read contacts using a new Xero instance. After setting up a FilesManager instance, I attempt to upload test.pdf:

files_manager = FilesManager('Files', credentials)
files_manager.upload_file('test.pdf')

The error returned is:

  File "XXXX/xero_testing/env/lib/python3.9/site-packages/xero/filesmanager.py", line 100, in wrapper
    raise XeroForbidden(response)
xero.exceptions.XeroForbidden: {"Type":null,"Title":"Forbidden","Status":403,"Detail":"AuthenticationUnsuccessful","Instance":"XXXX","Extensions":{}}

Other FileManager methods produce a similar error.

Analysis of problem

It seems that when making requests, FileManager methods incorrectly submit OAuth2 credentials as an auth keyword argument, as if they were OAuth1 credentials (e.g. https://2.python-requests.org/en/master/user/authentication/#oauth-1-authentication). See line 77 of xero/filesmanage.py here:

https://github.com/freakboy3742/pyxero/blob/35173e9bf5d20a61c37dc1d78710f6d9b34abc84/xero/filesmanager.py#L77

However, according to the Xero OAuth2 documentation, the credentials need to be submitted as request headers (see step 6, '6. Call the API', at https://developer.xero.com/documentation/oauth2/auth-flow).

Possible solution

A solution could be to remove the current line 77 from xero/filesmanage.py, auth=self.credentials.oauth,, and replace line 76 something similar to the following, in accordance with Xero's OAuth2 documentation specifications:

headers = {
        'Authorization': "Bearer " + self.credentials.token['access_token'],
        'Xero-tenant-id': self.credentials.tenant_id,
},

Doing so resolves issues on my machine and the test.pdf successfully uploads.

conal-aginic commented 3 years ago

This error also exists in basemanager.py and projectmanager.py. Thanks for the detailed issue @tarihala, really helped out!