If a viewset action is not implemented, the permission layer no longer returns
a 403. Instead, it lets DRF handle the request and return the appropriate 405
Method Not Allowed response, ensuring cleaner and more standard API error
handling.
Enhance scope manipulation by normalizing and sanitizing
scope values before processing.
Scopes are now converted to lowercase to ensure consistent behavior,
deduplicated while preserving their original order, and handled in a
deterministic way aligned with the intended authorization model.
The previous replace usage was too broad and could remove multiple
occurrences, which was not the original intention.
Replace the replace call with removeprefix, which more accurately
matches the expected behavior by only removing the prefix when present
at the start of the string.
Apply strict permission validation on the external API room endpoint to
enforce the principle of least privilege. Unlike the default API (which allows
unauthenticated room retrieval and filters access in the serializer), the
external API now only exposes rooms to users with explicit permissions.
This change fixes a security issue. Slug-based room retrieval, as supported
by the default API, is not introduced here but could be added later if needed.
Retrieving rooms by UUID is retained, as guessing a UUID is significantly harder
than a slug.
A dedicated permission class was created to avoid coupling permissions between
the default and external APIs. The external API enforces stricter access rules.
Access policies may be revisited based on user and integrator feedback. The
external API currently has no production usage.
When declaring scopes with our OIDC provider, they require us to prefix
each scope with our application name. This is to prevent reserving generic
scopes like rooms:list for only our app, as they manage a large federation.
I’m proposing a workaround where, if a resource server prefix is detected in
the scope, it’s stripped out. This solution is simple and sufficient
in my opinion.
Since the scopes are defined in the database, I don’t want to update
them directly. Additionally, each self-hosted instance may have a different
application name, so the prefix should be configurable via a Django setting.
Enforce the principle of least privilege by granting viewset permissions only
based on the scopes included in the token.
JWTs should never be issued without controlling which actions the application
is allowed to perform.
The first and minimal scope is to allow creating a room link. Additional actions
on the viewset will only be considered after this baseline scope is in place.