Skip to content

roles.has_permissions returns True for permissions the role does not hold #2184

Description

@jfrancoa

client.roles.has_permissions(...) returns True for permissions the role does not hold.

Cause

__has_permission in weaviate/rbac/executor.py checks only the status code:

def resp(res: Response) -> bool:
    return res.status_code == 200

POST /v1/authz/roles/{id}/has-permission answers 200 whenever the check runs. The answer is the boolean body ("type": "boolean" in the OpenAPI spec). The client ignores the body, so every permission on an existing role reads as held. A missing role (404) still returns False.

The code is the same on main (38f30c1) and in 4.23.1.

Reproduction

Client 4.23.1, server 1.38.19 with RBAC enabled:

import httpx
import weaviate
from weaviate.classes.init import Auth
from weaviate.classes.rbac import Permissions

client = weaviate.connect_to_local(port=8099, grpc_port=50099, auth_credentials=Auth.api_key("admin-key"))
client.roles.create(role_name="reader", permissions=Permissions.collections(collection="Books", read_config=True))

held = Permissions.collections(collection="Books", read_config=True)
not_held = Permissions.collections(collection="Books", delete_collection=True)
print(client.roles.has_permissions(permissions=held, role="reader"))
print(client.roles.has_permissions(permissions=not_held, role="reader"))

r = httpx.post(
    "http://localhost:8099/v1/authz/roles/reader/has-permission",
    headers={"Authorization": "Bearer admin-key"},
    json={"action": "delete_collections", "collections": {"collection": "Books"}},
)
print(r.status_code, r.text.strip())

Output:

True
True
200 false

The second line should be False. The server returns false for the same check.

Impact

Code that gates on has_permissions treats a missing permission as granted. Code that polls it to wait for a grant to propagate returns at once. The e2e suite in weaviate-e2e-tests has such a helper (wait_for_permission), and it has never waited.

The async client has the same bug, because both paths share __has_permission.

Suggested fix

Read the body on a 200:

def resp(res: Response) -> bool:
    return res.status_code == 200 and res.json() is True

integration/test_rbac.py only asserts that has_permissions is true for held permissions. A case that asserts False for a permission the role lacks would have caught this.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions