django-auth-adfs icon indicating copy to clipboard operation
django-auth-adfs copied to clipboard

Processing of claim mappings

Open peterfarrell opened this issue 4 years ago • 6 comments

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
Fund with Polar

peterfarrell avatar Jan 25 '21 21:01 peterfarrell

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 avatar Jan 26 '21 19:01 JonasKs

@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:

  1. The objectGUID property of on object never changes even if the object is renamed or moved.
  2. It is easy to bind to the object using the object GUID.
  3. 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.

peterfarrell avatar Jan 26 '21 20:01 peterfarrell

I can do a PR for the POST_VALIDATE_ACCESS_TOKEN_FUNCTION functionality.

Awesome!

Thanks for explaining that to me. :beers:

JonasKs avatar Jan 26 '21 20:01 JonasKs

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 avatar May 24 '21 14:05 AUitto

@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

peterfarrell avatar May 24 '21 14:05 peterfarrell

Aight, thanks for a swift response. I'll have a look at the workaround.

AUitto avatar May 24 '21 14:05 AUitto