Use message passing for changing permissions.

We used to handle permission change commands by directly mutating
the permissions field from the source client's thread, which is
racy.  We now use message passing.

Thanks to Vinayak Mishra.
This commit is contained in:
Juliusz Chroboczek
2026-04-30 00:50:22 +02:00
parent e3f098e189
commit 122db74fb8
+37 -38
View File
@@ -922,6 +922,10 @@ type pushClientAction struct {
data map[string]interface{} data map[string]interface{}
} }
type changePermissionsAction struct {
kind string
}
type permissionsChangedAction struct{} type permissionsChangedAction struct{}
type joinedAction struct { type joinedAction struct {
@@ -1222,6 +1226,29 @@ func handleAction(c *webClient, a any) error {
} }
} }
} }
case changePermissionsAction:
switch a.kind {
case "op":
c.permissions = addnew("op", c.permissions)
g := c.Group()
if g != nil && g.Description().AllowRecording {
c.permissions = addnew("record", c.permissions)
}
case "unop":
c.permissions = remove("op", c.permissions)
c.permissions = remove("record", c.permissions)
case "present":
c.permissions = addnew("present", c.permissions)
case "unpresent":
c.permissions = remove("present", c.permissions)
case "shutup":
c.permissions = remove("message", c.permissions)
case "unshutup":
c.permissions = addnew("message", c.permissions)
default:
return group.UserError("unknown permission")
}
c.action(permissionsChangedAction{})
case permissionsChangedAction: case permissionsChangedAction:
g := c.Group() g := c.Group()
if g == nil { if g == nil {
@@ -1338,41 +1365,6 @@ func closeDownConn(c *webClient, id string, message string) error {
return nil return nil
} }
func setPermissions(g *group.Group, id string, perm string) error {
client := g.GetClient(id)
if client == nil {
return group.UserError("no such user")
}
c, ok := client.(*webClient)
if !ok {
return group.UserError("this is not a real user")
}
switch perm {
case "op":
c.permissions = addnew("op", c.permissions)
if g.Description().AllowRecording {
c.permissions = addnew("record", c.permissions)
}
case "unop":
c.permissions = remove("op", c.permissions)
c.permissions = remove("record", c.permissions)
case "present":
c.permissions = addnew("present", c.permissions)
case "unpresent":
c.permissions = remove("present", c.permissions)
case "shutup":
c.permissions = remove("message", c.permissions)
case "unshutup":
c.permissions = addnew("message", c.permissions)
default:
return group.UserError("unknown permission")
}
c.action(permissionsChangedAction{})
return nil
}
func (c *webClient) Kick(id string, user *string, message string) error { func (c *webClient) Kick(id string, user *string, message string) error {
c.action(kickAction{id, user, message}) c.action(kickAction{id, user, message})
return nil return nil
@@ -1912,10 +1904,17 @@ func handleClientMessage(c *webClient, m clientMessage) error {
if !member("op", c.permissions) { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
err := setPermissions(g, m.Dest, m.Kind) t := g.GetClient(m.Dest)
if err != nil { if t == nil {
return c.error(err) return c.error(group.UserError("no suck user"))
} }
target, ok := t.(*webClient)
if !ok {
return c.error(group.UserError(
"this is not a real user",
))
}
target.action(changePermissionsAction{m.Kind})
case "identify": case "identify":
if !member("op", c.permissions) { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))