1+ from azure .identity import DefaultAzureCredential
2+ from django .db .backends .postgresql import base
3+
4+
5+ class DatabaseWrapper (base .DatabaseWrapper ):
6+ """
7+ Wrap the Postgres engine to support Azure passwordless login
8+ https://learn.microsoft.com/en-us/azure/developer/intro/passwordless-overview
9+
10+ This involves fetching a token at runtime to use as the database password.
11+
12+ The consequence of this is our database credentials aren't static - they
13+ expire. We therefore need to ensure that every new connection
14+ checks the expiry date, and fetches a new one if necessary.
15+
16+ Unless you disable persistent connections, each thread will maintain its own
17+ connection.
18+ Since Django 5.1 it is possible to configure a connection pool
19+ instead of using persistent connections, but that would require us to
20+ fix the credentials.
21+ See https://docs.djangoproject.com/en/5.2/ref/databases/#persistent-connections
22+ for more details of how this works.
23+ """
24+
25+ def __init__ (self , * args , ** kwargs ):
26+ super ().__init__ (* args , ** kwargs )
27+ self .azure_credential = DefaultAzureCredential ()
28+
29+ def _get_azure_connection_password (self ) -> str :
30+ # This makes use of in-memory token caching
31+ # https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/identity/azure-identity/TOKEN_CACHING.md#in-memory-token-caching
32+ return self .azure_credential .get_token (
33+ "https://ossrdbms-aad.database.windows.net/.default"
34+ ).token
35+
36+ def get_connection_params (self ) -> dict :
37+ params = super ().get_connection_params ()
38+ if params .get ("host" , "" ).endswith (".database.azure.com" ):
39+ params ["password" ] = self ._get_azure_connection_password ()
40+ return params
0 commit comments