1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-11-09 18:25:58 +01:00

Implement message permission and shutup command.

This commit is contained in:
Juliusz Chroboczek 2024-05-08 16:00:18 +02:00
parent 2b145317a5
commit 1315084185
9 changed files with 85 additions and 38 deletions

View file

@ -17,6 +17,8 @@ Galene 0.9 (unreleased)
* Implemented a contextual menu that triggers on a double click on * Implemented a contextual menu that triggers on a double click on
a chat entry. a chat entry.
* Added a new command "/stopshare". * Added a new command "/stopshare".
* Added a new permission "message" and new commands "shutup" and
"unshutup".
14 April 2024: Galene 0.8.2 14 April 2024: Galene 0.8.2

2
README
View file

@ -162,7 +162,7 @@ nobody will be able to join the group. The following fields are allowed:
- `users`: is a dictionary that maps user names to dictionaries with - `users`: is a dictionary that maps user names to dictionaries with
entries `password` and `permissions`; `permissions` should be one of entries `password` and `permissions`; `permissions` should be one of
`op`, `present` or `passive`; `op`, `present`, `message` or `observe`.
- `wildcard-user` is a dictionaries with entries `password` and `permissions` - `wildcard-user` is a dictionaries with entries `password` and `permissions`
that will be used for usernames with no matching entry in the `users` that will be used for usernames with no matching entry in the `users`
dictionary; dictionary;

View file

@ -24,8 +24,9 @@ type Permissions struct {
} }
var permissionsMap = map[string][]string{ var permissionsMap = map[string][]string{
"op": []string{"op", "present", "token"}, "op": []string{"op", "present", "message", "token"},
"present": []string{"present"}, "present": []string{"present", "message"},
"message": []string{"message"},
"observe": []string{}, "observe": []string{},
"admin": []string{"admin"}, "admin": []string{"admin"},
} }
@ -528,7 +529,7 @@ func upgradeDescription(desc *Description) error {
desc.Presenter = nil desc.Presenter = nil
} }
if desc.Other != nil { if desc.Other != nil {
upgradeUsers(desc.Other, "observe") upgradeUsers(desc.Other, "message")
desc.Other = nil desc.Other = nil
} }

View file

