permission.go

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