Tiered LDAP groups

This commit is contained in:
2026-10-29 14:17:16 -04:00
parent 0bffd7d993
commit 920a608fba
10 changed files with 565 additions and 8 deletions
+72
View File
@@ -42,6 +42,78 @@ Stripe outages do not extend locally recorded paid expiry. Already-issued Dex
tokens and downstream sessions do not instantly lose their cached claims; set
short token lifetimes and enforce reauthorization in each relying application.
## Service access by membership level
Additional LDAP service benefits are configured separately from Stripe prices in
[membership/service-groups.eom.json](membership/service-groups.eom.json). Set
`MEMBERSHIP_SERVICE_GROUPS_FILE` to that file, or supply an equivalent object in
`MEMBERSHIP_SERVICE_GROUPS_JSON` (which takes precedence). The default is empty;
the shipped file contains empty lists for levels 0–5, granting nothing until
you supply real, reviewed group DNs. Existing paid tiers and their `group_dn`
continue working unchanged.
The shape is a level-to-list mapping, for example:
```json
{
"0": ["cn=community-chat,ou=membership-services,dc=example,dc=org"],
"1": ["cn=community-chat,ou=membership-services,dc=example,dc=org"],
"2": [
"cn=community-chat,ou=membership-services,dc=example,dc=org",
"cn=game-servers,ou=membership-services,dc=example,dc=org"
]
}
```
Keys are canonical level strings: `"0"` or an existing paid tier's positive
level. Values are lists of full LDAP group DNs. Omitted levels/empty lists grant
no additional service access. **The highest currently paid level selects exactly
one list**. There is no implicit inheritance: in this example level 2 gets chat
and games, while levels 3–5 get neither unless explicitly listed. Level 0 means
free accounts only, not a baseline automatically inherited by paid accounts.
To offer a service to everyone, repeat its DN under every configured level.
An active Stripe and SHKeeper combination still selects the highest paid level;
it does not combine lower service lists. Normal per-tier group grants remain
additive for each valid paid entitlement, as before.
The worker computes the level from paid subscription/invoice expiry on **every
pass**, never from cached Dex groups or the account's stored level. Expiry falls
back to level 0 and revokes paid-only service access without contacting payment
providers. Editing/removing a DN, removing a tier or changing levels reconciles
on the next sweep. Historical `ManagedGroup` records are retained so old grants
are revoked even after configuration removal; management intent commits before
LDAP changes so failed writes can be retried safely. Login/registration records
intent but does not grant groups directly. Level 0 grants cover local membership
accounts (including first-time Dex sign-ins), not every LDAP account that has
never used this application.
**Provision and secure the groups before enabling a mapping:**
- Targets must already be `groupOfUniqueNames` groups with the configured
sentinel entry. Allow the worker narrowly scoped read/write of `uniqueMember`
on these groups only. No LDAP schema, group or ACL is created automatically.
- Mapping a DN hands this application control of that group's membership for
**every local member**, including revocations of manual grants to noneligible
members. Use dedicated groups for membership-controlled service access. Do not
map administrator, staff, existing independently managed entitlement groups,
the sentinel DN or groups where such removals would be unsafe. Configure
services/Dex to authorize these dedicated groups and review any service-specific
user provisioning requirements separately. Only administrators edit mappings;
browser forms and Dex claims cannot choose entitlement groups.
- Keep removed groups and ACL access available until the worker has successfully
removed old grants. Missing groups/LDAP outages fail synchronization and delay
grants/revocations; they must not be hidden by deleting historical tracking.
- Group names/tokens already cached by Dex and relying services must refresh.
Revocation is not an instantaneous logout across services.
For Kubernetes, use the role's `www_membership_service_groups` variable, not an
image-baked file: the role mounts its own mapping next to its tiers configuration.
Change the variable and rerun the role; its revision includes this mapping and
rolls web/worker so they reload it. Directly changing a ConfigMap does not reload
Django settings. Build/deploy the updated image and role once before using the
new setting. No database migration is needed for service mappings. Changes to
the benefit display text remain separate from actual authorization rules.
## Existing Stripe product
The live Stripe dashboard was inspected on 2026-10-23. This integration reuses