@ -63,11 +63,11 @@ var descJSON = `
"users": { "users": {
"jch": {"password": "topsecret", "permissions": "op"}, "jch": {"password": "topsecret", "permissions": "op"},
"john": {"password": "secret", "permissions": "present"}, "john": {"password": "secret", "permissions": "present"},
"james": {"password": "secret2", "permissions": "observe"}, "james": {"password": "secret2", "permissions": "message"},
"peter": {"password": "secret4"} "peter": {"password": "secret4"}
}, },
"wildcard-user": "wildcard-user":
{"permissions": "observe", "password": {"type":"wildcard"}} {"permissions": "message", "password": {"type":"wildcard"}}
}` }`
func TestDescriptionJSON(t *testing.T) { func TestDescriptionJSON(t *testing.T) {
@ -139,6 +139,10 @@ func TestUpgradeDescription(t *testing.T) {
} }
for k, v1 := range d1.Users { for k, v1 := range d1.Users {
if k == "peter" {
// not representable in the old format
continue
}
v2 := d2.Users[k] v2 := d2.Users[k]
if !reflect.DeepEqual(v1.Password, v2.Password) || if !reflect.DeepEqual(v1.Password, v2.Password) ||
!permissionsEqual( !permissionsEqual(

View file

@ -114,19 +114,19 @@ type credPerm struct {
var goodClients = []credPerm{ var goodClients = []credPerm{
{ {
ClientCredentials{Username: &jch, Password: "topsecret"}, ClientCredentials{Username: &jch, Password: "topsecret"},
[]string{"op", "present", "token"}, []string{"op", "present", "message", "token"},
}, },
{ {
ClientCredentials{Username: &john, Password: "secret"}, ClientCredentials{Username: &john, Password: "secret"},
[]string{"present"}, []string{"present", "message"},
}, },
{ {
ClientCredentials{Username: &james, Password: "secret2"}, ClientCredentials{Username: &james, Password: "secret2"},
[]string{}, []string{"message"},
}, },
{ {
ClientCredentials{Username: &paul, Password: "secret3"}, ClientCredentials{Username: &paul, Password: "secret3"},
[]string{}, []string{"message"},
}, },
{ {
ClientCredentials{Username: &peter, Password: "secret4"}, ClientCredentials{Username: &peter, Password: "secret4"},
@ -189,29 +189,29 @@ func TestExtraPermissions(t *testing.T) {
} }
} }
doit("jch", []string{"op", "token", "present"}) doit("jch", []string{"op", "token", "present", "message"})
doit("john", []string{"present"}) doit("john", []string{"present", "message"})
doit("james", []string{}) doit("james", []string{})
d.AllowRecording = true d.AllowRecording = true
d.UnrestrictedTokens = false d.UnrestrictedTokens = false
doit("jch", []string{"op", "record", "token", "present"}) doit("jch", []string{"op", "record", "token", "present", "message"})
doit("john", []string{"present"}) doit("john", []string{"present", "message"})
doit("james", []string{}) doit("james", []string{})
d.AllowRecording = false d.AllowRecording = false
d.UnrestrictedTokens = true d.UnrestrictedTokens = true
doit("jch", []string{"op", "token", "present"}) doit("jch", []string{"op", "token", "present", "message"})
doit("john", []string{"token", "present"}) doit("john", []string{"token", "present", "message"})
doit("james", []string{}) doit("james", []string{})
d.AllowRecording = true d.AllowRecording = true
d.UnrestrictedTokens = true d.UnrestrictedTokens = true
doit("jch", []string{"op", "record", "token", "present"}) doit("jch", []string{"op", "record", "token", "present", "message"})
doit("john", []string{"token", "present"}) doit("john", []string{"token", "present", "message"})
doit("james", []string{}) doit("james", []string{})
} }

View file

@ -1349,6 +1349,10 @@ func setPermissions(g *group.Group, id string, perm string) error {
c.permissions = addnew("present", c.permissions) c.permissions = addnew("present", c.permissions)
case "unpresent": case "unpresent":
c.permissions = remove("present", c.permissions) c.permissions = remove("present", c.permissions)
case "shutup":
c.permissions = remove("message", c.permissions)
case "unshutup":
c.permissions = addnew("message", c.permissions)
default: default:
return group.UserError("unknown permission") return group.UserError("unknown permission")
} }
@ -1570,6 +1574,10 @@ func handleClientMessage(c *webClient, m clientMessage) error {
return c.error(group.UserError("join a group first")) return c.error(group.UserError("join a group first"))
} }
if !member("message", c.permissions) {
return c.error(group.UserError("not authorised"))
}
now := time.Now() now := time.Now()
if m.Type == "chat" { if m.Type == "chat" {
@ -1855,7 +1863,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
return c.error(group.UserError("join a group first")) return c.error(group.UserError("join a group first"))
} }
switch m.Kind { switch m.Kind {
case "op", "unop", "present", "unpresent": case "op", "unop", "present", "unpresent", "shutup", "unshutup":
if !member("op", c.permissions) { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }

View file

@ -3288,10 +3288,13 @@ function makeToken(template) {
v["not-before"] = template["not-before"]; v["not-before"] = template["not-before"];
if('permissions' in template) if('permissions' in template)
v.permissions = template.permissions; v.permissions = template.permissions;
else if(serverConnection.permissions.indexOf('present') >= 0) else {
v.permissions = ['present'];
else
v.permissions = []; v.permissions = [];
if(serverConnection.permissions.indexOf('present') >= 0)
v.permissions.push('present');
if(serverConnection.permissions.indexOf('message') >= 0)
v.permissions.push('message');
}
serverConnection.groupAction('maketoken', v); serverConnection.groupAction('maketoken', v);
} }
@ -3520,6 +3523,20 @@ commands.unpresent = {
f: userCommand, f: userCommand,
}; };
commands.shutup = {
parameters: 'user',
description: 'revoke the right to send chat messages',
predicate: operatorPredicate,
f: userCommand,
};
commands.unshutup = {
parameters: 'user',
description: 'give the right to send chat messages',
predicate: operatorPredicate,
f: userCommand,
};
commands.mute = { commands.mute = {
parameters: 'user', parameters: 'user',
description: 'mute a remote user', description: 'mute a remote user',

View file

@ -106,6 +106,15 @@ func (token *Stateful) Check(host, group string, username *string) (string, []st
return user, token.Permissions, nil return user, token.Permissions, nil
} }
func member(v string, l []string) bool {
for _, w := range l {
if v == w {
return true
}
}
return false
}
// load updates the state from the corresponding file. // load updates the state from the corresponding file.
// called locked // called locked
func (state *state) load() (string, error) { func (state *state) load() (string, error) {
@ -155,6 +164,12 @@ func (state *state) load() (string, error) {
state.fileSize = 0 state.fileSize = 0
return "", err return "", err
} }
// the "message" permission was introduced in Galene 0.9,
// so add it to tokens read from disk. We can remove this
// hack in late 2024.
if !member("message", t.Permissions) {
t.Permissions = append(t.Permissions, "message")
}
ts[t.Token] = &t ts[t.Token] = &t
} }
state.tokens = ts state.tokens = ts

View file

@ -48,27 +48,27 @@ func TestStatefulCheck(t *testing.T) {
Token: "token", Token: "token",
Group: "group", Group: "group",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
} }
token2 := &Stateful{ token2 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "group",
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
} }
token3 := &Stateful{ token3 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "group",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &past, Expires: &past,
} }
token4 := &Stateful{ token4 := &Stateful{
Token: "token", Token: "token",
Group: "group", Group: "group",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
NotBefore: &nearFuture, NotBefore: &nearFuture,
} }
@ -85,27 +85,27 @@ func TestStatefulCheck(t *testing.T) {
group: "group", group: "group",
username: &user, username: &user,
expUsername: user, expUsername: user,
expPermissions: []string{"present"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token1, token: token1,
group: "group", group: "group",
username: &user2, username: &user2,
expUsername: user, expUsername: user,
expPermissions: []string{"present"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token1, token: token1,
group: "group", group: "group",
expUsername: user, expUsername: user,
expPermissions: []string{"present"}, expPermissions: []string{"present", "message"},
}, },
{ {
token: token2, token: token2,
group: "group", group: "group",
username: &user, username: &user,
expUsername: "", expUsername: "",
expPermissions: []string{"present"}, expPermissions: []string{"present", "message"},
}, },
} }
@ -232,14 +232,14 @@ func TestTokenStorage(t *testing.T) {
Token: "tok1", Token: "tok1",
Group: "test", Group: "test",
Username: &user1, Username: &user1,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
}, },
&Stateful{ &Stateful{
Token: "tok2", Token: "tok2",
Group: "test", Group: "test",
Username: &user2, Username: &user2,
Permissions: []string{"present", "record"}, Permissions: []string{"present", "record", "message"},
Expires: &nearFuture, Expires: &nearFuture,
NotBefore: &past, NotBefore: &past,
}, },
@ -247,7 +247,7 @@ func TestTokenStorage(t *testing.T) {
Token: "tok3", Token: "tok3",
Group: "test", Group: "test",
Username: &user3, Username: &user3,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &nearFuture, Expires: &nearFuture,
}, },
} }
@ -327,35 +327,35 @@ func TestExpire(t *testing.T) {
Token: "tok1", Token: "tok1",
Group: "test", Group: "test",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &now, Expires: &now,
}, },
&Stateful{ &Stateful{
Token: "tok2", Token: "tok2",
Group: "test", Group: "test",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &future, Expires: &future,
}, },
&Stateful{ &Stateful{
Token: "tok3", Token: "tok3",
Group: "test", Group: "test",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &now, Expires: &now,
}, },
&Stateful{ &Stateful{
Token: "tok4", Token: "tok4",
Group: "test", Group: "test",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &past, Expires: &past,
}, },
&Stateful{ &Stateful{
Token: "tok5", Token: "tok5",
Group: "test", Group: "test",
Username: &user, Username: &user,
Permissions: []string{"present"}, Permissions: []string{"present", "message"},
Expires: &longPast, Expires: &longPast,
}, },
} }