mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 18:33:15 +00:00
* feat: open Slack-triggered PRs as the triggering user Route the Slack per-user GitHub token through the dashboard OAuth store (the backend the self-service link prompt populates) and block runs that lack a valid user token, prompting the user to (re-)link. Per-user OAuth now wins over bot-token-only mode for mapped Slack/dashboard users. Flip commit/PR authorship across all sources: the triggering user is the commit author (via repo-local git identity using their resolvable GitHub noreply email) and open-swe[bot] is the Co-authored-by collaborator. * fix: address PR review — shell-escape commit identity, fix token cache impersonation - Shell-escape the triggering user's name/email with shlex.quote before embedding them in the repo-setup `git config` command, so a name like O'Connor (or a crafted one) can't break or inject into the command. - Stop consulting the shared thread-metadata token cache in _resolve_dashboard_user_token. Slack thread ids are shared across the conversation, so a cached token from a prior triggering user could be returned for the current github_login. Always resolve by login from the dashboard OAuth store instead. * feat: dashboard self-service user mapping + UI cleanup - Add session-scoped GET/PUT /dashboard/api/my-mapping so users can set their own work email / Slack member ID (keyed by their GitHub login, source=self). - Slack account-link prompt now redirects to Profile Settings after auth. - Rename "My Settings" -> "Profile Settings" and "Cloud Agents" -> "Open SWE Agent"; remove the Integrations tab/section (folded out, low value for now) and redirect /integrations to Profile Settings. - Add a "User mapping" section to Profile Settings (work email used by Slack and Linear, optional Slack member ID). - Make dashboard auth cookies scheme-aware: Secure;SameSite=None over HTTPS, non-Secure;SameSite=Lax over http://localhost so local login works. * feat: self-service Slack account linking via Sign in with Slack (OIDC) Replace the spoofable manual work-email/Slack-ID form with a verified "Sign in with Slack" flow so a logged-in GitHub user can only ever link their own Slack identity. - New agent/dashboard/slack_oauth.py: OIDC authorize URL, code exchange, userInfo identity parse, optional workspace gate, configured check. - routes.py: session-gated GET /slack/login and /slack/callback that upsert the mapping from Slack-verified user_id + email (source=slack_oauth). Remove the spoofable PUT /my-mapping; expose slack_oauth_enabled on /me. - UI: drop the editable inputs; add a Connect Slack button + status to the User mapping section. Admin-managed mappings are unaffected and still resolve at trigger time.
86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from agent.dashboard import slack_oauth
|
|
|
|
|
|
def test_slack_oauth_configured(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(slack_oauth, "SLACK_CLIENT_ID", "cid")
|
|
monkeypatch.setattr(slack_oauth, "SLACK_CLIENT_SECRET", "secret")
|
|
assert slack_oauth.slack_oauth_configured() is True
|
|
monkeypatch.setattr(slack_oauth, "SLACK_CLIENT_SECRET", "")
|
|
assert slack_oauth.slack_oauth_configured() is False
|
|
|
|
|
|
def test_build_authorize_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(slack_oauth, "SLACK_CLIENT_ID", "cid")
|
|
monkeypatch.setattr(slack_oauth, "SLACK_TEAM_ID", "")
|
|
url = slack_oauth.build_authorize_url(
|
|
redirect_uri="http://localhost:2024/dashboard/api/slack/callback", state="ST8"
|
|
)
|
|
parsed = urlparse(url)
|
|
q = parse_qs(parsed.query)
|
|
assert parsed.netloc == "slack.com"
|
|
assert q["response_type"] == ["code"]
|
|
assert q["scope"] == ["openid email profile"]
|
|
assert q["client_id"] == ["cid"]
|
|
assert q["redirect_uri"] == ["http://localhost:2024/dashboard/api/slack/callback"]
|
|
assert q["state"] == ["ST8"]
|
|
assert "team" not in q
|
|
|
|
|
|
def test_build_authorize_url_includes_team_when_configured(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(slack_oauth, "SLACK_CLIENT_ID", "cid")
|
|
monkeypatch.setattr(slack_oauth, "SLACK_TEAM_ID", "T123")
|
|
url = slack_oauth.build_authorize_url(redirect_uri="https://x/cb", state="S")
|
|
assert parse_qs(urlparse(url).query)["team"] == ["T123"]
|
|
|
|
|
|
def test_parse_slack_identity_success() -> None:
|
|
identity = slack_oauth.parse_slack_identity(
|
|
{
|
|
"ok": True,
|
|
"https://slack.com/user_id": "U999",
|
|
"https://slack.com/team_id": "T123",
|
|
"email": "dev@example.com",
|
|
"email_verified": True,
|
|
"name": "Dev",
|
|
}
|
|
)
|
|
assert identity.user_id == "U999"
|
|
assert identity.team_id == "T123"
|
|
assert identity.email == "dev@example.com"
|
|
assert identity.email_verified is True
|
|
assert identity.name == "Dev"
|
|
|
|
|
|
def test_parse_slack_identity_missing_user_id() -> None:
|
|
with pytest.raises(HTTPException):
|
|
slack_oauth.parse_slack_identity({"ok": True, "email": "x@y.com"})
|
|
|
|
|
|
def test_parse_slack_identity_not_ok() -> None:
|
|
with pytest.raises(HTTPException):
|
|
slack_oauth.parse_slack_identity({"ok": False, "error": "bad"})
|
|
|
|
|
|
def test_verify_team(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
ident = slack_oauth.SlackIdentity(
|
|
user_id="U1", team_id="T1", email="a@b.com", email_verified=True, name=None
|
|
)
|
|
# No workspace restriction configured → always allowed.
|
|
monkeypatch.setattr(slack_oauth, "SLACK_TEAM_ID", "")
|
|
slack_oauth.verify_team(ident)
|
|
# Matching workspace → allowed.
|
|
monkeypatch.setattr(slack_oauth, "SLACK_TEAM_ID", "T1")
|
|
slack_oauth.verify_team(ident)
|
|
# Different workspace → rejected.
|
|
monkeypatch.setattr(slack_oauth, "SLACK_TEAM_ID", "T2")
|
|
with pytest.raises(HTTPException):
|
|
slack_oauth.verify_team(ident)
|