DIRACGrid / diracx

The neXt DIRAC incarnation
GNU General Public License v3.0
9 stars 20 forks source link

feat: integration authz headers in Dirac client #87

Closed aldbr closed 1 year ago

aldbr commented 1 year ago

This PR aims to enhance the autorest Dirac client by encapsulating the configuration options such as the authorization headers. The primary purpose is to simplify code for the cli developers.

async with Dirac() as api:
    jobs = await api.jobs.search(
        parameters=None if all else parameter,
        search=condition if condition else None,
    )

Note: the Dirac client is able to auto refresh the access token.

Chosen solution

The solution is based on the azure.core architecture. We redefine 3 classes:

This solution was chosen after thorough research and analysis, as it was deemed (by myself) the simplest way to incorporate authorization headers into requests.

Considered alternatives

azure-identity

The python autorest documentation recommends using a credential type from the azure identity package to initialize the client.

The DeviceCodeCredentials seems adapted to our use case. It is able to obtain a token using the device_code flow and cache it. Then it is automatically refreshed when needed.

Problem: it is tightly coupled with Azure applications. DeviceCodeCredential inherits from InteractiveCredential, which inherits from MsalCredential. There are mandatory non-standard parameters such as tenant_id. Setting it to "" or None does not help. The only option would have been to create new classes inheriting from the mentioned classes, which would have been inefficient.

AzureAD authentication library

microsoft-authentication-library-for-python seems to provide a generic OAuth2 library but does not automatically refresh access tokens. Furthermore, it would probably be tricky to integrate to the Dirac autorest client.

authlib in combination with azure.core

authlib would help managing tokens. It is even able to automatically refresh access tokens. But the library would be limited in our context as the Dirac client is initialized each time a new command starts.

chrisburr commented 1 year ago

I think the general approach looks good and like the right way to go 👍

I'll do a specific review once you figure out the CI.

aldbr commented 1 year ago

mypy was tough but probably fair.

Here are the changes I performed to make mypy happy:

It is just for info in case there are better solutions.

The issue related to DIRAC Integration tests is expected, I am going to make a PR once we are okay with the structure of this one.

Update: mypy now complains about the redefinition of the DiracBearerTokenCredentialPolicy()._token() but I don't see how I could do this differently since I can't modify self._token.expires_on which a read-only attribute.

aldbr commented 1 year ago

The issue with the integration tests is related to the normal (non-aio) Dirac client, which is used by DIRAC, and which is actually not implemented here. The easiest option would be to almost duplicate the content of the aio Dirac client in diracx/client/_patch.py. I can see if there is an easy way to avoid duplicating too much code, I will work on that asap.

chrisburr commented 1 year ago

To get the CI green again I've pushed a workaround to main, this commit should be reverted: 2c70a6787

aldbr commented 1 year ago

I'm adding a comment to explain the latest changes because it is becoming hard to review and I am sorry for that: