diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 4dbd288e..6cde44fa 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -392,23 +392,35 @@ Information retrieval "Sessions": { "Id": "Client generated request ID", "Token": "Request token", - "TokenType": "Token type" + "Type": "Token type" } } - Valid known token types are: "contact", "token". + Valid known token types are: "contact". - Sesions (Response with Id, Token and Type from request and + Sessions (Response with Id, Token and Type from request and populated Session list). { "Type": "Sessions", "Sessions": { "Id": "ID as from request", - "Token": "Token as from request", - "TokenType": "Type as from request", - "Sessions": [ - "1", "2", "3", ... + "Token": "Token as in request", + "Type": "Type as in request", + "Users": [ + { + "Type": "Online", + "Id": "1", + "Ua": "Firefox 27", + "Status": {...} + }, + { + "Type": "Online", + "Id": "3", + "Userid": "u3", + "Ua": "Chrome 28", + "Status": {...} + }, ... ] } } diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index ef1209be..db3ed4ae 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -75,6 +75,11 @@ type DataSession struct { Status interface{} } +type DataUser struct { + Id string + Sessions int +} + type DataBye struct { Type string To string @@ -142,6 +147,7 @@ type DataIncoming struct { Conference *DataConference Alive *DataAlive Authentication *DataAuthentication + Sessions *DataSessions } type DataOutgoing struct { @@ -153,8 +159,10 @@ type DataOutgoing struct { type DataSessions struct { Type string Users []*DataSession - Index uint64 - Batch uint64 + Id string `json:",omitempty"` + Token string `json:",omitempty"` + Index uint64 `json:",omitempty"` + Batch uint64 `json:",omitempty"` } type DataConference struct { diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 1be5d7cd..448cd1b6 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -57,11 +57,13 @@ type HubStat struct { Rooms int `json:"rooms"` Connections int `json:"connections"` Sessions int `json:"sessions"` + Users int `json:"users"` Count uint64 `json:"count"` BroadcastChatMessages uint64 `json:"broadcastchatmessages"` UnicastChatMessages uint64 `json:"unicastchatmessages"` IdsInRoom map[string][]string `json:"idsinroom,omitempty"` SessionsById map[string]*DataSession `json:"sessionsbyid,omitempty"` + UsersById map[string]*DataUser `json:"usersbyid,omitempty"` ConnectionsByIdx map[string]string `json:"connectionsbyidx,omitempty"` } @@ -70,6 +72,7 @@ type Hub struct { connectionTable map[string]*Connection sessionTable map[string]*Session roomTable map[string]*RoomWorker + userTable map[string]*User version string config *Config sessionSecret []byte @@ -94,6 +97,7 @@ func NewHub(version string, config *Config, sessionSecret, encryptionSecret, tur connectionTable: make(map[string]*Connection), sessionTable: make(map[string]*Session), roomTable: make(map[string]*RoomWorker), + userTable: make(map[string]*User), version: version, config: config, sessionSecret: []byte(sessionSecret), @@ -128,6 +132,7 @@ func (h *Hub) Stat(details bool) *HubStat { Rooms: len(h.roomTable), Connections: len(h.connectionTable), Sessions: len(h.sessionTable), + Users: len(h.userTable), Count: h.count, BroadcastChatMessages: atomic.LoadUint64(&h.broadcastChatMessages), UnicastChatMessages: atomic.LoadUint64(&h.unicastChatMessages), @@ -147,6 +152,11 @@ func (h *Hub) Stat(details bool) *HubStat { sessions[sessionid] = session.Data() } stat.SessionsById = sessions + users := make(map[string]*DataUser) + for userid, user := range h.userTable { + users[userid] = user.Data() + } + stat.UsersById = users connections := make(map[string]string) for id, connection := range h.connectionTable { connections[fmt.Sprintf("%d", connection.Idx)] = id @@ -203,8 +213,8 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { if st == nil { sid := NewRandomString(32) id, _ := h.tickets.Encode("id", sid) - session = NewSession(id, sid, userid) - log.Println("Created new session id", len(id), id, sid, userid) + session = NewSession(id, sid) + log.Println("Created new session id", len(id), id, sid) } else { if userid == "" { userid = st.Userid @@ -212,7 +222,11 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { if !usersEnabled { userid = "" } - session = NewSession(st.Id, st.Sid, userid) + session = NewSession(st.Id, st.Sid) + } + + if userid != "" { + h.authenticateHandler(session, st, userid) } return session @@ -359,6 +373,15 @@ func (h *Hub) unregisterHandler(c *Connection) { session := c.Session delete(h.connectionTable, c.Id) delete(h.sessionTable, c.Id) + if session != nil && session.Userid != "" { + user, ok := h.userTable[session.Userid] + if ok { + empty := user.RemoveSession(session) + if empty { + delete(h.userTable, session.Userid) + } + } + } h.mutex.Unlock() if session != nil { h.buddyImages.Delete(session.Id) @@ -397,6 +420,51 @@ func (h *Hub) aliveHandler(c *Connection, alive *DataAlive) { } +func (h *Hub) sessionsHandler(c *Connection, sessions *DataSessions) { + + reply := false + + switch sessions.Type { + case "contact": + contact := &Contact{} + err := h.contacts.Decode("contactConfirmed", sessions.Token, contact) + if err != nil { + log.Println("Failed to decode incoming contact token", err) + return + } + if contact.A != c.Session.Userid && contact.B != c.Session.Userid { + log.Println("Ignoring foreign contact token") + return + } + h.mutex.RLock() + defer h.mutex.RUnlock() + user, ok := h.userTable[c.Session.Userid] + if !ok { + return + } + sessions.Users = user.SessionsData() + reply = true + default: + log.Println("Unkown incoming sessions request type", sessions.Type) + } + + if reply { + + sessionsJson := h.buffers.New() + encoder := json.NewEncoder(sessionsJson) + err := encoder.Encode(&DataOutgoing{From: c.Id, Data: sessions}) + if err != nil { + log.Println("Sessions error while encoding JSON", err) + sessionsJson.Decref() + return + } + c.send(sessionsJson) + sessionsJson.Decref() + + } + +} + func (h *Hub) sessionupdateHandler(s *SessionUpdate) uint64 { //fmt.Println("Userupdate", u) @@ -444,6 +512,25 @@ func (h *Hub) sessiontokenHandler(st *SessionToken) (string, error) { } +func (h *Hub) authenticateHandler(session *Session, st *SessionToken, userid string) error { + + err := session.Authenticate(h.realm, st, userid) + if err == nil { + // Authentication success. + h.mutex.Lock() + user, ok := h.userTable[session.Userid] + if !ok { + user = NewUser(session.Userid) + h.userTable[session.Userid] = user + } + h.mutex.Unlock() + user.AddSession(session) + } + + return err + +} + func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactRequest) error { var err error diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index f6da1486..9c7668de 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -168,6 +168,8 @@ func (s *Server) OnText(c *Connection, b Buffer) { } case "Alive": s.Alive(c, msg.Alive) + case "Sessions": + s.Sessions(c, msg.Sessions) default: log.Println("OnText unhandled message type", msg.Type) } @@ -196,6 +198,12 @@ func (s *Server) Alive(c *Connection, alive *DataAlive) { } +func (s *Server) Sessions(c *Connection, sessions *DataSessions) { + + c.h.sessionsHandler(c, sessions) + +} + func (s *Server) UpdateSession(c *Connection, su *SessionUpdate) uint64 { su.Id = c.Id @@ -243,9 +251,9 @@ func (s *Server) Users(c *Connection) { func (s *Server) Authenticate(c *Connection, st *SessionToken) bool { - err := c.Session.Authenticate(c.h.realm, st) + err := c.h.authenticateHandler(c.Session, st, "") if err == nil { - log.Println("Authentication success", c.Id, c.Idx, st.Userid) + log.Println("Authentication success", c.Id, c.Idx, c.Session.Userid) return true } else { log.Println("Authentication failed", err, c.Id, c.Idx, st.Userid, st.Nonce) diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index 1ba35e9b..0d6f4fdf 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -42,12 +42,11 @@ type Session struct { mutex sync.RWMutex } -func NewSession(id, sid, userid string) *Session { +func NewSession(id, sid string) *Session { return &Session{ - Id: id, - Sid: sid, - Userid: userid, + Id: id, + Sid: sid, } } @@ -96,7 +95,7 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { } -func (s *Session) Authenticate(realm string, st *SessionToken) error { +func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -104,20 +103,21 @@ func (s *Session) Authenticate(realm string, st *SessionToken) error { if s.Userid != "" { return errors.New("session already authenticated") } - if s.Nonce == "" || s.Nonce != st.Nonce { - return errors.New("nonce validation failed") - } - var userid string - err := sessionNonces.Decode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Nonce, &userid) - if err != nil { - return err - } - if st.Userid != userid { - return errors.New("user id mismatch") + if userid == "" { + if s.Nonce == "" || s.Nonce != st.Nonce { + return errors.New("nonce validation failed") + } + err := sessionNonces.Decode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Nonce, &userid) + if err != nil { + return err + } + if st.Userid != userid { + return errors.New("user id mismatch") + } + s.Nonce = "" } - s.Nonce = "" - s.Userid = st.Userid + s.Userid = userid s.UpdateRev++ return nil diff --git a/src/app/spreed-webrtc-server/user.go b/src/app/spreed-webrtc-server/user.go new file mode 100644 index 00000000..fc041bc0 --- /dev/null +++ b/src/app/spreed-webrtc-server/user.go @@ -0,0 +1,90 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "fmt" + "sync" +) + +type User struct { + Id string + sessionTable map[string]*Session + mutex sync.RWMutex +} + +func NewUser(id string) *User { + + user := &User{ + Id: id, + sessionTable: make(map[string]*Session), + } + return user + +} + +// Return true if first session. +func (u *User) AddSession(s *Session) bool { + first := false + u.mutex.Lock() + u.sessionTable[s.Id] = s + if len(u.sessionTable) == 1 { + fmt.Println("First session registered for user", u.Id) + first = true + } + u.mutex.Unlock() + return first +} + +// Return true if no session left. +func (u *User) RemoveSession(s *Session) bool { + last := false + u.mutex.Lock() + delete(u.sessionTable, s.Id) + if len(u.sessionTable) == 0 { + fmt.Println("Last session unregistered for user", u.Id) + last = true + } + u.mutex.Unlock() + return last +} + +func (u *User) Data() *DataUser { + u.mutex.RLock() + defer u.mutex.RUnlock() + return &DataUser{ + Id: u.Id, + Sessions: len(u.sessionTable), + } +} + +func (u *User) SessionsData() []*DataSession { + + sessions := make([]*DataSession, 0, len(u.sessionTable)) + u.mutex.RLock() + defer u.mutex.RUnlock() + for _, session := range u.sessionTable { + sessions = append(sessions, session.Data()) + } + return sessions + +}