permission.go

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