|
2d8a379…
|
lmata
|
1 |
package api |
|
2d8a379…
|
lmata
|
2 |
|
|
2d8a379…
|
lmata
|
3 |
import ( |
|
2d8a379…
|
lmata
|
4 |
"encoding/json" |
|
2d8a379…
|
lmata
|
5 |
"net/http" |
|
2d8a379…
|
lmata
|
6 |
"strings" |
|
2d8a379…
|
lmata
|
7 |
|
|
2d8a379…
|
lmata
|
8 |
"github.com/conflicthq/scuttlebot/internal/registry" |
|
2d8a379…
|
lmata
|
9 |
) |
|
2d8a379…
|
lmata
|
10 |
|
|
2d8a379…
|
lmata
|
11 |
type registerRequest struct { |
|
7830697…
|
lmata
|
12 |
Nick string `json:"nick"` |
|
7830697…
|
lmata
|
13 |
Type registry.AgentType `json:"type"` |
|
7830697…
|
lmata
|
14 |
Channels []string `json:"channels"` |
|
3e3b163…
|
lmata
|
15 |
OpsChannels []string `json:"ops_channels,omitempty"` |
|
7830697…
|
lmata
|
16 |
Permissions []string `json:"permissions"` |
|
ba75f34…
|
noreply
|
17 |
Skills []string `json:"skills,omitempty"` |
|
7830697…
|
lmata
|
18 |
RateLimit *registry.RateLimitConfig `json:"rate_limit,omitempty"` |
|
7830697…
|
lmata
|
19 |
Rules *registry.EngagementRules `json:"engagement,omitempty"` |
|
2d8a379…
|
lmata
|
20 |
} |
|
2d8a379…
|
lmata
|
21 |
|
|
2d8a379…
|
lmata
|
22 |
type registerResponse struct { |
|
1066004…
|
lmata
|
23 |
Credentials *registry.Credentials `json:"credentials"` |
|
2d8a379…
|
lmata
|
24 |
Payload *registry.SignedPayload `json:"payload"` |
|
2d8a379…
|
lmata
|
25 |
} |
|
2d8a379…
|
lmata
|
26 |
|
|
2d8a379…
|
lmata
|
27 |
func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) { |
|
2d8a379…
|
lmata
|
28 |
var req registerRequest |
|
2d8a379…
|
lmata
|
29 |
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
|
2d8a379…
|
lmata
|
30 |
writeError(w, http.StatusBadRequest, "invalid request body") |
|
2d8a379…
|
lmata
|
31 |
return |
|
2d8a379…
|
lmata
|
32 |
} |
|
2d8a379…
|
lmata
|
33 |
if req.Nick == "" { |
|
2d8a379…
|
lmata
|
34 |
writeError(w, http.StatusBadRequest, "nick is required") |
|
2d8a379…
|
lmata
|
35 |
return |
|
2d8a379…
|
lmata
|
36 |
} |
|
2d8a379…
|
lmata
|
37 |
if req.Type == "" { |
|
2d8a379…
|
lmata
|
38 |
req.Type = registry.AgentTypeWorker |
|
2d8a379…
|
lmata
|
39 |
} |
|
2d8a379…
|
lmata
|
40 |
|
|
7830697…
|
lmata
|
41 |
cfg := registry.EngagementConfig{ |
|
7830697…
|
lmata
|
42 |
Channels: req.Channels, |
|
3e3b163…
|
lmata
|
43 |
OpsChannels: req.OpsChannels, |
|
7830697…
|
lmata
|
44 |
Permissions: req.Permissions, |
|
7830697…
|
lmata
|
45 |
} |
|
7830697…
|
lmata
|
46 |
if req.RateLimit != nil { |
|
7830697…
|
lmata
|
47 |
cfg.RateLimit = *req.RateLimit |
|
7830697…
|
lmata
|
48 |
} |
|
7830697…
|
lmata
|
49 |
if req.Rules != nil { |
|
7830697…
|
lmata
|
50 |
cfg.Rules = *req.Rules |
|
7830697…
|
lmata
|
51 |
} |
|
7830697…
|
lmata
|
52 |
creds, payload, err := s.registry.Register(req.Nick, req.Type, cfg) |
|
2d8a379…
|
lmata
|
53 |
if err != nil { |
|
2d8a379…
|
lmata
|
54 |
if strings.Contains(err.Error(), "already registered") { |
|
2d8a379…
|
lmata
|
55 |
writeError(w, http.StatusConflict, err.Error()) |
|
2d8a379…
|
lmata
|
56 |
return |
|
2d8a379…
|
lmata
|
57 |
} |
|
2d8a379…
|
lmata
|
58 |
s.log.Error("register agent", "nick", req.Nick, "err", err) |
|
2d8a379…
|
lmata
|
59 |
writeError(w, http.StatusInternalServerError, "registration failed") |
|
2d8a379…
|
lmata
|
60 |
return |
|
2d8a379…
|
lmata
|
61 |
} |
|
2d8a379…
|
lmata
|
62 |
|
|
ba75f34…
|
noreply
|
63 |
// Set skills if provided. |
|
ba75f34…
|
noreply
|
64 |
if len(req.Skills) > 0 { |
|
ba75f34…
|
noreply
|
65 |
if agent, err := s.registry.Get(req.Nick); err == nil { |
|
ba75f34…
|
noreply
|
66 |
agent.Skills = req.Skills |
|
ba75f34…
|
noreply
|
67 |
_ = s.registry.Update(agent) |
|
ba75f34…
|
noreply
|
68 |
} |
|
ba75f34…
|
noreply
|
69 |
} |
|
1cbc747…
|
lmata
|
70 |
s.registry.Touch(req.Nick) |
|
e0d99ff…
|
lmata
|
71 |
go s.setAgentModes(req.Nick, req.Type, cfg) // async — don't block response |
|
2d8a379…
|
lmata
|
72 |
writeJSON(w, http.StatusCreated, registerResponse{ |
|
2d8a379…
|
lmata
|
73 |
Credentials: creds, |
|
2d8a379…
|
lmata
|
74 |
Payload: payload, |
|
2d8a379…
|
lmata
|
75 |
}) |
|
5ac549c…
|
lmata
|
76 |
} |
|
5ac549c…
|
lmata
|
77 |
|
|
5ac549c…
|
lmata
|
78 |
func (s *Server) handleAdopt(w http.ResponseWriter, r *http.Request) { |
|
5ac549c…
|
lmata
|
79 |
nick := r.PathValue("nick") |
|
5ac549c…
|
lmata
|
80 |
var req struct { |
|
5ac549c…
|
lmata
|
81 |
Type registry.AgentType `json:"type"` |
|
5ac549c…
|
lmata
|
82 |
Channels []string `json:"channels"` |
|
3e3b163…
|
lmata
|
83 |
OpsChannels []string `json:"ops_channels,omitempty"` |
|
5ac549c…
|
lmata
|
84 |
Permissions []string `json:"permissions"` |
|
5ac549c…
|
lmata
|
85 |
} |
|
5ac549c…
|
lmata
|
86 |
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
|
5ac549c…
|
lmata
|
87 |
writeError(w, http.StatusBadRequest, "invalid request body") |
|
5ac549c…
|
lmata
|
88 |
return |
|
5ac549c…
|
lmata
|
89 |
} |
|
5ac549c…
|
lmata
|
90 |
if req.Type == "" { |
|
5ac549c…
|
lmata
|
91 |
req.Type = registry.AgentTypeWorker |
|
5ac549c…
|
lmata
|
92 |
} |
|
5ac549c…
|
lmata
|
93 |
cfg := registry.EngagementConfig{ |
|
5ac549c…
|
lmata
|
94 |
Channels: req.Channels, |
|
3e3b163…
|
lmata
|
95 |
OpsChannels: req.OpsChannels, |
|
5ac549c…
|
lmata
|
96 |
Permissions: req.Permissions, |
|
5ac549c…
|
lmata
|
97 |
} |
|
5ac549c…
|
lmata
|
98 |
payload, err := s.registry.Adopt(nick, req.Type, cfg) |
|
5ac549c…
|
lmata
|
99 |
if err != nil { |
|
5ac549c…
|
lmata
|
100 |
if strings.Contains(err.Error(), "already registered") { |
|
5ac549c…
|
lmata
|
101 |
writeError(w, http.StatusConflict, err.Error()) |
|
5ac549c…
|
lmata
|
102 |
return |
|
5ac549c…
|
lmata
|
103 |
} |
|
5ac549c…
|
lmata
|
104 |
s.log.Error("adopt agent", "nick", nick, "err", err) |
|
5ac549c…
|
lmata
|
105 |
writeError(w, http.StatusInternalServerError, "adopt failed") |
|
5ac549c…
|
lmata
|
106 |
return |
|
5ac549c…
|
lmata
|
107 |
} |
|
3e3b163…
|
lmata
|
108 |
s.setAgentModes(nick, req.Type, cfg) |
|
5ac549c…
|
lmata
|
109 |
writeJSON(w, http.StatusOK, map[string]any{"nick": nick, "payload": payload}) |
|
2d8a379…
|
lmata
|
110 |
} |
|
2d8a379…
|
lmata
|
111 |
|
|
2d8a379…
|
lmata
|
112 |
func (s *Server) handleRotate(w http.ResponseWriter, r *http.Request) { |
|
2d8a379…
|
lmata
|
113 |
nick := r.PathValue("nick") |
|
2d8a379…
|
lmata
|
114 |
creds, err := s.registry.Rotate(nick) |
|
2d8a379…
|
lmata
|
115 |
if err != nil { |
|
2d8a379…
|
lmata
|
116 |
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "revoked") { |
|
2d8a379…
|
lmata
|
117 |
writeError(w, http.StatusNotFound, err.Error()) |
|
2d8a379…
|
lmata
|
118 |
return |
|
2d8a379…
|
lmata
|
119 |
} |
|
2d8a379…
|
lmata
|
120 |
s.log.Error("rotate credentials", "nick", nick, "err", err) |
|
2d8a379…
|
lmata
|
121 |
writeError(w, http.StatusInternalServerError, "rotation failed") |
|
2d8a379…
|
lmata
|
122 |
return |
|
2d8a379…
|
lmata
|
123 |
} |
|
2d8a379…
|
lmata
|
124 |
writeJSON(w, http.StatusOK, creds) |
|
2d8a379…
|
lmata
|
125 |
} |
|
2d8a379…
|
lmata
|
126 |
|
|
2d8a379…
|
lmata
|
127 |
func (s *Server) handleRevoke(w http.ResponseWriter, r *http.Request) { |
|
2d8a379…
|
lmata
|
128 |
nick := r.PathValue("nick") |
|
0902a34…
|
lmata
|
129 |
// Look up agent channels before revoking so we can remove access. |
|
0902a34…
|
lmata
|
130 |
if agent, err := s.registry.Get(nick); err == nil { |
|
0902a34…
|
lmata
|
131 |
s.removeAgentModes(nick, agent.Channels) |
|
0902a34…
|
lmata
|
132 |
} |
|
2d8a379…
|
lmata
|
133 |
if err := s.registry.Revoke(nick); err != nil { |
|
2d8a379…
|
lmata
|
134 |
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "revoked") { |
|
2d8a379…
|
lmata
|
135 |
writeError(w, http.StatusNotFound, err.Error()) |
|
2d8a379…
|
lmata
|
136 |
return |
|
2d8a379…
|
lmata
|
137 |
} |
|
2d8a379…
|
lmata
|
138 |
s.log.Error("revoke agent", "nick", nick, "err", err) |
|
2d8a379…
|
lmata
|
139 |
writeError(w, http.StatusInternalServerError, "revocation failed") |
|
2d8a379…
|
lmata
|
140 |
return |
|
2d8a379…
|
lmata
|
141 |
} |
|
2d8a379…
|
lmata
|
142 |
w.WriteHeader(http.StatusNoContent) |
|
2d8a379…
|
lmata
|
143 |
} |
|
2d8a379…
|
lmata
|
144 |
|
|
5ac549c…
|
lmata
|
145 |
func (s *Server) handleDelete(w http.ResponseWriter, r *http.Request) { |
|
5ac549c…
|
lmata
|
146 |
nick := r.PathValue("nick") |
|
0902a34…
|
lmata
|
147 |
// Look up agent channels before deleting so we can remove access. |
|
0902a34…
|
lmata
|
148 |
if agent, err := s.registry.Get(nick); err == nil { |
|
0902a34…
|
lmata
|
149 |
s.removeAgentModes(nick, agent.Channels) |
|
0902a34…
|
lmata
|
150 |
} |
|
5ac549c…
|
lmata
|
151 |
if err := s.registry.Delete(nick); err != nil { |
|
5ac549c…
|
lmata
|
152 |
if strings.Contains(err.Error(), "not found") { |
|
5ac549c…
|
lmata
|
153 |
writeError(w, http.StatusNotFound, err.Error()) |
|
5ac549c…
|
lmata
|
154 |
return |
|
5ac549c…
|
lmata
|
155 |
} |
|
5ac549c…
|
lmata
|
156 |
s.log.Error("delete agent", "nick", nick, "err", err) |
|
5ac549c…
|
lmata
|
157 |
writeError(w, http.StatusInternalServerError, "deletion failed") |
|
5ac549c…
|
lmata
|
158 |
return |
|
5ac549c…
|
lmata
|
159 |
} |
|
5ac549c…
|
lmata
|
160 |
w.WriteHeader(http.StatusNoContent) |
|
0902a34…
|
lmata
|
161 |
} |
|
0902a34…
|
lmata
|
162 |
|
|
50ba2ec…
|
noreply
|
163 |
// handleBulkDeleteAgents handles POST /v1/agents/bulk-delete. |
|
50ba2ec…
|
noreply
|
164 |
func (s *Server) handleBulkDeleteAgents(w http.ResponseWriter, r *http.Request) { |
|
50ba2ec…
|
noreply
|
165 |
var req struct { |
|
50ba2ec…
|
noreply
|
166 |
Nicks []string `json:"nicks"` |
|
50ba2ec…
|
noreply
|
167 |
} |
|
50ba2ec…
|
noreply
|
168 |
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
|
50ba2ec…
|
noreply
|
169 |
writeError(w, http.StatusBadRequest, "invalid request body") |
|
50ba2ec…
|
noreply
|
170 |
return |
|
50ba2ec…
|
noreply
|
171 |
} |
|
50ba2ec…
|
noreply
|
172 |
if len(req.Nicks) == 0 { |
|
50ba2ec…
|
noreply
|
173 |
writeError(w, http.StatusBadRequest, "nicks list is required") |
|
50ba2ec…
|
noreply
|
174 |
return |
|
50ba2ec…
|
noreply
|
175 |
} |
|
50ba2ec…
|
noreply
|
176 |
|
|
50ba2ec…
|
noreply
|
177 |
var deleted, failed int |
|
50ba2ec…
|
noreply
|
178 |
for _, nick := range req.Nicks { |
|
50ba2ec…
|
noreply
|
179 |
if agent, err := s.registry.Get(nick); err == nil { |
|
50ba2ec…
|
noreply
|
180 |
s.removeAgentModes(nick, agent.Channels) |
|
50ba2ec…
|
noreply
|
181 |
} |
|
50ba2ec…
|
noreply
|
182 |
if err := s.registry.Delete(nick); err != nil { |
|
50ba2ec…
|
noreply
|
183 |
s.log.Warn("bulk delete: failed", "nick", nick, "err", err) |
|
50ba2ec…
|
noreply
|
184 |
failed++ |
|
50ba2ec…
|
noreply
|
185 |
} else { |
|
50ba2ec…
|
noreply
|
186 |
deleted++ |
|
50ba2ec…
|
noreply
|
187 |
} |
|
50ba2ec…
|
noreply
|
188 |
} |
|
50ba2ec…
|
noreply
|
189 |
writeJSON(w, http.StatusOK, map[string]int{"deleted": deleted, "failed": failed}) |
|
50ba2ec…
|
noreply
|
190 |
} |
|
50ba2ec…
|
noreply
|
191 |
|
|
87e6978…
|
lmata
|
192 |
func (s *Server) handleUpdateAgent(w http.ResponseWriter, r *http.Request) { |
|
87e6978…
|
lmata
|
193 |
nick := r.PathValue("nick") |
|
87e6978…
|
lmata
|
194 |
var req struct { |
|
87e6978…
|
lmata
|
195 |
Channels []string `json:"channels"` |
|
87e6978…
|
lmata
|
196 |
} |
|
87e6978…
|
lmata
|
197 |
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
|
87e6978…
|
lmata
|
198 |
writeError(w, http.StatusBadRequest, "invalid request body") |
|
87e6978…
|
lmata
|
199 |
return |
|
87e6978…
|
lmata
|
200 |
} |
|
87e6978…
|
lmata
|
201 |
if err := s.registry.UpdateChannels(nick, req.Channels); err != nil { |
|
87e6978…
|
lmata
|
202 |
if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "revoked") { |
|
87e6978…
|
lmata
|
203 |
writeError(w, http.StatusNotFound, err.Error()) |
|
87e6978…
|
lmata
|
204 |
return |
|
87e6978…
|
lmata
|
205 |
} |
|
87e6978…
|
lmata
|
206 |
s.log.Error("update agent channels", "nick", nick, "err", err) |
|
87e6978…
|
lmata
|
207 |
writeError(w, http.StatusInternalServerError, "update failed") |
|
87e6978…
|
lmata
|
208 |
return |
|
87e6978…
|
lmata
|
209 |
} |
|
1cbc747…
|
lmata
|
210 |
s.registry.Touch(nick) |
|
87e6978…
|
lmata
|
211 |
w.WriteHeader(http.StatusNoContent) |
|
87e6978…
|
lmata
|
212 |
} |
|
87e6978…
|
lmata
|
213 |
|
|
2d8a379…
|
lmata
|
214 |
func (s *Server) handleListAgents(w http.ResponseWriter, r *http.Request) { |
|
2d8a379…
|
lmata
|
215 |
agents := s.registry.List() |
|
ba75f34…
|
noreply
|
216 |
// Filter by skill if ?skill= query param is present. |
|
ba75f34…
|
noreply
|
217 |
if skill := r.URL.Query().Get("skill"); skill != "" { |
|
ba75f34…
|
noreply
|
218 |
filtered := make([]*registry.Agent, 0) |
|
ba75f34…
|
noreply
|
219 |
for _, a := range agents { |
|
ba75f34…
|
noreply
|
220 |
for _, s := range a.Skills { |
|
ba75f34…
|
noreply
|
221 |
if strings.EqualFold(s, skill) { |
|
ba75f34…
|
noreply
|
222 |
filtered = append(filtered, a) |
|
ba75f34…
|
noreply
|
223 |
break |
|
ba75f34…
|
noreply
|
224 |
} |
|
ba75f34…
|
noreply
|
225 |
} |
|
ba75f34…
|
noreply
|
226 |
} |
|
ba75f34…
|
noreply
|
227 |
agents = filtered |
|
ba75f34…
|
noreply
|
228 |
} |
|
2d8a379…
|
lmata
|
229 |
writeJSON(w, http.StatusOK, map[string]any{"agents": agents}) |
|
2d8a379…
|
lmata
|
230 |
} |
|
2d8a379…
|
lmata
|
231 |
|
|
2d8a379…
|
lmata
|
232 |
func (s *Server) handleGetAgent(w http.ResponseWriter, r *http.Request) { |
|
2d8a379…
|
lmata
|
233 |
nick := r.PathValue("nick") |
|
2d8a379…
|
lmata
|
234 |
agent, err := s.registry.Get(nick) |
|
2d8a379…
|
lmata
|
235 |
if err != nil { |
|
2d8a379…
|
lmata
|
236 |
writeError(w, http.StatusNotFound, err.Error()) |
|
2d8a379…
|
lmata
|
237 |
return |
|
2d8a379…
|
lmata
|
238 |
} |
|
2d8a379…
|
lmata
|
239 |
writeJSON(w, http.StatusOK, agent) |
|
ba75f34…
|
noreply
|
240 |
} |
|
ba75f34…
|
noreply
|
241 |
|
|
ba75f34…
|
noreply
|
242 |
// handleAgentBlocker handles POST /v1/agents/{nick}/blocker. |
|
ba75f34…
|
noreply
|
243 |
// Agents or relays call this to escalate that an agent is stuck. |
|
ba75f34…
|
noreply
|
244 |
func (s *Server) handleAgentBlocker(w http.ResponseWriter, r *http.Request) { |
|
ba75f34…
|
noreply
|
245 |
nick := r.PathValue("nick") |
|
ba75f34…
|
noreply
|
246 |
var req struct { |
|
ba75f34…
|
noreply
|
247 |
Channel string `json:"channel,omitempty"` |
|
ba75f34…
|
noreply
|
248 |
Message string `json:"message"` |
|
ba75f34…
|
noreply
|
249 |
} |
|
ba75f34…
|
noreply
|
250 |
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
|
ba75f34…
|
noreply
|
251 |
writeError(w, http.StatusBadRequest, "invalid request body") |
|
ba75f34…
|
noreply
|
252 |
return |
|
ba75f34…
|
noreply
|
253 |
} |
|
ba75f34…
|
noreply
|
254 |
if req.Message == "" { |
|
ba75f34…
|
noreply
|
255 |
writeError(w, http.StatusBadRequest, "message is required") |
|
ba75f34…
|
noreply
|
256 |
return |
|
ba75f34…
|
noreply
|
257 |
} |
|
ba75f34…
|
noreply
|
258 |
|
|
ba75f34…
|
noreply
|
259 |
alert := "[blocker] " + nick |
|
ba75f34…
|
noreply
|
260 |
if req.Channel != "" { |
|
ba75f34…
|
noreply
|
261 |
alert += " in " + req.Channel |
|
ba75f34…
|
noreply
|
262 |
} |
|
ba75f34…
|
noreply
|
263 |
alert += ": " + req.Message |
|
ba75f34…
|
noreply
|
264 |
|
|
ba75f34…
|
noreply
|
265 |
// Post to #ops if bridge is available. |
|
ba75f34…
|
noreply
|
266 |
if s.bridge != nil { |
|
ba75f34…
|
noreply
|
267 |
_ = s.bridge.Send(r.Context(), "#ops", alert, "") |
|
ba75f34…
|
noreply
|
268 |
} |
|
ba75f34…
|
noreply
|
269 |
s.log.Warn("agent blocker", "nick", nick, "channel", req.Channel, "message", req.Message) |
|
ba75f34…
|
noreply
|
270 |
w.WriteHeader(http.StatusNoContent) |
|
0902a34…
|
lmata
|
271 |
} |
|
0902a34…
|
lmata
|
272 |
|
|
0902a34…
|
lmata
|
273 |
// agentModeLevel maps an agent type to the ChanServ access level it should |
|
0902a34…
|
lmata
|
274 |
// receive. Returns "" for types that get no special mode. |
|
0902a34…
|
lmata
|
275 |
func agentModeLevel(t registry.AgentType) string { |
|
0902a34…
|
lmata
|
276 |
switch t { |
|
0902a34…
|
lmata
|
277 |
case registry.AgentTypeOperator, registry.AgentTypeOrchestrator: |
|
0902a34…
|
lmata
|
278 |
return "OP" |
|
0902a34…
|
lmata
|
279 |
case registry.AgentTypeWorker: |
|
0902a34…
|
lmata
|
280 |
return "VOICE" |
|
0902a34…
|
lmata
|
281 |
default: |
|
0902a34…
|
lmata
|
282 |
return "" |
|
0902a34…
|
lmata
|
283 |
} |
|
0902a34…
|
lmata
|
284 |
} |
|
0902a34…
|
lmata
|
285 |
|
|
0902a34…
|
lmata
|
286 |
// setAgentModes grants the appropriate ChanServ access for an agent on all |
|
3e3b163…
|
lmata
|
287 |
// its assigned channels based on its type. For orchestrators with OpsChannels |
|
3e3b163…
|
lmata
|
288 |
// configured, +o is granted only on those channels and +v on the rest. |
|
3e3b163…
|
lmata
|
289 |
// No-op when topology is not configured or the agent type doesn't warrant a mode. |
|
3e3b163…
|
lmata
|
290 |
func (s *Server) setAgentModes(nick string, agentType registry.AgentType, cfg registry.EngagementConfig) { |
|
0902a34…
|
lmata
|
291 |
if s.topoMgr == nil { |
|
0902a34…
|
lmata
|
292 |
return |
|
0902a34…
|
lmata
|
293 |
} |
|
0902a34…
|
lmata
|
294 |
level := agentModeLevel(agentType) |
|
0902a34…
|
lmata
|
295 |
if level == "" { |
|
0902a34…
|
lmata
|
296 |
return |
|
0902a34…
|
lmata
|
297 |
} |
|
3e3b163…
|
lmata
|
298 |
|
|
3e3b163…
|
lmata
|
299 |
// Orchestrators with explicit OpsChannels get +o only on those channels |
|
3e3b163…
|
lmata
|
300 |
// and +v on remaining channels. |
|
3e3b163…
|
lmata
|
301 |
if level == "OP" && len(cfg.OpsChannels) > 0 { |
|
3e3b163…
|
lmata
|
302 |
opsSet := make(map[string]struct{}, len(cfg.OpsChannels)) |
|
3e3b163…
|
lmata
|
303 |
for _, ch := range cfg.OpsChannels { |
|
3e3b163…
|
lmata
|
304 |
opsSet[ch] = struct{}{} |
|
3e3b163…
|
lmata
|
305 |
} |
|
3e3b163…
|
lmata
|
306 |
for _, ch := range cfg.Channels { |
|
3e3b163…
|
lmata
|
307 |
if _, isOps := opsSet[ch]; isOps { |
|
3e3b163…
|
lmata
|
308 |
s.topoMgr.GrantAccess(nick, ch, "OP") |
|
3e3b163…
|
lmata
|
309 |
} else { |
|
3e3b163…
|
lmata
|
310 |
s.topoMgr.GrantAccess(nick, ch, "VOICE") |
|
3e3b163…
|
lmata
|
311 |
} |
|
3e3b163…
|
lmata
|
312 |
} |
|
3e3b163…
|
lmata
|
313 |
return |
|
3e3b163…
|
lmata
|
314 |
} |
|
3e3b163…
|
lmata
|
315 |
|
|
3e3b163…
|
lmata
|
316 |
for _, ch := range cfg.Channels { |
|
0902a34…
|
lmata
|
317 |
s.topoMgr.GrantAccess(nick, ch, level) |
|
0902a34…
|
lmata
|
318 |
} |
|
0902a34…
|
lmata
|
319 |
} |
|
0902a34…
|
lmata
|
320 |
|
|
0902a34…
|
lmata
|
321 |
// removeAgentModes revokes ChanServ access for an agent on all its assigned |
|
0902a34…
|
lmata
|
322 |
// channels. No-op when topology is not configured. |
|
0902a34…
|
lmata
|
323 |
func (s *Server) removeAgentModes(nick string, channels []string) { |
|
0902a34…
|
lmata
|
324 |
if s.topoMgr == nil { |
|
0902a34…
|
lmata
|
325 |
return |
|
0902a34…
|
lmata
|
326 |
} |
|
0902a34…
|
lmata
|
327 |
for _, ch := range channels { |
|
0902a34…
|
lmata
|
328 |
s.topoMgr.RevokeAccess(nick, ch) |
|
0902a34…
|
lmata
|
329 |
} |
|
2d8a379…
|
lmata
|
330 |
} |