Files
kandev/apps/backend/internal/github/mock_auth.go
T

193 lines
5.4 KiB
Go

package github
import (
"context"
"fmt"
"strings"
"sync"
)
// MockAuthState supplies workspace-scoped principals while delegating API
// behavior to the existing shared MockClient data set.
type MockAuthState struct {
mu sync.RWMutex
client *MockClient
capabilities map[string]map[GitHubAppCapability]bool
cliAccounts []GHAccount
}
func NewMockAuthState(client *MockClient) *MockAuthState {
return &MockAuthState{client: client, capabilities: make(map[string]map[GitHubAppCapability]bool)}
}
func (m *MockAuthState) ResolveAutomation(
_ context.Context,
connection *WorkspaceConnection,
_ ResolveCredentialRequest,
) (*ResolvedCredential, error) {
if m == nil || m.client == nil || connection == nil {
return nil, ErrGitHubNotConfigured
}
login := connection.Login
kind := AuthPrincipalHuman
installationID := int64(0)
if connection.Source == ConnectionSourceGitHubAppInstallation {
kind = AuthPrincipalApp
login = connection.InstallationAccountLogin
if connection.InstallationID != nil {
installationID = *connection.InstallationID
}
} else if connection.Source == ConnectionSourceLegacyShared && login == "" {
login, _ = m.client.GetAuthenticatedUser(context.Background())
}
return &ResolvedCredential{
Client: &mockPrincipalClient{MockClient: m.client, login: login},
Principal: AuthPrincipal{
Kind: kind, Source: connection.Source, Login: login, InstallationID: installationID,
},
Capabilities: m.workspaceCapabilities(connection.WorkspaceID),
RateTracker: NewRateTracker(nil, nil),
credential: "mock-token:" + connection.WorkspaceID,
}, nil
}
func (m *MockAuthState) ResolveUser(
_ context.Context,
connection *UserConnection,
_ ResolveCredentialRequest,
) (*ResolvedCredential, error) {
if m == nil || m.client == nil || connection == nil {
return nil, ErrGitHubPersonalRequired
}
return &ResolvedCredential{
Client: &mockPrincipalClient{MockClient: m.client, login: connection.Login},
Principal: AuthPrincipal{
Kind: AuthPrincipalHuman, Source: ConnectionSourceGitHubAppUser,
Login: connection.Login, UserID: connection.UserID,
},
Capabilities: allTokenCapabilities(),
RateTracker: NewRateTracker(nil, nil),
credential: "mock-user-token:" + connection.WorkspaceID + ":" + connection.UserID,
}, nil
}
func (m *MockAuthState) SetWorkspaceCapabilities(
workspaceID string,
capabilities map[GitHubAppCapability]bool,
) {
m.mu.Lock()
defer m.mu.Unlock()
m.capabilities[workspaceID] = cloneCapabilities(capabilities)
}
func (m *MockAuthState) DeleteWorkspace(workspaceID string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.capabilities, workspaceID)
}
func (m *MockAuthState) workspaceCapabilities(workspaceID string) map[GitHubAppCapability]bool {
m.mu.RLock()
capabilities, exists := m.capabilities[workspaceID]
m.mu.RUnlock()
if !exists {
return allTokenCapabilities()
}
return cloneCapabilities(capabilities)
}
func (m *MockAuthState) SetCLIAccounts(accounts []GHAccount) {
m.mu.Lock()
defer m.mu.Unlock()
m.cliAccounts = append([]GHAccount(nil), accounts...)
}
func (m *MockAuthState) ListCLIAccounts(context.Context) ([]GHAccount, error) {
m.mu.RLock()
defer m.mu.RUnlock()
return append([]GHAccount(nil), m.cliAccounts...), nil
}
func (m *MockAuthState) Reset() {
m.mu.Lock()
defer m.mu.Unlock()
m.capabilities = make(map[string]map[GitHubAppCapability]bool)
m.cliAccounts = nil
}
func cloneCapabilities(source map[GitHubAppCapability]bool) map[GitHubAppCapability]bool {
result := make(map[GitHubAppCapability]bool, len(source))
for capability, allowed := range source {
result[capability] = allowed
}
return result
}
// mockPrincipalClient overrides identity while sharing every seeded GitHub
// object with the installation-global MockClient.
type mockPrincipalClient struct {
*MockClient
login string
}
func (c *mockPrincipalClient) IsAuthenticated(context.Context) (bool, error) { return true, nil }
func (c *mockPrincipalClient) GetAuthenticatedUser(context.Context) (string, error) {
return c.login, nil
}
func (s *Service) enableMockAuth(client *MockClient) *MockAuthState {
if s.mockAuth != nil {
return s.mockAuth
}
state := NewMockAuthState(client)
s.mockAuth = state
s.ghAccountLister = state.ListCLIAccounts
if s.resolver != nil {
s.resolver.SetAutomationProvider(state)
s.resolver.SetUserProvider(state)
s.resolver.ghToken = func(_ context.Context, host, login string) (string, error) {
if strings.TrimSpace(login) == "" {
return "", fmt.Errorf("GitHub login is required")
}
return "mock-gh:" + host + ":" + login, nil
}
}
s.tokenClientFactory = func(token string) Client {
login := strings.TrimSpace(strings.TrimPrefix(token, "mock-pat:"))
if strings.HasPrefix(token, "mock-gh:") {
parts := strings.Split(token, ":")
login = parts[len(parts)-1]
}
if login == "" || login == token {
login, _ = client.GetAuthenticatedUser(context.Background())
}
return &mockPrincipalClient{MockClient: client, login: login}
}
return state
}
// ResetMockAuth clears in-memory mock identity state for one workspace, or
// all workspaces when workspaceID is empty.
func (s *Service) ResetMockAuth(workspaceID string) {
if s == nil {
return
}
if s.mockAuth == nil {
return
}
if workspaceID == "" {
s.mockAuth.Reset()
} else {
s.mockAuth.DeleteWorkspace(workspaceID)
s.mockAuth.SetCLIAccounts(nil)
}
if s.resolver != nil {
if workspaceID == "" {
s.resolver.InvalidateAll()
} else {
s.resolver.InvalidateWorkspace(workspaceID)
}
}
}