Conversation
add_user makes a new user a member of a tenant with no role: the API lists them with no roles, answers 409 for an existing key and 404 for a missing tenant, and delete_tenant_user removes them. get_user_tenants, on a container PDP, lists the tenants the user holds a role in, with their attributes, and drops each one the user leaves. A role-less member and a tenant the user never joined are not listed. The tests that call the PDP skip when it is the hosted cloud PDP. One test runs through the blocking client. Refs PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit.get_user_tenants(user, context=None), on the async and the
blocking client, posts {"user", "context"} to the PDP's /user-tenants
route and returns the tenants as TenantDetails models (key and
attributes). The user is serialized as check() does, and the context is
merged over the context store. TenantDetails is exported from permit,
next to AuthorizedUsersResult.
Only the container PDP serves the route; the cloud PDP answers 404. The
SDK raises that 404 as a PermitConnectionError saying the route needs a
container PDP. Any other error status raises a PermitConnectionError
with the status and the redacted error body, as check() does.
Part of PER-16678.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
permit.api.tenants.add_user(tenant_key, user_data) posts a UserCreate
(model or equivalent dict) to /v2/facts/{proj}/{env}/tenants/{t}/users
and returns the UserRead. The API creates the user as a member of the
tenant with no role, unless user_data lists role_assignments, and
answers 409 when a user with that key already exists.
The request is sent to the Permit REST API whatever
proxy_facts_via_pdp says: the tenants API now has an API-only client
next to the one that follows the setting. The other tenants calls keep
their routing. The get_user_tenants docstrings now name add_user as a
membership without a role, which the PDP does not list.
Part of PER-16678.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_groups_e2e.py and test_tenant_membership_e2e.py each defined the same delete_quietly_blocking, the blocking client's delete_quietly. It now lives in tests/utils.py next to delete_quietly, and both modules import it. Teardown behaves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_user_tenants documents that the cloud PDP does not serve /user-tenants and that the SDK raises its 404 as a PermitConnectionError naming the container PDP. The e2e (cloud PDP) job runs only tests/test_cloud_pdp_e2e.py, so a test there now calls get_user_tenants on the cloud PDP and checks for that error. It needs no policy. The workflow comment and CONTRIBUTING.md list it with the job's other checks. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The get_user_tenants tests in tests/test_tenant_membership_e2e.py skip, with the reason, when PDP_URL is the cloud PDP, which does not serve that query. The PDP_URL entry now says so next to where it says which PDP each e2e module uses. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
conftest.py spelled the cloud PDP address out twice, and test_cloud_pdp_e2e.py and test_tenant_membership_e2e.py each defined it to decide when to run or skip. Those checks must use the address the fixtures default to, so it is now one CLOUD_PDP_URL in tests/utils.py that all of them import. Which PDP each test uses, and when each skips, is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API removes only the user's tenant-level roles in the tenant. It answers 404 when the user holds none there, including a member that tenants.add_user() created, and the SDK raises that 404 as a plain PermitApiError, not a PermitNotFoundError. A user who still holds a role in another tenant stays a member with no role. When no tenant-level role is left in any tenant, the API deletes the user, which lets add_user() create a user with that key again. Say so in the delete_tenant_user and add_user docstrings and in the README, and point to users.delete() for removing a member with no role. Regenerate the sync stub. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The add_user e2e test removed the member it had just created with delete_tenant_user. The API answers 404 for a member with no role in the tenant and leaves them in it, so the test would fail in the CI jobs that run it against the API. The test now checks that 404 and that the member stays. It then gives the user a role in both tenants: delete_tenant_user in one tenant keeps the user a member there with no role, and delete_tenant_user in the other, the last role, deletes the user, so users.get answers 404. The module docstring says the same. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API also answers 404 when a role assignment in the user data names a role, tenant or resource that does not exist, and 409 when one names a tenant other than the one its resource instance is in. Add both to the Raises section and regenerate the sync stub. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README read as if role_assignments in the user data gave the user a role in the tenant add_user names. The API grants each one in the tenant it names, as users.create() does, which the add_user docstring already says. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_tenant_details_gives_each_tenant_its_own_attributes passes with a
shared {} default too, because pydantic v1 copies field defaults per
instance on both lanes. The default itself stays covered by the
get_user_tenants parsing test, where a tenant with no attributes parses
to an empty dict.
Part of PER-16678.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_user_tenants puts the PDP's error body into the PermitConnectionError it raises, as check() does. The test that pins API key redaction for those errors covered check, bulk_check and authorized_users only. Add get_user_tenants to it. Part of PER-16678. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tenant users route creates the user and answers 409 for an existing key, so create_user says what it does. add_user stays as an alias that warns once, at the caller's line, and calls create_user. It goes in permit 4.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependency Security AuditScanned: pyproject.toml dependencies + dev group, resolved at Python 3.10 (the current resolution, and the lowest versions the published specs permit under each pydantic major) ✅ No known vulnerabilities found. Both the resolved dependency set and the lowest versions the published specs permit are clean at HIGH and CRITICAL. |
* origin/per-16677/groups-api: Invite with a role of the invited resource in the invites e2e test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * origin/per-16677/groups-api: Poll for the PDP's role assignment list in the RBAC e2e tests
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear issues
tenants.create_user(withadd_useras a deprecated alias) andget_user_tenantsto the Python SDK./user-tenants; the SDK documents and tests the current behaviour.Why
The SDK could list a tenant's users and remove them, but it could not add a user to a tenant through the tenant users route. It also had no way to ask the PDP which tenants a user belongs to. The Java SDK has the second as
getUserTenants.What changed
permit.api.tenants.create_user(tenant_key, user_data) -> UserRead, on the async and blocking clients. It posts the user (UserCreateor an equivalent dict) to/v2/facts/{proj}/{env}/tenants/{tenant}/users. It always calls the Permit REST API, even withproxy_facts_via_pdp, because the PDP forwards this route without waiting for the policy sync. The API creates a new user who belongs to the tenant with no role. Anyrole_assignmentsin the user data are granted in the tenant each one names. An existing key gets 409 (PermitAlreadyExistsError). A missing tenant, or a missing role, tenant or resource named by an inline grant, gets 404 (PermitNotFoundError). It needs an environment-level API key, or a broader key with the SDK's API context set to the environment.TenantsApibuilds this client through a new API-only property. The routing of its other methods is unchanged.permit.get_user_tenants(user, context=None) -> list[TenantDetails], onpermit.Permit,permit.sync.Permitand the enforcer. It posts{"user", "context"}to the PDP's/user-tenantsroute. The user is serialized ascheck()serializes it, and the context is merged over the context store. The PDP lists the tenants in which the user holds a tenant-level role. A member with no role is not listed. Only the container PDP serves the route. When the PDP answers 404, as the cloud PDP does, the SDK raises aPermitConnectionErrorsaying the call needs a container PDP. Any other error status raises aPermitConnectionErrorwith the status and the redacted error body, ascheck()does. A PDP that cannot be reached raises the same connection error as the other PDP calls.permit.api.tenants.add_user()is a deprecated alias ofcreate_user(): it issues oneDeprecationWarningat the caller's line, then callscreate_user(). It will be removed in permit 4.0. The README lists it under Deprecations.TenantDetails(key, attributes)inpermit.enforcement.interfaces, exported frompermitnext toAuthorizedUsersResult.delete_tenant_userdocstring now says what the API does. It removes the user's tenant-level roles in the tenant. It answers 404, raised as aPermitApiError, when the user holds none there, which includes a member thatcreate_user()created; such a member is removed withusers.delete(). The user stays a member while they hold a role in another tenant. The API deletes the user once no tenant-level role is left in any tenant.permit/_sync_types.pyi) is regenerated, andtests/type_check/consumer.pycalls both methods on both clients.tests/test_tenant_membership_offline.py: 71 offline wire tests.tests/test_tenant_membership_e2e.py: 3 e2e tests. The two that callget_user_tenantsskip, with a reason, when the PDP is the cloud PDP.tests/test_cloud_pdp_e2e.py: a new test checks that the cloud PDP's 404 forget_user_tenantsraises the container-PDP error.tests/test_fix_logging.py: the API key redaction test now also coversget_user_tenants.tests/utils.py: now holdsCLOUD_PDP_URLanddelete_quietly_blocking, shared with the groups e2e tests.e2e (cloud PDP)workflow comment describe the new tests.Behaviour changes
None for existing methods.
create_user, its deprecated aliasadd_user,get_user_tenantsand theTenantDetailsexport are new.delete_tenant_userchanges only in its docstring.How it was tested
tests/test_typing_surface.py(stub drift, consumer type check): 3 passed on both lanes.pre-commit run --all-files(ruff, ruff format, mypy, typos,uv lock --check),actionlintandzizmor: no findings.create_userwithproxy_facts_via_pdpon and off, andget_user_tenantswith a string user and a dict user (with attributes), with and without context. They also cover a PDP 404, a PDP 500, an unreachable PDP and the parsed return types.add_userstops warning or stops passing its arguments through. 21 mutants of the new code, all caught by the offline tests. They includecreate_userthrough the PDP proxy, wrong path or verb, a dropped body or context, a missing 404 branch, an unredacted or missing error body, an unparsed response, the sync override removed, the export removed and a stale stub. The cloud-PDP test passes only on a 404; it fails on 200, 401, 405, 500 and an unreachable PDP.Owner actions before merge
pytestjobs ande2e (latest PDP image)pass. They are the first run of the e2e module against the real API and PDP.get_user_tenantsshould list members with no role and whetherdelete_tenant_usershould remove them. If the PDP or API changes, update the docstrings, README and e2e assertion with it.🤖 Generated with Claude Code