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}