Skip to content

Instantly share code, notes, and snippets.

@sean-m
Last active December 19, 2025 22:22
Show Gist options
  • Select an option

  • Save sean-m/10696cbb346ec144511470481adbc97d to your computer and use it in GitHub Desktop.

Select an option

Save sean-m/10696cbb346ec144511470481adbc97d to your computer and use it in GitHub Desktop.

Python3 FastAPI based REST API for storing and vending group memberships. Groups can have many members. Members are simply records with their type annoted in a 'type' column'. Every record, group or member, has an entry in a records table (types detailed below). The API is self documenting with OpenAPI (swagger) metadata.

The intention behind operations is to allow for eventual garbage collection of member records. These are units of work that operate on records within the database but shouldn't lock tables. Efforts will be made to perform partial updates and soft-delete memberships when external sytems notify of memberships being removed, the issue is that delta synchronization systems carry no guarantees, sometimes the system just doesn't tell you when someone was removed. That shorcoming can be worked around by triggering a member clean-up operation after a full-update, any membership whos last_operation_id value is less than the most recent completed full-update can be assumed no longer a member and soft-deleted.

Postgresql is the database target with SQLAlchemy as the ORM.

Record Types

Resource record

Name Description
id uuid
type int
subtype int
deleted bool - soft-delete
typename varchar
description varchar
displayname varchar
identifier varchar - may be used for access control: email address, or samaccountname
sourceSystem varchar - Entra Tenant ID, Active Directory domain name, Workday tenant, etc.
created datetime
modified datetime
deleted datetime
notes text
tags varchar[]

Group member record

Name Description
id ulong
source ulong
group_id foreign key
member_id foreign key
member_name varchar - human readable name
removed bool - soft deleted membership record
contributing bool - determines whether or not an imported group membership contributes to a unified group membership, more below
last_operation_id ulong
created_timestamp datetime
removed_timestamp datetime - timestamp identifying when a membership was soft-deleted

Operation record

These records track operations that maintain group memberships. These are usually scripted operations that pull group membership records from directory services: LDAP, Entra, AWS Cogneto, etc.

Name Description
id ulong
type int
description varchar - full-update, partial-update, delete, reconcile
status varchar - running, pending, complete, failed
created datetime
modified datetime - tracks the last time status was updated
completed datetime - tracks when the operation reached the complete or failed status

Cohort Discovery

This idea came from a project at work where syncing group memberships across systems is a requirement but mapping a given user to its counterpart in another system has nuances. They utilize separation of duties for accounts, meaning a given person may have multiple user accounts in a single directory. No identity management systems that I know of handle this case; I've expressly asked Okta, Ping, ForgeRock, and Microsoft, nada. While less of an issue for group syncing, because account naming conventions can usually serve for mapping cross directory memberships, managing an individual's identity requires associating all accounts related to all duties of the individual: standard, privileged, training, testing, etc.

The refactored group membership changes function is working with great performance now but has identified an issue in how associated accounts are identified. In instances where a user has multiple standard member accounts, cloud native or otherwise in a tenant, only one of those accounts is selected for membership eligibility. This has the effect of erroneously identifying a guest in another tenant, associated to their other member account as needing removal from a group.

User Synced Group (Home) Synced Group (Foo) Synced Group (Bar)
Sean 1 (Home) Member Member Member
Sean 2 (Home) Not a member Not a member Not a member

In the case where we're evaluating who needs to be removed from group memberships across a tenant, if Sean 2 is mistaken for Sean 1, Sean 1 will be flagged as needing removal from the Foo and Bar tenants. The way identity correlation is handled currently, all associated users are related through a "prime" user reference, think Identity vs Persona.

graph TD;
    P1(Prime) <--- S1(Sean 1 - Home);
    P1 <--- S2(Sean 2 - Home);
    P1 <--- S1a(Sean 1 - Bar);
    P1 <--- S2a(Sean 2 - Bar);
    P1 <--- S1b(Sean 1 - Foo);
    P1 <--- S2b(Sean 2 - Foo);
Loading

Figure 1 Single tier of association.

All users across tenants are associated through a single prime record. When performing the crosswalk, there's no disambiguation between Sean 1 and Sean 2 across the tenant boundary. Record types: prime, native, guest, privileged are the only distinctions.

graph TD;
    P0(Prime) -..-> WD(Workday Sean)

    P0 <--- P1
    P0 <--- P2
    P0 <--- P3

    subgraph Sean 1 Cohort
        P1(Prime Sean 1) <--- S1(Native - Home);
        P1 <--- S1a(Guest - Bar);
        P1 <--- S1b(Guest - Foo);
    end

    subgraph Sean 2 Cohort
        P2(Prime Sean 2) <--- S2(Native - Home);
        P2 <--- S2b(Guest Foo);
        P2 <--- S2a(Guest Bar);
    end

    subgraph Sean PA Cohort
        P3(Prime Sean) <--- SP2(Privileged - Home);
        P3 <--- SPa(Guest Foo);
        P3 <--- SPb(Guest Bar);
    end
Loading

Figure 2 Multi-tier association, allowing for propagating changes based on degree of separation.

A multi-tiered association where a cohort can be joined to associated cohorts by linking prime references. Resolving the root prime reference can be done recursively until a prime account is found with a globalId of null. Once the root reference is resolved, all associated accounts can be derived for performing actions that are global (disabling from HR employment status change) versus actions local to a cohort (updating display name changes).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment