Adding support for using OAuth2 APIs

Rationale

There are APIs that Tryton may need to connect to that require oauth2 authorisation. To avoid re-implementing the common parts of this process it would be good if there were classes/methods/wizards/etc available that provided the common functionality.

This would be helpful when adding Support for UK HMRC Making Tax Digital (MTD), as it provides a RESTful API that requires oauth2 authorization.

There also appears to be an API called PISTE that uses oauth2 which could also use these common parts.

Proposal

I am not too sure the best way of doing this, this is one suggestion which I think may work, but I would welcome any improvements, or better / alternative ways of doing this.

Add a new oauth2 module that will provide:

  • OAuth2APIProvider - A model that represents the company or organisation that provides the API service.

  • OAuth2API - A model that represents the API itself.

    This would contain the properties of the API, such as the Base URL, Token Endpoint, Redirect URL, Authorization URL, etc. There would be a Many2One to the OAuth2Provider. The records in this model would describe the provider’s Production, Testing, etc API.

  • OAuth2APIUserMixin - A mixin applied to models whose records are API Users.

    This mixin would be added to models such as User, Company or Employee, depending on the provider or API. It would provide a means of allowing the selection of the API to use with the provider, and a method that would return the session for the API User’s currently selected API.

  • OAuth2Application - Represents the Tryton application and implements the OAuth2APIUserMixin.

    Used with the backend application flow when the API User is the Tryton application itself.

  • OAuth2Session - Details about an API User’s connection to an OAuth2API.

    This model would store the details about a connection to the API that need to be saved across requests. There would be a Reference to the API User, and a Many2One to the OAuth2API. Details about the session, such as the Token, State (used to mitigate CSRF, and to find the session after authorization - see below) and any Errors related to the connection would also be stored here.

    A classmethod would be available to get a session for the Provider, API User model, and scope. This would use the current context to work out which individual Company, User, etc. the session should be for.

    Each OAuth2Session would provide a property / getter which would return the requests_oauth2lib session object which is then used to call methods on the API.

  • OAuth2APIEndpoint - This class would represent an endpoint in the API.

    It would have properties to define the endpoint, provider, http method, API User model, scope, etc. The objects created from this class would be callable, and calling them would use the OAuth2Session.get classmethod to get an appropriate session to use to send the request to the api and get the result. The result would then be returned to the caller, or an exception thrown if the call to the API couldn’t be done.

  • OAuth2WizardMixin (or extension to Wizard class).

    This would help when creating wizards that make calls to the OAuth2 API. It would catch any OAuth2Unauthorized exceptions that are thrown when making calls to the API, and would add some states that guide the user through, or automatically perform, the authorization process.

    To authorize the user, one of the states would send a URL Action to the client that will open the authorization endpoint in the user’s browser. The user will authenticate with the service and grant the appropriate authority. When submitted this will then send the user’s browser to the redirect_url which must be setup to point to their trytond server. A route at this redirect_url will get the request and use the code from the authorization along with the state to get and save the OAuth2 token in the OAuth2Session.

    Now authorized, the wizard could then send the user back to the state where the OAuth2Unauthorized execption occurred so they could try again, as now the request should be successful.

    A wizard derived from this mixin or extension could then be used for most of the calls to the API.

Implementation

For me, managing such constraint with a generic API is too much complex for almost no benefit. For me, such abstraction will make writing a module using oauth more complicated than just creating the necessary endpoint. Because the coding will be about filling data in the database instead of writing code. Indeed it is just a matter of requesting token and refreshing them when needed. This can just be handled case by case because each endpoint will be unique.
All the abstraction is already provided by packages like requests-oauthlib.

1 Like

Thanks, that’s very useful advice.