Repository navigation
Let a superuser issue a personal token for another user - #1117
Merged
Merged
Conversation
POST /api/v2/users/<id>/personal_tokens/ always created a token for the requester, whatever <id> was, and returned 201. GET on the same URL listed the requester's tokens. A user created without a password can't log in to create its own token, so the only way to give one a token was awx-manage create_oauth2_token on the controller. The token now belongs to <id>. Users can still create tokens for themselves; only a superuser may create one for someone else, and anyone else gets 403. This matches OAuth2TokenAccess, which already limits reading and revoking personal tokens to the owner and superusers. Because the sub-list permission check asks can_add about the user in the URL, non-superusers also get 403 from another user's tokens/ and authorized_tokens/; superusers there still get their own token, as before. A token for someone else is refused (400) when the owner is inactive, or is a local user while DISABLE_LOCAL_AUTH is on, since turning that setting on revokes those users' tokens. The external-account check (ALLOW_OAUTH2_FOR_EXTERNAL_USERS) applies to the owner. GET lists <id>'s personal tokens, filtered by what the requester may read. The activity-stream entry for a token whose owner isn't the actor records the owner in changes.user and links the entry to the owner's activity stream; the actor is still the requester. related ctrliq#1116 Assisted-by: Claude Opus 5.5 Signed-off-by: Quanah Gibson-Mount <quanah@dropbox.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The implementation consistently enforces the intended ownership and RBAC behavior with comprehensive functional coverage.
0 open findings
What changed in this PR
Enables superusers to issue personal OAuth2 tokens for another user while preserving RBAC, authentication restrictions, and auditability.
Changes:
- Uses the URL user as the personal token owner with appropriate validation and permissions.
- Lists the URL user’s readable personal tokens.
- Records both issuer and owner in activity streams and updates documentation and tests.
| File | Description |
|---|---|
ascender/api/serializers/oauth2.py |
Supports serializer-specific token ownership. |
ascender/api/serializers/user.py |
Assigns and validates the target token owner. |
ascender/api/views/user.py |
Lists the target user’s accessible personal tokens. |
ascender/main/access.py |
Restricts cross-user issuance to superusers. |
ascender/main/signals.py |
Audits and links cross-user token ownership. |
ascender/main/tests/functional/api/test_oauth.py |
Covers permissions, validation, listing, authentication, and auditing. |
docs/auth/oauth.md |
Updates the endpoint example and ownership guidance. |
docs/docsite/rst/administration/oauth2_token_auth.rst |
Documents issuance and listing permissions. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
cigamit
approved these changes
Oct 14, 2026
Collaborator
|
Tested and worked as planned. No issues found. |
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.
SUMMARY
Assisted-by: Claude Opus 5.5. Drafted with AI assistance; the commit carries the same trailer.
POST /api/v2/users/<id>/personal_tokens/ignored<id>: every token serializer set the owner to the requester, so an admin got201and a token of their own.GETon the same URL listed the requester's tokens. That left no API path to give a token to a user who can't log in, such as a service account created without a password (#1090); the workaround wasawx-manage create_oauth2_tokenon the controller.With this change:
<id>. Users still create tokens for themselves; a superuser can create one for any user; anyone else gets403.400if the owner is inactive, or is a local user whileDISABLE_LOCAL_AUTHis on (turning that on revokes those users' tokens, so issuing new ones would undo it). TheALLOW_OAUTH2_FOR_EXTERNAL_USERScheck applies to the owner.GETlists<id>'s personal tokens, filtered by what the requester may read underOAuth2TokenAccess.actoris the requester,changes.useris the owner ("svc-ci (7)"), and the entry is linked to the owner so it shows in their activity stream./api/v2/users/1/personal_tokens/example indocs/auth/oauth.mdbecomes<user_id>, and both OAuth docs say who may create a token for whom.Design: superusers only, rather than anyone who may
changethe user.OAuth2TokenAccessalready limits reading and revoking personal tokens to the owner and superusers; letting org admins issue them would leave them unable to list or revoke what they issued. Widening that is one condition inOAuth2TokenAccess.can_add, plus the matching read/delete rules, if you want it.Compatibility: this changes what an existing endpoint returns. A superuser posting to another id now gets a token for that user, not themselves, and the response's
usershows which. A non-superuser posting to another visible id gets403instead of201; a superuser posting to a nonexistent id gets404instead of201. Because the sub-list permission check askscan_addabout the user in the URL, a non-superuser'sPOSTto another user'stokens/orauthorized_tokens/also gets403now; superusers there still get their own token./api/v2/tokens/, the UI andctrliq.ascenderare unaffected, and the OpenAPI schema is unchanged. Details and the before/after table are in the issue.related #1116
ISSUE TYPE
COMPONENT NAME
ASCENDER VERSION
ADDITIONAL INFORMATION
Tests, in
ascender/main/tests/functional/api/test_oauth.py:test_superuser_issues_personal_token_for_another_user:201, owner is the target, and the token authenticates as the target.test_personal_token_for_another_user_needs_superuser: a normal user, an org admin of the target's organization and a system auditor all get403, and no token is created.test_personal_token_for_missing_user(404),test_personal_token_for_another_external_user(403whenALLOW_OAUTH2_FOR_EXTERNAL_USERSis off),test_personal_token_for_another_inactive_user(400),test_personal_token_for_another_user_with_local_auth_disabled(400for a local user,201for an LDAP one).test_personal_token_for_self: unchanged for an admin and a normal user, through bothusers/<id>/personal_tokens/and/api/v2/tokens/.test_other_token_sub_urls_still_issue_to_the_requester:tokens/andauthorized_tokens/give an org admin403for a member and a superuser a token of their own.test_personal_token_list_is_the_url_users: the target's tokens for superusers, the owner and system auditors, an empty list for another user who can see the target,403for one who can't; application tokens excluded.test_personal_token_for_another_user_activity_stream:actor,changes.userand the owner link for a token issued to someone else, nothing extra for one the actor issues to themselves.Run locally in
python:3.14besidepostgres:15, as CI does: