permission.go

  1package permission
  2
  3import (
  4	"errors"
  5	"path/filepath"
  6	"slices"
  7	"sync"
  8
  9	"github.com/google/uuid"
 10	"github.com/opencode-ai/opencode/internal/config"
 11	"github.com/opencode-ai/opencode/internal/pubsub"
 12)
 13
 14var ErrorPermissionDenied = errors.New("permission denied")
 15
 16type CreatePermissionRequest struct {
 17	SessionID   string `json:"session_id"`
 18	ToolName    string `json:"tool_name"`
 19	Description string `json:"description"`
 20	Action      string `json:"action"`
 21	Params      any    `json:"params"`
 22	Path        string `json:"path"`
 23}
 24
 25type PermissionRequest struct {
 26	ID          string `json:"id"`
 27	SessionID   string `json:"session_id"`
 28	ToolName    string `json:"tool_name"`
 29	Description string `json:"description"`
 30	Action      string `json:"action"`
 31	Params      any    `json:"params"`
 32	Path        string `json:"path"`
 33}
 34
 35type Service interface {
 36	pubsub.Suscriber[PermissionRequest]
 37	GrantPersistant(permission PermissionRequest)
 38	Grant(permission PermissionRequest)
 39	Deny(permission PermissionRequest)
 40	Request(opts CreatePermissionRequest) bool
 41	AutoApproveSession(sessionID string)
 42}
 43
 44type permissionService struct {
 45	*pubsub.Broker[PermissionRequest]
 46
 47	sessionPermissions  []PermissionRequest
 48	pendingRequests     sync.Map
 49	autoApproveSessions []string
 50}
 51
 52func (s *permissionService) GrantPersistant(permission PermissionRequest) {
 53	respCh, ok := s.pendingRequests.Load(permission.ID)
 54	if ok {
 55		respCh.(chan bool) <- true
 56	}
 57	s.sessionPermissions = append(s.sessionPermissions, permission)
 58}
 59
 60func (s *permissionService) Grant(permission PermissionRequest) {
 61	respCh, ok := s.pendingRequests.Load(permission.ID)
 62	if ok {
 63		respCh.(chan bool) <- true
 64	}
 65}
 66
 67func (s *permissionService) Deny(permission PermissionRequest) {
 68	respCh, ok := s.pendingRequests.Load(permission.ID)
 69	if ok {
 70		respCh.(chan bool) <- false
 71	}
 72}
 73
 74func (s *permissionService) Request(opts CreatePermissionRequest) bool {
 75	if slices.Contains(s.autoApproveSessions, opts.SessionID) {
 76		return true
 77	}
 78	dir := filepath.Dir(opts.Path)
 79	if dir == "." {
 80		dir = config.WorkingDirectory()
 81	}
 82	permission := PermissionRequest{
 83		ID:          uuid.New().String(),
 84		Path:        dir,
 85		SessionID:   opts.SessionID,
 86		ToolName:    opts.ToolName,
 87		Description: opts.Description,
 88		Action:      opts.Action,
 89		Params:      opts.Params,
 90	}
 91
 92	for _, p := range s.sessionPermissions {
 93		if p.ToolName == permission.ToolName && p.Action == permission.Action && p.SessionID == permission.SessionID && p.Path == permission.Path {
 94			return true
 95		}
 96	}
 97
 98	respCh := make(chan bool, 1)
 99
100	s.pendingRequests.Store(permission.ID, respCh)
101	defer s.pendingRequests.Delete(permission.ID)
102
103	s.Publish(pubsub.CreatedEvent, permission)
104
105	// Wait for the response with a timeout
106	resp := <-respCh
107	return resp
108}
109
110func (s *permissionService) AutoApproveSession(sessionID string) {
111	s.autoApproveSessions = append(s.autoApproveSessions, sessionID)
112}
113
114func NewPermissionService() Service {
115	return &permissionService{
116		Broker:             pubsub.NewBroker[PermissionRequest](),
117		sessionPermissions: make([]PermissionRequest, 0),
118	}
119}