Commit a86fb08f authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Replace ClientPermissions with a list of strings.

Now that we support external auth, the permissions list is
open-ended.  Make it a list for simplicity.
parent 439dbaba
...@@ -385,7 +385,7 @@ allowed to join, then the authorisation server replies with a signed JWT ...@@ -385,7 +385,7 @@ allowed to join, then the authorisation server replies with a signed JWT
{ {
"sub": username, "sub": username,
"aud": "https://galene.example.org/group/groupname", "aud": "https://galene.example.org/group/groupname",
"permissions": ["present": true], "permissions": ["present"],
"iat": now, "iat": now,
"exp": now + 30s, "exp": now + 30s,
"iss": authorisation server URL "iss": authorisation server URL
......
...@@ -59,21 +59,19 @@ func (client *Client) Username() string { ...@@ -59,21 +59,19 @@ func (client *Client) Username() string {
return "RECORDING" return "RECORDING"
} }
func (client *Client) SetPermissions(perms group.ClientPermissions) { func (client *Client) SetPermissions(perms []string) {
return return
} }
func (client *Client) Permissions() group.ClientPermissions { func (client *Client) Permissions() []string {
return group.ClientPermissions{ return []string{"system"}
System: true,
}
} }
func (client *Client) Data() map[string]interface{} { func (client *Client) Data() map[string]interface{} {
return nil return nil
} }
func (client *Client) PushClient(group, kind, id, username string, permissions group.ClientPermissions, data map[string]interface{}) error { func (client *Client) PushClient(group, kind, id, username string, perms []string, data map[string]interface{}) error {
return nil return nil
} }
......
...@@ -81,13 +81,6 @@ type ClientPattern struct { ...@@ -81,13 +81,6 @@ type ClientPattern struct {
Password *Password `json:"password,omitempty"` Password *Password `json:"password,omitempty"`
} }
type ClientPermissions struct {
Op bool `json:"op,omitempty"`
Present bool `json:"present,omitempty"`
Record bool `json:"record,omitempty"`
System bool `json:"system,omitempty"`
}
type ClientCredentials struct { type ClientCredentials struct {
System bool System bool
Username string Username string
...@@ -99,12 +92,12 @@ type Client interface { ...@@ -99,12 +92,12 @@ type Client interface {
Group() *Group Group() *Group
Id() string Id() string
Username() string Username() string
Permissions() ClientPermissions Permissions() []string
SetPermissions(ClientPermissions) SetPermissions([]string)
Data() map[string]interface{} Data() map[string]interface{}
PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error PushConn(g *Group, id string, conn conn.Up, tracks []conn.UpTrack, replace string) error
RequestConns(target Client, g *Group, id string) error RequestConns(target Client, g *Group, id string) error
Joined(group, kind string) error Joined(group, kind string) error
PushClient(group, kind, id, username string, permissions ClientPermissions, data map[string]interface{}) error PushClient(group, kind, id, username string, perms []string, data map[string]interface{}) error
Kick(id, user, message string) error Kick(id, user, message string) error
} }
...@@ -544,6 +544,15 @@ func deleteUnlocked(g *Group) bool { ...@@ -544,6 +544,15 @@ func deleteUnlocked(g *Group) bool {
return true return true
} }
func member(v string, l []string) bool {
for _, w := range l {
if v == w {
return true
}
}
return false
}
func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) { func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) {
g, err := Add(group, nil) g, err := Add(group, nil)
if err != nil { if err != nil {
...@@ -555,7 +564,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) ...@@ -555,7 +564,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
clients := g.getClientsUnlocked(nil) clients := g.getClientsUnlocked(nil)
if !c.Permissions().System { if !member("system", c.Permissions()) {
perms, err := g.description.GetPermission(group, creds) perms, err := g.description.GetPermission(group, creds)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -563,7 +572,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) ...@@ -563,7 +572,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
c.SetPermissions(perms) c.SetPermissions(perms)
if !perms.Op { if !member("op", perms) {
if g.locked != nil { if g.locked != nil {
m := *g.locked m := *g.locked
if m == "" { if m == "" {
...@@ -574,7 +583,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) ...@@ -574,7 +583,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
if g.description.Autokick { if g.description.Autokick {
ops := false ops := false
for _, c := range clients { for _, c := range clients {
if c.Permissions().Op { if member("op", c.Permissions()) {
ops = true ops = true
break break
} }
...@@ -588,7 +597,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error) ...@@ -588,7 +597,7 @@ func AddClient(group string, c Client, creds ClientCredentials) (*Group, error)
} }
} }
if !perms.Op && g.description.MaxClients > 0 { if !member("op", perms) && g.description.MaxClients > 0 {
if len(g.clients) >= g.description.MaxClients { if len(g.clients) >= g.description.MaxClients {
return nil, UserError("too many users") return nil, UserError("too many users")
} }
...@@ -627,7 +636,7 @@ func autoLockKick(g *Group, clients []Client) { ...@@ -627,7 +636,7 @@ func autoLockKick(g *Group, clients []Client) {
return return
} }
for _, c := range clients { for _, c := range clients {
if c.Permissions().Op { if member("op", c.Permissions()) {
return return
} }
} }
...@@ -665,7 +674,7 @@ func DelClient(c Client) { ...@@ -665,7 +674,7 @@ func DelClient(c Client) {
c.Joined(g.Name(), "leave") c.Joined(g.Name(), "leave")
for _, cc := range clients { for _, cc := range clients {
cc.PushClient( cc.PushClient(
g.Name(), "delete", c.Id(), "", ClientPermissions{}, nil, g.Name(), "delete", c.Id(), "", nil, nil,
) )
} }
autoLockKick(g, clients) autoLockKick(g, clients)
...@@ -1066,82 +1075,76 @@ func GetDescription(name string) (*Description, error) { ...@@ -1066,82 +1075,76 @@ func GetDescription(name string) (*Description, error) {
return &desc, nil return &desc, nil
} }
func (desc *Description) GetPermission(group string, creds ClientCredentials) (ClientPermissions, error) { func (desc *Description) GetPermission(group string, creds ClientCredentials) ([]string, error) {
var p ClientPermissions
if !desc.AllowAnonymous && creds.Username == "" { if !desc.AllowAnonymous && creds.Username == "" {
return p, ErrAnonymousNotAuthorised return nil, ErrAnonymousNotAuthorised
} }
if found, good := matchClient(group, creds, desc.Op); found { if creds.Token == "" {
if good { if found, good := matchClient(group, creds, desc.Op); found {
p.Op = true if good {
p.Present = true var p []string
if desc.AllowRecording { p = []string{"op", "present"}
p.Record = true if desc.AllowRecording {
p = append(p, "record")
}
return p, nil
} }
return p, nil return nil, ErrNotAuthorised
} }
return p, ErrNotAuthorised if found, good := matchClient(group, creds, desc.Presenter); found {
} if good {
if found, good := matchClient(group, creds, desc.Presenter); found { return []string{"present"}, nil
if good { }
p.Present = true return nil, ErrNotAuthorised
return p, nil
} }
return p, ErrNotAuthorised if found, good := matchClient(group, creds, desc.Other); found {
} if good {
if found, good := matchClient(group, creds, desc.Other); found { return nil, nil
if good { }
return p, nil return nil, ErrNotAuthorised
} }
return p, ErrNotAuthorised return nil, ErrNotAuthorised
} }
if creds.Token != "" { aud, perms, err := token.Valid(
aud, perms, err := token.Valid( creds.Username, creds.Token, desc.AuthKeys,
creds.Username, creds.Token, desc.AuthKeys, )
) if err != nil {
if err != nil { log.Printf("Token authentication: %v", err)
log.Printf("Token authentication: %v", err) return nil, ErrNotAuthorised
return p, ErrNotAuthorised }
} conf, err := GetConfiguration()
conf, err := GetConfiguration() if err != nil {
log.Printf("Read config.json: %v", err)
return nil, err
}
ok := false
for _, u := range aud {
url, err := url.Parse(u)
if err != nil { if err != nil {
log.Printf("Read config.json: %v", err) log.Printf("Token URL: %v", err)
return p, err continue
} }
ok := false // if canonicalHost is not set, we allow tokens
for _, u := range aud { // for any domain name. Hopefully different
url, err := url.Parse(u) // servers use distinct keys.
if err != nil { if conf.CanonicalHost != "" {
log.Printf("Token URL: %v", err) if !strings.EqualFold(
url.Host, conf.CanonicalHost,
) {
continue continue
} }
// if canonicalHost is not set, we allow tokens
// for any domain name. Hopefully different
// servers use distinct keys.
if conf.CanonicalHost != "" {
if !strings.EqualFold(
url.Host, conf.CanonicalHost,
) {
continue
}
}
if url.Path == path.Join("/group", group)+"/" {
ok = true
break
}
} }
if !ok { if url.Path == path.Join("/group", group)+"/" {
return p, ErrNotAuthorised ok = true
break
} }
p.Op, _ = perms["op"].(bool)
p.Present, _ = perms["present"].(bool)
p.Record, _ = perms["record"].(bool)
return p, nil
} }
if !ok {
return p, ErrNotAuthorised return nil, ErrNotAuthorised
}
return perms, nil
} }
type Status struct { type Status struct {
......
...@@ -132,29 +132,29 @@ var badClients = []ClientCredentials{ ...@@ -132,29 +132,29 @@ var badClients = []ClientCredentials{
type credPerm struct { type credPerm struct {
c ClientCredentials c ClientCredentials
p ClientPermissions p []string
} }
var goodClients = []credPerm{ var goodClients = []credPerm{
{ {
ClientCredentials{Username: "jch", Password: "topsecret"}, ClientCredentials{Username: "jch", Password: "topsecret"},
ClientPermissions{Op: true, Present: true}, []string{"op", "present"},
}, },
{ {
ClientCredentials{Username: "john", Password: "secret"}, ClientCredentials{Username: "john", Password: "secret"},
ClientPermissions{Present: true}, []string{"present"},
}, },
{ {
ClientCredentials{Username: "john", Password: "secret2"}, ClientCredentials{Username: "john", Password: "secret2"},
ClientPermissions{Present: true}, []string{"present"},
}, },
{ {
ClientCredentials{Username: "james", Password: "secret3"}, ClientCredentials{Username: "james", Password: "secret3"},
ClientPermissions{}, nil,
}, },
{ {
ClientCredentials{Username: "paul", Password: "secret3"}, ClientCredentials{Username: "paul", Password: "secret3"},
ClientPermissions{}, nil,
}, },
} }
......
...@@ -56,7 +56,7 @@ type webClient struct { ...@@ -56,7 +56,7 @@ type webClient struct {
group *group.Group group *group.Group
id string id string
username string username string
permissions group.ClientPermissions permissions []string
data map[string]interface{} data map[string]interface{}
requested map[string][]string requested map[string][]string
done chan struct{} done chan struct{}
...@@ -86,7 +86,7 @@ func (c *webClient) Username() string { ...@@ -86,7 +86,7 @@ func (c *webClient) Username() string {
return c.username return c.username
} }
func (c *webClient) Permissions() group.ClientPermissions { func (c *webClient) Permissions() []string {
return c.permissions return c.permissions
} }
...@@ -94,13 +94,13 @@ func (c *webClient) Data() map[string]interface{} { ...@@ -94,13 +94,13 @@ func (c *webClient) Data() map[string]interface{} {
return c.data return c.data
} }
func (c *webClient) SetPermissions(perms group.ClientPermissions) { func (c *webClient) SetPermissions(perms []string) {
c.permissions = perms c.permissions = perms
} }
func (c *webClient) PushClient(group, kind, id, username string, permissions group.ClientPermissions, data map[string]interface{}) error { func (c *webClient) PushClient(group, kind, id, username string, perms []string, data map[string]interface{}) error {
return c.action(pushClientAction{ return c.action(pushClientAction{
group, kind, id, username, permissions, data, group, kind, id, username, perms, data,
}) })
} }
...@@ -115,7 +115,7 @@ type clientMessage struct { ...@@ -115,7 +115,7 @@ type clientMessage struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"` Token string `json:"token,omitempty"`
Privileged bool `json:"privileged,omitempty"` Privileged bool `json:"privileged,omitempty"`
Permissions *group.ClientPermissions `json:"permissions,omitempty"` Permissions []string `json:"permissions,omitempty"`
Status *group.Status `json:"status,omitempty"` Status *group.Status `json:"status,omitempty"`
Data map[string]interface{} `json:"data,omitempty"` Data map[string]interface{} `json:"data,omitempty"`
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
...@@ -900,7 +900,7 @@ type pushClientAction struct { ...@@ -900,7 +900,7 @@ type pushClientAction struct {
kind string kind string
id string id string
username string username string
permissions group.ClientPermissions permissions []string
data map[string]interface{} data map[string]interface{}
} }
...@@ -919,6 +919,33 @@ type kickAction struct { ...@@ -919,6 +919,33 @@ type kickAction struct {
var errEmptyId = group.ProtocolError("empty id") var errEmptyId = group.ProtocolError("empty id")
func member(v string, l []string) bool {
for _, w := range l {
if v == w {
return true
}
}
return false
}
func remove(v string, l []string) []string {
for i, w := range l {
if v == w {
l = append(l[:i], l[i+1:]...)
return l
}
}
return l
}
func addnew(v string, l []string) []string {
if member(v, l) {
return l
}
l = append(l, v)
return l
}
func clientLoop(c *webClient, ws *websocket.Conn) error { func clientLoop(c *webClient, ws *websocket.Conn) error {
read := make(chan interface{}, 1) read := make(chan interface{}, 1)
go clientReader(ws, read, c.done) go clientReader(ws, read, c.done)
...@@ -1102,12 +1129,13 @@ func handleAction(c *webClient, a interface{}) error { ...@@ -1102,12 +1129,13 @@ func handleAction(c *webClient, a interface{}) error {
log.Printf("got client for wrong group") log.Printf("got client for wrong group")
return nil return nil
} }
perms := append([]string(nil), a.permissions...)
return c.write(clientMessage{ return c.write(clientMessage{
Type: "user", Type: "user",
Kind: a.kind, Kind: a.kind,
Id: a.id, Id: a.id,
Username: a.username, Username: a.username,
Permissions: &a.permissions, Permissions: perms,
Data: a.data, Data: a.data,
}) })
case joinedAction: case joinedAction:
...@@ -1121,13 +1149,13 @@ func handleAction(c *webClient, a interface{}) error { ...@@ -1121,13 +1149,13 @@ func handleAction(c *webClient, a interface{}) error {
data = g.Data() data = g.Data()
} }
} }
perms := c.permissions perms := append([]string(nil), c.permissions...)
return c.write(clientMessage{ return c.write(clientMessage{
Type: "joined", Type: "joined",
Kind: a.kind, Kind: a.kind,
Group: a.group, Group: a.group,
Username: c.username, Username: c.username,
Permissions: &perms, Permissions: perms,
Status: status, Status: status,
Data: data, Data: data,
RTCConfiguration: ice.ICEConfiguration(), RTCConfiguration: ice.ICEConfiguration(),
...@@ -1137,18 +1165,18 @@ func handleAction(c *webClient, a interface{}) error { ...@@ -1137,18 +1165,18 @@ func handleAction(c *webClient, a interface{}) error {
if g == nil { if g == nil {
return errors.New("Permissions changed in no group") return errors.New("Permissions changed in no group")
} }
perms := c.permissions perms := append([]string(nil), c.permissions...)
status := g.Status(true) status := g.Status(true)
c.write(clientMessage{ c.write(clientMessage{
Type: "joined", Type: "joined",
Kind: "change", Kind: "change",
Group: g.Name(), Group: g.Name(),
Username: c.username, Username: c.username,
Permissions: &perms, Permissions: perms,
Status: &status, Status: &status,
RTCConfiguration: ice.ICEConfiguration(), RTCConfiguration: ice.ICEConfiguration(),
}) })
if !c.permissions.Present { if member("present", c.permissions) {
up := getUpConns(c) up := getUpConns(c)
for _, u := range up { for _, u := range up {
err := delUpConn( err := delUpConn(
...@@ -1220,7 +1248,7 @@ func leaveGroup(c *webClient) { ...@@ -1220,7 +1248,7 @@ func leaveGroup(c *webClient) {
} }
group.DelClient(c) group.DelClient(c)
c.permissions = group.ClientPermissions{} c.permissions = nil
c.data = nil c.data = nil
c.requested = make(map[string][]string) c.requested = make(map[string][]string)
c.group = nil c.group = nil
...@@ -1260,17 +1288,17 @@ func setPermissions(g *group.Group, id string, perm string) error { ...@@ -1260,17 +1288,17 @@ func setPermissions(g *group.Group, id string, perm string) error {
switch perm { switch perm {
case "op": case "op":
c.permissions.Op = true c.permissions = addnew("op", c.permissions)
if g.Description().AllowRecording { if g.Description().AllowRecording {
c.permissions.Record = true c.permissions = addnew("record", c.permissions)
} }
case "unop": case "unop":
c.permissions.Op = false c.permissions = remove("op", c.permissions)
c.permissions.Record = false c.permissions = remove("record", c.permissions)
case "present": case "present":
c.permissions.Present = true c.permissions = addnew("present", c.permissions)
case "unpresent": case "unpresent":
c.permissions.Present = false c.permissions = remove("present", c.permissions)
default: default:
return group.UserError("unknown permission") return group.UserError("unknown permission")
} }
...@@ -1356,7 +1384,6 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1356,7 +1384,6 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Kind: "fail", Kind: "fail",
Group: m.Group, Group: m.Group,
Username: c.username, Username: c.username,
Permissions: &group.ClientPermissions{},
Value: s, Value: s,
}) })
} }
...@@ -1368,7 +1395,6 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1368,7 +1395,6 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Kind: "redirect", Kind: "redirect",
Group: m.Group, Group: m.Group,
Username: c.username, Username: c.username,
Permissions: &group.ClientPermissions{},
Value: redirect, Value: redirect,
}) })
} }
...@@ -1407,7 +1433,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1407,7 +1433,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
if m.Id == "" { if m.Id == "" {
return errEmptyId return errEmptyId
} }
if !c.permissions.Present { if !member("present", c.permissions) {
if m.Replace != "" { if m.Replace != "" {
delUpConn(c, m.Replace, c.id, true) delUpConn(c, m.Replace, c.id, true)
} }
...@@ -1508,7 +1534,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1508,7 +1534,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Source: m.Source, Source: m.Source,
Dest: m.Dest, Dest: m.Dest,
Username: m.Username, Username: m.Username,
Privileged: c.permissions.Op, Privileged: member("op", c.permissions),
Time: tm, Time: tm,
Kind: m.Kind, Kind: m.Kind,
NoEcho: m.NoEcho, NoEcho: m.NoEcho,
...@@ -1554,7 +1580,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1554,7 +1580,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
log.Printf("broadcast(clearchat): %v", err) log.Printf("broadcast(clearchat): %v", err)
} }
case "lock", "unlock": case "lock", "unlock":
if !c.permissions.Op { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
message := "" message := ""
...@@ -1564,7 +1590,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1564,7 +1590,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
} }
g.SetLocked(m.Kind == "lock", message) g.SetLocked(m.Kind == "lock", message)
case "record": case "record":
if !c.permissions.Record { if !member("record", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
for _, cc := range g.GetClients(c) { for _, cc := range g.GetClients(c) {
...@@ -1585,7 +1611,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1585,7 +1611,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
} }
requestConns(disk, c.group, "") requestConns(disk, c.group, "")
case "unrecord": case "unrecord":
if !c.permissions.Record { if !member("record", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
for _, cc := range g.GetClients(c) { for _, cc := range g.GetClients(c) {
...@@ -1596,7 +1622,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1596,7 +1622,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
} }
} }
case "subgroups": case "subgroups":
if !c.permissions.Op { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
s := "" s := ""
...@@ -1616,7 +1642,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1616,7 +1642,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
Value: s, Value: s,
}) })
case "setdata": case "setdata":
if !c.permissions.Op { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
data, ok := m.Value.(map[string]interface{}) data, ok := m.Value.(map[string]interface{})
...@@ -1636,7 +1662,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1636,7 +1662,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
} }
switch m.Kind { switch m.Kind {
case "op", "unop", "present", "unpresent": case "op", "unop", "present", "unpresent":
if !c.permissions.Op { 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) err := setPermissions(g, m.Dest, m.Kind)
...@@ -1644,7 +1670,7 @@ func handleClientMessage(c *webClient, m clientMessage) error { ...@@ -1644,7 +1670,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
return c.error(err) return c.error(err)
} }
case "kick": case "kick":
if !c.permissions.Op { if !member("op", c.permissions) {
return c.error(group.UserError("not authorised")) return c.error(group.UserError("not authorised"))
} }
message := "" message := ""
...@@ -1769,7 +1795,7 @@ func clientWriter(conn *websocket.Conn, ch <-chan interface{}, done chan<- struc ...@@ -1769,7 +1795,7 @@ func clientWriter(conn *websocket.Conn, ch <-chan interface{}, done chan<- struc
} }
func (c *webClient) Warn(oponly bool, message string) error { func (c *webClient) Warn(oponly bool, message string) error {
if oponly && !c.permissions.Op { if oponly && !member("op", c.permissions) {
return nil return nil
} }
......
...@@ -402,6 +402,7 @@ function setVisibility(id, visible) { ...@@ -402,6 +402,7 @@ function setVisibility(id, visible) {
function setButtonsVisibility() { function setButtonsVisibility() {
let connected = serverConnection && serverConnection.socket; let connected = serverConnection && serverConnection.socket;
let permissions = serverConnection.permissions; let permissions = serverConnection.permissions;
let present = permissions.indexOf('present') >= 0;
let local = !!findUpMedia('camera'); let local = !!findUpMedia('camera');
let canWebrtc = !(typeof RTCPeerConnection === 'undefined'); let canWebrtc = !(typeof RTCPeerConnection === 'undefined');
let canFile = let canFile =
...@@ -413,19 +414,19 @@ function setButtonsVisibility() { ...@@ -413,19 +414,19 @@ function setButtonsVisibility() {
let mobilelayout = isMobileLayout(); let mobilelayout = isMobileLayout();
// don't allow multiple presentations // don't allow multiple presentations
setVisibility('presentbutton', canWebrtc && permissions.present && !local); setVisibility('presentbutton', canWebrtc && present && !local);
setVisibility('unpresentbutton', local); setVisibility('unpresentbutton', local);
setVisibility('mutebutton', !connected || permissions.present); setVisibility('mutebutton', !connected || present);
// allow multiple shared documents // allow multiple shared documents
setVisibility('sharebutton', canWebrtc && permissions.present && setVisibility('sharebutton', canWebrtc && present &&
('getDisplayMedia' in navigator.mediaDevices)); ('getDisplayMedia' in navigator.mediaDevices));
setVisibility('mediaoptions', permissions.present); setVisibility('mediaoptions', present);
setVisibility('sendform', permissions.present); setVisibility('sendform', present);
setVisibility('simulcastform', permissions.present); setVisibility('simulcastform', present);
setVisibility('fileform', canFile && permissions.present); setVisibility('fileform', canFile && present);
setVisibility('collapse-video', mediacount && mobilelayout); setVisibility('collapse-video', mediacount && mobilelayout);
} }
...@@ -2012,9 +2013,9 @@ function userMenu(elt) { ...@@ -2012,9 +2013,9 @@ function userMenu(elt) {
items.push({label: 'Send file', onClick: () => { items.push({label: 'Send file', onClick: () => {
sendFile(id); sendFile(id);
}}); }});
if(serverConnection.permissions.op) { if(serverConnection.permissions.indexOf('op') >= 0) {
items.push({type: 'seperator'}); // sic items.push({type: 'seperator'}); // sic
if(user.permissions.present) if(user.permissions.indexOf('present') >= 0)
items.push({label: 'Forbid presenting', onClick: () => { items.push({label: 'Forbid presenting', onClick: () => {
serverConnection.userAction('unpresent', id); serverConnection.userAction('unpresent', id);
}}); }});
...@@ -2139,12 +2140,14 @@ function gotUser(id, kind) { ...@@ -2139,12 +2140,14 @@ function gotUser(id, kind) {
function displayUsername() { function displayUsername() {
document.getElementById('userspan').textContent = username; document.getElementById('userspan').textContent = username;
let op = serverConnection.permissions.indexOf('op') >= 0;
let present = serverConnection.permissions.indexOf('present') >= 0;
let text = ''; let text = '';
if(serverConnection.permissions.op && serverConnection.permissions.present) if(op && present)
text = '(op, presenter)'; text = '(op, presenter)';
else if(serverConnection.permissions.op) else if(op)
text = 'operator'; text = 'operator';
else if(serverConnection.permissions.present) else if(present)
text = 'presenter'; text = 'presenter';
document.getElementById('permspan').textContent = text; document.getElementById('permspan').textContent = text;
} }
...@@ -2178,7 +2181,7 @@ function setTitle(title) { ...@@ -2178,7 +2181,7 @@ function setTitle(title) {
/** /**
* @this {ServerConnection} * @this {ServerConnection}
* @param {string} group * @param {string} group
* @param {Object<string,boolean>} perms * @param {Array<string>} perms
* @param {Object<string,any>} status * @param {Object<string,any>} status
* @param {Object<string,any>} data * @param {Object<string,any>} data
* @param {string} message * @param {string} message
...@@ -2226,7 +2229,8 @@ async function gotJoined(kind, group, perms, status, data, message) { ...@@ -2226,7 +2229,8 @@ async function gotJoined(kind, group, perms, status, data, message) {
else else
this.request(mapRequest(getSettings().request)); this.request(mapRequest(getSettings().request));
if(serverConnection.permissions.present && !findUpMedia('camera')) { if(serverConnection.permissions.indexOf('present') >= 0 &&
!findUpMedia('camera')) {
if(present) { if(present) {
if(present === 'mike') if(present === 'mike')
updateSettings({video: ''}); updateSettings({video: ''});
...@@ -3050,14 +3054,14 @@ let commands = {}; ...@@ -3050,14 +3054,14 @@ let commands = {};
function operatorPredicate() { function operatorPredicate() {
if(serverConnection && serverConnection.permissions && if(serverConnection && serverConnection.permissions &&
serverConnection.permissions.op) serverConnection.permissions.indexOf('op') >= 0)
return null; return null;
return 'You are not an operator'; return 'You are not an operator';
} }
function recordingPredicate() { function recordingPredicate() {
if(serverConnection && serverConnection.permissions && if(serverConnection && serverConnection.permissions &&
serverConnection.permissions.record) serverConnection.permissions.indexOf('record') >= 0)
return null; return null;
return 'You are not allowed to record'; return 'You are not allowed to record';
} }
......
...@@ -63,7 +63,7 @@ function newLocalId() { ...@@ -63,7 +63,7 @@ function newLocalId() {
/** /**
* @typedef {Object} user * @typedef {Object} user
* @property {string} username * @property {string} username
* @property {Object<string,boolean>} permissions * @property {Array<string>} permissions
* @property {Object<string,any>} data * @property {Object<string,any>} data
* @property {Object<string,Object<string,boolean>>} down * @property {Object<string,Object<string,boolean>>} down
*/ */
...@@ -126,9 +126,9 @@ function ServerConnection() { ...@@ -126,9 +126,9 @@ function ServerConnection() {
/** /**
* The permissions granted to this connection. * The permissions granted to this connection.
* *
* @type {Object<string,boolean>} * @type {Array<string>}
*/ */
this.permissions = {}; this.permissions = [];
/** /**
* userdata is a convenient place to attach data to a ServerConnection. * userdata is a convenient place to attach data to a ServerConnection.
* It is not used by the library. * It is not used by the library.
...@@ -164,7 +164,7 @@ function ServerConnection() { ...@@ -164,7 +164,7 @@ function ServerConnection() {
* *
* kind is one of 'join', 'fail', 'change' or 'leave'. * kind is one of 'join', 'fail', 'change' or 'leave'.
* *
* @type{(this: ServerConnection, kind: string, group: string, permissions: Object<string,boolean>, status: Object<string,any>, data: Object<string,any>, message: string) => void} * @type{(this: ServerConnection, kind: string, group: string, permissions: Array<string>, status: Object<string,any>, data: Object<string,any>, message: string) => void}
*/ */
this.onjoined = null; this.onjoined = null;
/** /**
...@@ -207,7 +207,7 @@ function ServerConnection() { ...@@ -207,7 +207,7 @@ function ServerConnection() {
* @property {string} [password] * @property {string} [password]
* @property {string} [token] * @property {string} [token]
* @property {boolean} [privileged] * @property {boolean} [privileged]
* @property {Object<string,boolean>} [permissions] * @property {Array<string>} [permissions]
* @property {Object<string,any>} [status] * @property {Object<string,any>} [status]
* @property {Object<string,any>} [data] * @property {Object<string,any>} [data]
* @property {string} [group] * @property {string} [group]
...@@ -271,7 +271,7 @@ ServerConnection.prototype.connect = async function(url) { ...@@ -271,7 +271,7 @@ ServerConnection.prototype.connect = async function(url) {
resolve(sc); resolve(sc);
}; };
this.socket.onclose = function(e) { this.socket.onclose = function(e) {
sc.permissions = {}; sc.permissions = [];
for(let id in sc.up) { for(let id in sc.up) {
let c = sc.up[id]; let c = sc.up[id];
c.close(); c.close();
...@@ -286,7 +286,7 @@ ServerConnection.prototype.connect = async function(url) { ...@@ -286,7 +286,7 @@ ServerConnection.prototype.connect = async function(url) {
sc.onuser.call(sc, id, 'delete'); sc.onuser.call(sc, id, 'delete');
} }
if(sc.group && sc.onjoined) if(sc.group && sc.onjoined)
sc.onjoined.call(sc, 'leave', sc.group, {}, {}, {}, ''); sc.onjoined.call(sc, 'leave', sc.group, [], {}, {}, '');
sc.group = null; sc.group = null;
sc.username = null; sc.username = null;
if(sc.onclose) if(sc.onclose)
...@@ -337,7 +337,7 @@ ServerConnection.prototype.connect = async function(url) { ...@@ -337,7 +337,7 @@ ServerConnection.prototype.connect = async function(url) {
} }
if(sc.onjoined) if(sc.onjoined)
sc.onjoined.call(sc, m.kind, m.group, sc.onjoined.call(sc, m.kind, m.group,
m.permissions || {}, m.permissions || [],
m.status, m.data, m.status, m.data,
m.value || null); m.value || null);
break; break;
...@@ -348,7 +348,7 @@ ServerConnection.prototype.connect = async function(url) { ...@@ -348,7 +348,7 @@ ServerConnection.prototype.connect = async function(url) {
console.warn(`Duplicate user ${m.id} ${m.username}`); console.warn(`Duplicate user ${m.id} ${m.username}`);
sc.users[m.id] = { sc.users[m.id] = {
username: m.username, username: m.username,
permissions: m.permissions || {}, permissions: m.permissions || [],
data: m.data || {}, data: m.data || {},
down: {}, down: {},
}; };
...@@ -358,13 +358,13 @@ ServerConnection.prototype.connect = async function(url) { ...@@ -358,13 +358,13 @@ ServerConnection.prototype.connect = async function(url) {
console.warn(`Unknown user ${m.id} ${m.username}`); console.warn(`Unknown user ${m.id} ${m.username}`);
sc.users[m.id] = { sc.users[m.id] = {
username: m.username, username: m.username,
permissions: m.permissions || {}, permissions: m.permissions || [],
data: m.data || {}, data: m.data || {},
down: {}, down: {},
}; };
} else { } else {
sc.users[m.id].username = m.username; sc.users[m.id].username = m.username;
sc.users[m.id].permissions = m.permissions || {}; sc.users[m.id].permissions = m.permissions || [];
sc.users[m.id].data = m.data || {}; sc.users[m.id].data = m.data || {};
} }
break; break;
......
...@@ -105,7 +105,7 @@ func getKey(header map[string]interface{}, keys []map[string]interface{}) (inter ...@@ -105,7 +105,7 @@ func getKey(header map[string]interface{}, keys []map[string]interface{}) (inter
return nil, errors.New("key not found") return nil, errors.New("key not found")
} }
func Valid(username, token string, keys []map[string]interface{}) ([]string, map[string]interface{}, error) { func Valid(username, token string, keys []map[string]interface{}) ([]string, []string, error) {
tok, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { tok, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
return getKey(t.Header, keys) return getKey(t.Header, keys)
}) })
...@@ -118,19 +118,33 @@ func Valid(username, token string, keys []map[string]interface{}) ([]string, map ...@@ -118,19 +118,33 @@ func Valid(username, token string, keys []map[string]interface{}) ([]string, map
if !ok || sub != username { if !ok || sub != username {
return nil, nil, ErrUnexpectedSub return nil, nil, ErrUnexpectedSub
} }
aud, ok := claims["aud"]
var res []string var aud []string
if ok { if a, ok := claims["aud"]; ok && a != nil {
switch aud := aud.(type) { switch a := a.(type) {
case string: case string:
res = []string{aud} aud = []string{a}
case []string: case []string:
res = aud aud = a
} }
} }
perms, ok := claims["permissions"].(map[string]interface{})
if !ok { var perms []string
return nil, nil, errors.New("invalid 'permissions' field") if p, ok := claims["permissions"]; ok && p != nil {
pp, ok := p.([]interface{})
if !ok {
return nil, nil,
errors.New("invalid 'permissions' field")
}
perms = make([]string, len(pp))
for i, v := range pp {
w, ok := v.(string)
if !ok {
return nil, nil,
errors.New("invalid 'permissions' field")
}
perms[i] = w
}
} }
return res, perms, nil return aud, perms, nil
} }
...@@ -58,13 +58,7 @@ func TestES256(t *testing.T) { ...@@ -58,13 +58,7 @@ func TestES256(t *testing.T) {
} }
func TestValid(t *testing.T) { func TestValid(t *testing.T) {
key := `{ key := `{"alg":"HS256","k":"H7pCkktUl5KyPCZ7CKw09y1j460tfIv4dRcS1XstUKY","key_ops":["sign","verify"],"kty":"oct"}`
"kty":"EC",
"alg":"ES256",
"crv":"P-256",
"x":"CBo2DHISffe8bVr6bNspCiHK3zK9pfMGfWtpHnk9-Lw",
"y":"sD5dQ-bJu8AfRGLfA6MigQyUIOQHcYx6HQOdfIbLjHo"
}`
var k map[string]interface{} var k map[string]interface{}
err := json.Unmarshal([]byte(key), &k) err := json.Unmarshal([]byte(key), &k)
if err != nil { if err != nil {
...@@ -73,7 +67,7 @@ func TestValid(t *testing.T) { ...@@ -73,7 +67,7 @@ func TestValid(t *testing.T) {
keys := []map[string]interface{}{k} keys := []map[string]interface{}{k}
goodToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6eyJwcmVzZW50Ijp0cnVlfSwiaWF0IjoxNjQ1MTk1MzkxLCJleHAiOjIyNzU5MTUzOTEsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQ.PMgfwYwSLSFIfcNJdOEfHEZ41HM2CzbATuS1fTxncbaGyX-xXq7d9V04enXpLOMGnAlsZpOJvd7eJN2mngJMAg" goodToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDI5NCwiZXhwIjoyOTA2NzUwMjk0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.6xXpgBkBMn4PSBpnwYHb-gRn_Q97Yq9DoKkAf2_6iwc"
aud, perms, err := Valid("john", goodToken, keys) aud, perms, err := Valid("john", goodToken, keys)
...@@ -83,9 +77,7 @@ func TestValid(t *testing.T) { ...@@ -83,9 +77,7 @@ func TestValid(t *testing.T) {
if !reflect.DeepEqual(aud, []string{"https://galene.org:8443/group/auth/"}) { if !reflect.DeepEqual(aud, []string{"https://galene.org:8443/group/auth/"}) {
t.Errorf("Unexpected aud: %v", aud) t.Errorf("Unexpected aud: %v", aud)
} }
if !reflect.DeepEqual( if !reflect.DeepEqual(perms, []string{"present"}) {
perms, map[string]interface{}{"present": true},
) {
t.Errorf("Unexpected perms: %v", perms) t.Errorf("Unexpected perms: %v", perms)
} }
} }
...@@ -95,7 +87,7 @@ func TestValid(t *testing.T) { ...@@ -95,7 +87,7 @@ func TestValid(t *testing.T) {
t.Errorf("Token should have bad username") t.Errorf("Token should have bad username")
} }
badToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6eyJwcmVzZW50Ijp0cnVlfSwiaWF0IjoxNjQ1MTk2MDE5LCJleHAiOjIyNjAzNjQwMTksImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQ.4TN5zxzuKeNIw0rX0yirEkVYF1d0FHI_Lezmsa27ayi0R4ocSgTZ3q2bmlACXvyuoBqEEbuP4e77BUbGCHmpSg" badToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDQ2OSwiZXhwIjoyOTA2NzUwNDY5LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0."
_, _, err = Valid("john", badToken, keys) _, _, err = Valid("john", badToken, keys)
...@@ -104,7 +96,7 @@ func TestValid(t *testing.T) { ...@@ -104,7 +96,7 @@ func TestValid(t *testing.T) {
t.Errorf("Token should fail") t.Errorf("Token should fail")
} }
expiredToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6eyJwcmVzZW50Ijp0cnVlfSwiaWF0IjoxNjQ1MTk1NTY3LCJleHAiOjE2NDUxOTU1OTcsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQ.GXcLeyNVr5cnZjIECENyjMLH1HyNKWKkHMc9onvqA_RVYMyDLeeR_3NKH9Y7eKSXWC8jhatDWtH7Ed3KdsSxAA" expiredToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDMyMiwiZXhwIjoxNjQ1MzEwMzUyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0.jyqRhoV6iK54SvlP33Fy630aDo-sLNmKKi1kcfqs378"
_, _, err = Valid("john", expiredToken, keys) _, _, err = Valid("john", expiredToken, keys)
...@@ -112,8 +104,7 @@ func TestValid(t *testing.T) { ...@@ -112,8 +104,7 @@ func TestValid(t *testing.T) {
t.Errorf("Token should be expired") t.Errorf("Token should be expired")
} }
noneToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6eyJwcmVzZW50Ijp0cnVlfSwiaWF0IjoxNjQ1MTk1NzgyLCJleHAiOjIyNjAzNjM3ODIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTIzNC8ifQ." noneToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdWIiOiJqb2huIiwiYXVkIjoiaHR0cHM6Ly9nYWxlbmUub3JnOjg0NDMvZ3JvdXAvYXV0aC8iLCJwZXJtaXNzaW9ucyI6WyJwcmVzZW50Il0sImlhdCI6MTY0NTMxMDQwMSwiZXhwIjoxNjQ1MzEwNDMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEyMzQvIn0."
_, _, err = Valid("john", noneToken, keys) _, _, err = Valid("john", noneToken, keys)
if err == nil { if err == nil {
t.Errorf("Unsigned token should fail") t.Errorf("Unsigned token should fail")
......
...@@ -592,7 +592,16 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, groupname str ...@@ -592,7 +592,16 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, groupname str
Password: pass, Password: pass,
}, },
) )
if err != nil || !p.Record { record := false
if err == nil {
for _, v := range p {
if v == "record" {
record = true
break
}
}
}
if err != nil || !record {
if err == group.ErrNotAuthorised { if err == group.ErrNotAuthorised {
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment