193 lines
5.4 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|