From 2890d21c98549b23cb82919cfc4e090e070ed181 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Sat, 25 Apr 2020 02:25:51 +0200 Subject: [PATCH] Add group permissions. --- client.go | 17 +++++-- group.go | 124 +++++++++++++++++++++++++++++++++++++++++------- sfu.go | 3 ++ static/sfu.html | 4 +- static/sfu.js | 10 ++++ 5 files changed, 136 insertions(+), 22 deletions(-) diff --git a/client.go b/client.go index abd256d..876f01d 100644 --- a/client.go +++ b/client.go @@ -89,6 +89,7 @@ type clientMessage struct { Id string `json:"id,omitempty"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` + Permissions userPermission `json:"permissions,omitempty"` Group string `json:"group,omitempty"` Value string `json:"value,omitempty"` Message string `json:"message,omitempty"` @@ -149,7 +150,7 @@ func startClient(conn *websocket.Conn) (err error) { c.writerDone = make(chan struct{}) go clientWriter(conn, c.writeCh, c.writerDone) - g, users, err := addClient(m.Group, c) + g, users, err := addClient(m.Group, c, m.Username, m.Password) if err != nil { return } @@ -262,7 +263,7 @@ func addUpConn(c *client, id string) (*upConnection, error) { clients := c.group.getClients(c) for _, cc := range clients { cc.action(addTrackAction{id, local, u, done}) - if(done && u.label != "") { + if done && u.label != "" { cc.action(addLabelAction{id, u.label}) } } @@ -389,7 +390,7 @@ func delDownConn(c *client, id string) { log.Printf("Deleting unknown connection") return } - conn := c.down[id]; + conn := c.down[id] if conn == nil { log.Printf("Deleting unknown connection") return @@ -667,6 +668,11 @@ func clientLoop(c *client, conn *websocket.Conn) error { g := c.group + c.write(clientMessage{ + Type: "permissions", + Permissions: c.permissions, + }) + for _, cc := range g.getClients(c) { cc.action(pushTracksAction{c}) } @@ -721,7 +727,7 @@ func clientLoop(c *client, conn *websocket.Conn) error { for _, u := range c.up { var done bool for i, p := range u.pairs { - done = i >= u.streamCount - 1 + done = i >= u.streamCount-1 a.c.action(addTrackAction{ u.id, p.local, u, done, @@ -747,6 +753,9 @@ func clientLoop(c *client, conn *websocket.Conn) error { func handleClientMessage(c *client, m clientMessage) error { switch m.Type { case "offer": + if !c.permissions.Present { + return userError("not authorized") + } if m.Offer == nil { return protocolError("null offer") } diff --git a/group.go b/group.go index 252afe4..f75026d 100644 --- a/group.go +++ b/group.go @@ -6,7 +6,10 @@ package main import ( + "encoding/json" "log" + "os" + "path/filepath" "sync" "github.com/pion/webrtc/v2" @@ -34,20 +37,21 @@ type downConnection struct { } type client struct { - group *group - id string - username string - done chan struct{} - writeCh chan interface{} - writerDone chan struct{} - actionCh chan interface{} - down map[string]*downConnection - up map[string]*upConnection + group *group + id string + username string + permissions userPermission + done chan struct{} + writeCh chan interface{} + writerDone chan struct{} + actionCh chan interface{} + down map[string]*downConnection + up map[string]*upConnection } type group struct { - name string - public bool + name string + description *groupDescription mu sync.Mutex clients []*client @@ -102,8 +106,13 @@ func addGroup(name string) (*group, error) { g := groups.groups[name] if g == nil { + desc, err := getDescription(name) + if err != nil { + return nil, err + } g = &group{ - name: name, + name: name, + description: desc, } groups.groups[name] = g } @@ -130,12 +139,18 @@ type userid struct { username string } -func addClient(name string, client *client) (*group, []userid, error) { +func addClient(name string, client *client, user, pass string) (*group, []userid, error) { g, err := addGroup(name) if err != nil { return nil, nil, err } + perms, err := getPermission(g.description, user, pass) + if err != nil { + return nil, nil, err + } + client.permissions = perms + var users []userid g.mu.Lock() defer g.mu.Unlock() @@ -182,8 +197,8 @@ func (g *group) Range(f func(c *client) bool) { defer g.mu.Unlock() for _, c := range g.clients { ok := f(c) - if(!ok){ - break; + if !ok { + break } } } @@ -218,6 +233,83 @@ func (c *client) action(m interface{}) error { } } +type groupUser struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +func matchUser(user, pass string, users []groupUser) (bool, bool) { + for _, u := range users { + if (u.Username == "" || u.Username == user) { + return true, (u.Password == "" || u.Password == pass) + } + } + return false, false +} + +type groupDescription struct { + Public bool `json:"public,omitempty"` + AllowAnonymous bool `json:"allow-anonymous,omitempty"` + Admin []groupUser `json:"admin,omitempty"` + Presenter []groupUser `json:"presenter,omitempty"` + Other []groupUser `json:"other,omitempty"` +} + +func getDescription(name string) (*groupDescription, error) { + r, err := os.Open(filepath.Join(groupsDir, name+".json")) + if err != nil { + if os.IsNotExist(err) { + err = userError("group does not exist") + } else if os.IsPermission(err) { + err = userError("unauthorised") + } + return nil, err + } + defer r.Close() + + var desc groupDescription + d := json.NewDecoder(r) + err = d.Decode(&desc) + if err != nil { + return nil, err + } + return &desc, nil +} + +type userPermission struct { + Admin bool `json:"admin,omitempty"` + Present bool `json:"present,omitempty"` +} + +func getPermission(desc *groupDescription, user, pass string) (userPermission, error) { + var p userPermission + if !desc.AllowAnonymous && user == "" { + return p, userError("anonymous users not allowed in this group") + } + if found, good := matchUser(user, pass, desc.Admin); found { + if good { + p.Admin = true + p.Present = true + return p, nil + } + return p, userError("not authorized") + } + if found, good := matchUser(user, pass, desc.Presenter); found { + if good { + p.Present = true + return p, nil + } + return p, userError("not authorized") + } + if found, good := matchUser(user, pass, desc.Other); found { + if good { + return p, nil + } + return p, userError("not authorized") + } + return p, userError("not authorized") +} + type publicGroup struct { Name string `json:"name"` ClientCount int `json:"clientCount"` @@ -228,7 +320,7 @@ func getPublicGroups() []publicGroup { groups.mu.Lock() defer groups.mu.Unlock() for _, g := range groups.groups { - if g.public { + if g.description.Public { gs = append(gs, publicGroup{ Name: g.name, ClientCount: len(g.clients), diff --git a/sfu.go b/sfu.go index b0a720a..223cd0b 100644 --- a/sfu.go +++ b/sfu.go @@ -22,6 +22,7 @@ import ( var httpAddr string var staticRoot string var dataDir string +var groupsDir string var iceFilename string func main() { @@ -30,6 +31,8 @@ func main() { "web server root `directory`") flag.StringVar(&dataDir, "data", "./data/", "data `directory`") + flag.StringVar(&groupsDir, "groups", "./groups/", + "group description `directory`") flag.Parse() iceFilename = filepath.Join(staticRoot, "ice-servers.json") diff --git a/static/sfu.html b/static/sfu.html index a331a2e..0ab0b1e 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -29,9 +29,9 @@
- + - +
diff --git a/static/sfu.js b/static/sfu.js index ced50fc..9a683cc 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -279,8 +279,10 @@ function serverConnect() { socket.onclose = function(e) { setConnected(false); document.getElementById('presenterbox').checked = false; + document.getElementById('presenterbox').disabled = true; setLocalMedia(); document.getElementById('sharebox').checked = false; + document.getElementById('sharebox').disabled = true; setShareMedia(); reject(new Error('websocket close ' + e.code + ' ' + e.reason)); }; @@ -305,6 +307,9 @@ function serverConnect() { case 'label': gotLabel(m.id, m.value); break; + case 'permissions': + gotPermissions(m.permissions); + break; case 'user': gotUser(m.id, m.username, m.del); break; @@ -483,6 +488,11 @@ function gotUser(id, name, del) { addUser(id, name); } +function gotPermissions(permissions) { + document.getElementById('presenterbox').disabled = !permissions.present; + document.getElementById('sharebox').disabled = !permissions.present; +} + const urlRegexp = /https?:\/\/[-a-zA-Z0-9@:%/._\+~#=?]+[-a-zA-Z0-9@:%/_\+~#=]/g; function formatLine(line) {