django-auth-adfs
django-auth-adfs copied to clipboard
Processing of claim mappings
Wondering how best to handle this a situation where the claim data needs to be transformed into something useable. By default, Microsoft AD sends Object GUIDs (UUIDs) as base64 encoded strings in little-endian byte order.
In this example, this was the only place for use to convert / transform that GUID into something usable.
import base64
import uuid
from django_auth_adfs.backend import AdfsAccessTokenBackend
class CustomAdfsAccessTokenBackend(AdfsAccessTokenBackend):
def validate_access_token(self, access_token):
claims = super().validate_access_token(access_token=access_token)
# Transform base64 objectGUID to a legit UUID
if claims['objectGUID']:
claims['objectGUID'] = uuid.UUID(bytes_le=base64.b64decode(claims['objectGUID']))
return claims
This needs to be transformed / converted before create_user
as it's needed by custom create_user
method.
One idea is allow a person to set a callable on the mappings:
import uuid
def transform_objectguid(value):
return uuid.UUID(bytes_le=base64.b64decode(value)
'CLAIM_MAPPING': {
'first_name': 'FirstName',
'last_name': 'LastName',
'email': 'Email',
'phone_number': 'TelephoneNumber',
'ad_object_guid': {
'name': 'objectGUID',
'transform': transform_objectguid
},
}
Another idea is a post_validate_access_token_hook
:
import uuid
def post_validate_access_token(claims):
# Transform base64 objectGUID to real UUID
if claims['objectGUID']:
claims['objectGUID'] = uuid.UUID(bytes_le=base64.b64decode(claims['objectGUID']))
return claims
'CLAIM_MAPPING': {
'first_name': 'FirstName',
'last_name': 'LastName',
'email': 'Email',
'phone_number': 'TelephoneNumber',
'ad_object_guid': 'objectGUID'
}
'POST_VALIDATE_ACCESS_TOKEN_FUNCTION': post_validate_access_token # or 'dot.path.to.function' takes claims dict
What's the use case for even storing the objectGUID
? :thinking:
As for the implementations, I think a single setting such as POST_VALIDATE_ACCESS_TOKEN_FUNCTION
would be preferred. It won't break backwards compatibility, easier to document and can do the same.
@JonasKs I can do a PR for the POST_VALIDATE_ACCESS_TOKEN_FUNCTION
functionality.
The use case is being able to match up the objectGUID
from a claim to a user that sync'ed into an application by a different system. The objectGUID
is used to ensure the user sync'ed in from the other system matches the person authenticated. The objectGUID
is the only stable identifier in AD for a user profile especially when there is potential for recycling of email addresses and/or usernames. DNs change when objects are moved and not a stable identifier.
From: https://docs.microsoft.com/en-us/windows/win32/ad/using-objectguid-to-bind-to-an-object
If an application stores or caches identifiers or references to objects stored in Active Directory Domain Services, the object GUID is the best identifier to use for several reasons:
- The objectGUID property of on object never changes even if the object is renamed or moved.
- It is easy to bind to the object using the object GUID.
- If the object is renamed or moved, the objectGUID property provides a single identifier that can be used to quickly find and identify the object rather than having to compose a query that has conditions for all properties that would identify that object.
I can do a PR for the POST_VALIDATE_ACCESS_TOKEN_FUNCTION functionality.
Awesome!
Thanks for explaining that to me. :beers:
Hey, is there anything regarding this issue? I'm attempting to use oid in claim as the id for a newly created user, and I'm facing similar issue with the UUID.
@AUitto I haven't had time to implement a PR yet. Current workaround is to create custom backends. Then be sure to use your custom backends in your Settings.py
instead of the ones provided by Django Auth ADFS directly.
import base64
import uuid
from django_auth_adfs.backend import AdfsAuthCodeBackend, AdfsAccessTokenBackend
class CustomAdfsAuthCodeBackend(AdfsAuthCodeBackend):
def validate_access_token(self, access_token):
claims = super().validate_access_token(access_token=access_token)
# Transform base64 objectGUID to real UUID
if claims['objectGUID']:
claims['objectGUID'] = uuid.UUID(bytes_le=base64.b64decode(claims['objectGUID']))
class CustomAdfsAccessTokenBackend(AdfsAccessTokenBackend):
def validate_access_token(self, access_token):
claims = super().validate_access_token(access_token=access_token)
# Transform base64 objectGUID to real UUID
if claims['objectGUID']:
claims['objectGUID'] = uuid.UUID(bytes_le=base64.b64decode(claims['objectGUID']))
return claims
Aight, thanks for a swift response. I'll have a look at the workaround.