Skip to content

Enforce user ACL on datapoint write for authenticated non-admin users#55

Open
Micsi wants to merge 1 commit into
mainfrom
codex/propose-fix-for-user-page-acl-bypass
Open

Enforce user ACL on datapoint write for authenticated non-admin users#55
Micsi wants to merge 1 commit into
mainfrom
codex/propose-fix-for-user-page-acl-bypass

Conversation

@Micsi
Copy link
Copy Markdown
Owner

@Micsi Micsi commented May 18, 2026

Motivation

  • The datapoint write endpoint allowed an authenticated non-admin to omit the client-controlled X-Page-Id header and thereby skip the per-page user ACL, enabling unauthorized writes to datapoints on pages where the user is not assigned.
  • The change closes this authorization gap while preserving the intended model where admins remain able to write and assigned users can write via a page context.

Description

  • Added an admin lookup in the write handler to detect whether the authenticated user is an admin via await db.fetchone("SELECT is_admin FROM users WHERE username = ?", (user,)) and treat admins as allowed to write without a page header.
  • For non-admin authenticated users, require a X-Page-Id header and enforce the page ACL by calling await _resolve_page_access(...) and await _check_user_access(...) when the page access resolves to user.
  • Preserved existing behavior for unauthenticated requests and for public/protected/readonly page types, and kept changes localized to obs/api/v1/datapoints.py only.

Testing

  • Compiled the modified file with python -m compileall obs/api/v1/datapoints.py, which succeeded.
  • Attempted to run pytest -q tests/integration/test_datapoints.py::test_write_value_and_read_back but the test run failed in this environment due to a missing test dependency (pytest_asyncio).
  • No integration test failures caused by the change were observed in this environment beyond the dependency issue preventing full test execution.

Codex Task

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

if not await _check_user_access(db, page_id, user):

P1 Badge Resolve API key owner before checking page user ACL

When access == "user", this check passes user straight into _check_user_access, but under API-key auth user is the key's display name (see obs/api/auth.py:get_current_user) rather than the owner username in api_keys.owner. That makes legitimate API-key callers fail authorization on assigned user pages unless the key name exactly matches the username, introducing a 403 regression for non-admin automation clients after this ACL change.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread obs/api/v1/datapoints.py
Comment on lines +275 to +276
user_row = await db.fetchone("SELECT is_admin FROM users WHERE username = ?", (user,))
is_admin = bool(user_row and user_row["is_admin"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive admin status from API key owner

This admin check uses username = user, but in API-key authentication user is the API key's name (from get_current_user), not the key owner username stored in api_keys.owner (create_api_key). As a result, admin-owned API keys whose display name differs from the admin username are misclassified as non-admin and now get 401 when X-Page-Id is omitted, which is a regression from prior authenticated-write behavior and breaks headless integrations that use API keys.

Useful? React with 👍 / 👎.

Comment thread obs/api/v1/datapoints.py
Comment on lines +275 to +276
user_row = await db.fetchone("SELECT is_admin FROM users WHERE username = ?", (user,))
is_admin = bool(user_row and user_row["is_admin"])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use immutable principal for admin authorization

This branch treats any authenticated identity string that matches a users.username row as admin, but for API-key auth the identity is caller-controlled key metadata (api_keys.name), not a verified username. Because /api/v1/auth/apikeys lets non-admin users create keys with arbitrary names, naming a key after an admin account causes is_admin to resolve true here and bypasses the new non-admin X-Page-Id requirement, so the intended ACL hardening can be bypassed.

Useful? React with 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant