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}