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