mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 13:38:55 +08:00
fix(jira-dc): don't org-gate a personal-workspace Jira DC integration (#15036)
This commit is contained in:
@@ -90,6 +90,12 @@ async def _workspace_matches_context(
|
||||
workspace_org_id = getattr(workspace, 'org_id', None)
|
||||
if workspace_org_id is None:
|
||||
return True
|
||||
# A workspace stamped with its creator's *personal* org (org_id == the
|
||||
# admin's own user id) is an instance-wide integration, not a tenant
|
||||
# boundary, so don't org-gate it. Real team/default orgs keep enforcing.
|
||||
admin_user_id = getattr(workspace, 'admin_user_id', None)
|
||||
if admin_user_id is not None and str(workspace_org_id) == str(admin_user_id):
|
||||
return True
|
||||
return await _effective_org_matches(
|
||||
workspace_id=workspace.id,
|
||||
workspace_org_id=workspace_org_id,
|
||||
|
||||
+112
-1
@@ -32,7 +32,7 @@ class FakeUserContext:
|
||||
return self.user_id
|
||||
|
||||
|
||||
def _linked_store(*, org_id: UUID | None = ORG_ID):
|
||||
def _linked_store(*, org_id: UUID | None = ORG_ID, admin_user_id: str = 'admin-user'):
|
||||
store = MagicMock()
|
||||
store.get_user_by_active_workspace = AsyncMock(
|
||||
return_value=SimpleNamespace(jira_dc_workspace_id=7)
|
||||
@@ -43,6 +43,7 @@ def _linked_store(*, org_id: UUID | None = ORG_ID):
|
||||
name='jira.example.com',
|
||||
status='active',
|
||||
org_id=org_id,
|
||||
admin_user_id=admin_user_id,
|
||||
)
|
||||
)
|
||||
return store
|
||||
@@ -197,3 +198,113 @@ async def test_enricher_propagates_token_error_for_jira_triggered_start():
|
||||
jwt_service=MagicMock(),
|
||||
access_token_hard_timeout=timedelta(minutes=5),
|
||||
)
|
||||
|
||||
|
||||
# A personal org's id == its owner's user id, so a JDC workspace created in
|
||||
# personal-workspace mode is stamped org_id == admin_user_id.
|
||||
PERSONAL_ORG_ID = UUID('00000000-0000-0000-0000-000000000789')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enricher_allows_personal_workspace_org_for_non_creator():
|
||||
# Personal-workspace install: the JDC workspace (one company's Jira) is
|
||||
# stamped with its creator's personal org. A *different* user must still get
|
||||
# their own token -- it's an instance-wide integration, not a tenant boundary.
|
||||
store = _linked_store(org_id=PERSONAL_ORG_ID, admin_user_id=str(PERSONAL_ORG_ID))
|
||||
jwt_service = MagicMock()
|
||||
jwt_service.create_jws_token.return_value = 'signed-token'
|
||||
|
||||
with (
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JIRA_DC_ENABLE_OAUTH',
|
||||
True,
|
||||
),
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JIRA_DC_BASE_URL',
|
||||
'https://jira.example.com',
|
||||
),
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JiraDcIntegrationStore.get_instance',
|
||||
return_value=store,
|
||||
),
|
||||
):
|
||||
enrichment = await JiraDcConversationSecretEnricher().enrich(
|
||||
user_context=FakeUserContext(user_id='other-user', org_id=ORG_ID),
|
||||
user=MagicMock(id='other-user'),
|
||||
trigger=ConversationTrigger.SLACK,
|
||||
system_message_suffix=None,
|
||||
web_url='https://openhands.example.com',
|
||||
jwt_service=jwt_service,
|
||||
access_token_hard_timeout=timedelta(minutes=5),
|
||||
)
|
||||
|
||||
assert 'JIRA_DC_TOKEN' in enrichment.secrets
|
||||
assert 'JIRA_DC_TOKEN' in (enrichment.system_message_suffix or '')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enricher_still_blocks_real_cross_org_workspace():
|
||||
# A genuine team/default org (org_id != admin_user_id) keeps enforcing
|
||||
# isolation: a user from a different org gets no token.
|
||||
store = _linked_store(org_id=OTHER_ORG_ID, admin_user_id='admin-user')
|
||||
|
||||
with (
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JIRA_DC_ENABLE_OAUTH',
|
||||
True,
|
||||
),
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JIRA_DC_BASE_URL',
|
||||
'https://jira.example.com',
|
||||
),
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JiraDcIntegrationStore.get_instance',
|
||||
return_value=store,
|
||||
),
|
||||
):
|
||||
enrichment = await JiraDcConversationSecretEnricher().enrich(
|
||||
user_context=FakeUserContext(org_id=ORG_ID),
|
||||
user=MagicMock(id='kc-user'),
|
||||
trigger=ConversationTrigger.SLACK,
|
||||
system_message_suffix='Existing instructions.',
|
||||
web_url='https://openhands.example.com',
|
||||
jwt_service=MagicMock(),
|
||||
access_token_hard_timeout=timedelta(minutes=5),
|
||||
)
|
||||
|
||||
assert enrichment.secrets == {}
|
||||
assert enrichment.system_message_suffix == 'Existing instructions.'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enricher_injects_for_null_org_workspace():
|
||||
# An unscoped workspace (org_id is None) is instance-wide -- regression guard.
|
||||
store = _linked_store(org_id=None)
|
||||
jwt_service = MagicMock()
|
||||
jwt_service.create_jws_token.return_value = 'signed-token'
|
||||
|
||||
with (
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JIRA_DC_ENABLE_OAUTH',
|
||||
True,
|
||||
),
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JIRA_DC_BASE_URL',
|
||||
'https://jira.example.com',
|
||||
),
|
||||
patch(
|
||||
'integrations.jira_dc.jira_dc_conversation_secret_enricher.JiraDcIntegrationStore.get_instance',
|
||||
return_value=store,
|
||||
),
|
||||
):
|
||||
enrichment = await JiraDcConversationSecretEnricher().enrich(
|
||||
user_context=FakeUserContext(user_id='anyone', org_id=None),
|
||||
user=MagicMock(id='anyone'),
|
||||
trigger=ConversationTrigger.SLACK,
|
||||
system_message_suffix=None,
|
||||
web_url='https://openhands.example.com',
|
||||
jwt_service=jwt_service,
|
||||
access_token_hard_timeout=timedelta(minutes=5),
|
||||
)
|
||||
|
||||
assert 'JIRA_DC_TOKEN' in enrichment.secrets
|
||||
|
||||
Reference in New Issue
Block a user