mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-10-23 20:02:57 -05:00
## Description Give each user and scheduled task its own action settings. Workspace-disabled actions always remain blocked. Admins can require workspace settings or let users choose their own settings. Scheduled tasks now show app and MCP cards. Apps start disabled. When creating a task, enabling an app copies the user's current settings. Existing tasks keep their saved choices when apps are enabled again; missing choices require approval. Later requests use the task's saved settings and current workspace restrictions. One migration replaces app-wide task pre-approvals with per-action settings. Existing tasks receive auto-approval only for known actions in apps they already pre-approved. Apps remain enabled even if they have no known actions. New actions require approval. The migration remains with the code that reads the new tables. Action recognition identifies the request. One policy resolver then applies workspace restrictions, task membership, personal or task settings, and session grants. Failed recognition or policy lookup blocks the request. A completed scheduled session cannot use personal settings for later requests. The next PR adds app settings editors. This PR includes the task editor because its payload changes with the task API. ## How Has This Been Tested? The complete stack passed 71 local Kind scenario checks, covering browser editing, persistence, access restrictions, real proxy requests, and scheduled execution. Checks include a real 180-second approval timeout, live policy changes, and completed-task request rejection. This PR’s intermediate tree passed 1,053 backend unit tests, 148 database tests, five route-contract tests, and 39 frontend tests. Python and TypeScript checks and changed-file pre-commit checks passed. No migration was applied to the user's database during this verification. ## Additional Options - [ ] [Optional] Please cherry-pick this PR to the latest release version. - [x] [Optional] Override Linear Check <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added per-action approval settings for connected apps and MCP tools, with workspace, personal, and scheduled-task controls. * Scheduled tasks can enable apps and configure individual actions when creating or editing tasks; settings are also visible on task details. New tasks start with apps disabled. * Policy editors support searching, bulk changes, and resetting editable choices. * **Security & Permissions** * Workspace restrictions remain binding, and task settings are checked during active runs. * Actions requiring approval still prompt users when app credentials are unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
49 lines
1.9 KiB
Python
49 lines
1.9 KiB
Python
"""Recognition uses provider catalogs without loading permission or credential state."""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from onyx.db.enums import ActionPolicy, ExternalAppType, GatedAppKind
|
|
from onyx.db.models import ExternalApp
|
|
from onyx.external_apps.matching.engine import (
|
|
WHOLE_DOMAIN_ACTION_TYPE,
|
|
AllMatchedActions,
|
|
GatedTarget,
|
|
recognize_actions,
|
|
)
|
|
from onyx.external_apps.matching.request import ProxiedRequest
|
|
|
|
|
|
def test_rejects_empty_actions() -> None:
|
|
with pytest.raises(ValidationError, match="actions must be non-empty"):
|
|
AllMatchedActions(
|
|
actions=(),
|
|
target=GatedTarget(kind=GatedAppKind.EXTERNAL_APP, id=1, app_name="X"),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("path", "action_id", "default"),
|
|
[
|
|
("/api/conversations.list", "slack.channels.read", ActionPolicy.ALWAYS),
|
|
("/api/chat.postMessage", "slack.messages.write", ActionPolicy.ASK),
|
|
("/api/unknown", WHOLE_DOMAIN_ACTION_TYPE, ActionPolicy.ASK),
|
|
],
|
|
)
|
|
def test_recognition_preserves_action_identity_and_catalog_default(
|
|
path: str, action_id: str, default: ActionPolicy
|
|
) -> None:
|
|
app = ExternalApp(id=1, name="Slack", app_type=ExternalAppType.SLACK)
|
|
matched = recognize_actions(app, ProxiedRequest(method="POST", path=path))
|
|
assert matched.target.key == (GatedAppKind.EXTERNAL_APP, app.id)
|
|
assert matched.governing_action.action_type == action_id
|
|
assert matched.governing_action.policy == default
|
|
|
|
|
|
def test_custom_app_unknown_action_preserves_display_name() -> None:
|
|
app = ExternalApp(id=2, name="Customer CRM", app_type=ExternalAppType.CUSTOM)
|
|
matched = recognize_actions(app, ProxiedRequest(method="POST", path="/anything"))
|
|
assert matched.app_name == "Customer CRM"
|
|
assert matched.governing_action.action_type == WHOLE_DOMAIN_ACTION_TYPE
|
|
assert matched.governing_action.policy == ActionPolicy.ASK
|