mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-04 16:34:55 +01:00
wip: refactoring
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
package permission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/opencode-ai/opencode/internal/config"
|
||||
"github.com/opencode-ai/opencode/internal/pubsub"
|
||||
@@ -32,56 +36,141 @@ type PermissionRequest struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type PermissionResponse struct {
|
||||
Request PermissionRequest
|
||||
Granted bool
|
||||
}
|
||||
|
||||
const (
|
||||
EventPermissionRequested pubsub.EventType = "permission_requested"
|
||||
EventPermissionGranted pubsub.EventType = "permission_granted"
|
||||
EventPermissionDenied pubsub.EventType = "permission_denied"
|
||||
EventPermissionPersisted pubsub.EventType = "permission_persisted"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
pubsub.Suscriber[PermissionRequest]
|
||||
GrantPersistant(permission PermissionRequest)
|
||||
Grant(permission PermissionRequest)
|
||||
Deny(permission PermissionRequest)
|
||||
Request(opts CreatePermissionRequest) bool
|
||||
AutoApproveSession(sessionID string)
|
||||
pubsub.Subscriber[PermissionRequest]
|
||||
SubscribeToResponseEvents(ctx context.Context) <-chan pubsub.Event[PermissionResponse]
|
||||
|
||||
GrantPersistant(ctx context.Context, permission PermissionRequest)
|
||||
Grant(ctx context.Context, permission PermissionRequest)
|
||||
Deny(ctx context.Context, permission PermissionRequest)
|
||||
Request(ctx context.Context, opts CreatePermissionRequest) bool
|
||||
AutoApproveSession(ctx context.Context, sessionID string)
|
||||
IsAutoApproved(ctx context.Context, sessionID string) bool
|
||||
}
|
||||
|
||||
type permissionService struct {
|
||||
*pubsub.Broker[PermissionRequest]
|
||||
broker *pubsub.Broker[PermissionRequest]
|
||||
responseBroker *pubsub.Broker[PermissionResponse]
|
||||
|
||||
sessionPermissions []PermissionRequest
|
||||
sessionPermissions map[string][]PermissionRequest
|
||||
pendingRequests sync.Map
|
||||
autoApproveSessions []string
|
||||
autoApproveSessions map[string]bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *permissionService) GrantPersistant(permission PermissionRequest) {
|
||||
var globalPermissionService *permissionService
|
||||
|
||||
func InitService() error {
|
||||
if globalPermissionService != nil {
|
||||
return fmt.Errorf("permission service already initialized")
|
||||
}
|
||||
globalPermissionService = &permissionService{
|
||||
broker: pubsub.NewBroker[PermissionRequest](),
|
||||
responseBroker: pubsub.NewBroker[PermissionResponse](),
|
||||
sessionPermissions: make(map[string][]PermissionRequest),
|
||||
autoApproveSessions: make(map[string]bool),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetService() *permissionService {
|
||||
if globalPermissionService == nil {
|
||||
panic("permission service not initialized. Call permission.InitService() first.")
|
||||
}
|
||||
return globalPermissionService
|
||||
}
|
||||
|
||||
func (s *permissionService) GrantPersistant(ctx context.Context, permission PermissionRequest) {
|
||||
s.mu.Lock()
|
||||
s.sessionPermissions[permission.SessionID] = append(s.sessionPermissions[permission.SessionID], permission)
|
||||
s.mu.Unlock()
|
||||
|
||||
respCh, ok := s.pendingRequests.Load(permission.ID)
|
||||
if ok {
|
||||
respCh.(chan bool) <- true
|
||||
select {
|
||||
case respCh.(chan bool) <- true:
|
||||
case <-ctx.Done():
|
||||
slog.Warn("Context cancelled while sending grant persistent response", "request_id", permission.ID)
|
||||
}
|
||||
}
|
||||
s.sessionPermissions = append(s.sessionPermissions, permission)
|
||||
s.responseBroker.Publish(EventPermissionPersisted, PermissionResponse{Request: permission, Granted: true})
|
||||
}
|
||||
|
||||
func (s *permissionService) Grant(permission PermissionRequest) {
|
||||
func (s *permissionService) Grant(ctx context.Context, permission PermissionRequest) {
|
||||
respCh, ok := s.pendingRequests.Load(permission.ID)
|
||||
if ok {
|
||||
respCh.(chan bool) <- true
|
||||
select {
|
||||
case respCh.(chan bool) <- true:
|
||||
case <-ctx.Done():
|
||||
slog.Warn("Context cancelled while sending grant response", "request_id", permission.ID)
|
||||
}
|
||||
}
|
||||
s.responseBroker.Publish(EventPermissionGranted, PermissionResponse{Request: permission, Granted: true})
|
||||
}
|
||||
|
||||
func (s *permissionService) Deny(permission PermissionRequest) {
|
||||
func (s *permissionService) Deny(ctx context.Context, permission PermissionRequest) {
|
||||
respCh, ok := s.pendingRequests.Load(permission.ID)
|
||||
if ok {
|
||||
respCh.(chan bool) <- false
|
||||
select {
|
||||
case respCh.(chan bool) <- false:
|
||||
case <-ctx.Done():
|
||||
slog.Warn("Context cancelled while sending deny response", "request_id", permission.ID)
|
||||
}
|
||||
}
|
||||
s.responseBroker.Publish(EventPermissionDenied, PermissionResponse{Request: permission, Granted: false})
|
||||
}
|
||||
|
||||
func (s *permissionService) Request(opts CreatePermissionRequest) bool {
|
||||
if slices.Contains(s.autoApproveSessions, opts.SessionID) {
|
||||
func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRequest) bool {
|
||||
s.mu.RLock()
|
||||
if s.autoApproveSessions[opts.SessionID] {
|
||||
s.mu.RUnlock()
|
||||
return true
|
||||
}
|
||||
dir := filepath.Dir(opts.Path)
|
||||
if dir == "." {
|
||||
dir = config.WorkingDirectory()
|
||||
|
||||
requestPath := opts.Path
|
||||
if !filepath.IsAbs(requestPath) {
|
||||
requestPath = filepath.Join(config.WorkingDirectory(), requestPath)
|
||||
}
|
||||
permission := PermissionRequest{
|
||||
requestPath = filepath.Clean(requestPath)
|
||||
|
||||
if permissions, ok := s.sessionPermissions[opts.SessionID]; ok {
|
||||
for _, p := range permissions {
|
||||
storedPath := p.Path
|
||||
if !filepath.IsAbs(storedPath) {
|
||||
storedPath = filepath.Join(config.WorkingDirectory(), storedPath)
|
||||
}
|
||||
storedPath = filepath.Clean(storedPath)
|
||||
|
||||
if p.ToolName == opts.ToolName && p.Action == opts.Action &&
|
||||
(requestPath == storedPath || strings.HasPrefix(requestPath, storedPath+string(filepath.Separator))) {
|
||||
s.mu.RUnlock()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
normalizedPath := opts.Path
|
||||
if !filepath.IsAbs(normalizedPath) {
|
||||
normalizedPath = filepath.Join(config.WorkingDirectory(), normalizedPath)
|
||||
}
|
||||
normalizedPath = filepath.Clean(normalizedPath)
|
||||
|
||||
permissionReq := PermissionRequest{
|
||||
ID: uuid.New().String(),
|
||||
Path: dir,
|
||||
Path: normalizedPath,
|
||||
SessionID: opts.SessionID,
|
||||
ToolName: opts.ToolName,
|
||||
Description: opts.Description,
|
||||
@@ -89,31 +178,69 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
|
||||
Params: opts.Params,
|
||||
}
|
||||
|
||||
for _, p := range s.sessionPermissions {
|
||||
if p.ToolName == permission.ToolName && p.Action == permission.Action && p.SessionID == permission.SessionID && p.Path == permission.Path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
respCh := make(chan bool, 1)
|
||||
s.pendingRequests.Store(permissionReq.ID, respCh)
|
||||
defer s.pendingRequests.Delete(permissionReq.ID)
|
||||
|
||||
s.pendingRequests.Store(permission.ID, respCh)
|
||||
defer s.pendingRequests.Delete(permission.ID)
|
||||
s.broker.Publish(EventPermissionRequested, permissionReq)
|
||||
|
||||
s.Publish(pubsub.CreatedEvent, permission)
|
||||
|
||||
// Wait for the response with a timeout
|
||||
resp := <-respCh
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *permissionService) AutoApproveSession(sessionID string) {
|
||||
s.autoApproveSessions = append(s.autoApproveSessions, sessionID)
|
||||
}
|
||||
|
||||
func NewPermissionService() Service {
|
||||
return &permissionService{
|
||||
Broker: pubsub.NewBroker[PermissionRequest](),
|
||||
sessionPermissions: make([]PermissionRequest, 0),
|
||||
select {
|
||||
case resp := <-respCh:
|
||||
return resp
|
||||
case <-ctx.Done():
|
||||
slog.Warn("Permission request timed out or context cancelled", "request_id", permissionReq.ID, "tool", opts.ToolName)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *permissionService) AutoApproveSession(ctx context.Context, sessionID string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.autoApproveSessions[sessionID] = true
|
||||
}
|
||||
|
||||
func (s *permissionService) IsAutoApproved(ctx context.Context, sessionID string) bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.autoApproveSessions[sessionID]
|
||||
}
|
||||
|
||||
func (s *permissionService) Subscribe(ctx context.Context) <-chan pubsub.Event[PermissionRequest] {
|
||||
return s.broker.Subscribe(ctx)
|
||||
}
|
||||
|
||||
func (s *permissionService) SubscribeToResponseEvents(ctx context.Context) <-chan pubsub.Event[PermissionResponse] {
|
||||
return s.responseBroker.Subscribe(ctx)
|
||||
}
|
||||
|
||||
func GrantPersistant(ctx context.Context, permission PermissionRequest) {
|
||||
GetService().GrantPersistant(ctx, permission)
|
||||
}
|
||||
|
||||
func Grant(ctx context.Context, permission PermissionRequest) {
|
||||
GetService().Grant(ctx, permission)
|
||||
}
|
||||
|
||||
func Deny(ctx context.Context, permission PermissionRequest) {
|
||||
GetService().Deny(ctx, permission)
|
||||
}
|
||||
|
||||
func Request(ctx context.Context, opts CreatePermissionRequest) bool {
|
||||
return GetService().Request(ctx, opts)
|
||||
}
|
||||
|
||||
func AutoApproveSession(ctx context.Context, sessionID string) {
|
||||
GetService().AutoApproveSession(ctx, sessionID)
|
||||
}
|
||||
|
||||
func IsAutoApproved(ctx context.Context, sessionID string) bool {
|
||||
return GetService().IsAutoApproved(ctx, sessionID)
|
||||
}
|
||||
|
||||
func SubscribeToRequests(ctx context.Context) <-chan pubsub.Event[PermissionRequest] {
|
||||
return GetService().Subscribe(ctx)
|
||||
}
|
||||
|
||||
func SubscribeToResponses(ctx context.Context) <-chan pubsub.Event[PermissionResponse] {
|
||||
return GetService().SubscribeToResponseEvents(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user