Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

646 lines
25 KiB
Go

package implementationhost
import (
"context"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"sync"
"testing"
"time"
agentruntime "github.com/tutti-os/tutti/packages/agent/daemon/runtime"
market "github.com/tutti-os/tutti/packages/connector/daemon/core"
connectorruntime "github.com/tutti-os/tutti/packages/connector/runtime"
)
func TestAttachCredentialBrokerPreservesEmptyNativeCLIArguments(t *testing.T) {
preparedPath := t.TempDir()
if err := os.WriteFile(filepath.Join(preparedPath, "credential-broker.mjs"), []byte("export {};\n"), 0o600); err != nil {
t.Fatal(err)
}
route := &connectorRoute{cliLaunch: &managedCLILaunch{
arguments: []string{}, cwd: preparedPath,
executable: connectorruntime.ConnectorExecutable{Path: "/managed/lark-cli"},
}}
err := (&Host{}).attachCredentialBroker(route, &market.ManagedCredentialBroker{
Entrypoint: "credential-broker.mjs", TimeoutMS: 300_000,
}, market.PreparedArtifactReceipt{PreparedPath: preparedPath}, connectorruntime.ConnectorExecutable{}, "", nil)
if err != nil {
t.Fatal(err)
}
payload, err := json.Marshal(route.credentialBrokerLaunch.cliLaunch)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(payload), `"arguments":[]`) {
t.Fatalf("credential broker CLI launch = %s, want empty JSON array", payload)
}
}
type credentialAuthorizationHostStub struct {
mu sync.Mutex
route *connectorRoute
connections []agentruntime.ProcessConnection
requests []credentialBrokerRequest
observed []market.AuthorizationState
}
func (stub *credentialAuthorizationHostStub) authorizationRoute(context.Context, market.OperationScope, market.Connector) (*connectorRoute, error) {
if stub.route == nil {
return nil, errors.New("route unavailable")
}
return stub.route, nil
}
func (stub *credentialAuthorizationHostStub) observeAuthorization(_ *connectorRoute, state market.AuthorizationState) {
stub.mu.Lock()
defer stub.mu.Unlock()
stub.observed = append(stub.observed, state)
}
func (stub *credentialAuthorizationHostStub) authorizationObservations() []market.AuthorizationState {
stub.mu.Lock()
defer stub.mu.Unlock()
return append([]market.AuthorizationState(nil), stub.observed...)
}
func (*credentialAuthorizationHostStub) releaseAuthorizationRoute(*connectorRoute) {}
func (stub *credentialAuthorizationHostStub) startCredentialBroker(
_ context.Context,
_ *connectorRoute,
request credentialBrokerRequest,
) (agentruntime.ProcessConnection, uint64, error) {
stub.requests = append(stub.requests, request)
if len(stub.connections) == 0 {
return nil, 0, errors.New("unexpected credential broker start")
}
connection := stub.connections[0]
stub.connections = stub.connections[1:]
return connection, uint64(len(stub.requests)), nil
}
type credentialBrokerConnection struct {
frames chan agentruntime.ProcessFrame
closed chan struct{}
closeOnce sync.Once
}
func newCredentialBrokerConnection() *credentialBrokerConnection {
return &credentialBrokerConnection{frames: make(chan agentruntime.ProcessFrame, 8), closed: make(chan struct{})}
}
func (*credentialBrokerConnection) Send([]byte) error { return nil }
func (*credentialBrokerConnection) CloseInput() error { return nil }
func (*credentialBrokerConnection) Terminate() error { return nil }
func (*credentialBrokerConnection) Kill() error { return nil }
func (connection *credentialBrokerConnection) Close() error {
connection.closeOnce.Do(func() {
close(connection.closed)
close(connection.frames)
})
return nil
}
func (connection *credentialBrokerConnection) Recv() (agentruntime.ProcessFrame, error) {
frame, ok := <-connection.frames
if !ok {
return agentruntime.ProcessFrame{}, io.EOF
}
return frame, nil
}
func (connection *credentialBrokerConnection) RecvContext(ctx context.Context) (agentruntime.ProcessFrame, error) {
select {
case <-ctx.Done():
return agentruntime.ProcessFrame{}, ctx.Err()
case frame, ok := <-connection.frames:
if !ok {
return agentruntime.ProcessFrame{}, io.EOF
}
return frame, nil
}
}
func TestManagedCredentialAuthorizationContinuesConnectorOwnedBroker(t *testing.T) {
connection := newCredentialBrokerConnection()
host := &credentialAuthorizationHostStub{
route: &connectorRoute{id: "default\x00lark-cli", credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute,
allowedHosts: map[string]struct{}{
"open.feishu.cn": {},
"accounts.feishu.cn": {},
},
}},
connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
request := market.AuthorizationStartRequest{OperationID: "authorize-1", Connector: market.Connector{Key: "lark-cli"}}
firstResult := make(chan market.AuthorizationSession, 1)
firstError := make(chan error, 1)
go func() {
session, err := provider.Begin(context.Background(), request)
firstResult <- session
firstError <- err
}()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://open.feishu.cn/page/cli?user_code=opaque"}` + "\n")}
if err := <-firstError; err != nil {
t.Fatal(err)
}
first := <-firstResult
if first.State != market.AuthorizationStatePending || first.AuthorizationURL != "https://open.feishu.cn/page/cli?user_code=opaque" {
t.Fatalf("first session = %#v", first)
}
if first.StepRevision != 1 {
t.Fatalf("first step revision = %d, want 1", first.StepRevision)
}
secondResult := make(chan market.AuthorizationSession, 1)
secondError := make(chan error, 1)
go func() {
continuedRequest := request
continuedRequest.AfterStepRevision = first.StepRevision
session, err := provider.Begin(context.Background(), continuedRequest)
secondResult <- session
secondError <- err
}()
select {
case result := <-secondResult:
t.Fatalf("continuation returned the current step before a later event: %#v", result)
case <-time.After(20 * time.Millisecond):
}
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://accounts.feishu.cn/device?user_code=user"}` + "\n")}
if err := <-secondError; err != nil {
t.Fatal(err)
}
second := <-secondResult
if second.State != market.AuthorizationStatePending {
t.Fatalf("second session = %#v", second)
}
if second.AuthorizationURL != "https://accounts.feishu.cn/device?user_code=user" || second.StepRevision != 2 {
t.Fatalf("second step = %#v", second)
}
exitCode := 0
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"connected"}` + "\n"), ExitCode: &exitCode}
terminalRequest := request
terminalRequest.AfterStepRevision = second.StepRevision
connected := awaitAuthorizationSession(t, provider, terminalRequest, "")
if connected.State != market.AuthorizationStateConnected {
t.Fatalf("connected session = %#v", connected)
}
if connected.StepRevision != 3 {
t.Fatalf("connected step revision = %d, want 3", connected.StepRevision)
}
if !reflect.DeepEqual(host.requests, []credentialBrokerRequest{{Protocol: market.CredentialBrokerProtocolV1, Operation: "begin"}}) {
t.Fatalf("broker requests = %#v", host.requests)
}
observed := awaitAuthorizationObservations(t, host, 3)
if !reflect.DeepEqual(observed, []market.AuthorizationState{market.AuthorizationStatePending, market.AuthorizationStatePending, market.AuthorizationStateConnected}) {
t.Fatalf("authorization observations = %#v", observed)
}
}
func TestManagedCredentialAuthorizationObservesActiveBrokerWithoutConcurrentInspection(t *testing.T) {
connection := newCredentialBrokerConnection()
route := &connectorRoute{
id: "account-1\x00lark-cli", connectorKey: "lark-cli", connectionID: "account-1",
releaseDigest: strings.Repeat("a", 64), credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute, allowedHosts: map[string]struct{}{"accounts.feishu.cn": {}},
},
}
host := &credentialAuthorizationHostStub{
route: route, connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
connector := market.Connector{Key: "lark-cli", Release: market.Release{ReleaseDigest: route.releaseDigest}}
request := market.AuthorizationStartRequest{
OperationID: "authorize-lark", Scope: market.OperationScope{AccountID: "user-1"}, Connector: connector,
}
beginResult := make(chan market.AuthorizationSession, 1)
beginError := make(chan error, 1)
go func() {
session, err := provider.Begin(context.Background(), request)
beginResult <- session
beginError <- err
}()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://accounts.feishu.cn/device"}` + "\n")}
if err := <-beginError; err != nil {
t.Fatal(err)
}
session := <-beginResult
observation, err := provider.Observe(context.Background(), market.AuthorizationObserveRequest{
Scope: request.Scope, Connector: connector, Release: connector.Release, Session: session,
})
if err != nil {
t.Fatal(err)
}
if observation.State != market.AuthorizationObservationPending || observation.ConnectionID != route.connectionID ||
observation.AuthorizationSessionID != session.SessionID {
t.Fatalf("active authorization observation = %#v", observation)
}
if !reflect.DeepEqual(host.requests, []credentialBrokerRequest{{Protocol: market.CredentialBrokerProtocolV1, Operation: "begin"}}) {
t.Fatalf("broker requests = %#v, want no concurrent inspection", host.requests)
}
if err := provider.Cancel(context.Background(), market.AuthorizationCancelRequest{OperationID: request.OperationID}); err != nil {
t.Fatal(err)
}
}
func TestManagedCredentialAuthorizationObservesPersistedSessionWithInspection(t *testing.T) {
exitCode := 0
connection := newCredentialBrokerConnection()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"disconnected"}` + "\n"), ExitCode: &exitCode}
route := &connectorRoute{
id: "account-1\x00lark-cli", connectorKey: "lark-cli", connectionID: "account-1",
releaseDigest: strings.Repeat("a", 64), credentialBrokerLaunch: &managedCredentialBrokerLaunch{timeout: 5 * time.Minute},
}
host := &credentialAuthorizationHostStub{
route: route, connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
release := market.Release{ReleaseDigest: route.releaseDigest}
observation, err := provider.Observe(context.Background(), market.AuthorizationObserveRequest{
Scope: market.OperationScope{AccountID: "user-1"}, Connector: market.Connector{Key: "lark-cli"}, Release: release,
Session: market.AuthorizationSession{OperationID: "persisted", SessionID: "persisted/credential-broker"},
})
if err != nil {
t.Fatal(err)
}
if observation.State != market.AuthorizationObservationPending {
t.Fatalf("persisted authorization observation = %#v", observation)
}
if !reflect.DeepEqual(host.requests, []credentialBrokerRequest{{Protocol: market.CredentialBrokerProtocolV1, Operation: "inspect"}}) {
t.Fatalf("broker requests = %#v, want durable inspection", host.requests)
}
}
func TestManagedCredentialAuthorizationReturnsDeviceCodeFromBroker(t *testing.T) {
connection := newCredentialBrokerConnection()
host := &credentialAuthorizationHostStub{
route: &connectorRoute{id: "default\x00github-cli", credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute, allowedHosts: map[string]struct{}{"github.com": {}},
}},
connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
result := make(chan market.AuthorizationSession, 1)
resultErr := make(chan error, 1)
go func() {
session, err := provider.Begin(context.Background(), market.AuthorizationStartRequest{
OperationID: "authorize-github", Connector: market.Connector{Key: "github-cli"},
AfterStepRevision: 4, StepRevisionBase: 4,
})
result <- session
resultErr <- err
}()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://github.com/login/device","code":"ABCD-EFGH"}` + "\n")}
if err := <-resultErr; err != nil {
t.Fatal(err)
}
session := <-result
if session.AuthorizationURL != "https://github.com/login/device" || session.UserCode != "ABCD-EFGH" || session.StepRevision != 5 {
t.Fatalf("authorization session = %#v", session)
}
}
func TestManagedCredentialAuthorizationDisconnectUsesBrokerProtocol(t *testing.T) {
exitCode := 0
connection := newCredentialBrokerConnection()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"disconnected"}` + "\n"), ExitCode: &exitCode}
host := &credentialAuthorizationHostStub{
route: &connectorRoute{id: "default\x00lark-cli", credentialBrokerLaunch: &managedCredentialBrokerLaunch{timeout: 5 * time.Minute}},
connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
err := provider.Disconnect(context.Background(), market.AuthorizationDisconnectRequest{Connector: market.Connector{Key: "lark-cli"}})
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(host.requests, []credentialBrokerRequest{{Protocol: market.CredentialBrokerProtocolV1, Operation: "disconnect"}}) {
t.Fatalf("broker requests = %#v", host.requests)
}
observed := awaitAuthorizationObservations(t, host, 1)
if !reflect.DeepEqual(observed, []market.AuthorizationState{market.AuthorizationStateDisconnected}) {
t.Fatalf("authorization observations = %#v", observed)
}
}
func TestManagedCredentialAuthorizationInspectReturnsFencedObservation(t *testing.T) {
exitCode := 0
connection := newCredentialBrokerConnection()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"expired","code":"token_expired","message":"login expired"}` + "\n"), ExitCode: &exitCode}
host := &credentialAuthorizationHostStub{
route: &connectorRoute{id: "account-1\x00lark-cli", connectorKey: "lark-cli", connectionID: "account-1",
releaseDigest: strings.Repeat("a", 64), credentialBrokerLaunch: &managedCredentialBrokerLaunch{timeout: 5 * time.Minute}},
connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
connector := market.Connector{Key: "lark-cli", Release: market.Release{ReleaseDigest: strings.Repeat("a", 64)}}
observation, err := provider.Inspect(context.Background(), market.AuthorizationInspectRequest{
Scope: market.OperationScope{AccountID: "user-1"}, Connector: connector,
AccountGeneration: 3, VMAssignmentID: "vm-1", AuthorizationSessionID: "auth-1",
AuthorizationGeneration: 4, DesktopBootEpoch: "desktop-1", GuestBootID: "guest-1",
RuntimeEpoch: "runtime-1", StateRevision: 9,
})
if err != nil {
t.Fatal(err)
}
if observation.State != market.AuthorizationObservationExpired || observation.FailureCode != "token_expired" ||
observation.AccountID != "user-1" || observation.AccountGeneration != 3 || observation.VMAssignmentID != "vm-1" ||
observation.ConnectorKey != "lark-cli" || observation.ConnectionID != "account-1" ||
observation.ReleaseDigest != strings.Repeat("a", 64) || observation.StateRevision != 9 || observation.ObservedAt.IsZero() {
t.Fatalf("observation = %#v", observation)
}
if !reflect.DeepEqual(host.requests, []credentialBrokerRequest{{Protocol: market.CredentialBrokerProtocolV1, Operation: "inspect"}}) {
t.Fatalf("broker requests = %#v", host.requests)
}
}
func TestInstallationTargetsReleaseAcceptsOnlyActiveCurrentOrCandidate(t *testing.T) {
tests := []struct {
name string
installation market.Installation
releaseDigest string
want bool
}{
{
name: "installed current release",
installation: market.Installation{
State: market.InstallationStateInstalled, InstalledReleaseDigest: "current",
},
releaseDigest: "current",
want: true,
},
{
name: "installing candidate release",
installation: market.Installation{
State: market.InstallationStateInstalling, CandidateReleaseDigest: "candidate",
},
releaseDigest: "candidate",
want: true,
},
{
name: "updating candidate release",
installation: market.Installation{
State: market.InstallationStateUpdating, InstalledReleaseDigest: "current", CandidateReleaseDigest: "candidate",
},
releaseDigest: "candidate",
want: true,
},
{
name: "updating superseded current release",
installation: market.Installation{
State: market.InstallationStateUpdating, InstalledReleaseDigest: "current", CandidateReleaseDigest: "candidate",
},
releaseDigest: "current",
},
{
name: "failed candidate release",
installation: market.Installation{
State: market.InstallationStateFailed, CandidateReleaseDigest: "candidate",
},
releaseDigest: "candidate",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := installationTargetsRelease(test.installation, test.releaseDigest); got != test.want {
t.Fatalf("installationTargetsRelease() = %v, want %v", got, test.want)
}
})
}
}
func awaitAuthorizationObservations(t *testing.T, host *credentialAuthorizationHostStub, count int) []market.AuthorizationState {
t.Helper()
deadline := time.Now().Add(time.Second)
for {
observed := host.authorizationObservations()
if len(observed) >= count {
return observed
}
if time.Now().After(deadline) {
t.Fatalf("authorization observations = %#v, want at least %d", observed, count)
}
time.Sleep(time.Millisecond)
}
}
func TestManagedCredentialAuthorizationSharesOneBrokerAcrossConcurrentBegins(t *testing.T) {
connection := newCredentialBrokerConnection()
host := &credentialAuthorizationHostStub{
route: &connectorRoute{id: "default\x00example", credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute, allowedHosts: map[string]struct{}{"accounts.example.com": {}},
}},
connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
results := make(chan market.AuthorizationSession, 2)
errors := make(chan error, 2)
for index := 0; index < 2; index++ {
go func() {
session, err := provider.Begin(context.Background(), market.AuthorizationStartRequest{
OperationID: "authorize", Connector: market.Connector{Key: "example"},
})
results <- session
errors <- err
}()
}
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://accounts.example.com/device"}` + "\n")}
for index := 0; index < 2; index++ {
if err := <-errors; err != nil {
t.Fatal(err)
}
if result := <-results; result.AuthorizationURL != "https://accounts.example.com/device" {
t.Fatalf("authorization session = %#v", result)
}
}
if len(host.requests) != 1 {
t.Fatalf("credential broker starts = %d, want 1", len(host.requests))
}
}
func TestManagedCredentialAuthorizationRestartsFailedBrokerOnFirstRetry(t *testing.T) {
failedConnection := newCredentialBrokerConnection()
retryConnection := newCredentialBrokerConnection()
route := &connectorRoute{id: "default\x00example", credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute, allowedHosts: map[string]struct{}{"accounts.example.com": {}},
}}
host := &credentialAuthorizationHostStub{
route: route, connections: []agentruntime.ProcessConnection{failedConnection, retryConnection},
}
provider := newManagedCredentialAuthorizationProvider(host)
firstRequest := market.AuthorizationStartRequest{OperationID: "authorize-first", Connector: market.Connector{Key: "example"}}
firstResult := make(chan market.AuthorizationSession, 1)
firstError := make(chan error, 1)
go func() {
session, err := provider.Begin(context.Background(), firstRequest)
firstResult <- session
firstError <- err
}()
failedConnection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://accounts.example.com/first"}` + "\n")}
if err := <-firstError; err != nil {
t.Fatal(err)
}
if result := <-firstResult; result.AuthorizationURL != "https://accounts.example.com/first" {
t.Fatalf("first authorization session = %#v", result)
}
exitCode := 1
failedConnection.frames <- agentruntime.ProcessFrame{Stderr: []byte("broker failed"), ExitCode: &exitCode}
awaitCachedAuthorizationFailure(t, provider, firstRequest.OperationID)
retryConnection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://accounts.example.com/retry"}` + "\n")}
retry, err := provider.Begin(context.Background(), market.AuthorizationStartRequest{
OperationID: "authorize-retry", Connector: market.Connector{Key: "example"},
})
if err != nil {
t.Fatal(err)
}
if retry.AuthorizationURL != "https://accounts.example.com/retry" {
t.Fatalf("retry authorization session = %#v", retry)
}
if len(host.requests) != 2 {
t.Fatalf("credential broker starts = %d, want 2", len(host.requests))
}
}
func TestManagedCredentialAuthorizationCancelTerminatesAndWaitsForBrokerExit(t *testing.T) {
connection := newCredentialBrokerConnection()
route := &connectorRoute{id: "default\x00dingtalk-cli", credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute, allowedHosts: map[string]struct{}{"login.dingtalk.com": {}},
}}
host := &credentialAuthorizationHostStub{route: route, connections: []agentruntime.ProcessConnection{connection}}
provider := newManagedCredentialAuthorizationProvider(host)
request := market.AuthorizationStartRequest{OperationID: "authorize-a", Connector: market.Connector{Key: "dingtalk-cli"}}
beginDone := make(chan error, 1)
go func() {
_, err := provider.Begin(context.Background(), request)
beginDone <- err
}()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://login.dingtalk.com/oauth"}` + "\n")}
if err := <-beginDone; err != nil {
t.Fatal(err)
}
cancelDone := make(chan error, 1)
go func() {
cancelDone <- provider.Cancel(context.Background(), market.AuthorizationCancelRequest{OperationID: request.OperationID})
}()
select {
case <-connection.closed:
case <-time.After(time.Second):
t.Fatal("cancel did not close the credential broker connection")
}
select {
case err := <-cancelDone:
if err != nil {
t.Fatal(err)
}
case <-time.After(time.Second):
t.Fatal("cancel did not wait for credential broker termination")
}
provider.mu.Lock()
if provider.sessions[request.OperationID] != nil || provider.activeByRoute[route.id] != "" {
t.Fatalf("canceled session remained active: sessions=%#v routes=%#v", provider.sessions, provider.activeByRoute)
}
provider.mu.Unlock()
if observed := host.authorizationObservations(); !reflect.DeepEqual(observed, []market.AuthorizationState{market.AuthorizationStatePending}) {
t.Fatalf("authorization observations after cancel = %#v", observed)
}
}
func awaitCachedAuthorizationFailure(t *testing.T, provider *managedCredentialAuthorizationProvider, operationID string) {
t.Helper()
deadline := time.Now().Add(time.Second)
for {
provider.mu.Lock()
session := provider.sessions[operationID]
provider.mu.Unlock()
if session != nil {
_, _, _, _, err := session.snapshot()
if err != nil {
return
}
}
if time.Now().After(deadline) {
t.Fatal("credential broker session did not fail")
}
time.Sleep(time.Millisecond)
}
}
func TestManagedCredentialAuthorizationRejectsUntrustedURL(t *testing.T) {
connection := newCredentialBrokerConnection()
host := &credentialAuthorizationHostStub{
route: &connectorRoute{id: "default\x00example", credentialBrokerLaunch: &managedCredentialBrokerLaunch{
timeout: 5 * time.Minute, allowedHosts: map[string]struct{}{"accounts.example.com": {}},
}},
connections: []agentruntime.ProcessConnection{connection},
}
provider := newManagedCredentialAuthorizationProvider(host)
result := make(chan error, 1)
go func() {
_, err := provider.Begin(context.Background(), market.AuthorizationStartRequest{
OperationID: "authorize-unsafe", Connector: market.Connector{Key: "example"},
})
result <- err
}()
connection.frames <- agentruntime.ProcessFrame{Stdout: []byte(`{"type":"authorization_url","url":"https://accounts.example.com.attacker.test/login"}` + "\n")}
if err := <-result; err == nil {
t.Fatal("untrusted authorization URL was accepted")
}
}
func awaitAuthorizationSession(
t *testing.T,
provider *managedCredentialAuthorizationProvider,
request market.AuthorizationStartRequest,
wantedURL string,
) market.AuthorizationSession {
t.Helper()
deadline := time.Now().Add(time.Second)
for {
session, err := provider.Begin(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if session.AuthorizationURL == wantedURL &&
(wantedURL != "" || session.State == market.AuthorizationStateConnected) {
return session
}
if time.Now().After(deadline) {
t.Fatalf("authorization session did not reach URL %q: %#v", wantedURL, session)
}
time.Sleep(time.Millisecond)
}
}
func TestSafeCredentialBrokerURLRequiresExactHTTPSHost(t *testing.T) {
allowed := map[string]struct{}{"accounts.feishu.cn": {}}
if !safeCredentialBrokerURL("https://accounts.feishu.cn/device", allowed) {
t.Fatal("allowed credential broker URL was rejected")
}
for _, value := range []string{
"http://accounts.feishu.cn/device",
"https://accounts.feishu.cn.attacker.test/device",
"https://user@accounts.feishu.cn/device",
"https://accounts.feishu.cn:444/device",
} {
if safeCredentialBrokerURL(value, allowed) {
t.Fatalf("unsafe credential broker URL was accepted: %s", value)
}
}
}