diff --git a/README.PROTOCOL b/README.PROTOCOL index 2c001c5..82e8c33 100644 --- a/README.PROTOCOL +++ b/README.PROTOCOL @@ -310,7 +310,7 @@ A chat message may be sent using a `chat` message. ```javascript { type: 'chat', - kind: '' or 'me', + kind: null or 'me' or 'caption', source: source-id, username: username, dest: dest-id, @@ -321,6 +321,13 @@ A chat message may be sent using a `chat` message. } ``` +The field `kind` can have one of the following values: + + - `null` or the empty string, a normal chat message; + - `'me'`, an IRC-style first-person message; + - `'caption'`, a caption or subtitle (this requires the sender to have + the `caption` permission). + If `dest` is empty, the message is a broadcast message, destined to all of the clients in the group. If `source` is empty, then the message was originated by the server. The message is forwarded by the server without diff --git a/galenectl/galenectl_test.go b/galenectl/galenectl_test.go index 99dfcdd..175206d 100644 --- a/galenectl/galenectl_test.go +++ b/galenectl/galenectl_test.go @@ -42,7 +42,7 @@ func TestMakePassword(t *testing.T) { func TestFormatPermissions(t *testing.T) { tests := []struct{ j, v, p string }{ - {`"op"`, "op", "[mopt]"}, + {`"op"`, "op", "[cmopt]"}, {`"present"`, "present", "[mp]"}, {`"observe"`, "observe", "[]"}, {`"admin"`, "admin", "[a]"}, diff --git a/group/description.go b/group/description.go index 095fca6..f6eaf81 100644 --- a/group/description.go +++ b/group/description.go @@ -26,10 +26,11 @@ type Permissions struct { } var permissionsMap = map[string][]string{ - "op": {"op", "present", "message", "token"}, + "op": {"op", "present", "message", "caption", "token"}, "present": {"present", "message"}, "message": {"message"}, "observe": {}, + "caption": {"caption"}, "admin": {"admin"}, } diff --git a/group/group_test.go b/group/group_test.go index 3e2a5bb..1d8bb89 100644 --- a/group/group_test.go +++ b/group/group_test.go @@ -90,7 +90,7 @@ func TestChatHistory(t *testing.T) { if lh := len(g.GetChatHistory()); lh != l*3/4 { t.Errorf("Expected %v, got %v", l*3/4, lh) } - g.ClearChatHistory("", ""); + g.ClearChatHistory("", "") if lh := len(g.GetChatHistory()); lh != 0 { t.Errorf("Expected 0, got %v", lh) } @@ -125,6 +125,8 @@ var john = "john" var james = "james" var paul = "paul" var peter = "peter" +var admin = "admin" +var stt = "speech-to-text" var badClients = []ClientCredentials{ {Username: &jch, Password: "foo"}, @@ -137,10 +139,27 @@ type credPerm struct { p []string } +var desc2JSON = ` +{ + "max-history-age": 10, + "auto-subgroups": true, + "users": { + "jch": {"password": "topsecret", "permissions": "op"}, + "john": {"password": "secret", "permissions": "present"}, + "james": {"password": "secret2", "permissions": "message"}, + "peter": {"password": "secret4"}, + "admin": {"password": "admin", "permissions": "admin"}, + "speech-to-text": {"password": {"type": "wildcard"}, + "permissions": "caption"} + }, + "wildcard-user": + {"permissions": "message", "password": {"type":"wildcard"}} +}` + var goodClients = []credPerm{ { ClientCredentials{Username: &jch, Password: "topsecret"}, - []string{"op", "present", "message", "token"}, + []string{"op", "present", "message", "caption", "token"}, }, { ClientCredentials{Username: &john, Password: "secret"}, @@ -158,11 +177,19 @@ var goodClients = []credPerm{ ClientCredentials{Username: &peter, Password: "secret4"}, []string{}, }, + { + ClientCredentials{Username: &admin, Password: "admin"}, + []string{"admin"}, + }, + { + ClientCredentials{Username: &stt}, + []string{"caption"}, + }, } func TestPermissions(t *testing.T) { var g Group - err := json.Unmarshal([]byte(descJSON), &g.description) + err := json.Unmarshal([]byte(desc2JSON), &g.description) if err != nil { t.Fatalf("unmarshal: %v", err) } @@ -209,41 +236,46 @@ func TestExtraPermissions(t *testing.T) { } doit := func(u string, p []string) { + t.Helper() pu := d.Users[u].Permissions.Permissions(&d) if !permissionsEqual(pu, p) { t.Errorf("%v: expected %v, got %v", u, p, pu) } } - doit("jch", []string{"op", "token", "present", "message"}) + doit("jch", []string{"op", "token", "present", "message", "caption"}) doit("john", []string{"present", "message"}) doit("james", []string{}) d.AllowRecording = true d.UnrestrictedTokens = false - doit("jch", []string{"op", "record", "token", "present", "message"}) + doit("jch", []string{ + "op", "record", "token", "present", "message", "caption", + }) doit("john", []string{"present", "message"}) doit("james", []string{}) d.AllowRecording = false d.UnrestrictedTokens = true - doit("jch", []string{"op", "token", "present", "message"}) + doit("jch", []string{"op", "token", "present", "message", "caption"}) doit("john", []string{"token", "present", "message"}) doit("james", []string{}) d.AllowRecording = true d.UnrestrictedTokens = true - doit("jch", []string{"op", "record", "token", "present", "message"}) + doit("jch", []string{ + "op", "record", "token", "present", "message", "caption", + }) doit("john", []string{"token", "present", "message"}) doit("james", []string{}) } func TestUsernameTaken(t *testing.T) { var g Group - err := json.Unmarshal([]byte(descJSON), &g.description) + err := json.Unmarshal([]byte(desc2JSON), &g.description) if err != nil { t.Fatalf("unmarshal: %v", err) } diff --git a/rtpconn/webclient.go b/rtpconn/webclient.go index 2fe9842..5e6567c 100644 --- a/rtpconn/webclient.go +++ b/rtpconn/webclient.go @@ -1572,7 +1572,11 @@ func handleClientMessage(c *webClient, m clientMessage) error { return c.error(group.UserError("join a group first")) } - if !member("message", c.permissions) { + required := "message" + if m.Type == "chat" && m.Kind == "caption" { + required = "caption" + } + if !member(required, c.permissions) { return c.error(group.UserError("not authorised")) }