class*, config, ldap, firestore, logger)

Bases: object

Retrieve user metadata from external systems.

In some cases, we take user metadata from external systems. Examples are:

  1. Resolve a unique identifier to a username via LDAP.

  2. Get user group membership from LDAP.

  3. Get UID or GID from LDAP.

  4. Assign and manage UIDs and GIDs via Google Firestore.

This service manages those interactions. LDAP and Firestore data is cached via their service objects.

  • config (Config) – Gafaelfawr configuration.

  • ldap (LDAPService | None) – LDAP service for user metadata, if LDAP was configured.

  • firestore (FirestoreService | None) – Service for Firestore UID/GID lookups, if Firestore was configured.

  • logger (BoundLogger) – Logger to use.

Methods Summary


Get scopes from user information.

get_user_info_from_token(token_data, *[, ...])

Get the user information from a token.


Invalidate any cached data for a given user.

Methods Documentation

async get_scopes(user_info)

Get scopes from user information.

Used to determine the scope claim of a token issued based on an OpenID Connect authentication.

  • TokenUserInfo – User information for a user.

  • user_info (TokenUserInfo)


The scopes generated from the group membership based on the group_mapping configuration parameter, or None if the user was not a member of any known group.

Return type:

list of str or None

async get_user_info_from_token(token_data, *, uncached=False)

Get the user information from a token.

Information stored with the token takes precedence over information from LDAP. If the token information is None and LDAP is configured, retrieve it dynamically from LDAP.

  • token_data (TokenData) – Data from the authentication token.

  • uncached (bool, default: False) – Bypass the cache, used for health checks.


User information for the holder of that token.

Return type:


  • FirestoreError – UID/GID allocation using Firestore failed.

  • LDAPError – Gafaelfawr was configured to get user groups, username, or numeric UID from LDAP, but the attempt failed due to some error.

async invalidate_cache(username)

Invalidate any cached data for a given user.

Used after failed login due to missing group memberships, so that if the user immediately fixes the problem, they don’t have to wait for the LDAP cache to expire.


username (str) – User for which to invalidate cached data.

Return type:



This should be sufficient for the most common cases of invalid group membership even if there are multiple instances of Gafaelfawr running.

Retrieving LDAP information for a user only happens either (a) during the login process, or (b) when checking a user’s token (via the /auth route, the API, etc.). For the typical case of a user who hasn’t onboarded yet, (b) is impossible since they haven’t previously authenticated and have no tokens. When they go through the login process, we will retrieve their LDAP information and cache it, but then we ask whether they’re authorized. If not, we don’t issue them a token and then invalidate the cache using this method. These both happen as part of processing the same request, so they can’t be split across multiple instances.

Therefore, in that normal case, this method will remove information that was cached as part of the same request, and no other instance of Gafaelfawr could have cached LDAP data because the user has not successfully authenticated.

This cache invalidation could be insufficient if the user had previously authenticated and then their user information in LDAP changed such that they’re no longer a member of an eligible group. In that case, a cache invalidation done by the login process may be undone by the user using an existing unexpired token to authenticate to something else, resulting in an LDAP query that will be cached. This could cause the confusing behavior that we were hoping to avoid: bad LDAP data cached until the cache timeout.

That said, hopefully this case will be rare compared to the more typical case of some onboarding problem. In the case where a user is being invalidated entirely, we would normally delete all of their tokens as well, avoiding this conflict.