diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 65d28cc2..6c99bc8f 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -68,7 +68,8 @@ Sending vs receiving document data encapsulation "From": "4", "To": "5", "Data": {}, - "Iid": "request-identifier-unique-to-client" + "Iid": "request-identifier-unique-to-client", + "A": "attestation-session-token" } The Data key contains the real Document. @@ -80,9 +81,11 @@ Sending vs receiving document data encapsulation To : The Id, the server send this Document to. Should be the same as your current Self Id. Data : Contains the payload. - Iid : Optional request identifier to match this response to the calling + Iid : Request identifier to match this response to the calling request. Only available when sent by the client and the requested - type implementation does support it. + type implementation does support it (optional). + A : Session attestation token. Only available for incoming data + created by other sessions (optional). Special purpose documents for channling @@ -400,11 +403,16 @@ Information retrieval "Type": "Sessions", "Sessions": { "Type": "Token type", - "Token": "Request token" + "Token": "Token data" } } - Valid known token types are: "contact". + Valid known token types are: + contact: + Token data retrieved when a contact request is accepted. + session: + Token data retrieved on incoming messages as A field (attestation + token). Sessions (Response with Id, Token and Type from request and populated Session list). diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index 726d3005..fc9cea3e 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -158,6 +158,7 @@ type DataOutgoing struct { From string To string Iid string `json:",omitempty"` + A string `json:",omitempty"` } type DataSessions struct { diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 777eecbd..a0498a26 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -46,6 +46,7 @@ const ( maxUsersLength = 5000 ) +// TODO(longsleep): Get rid of MessageRequest type. type MessageRequest struct { From string To string @@ -79,6 +80,7 @@ type Hub struct { encryptionSecret []byte turnSecret []byte tickets *securecookie.SecureCookie + attestations *securecookie.SecureCookie count uint64 mutex sync.RWMutex buffers BufferCache @@ -114,11 +116,14 @@ func NewHub(version string, config *Config, sessionSecret, encryptionSecret, tur h.tickets.MaxAge(86400 * 30) // 30 days h.tickets.HashFunc(sha256.New) h.tickets.BlockFunc(aes.NewCipher) + h.attestations = securecookie.New(h.sessionSecret, nil) + h.attestations.MaxAge(300) // 5 minutes + h.tickets.HashFunc(sha256.New) h.buffers = NewBufferCache(1024, bytes.MinRead) h.buddyImages = NewImageCache() h.tokenName = fmt.Sprintf("token@%s", h.realm) h.contacts = securecookie.New(h.sessionSecret, h.encryptionSecret) - h.contacts.MaxAge(0) + h.contacts.MaxAge(0) // Forever h.contacts.HashFunc(sha256.New) h.contacts.BlockFunc(aes.NewCipher) return h @@ -214,7 +219,7 @@ 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) + session = NewSession(h, id, sid) log.Println("Created new session id", len(id), id, sid) } else { if userid == "" { @@ -223,7 +228,7 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session { if !usersEnabled { userid = "" } - session = NewSession(st.Id, st.Sid) + session = NewSession(h, st.Id, st.Sid) } if userid != "" { @@ -250,6 +255,25 @@ func (h *Hub) ValidateSession(id, sid string) bool { } +/* +func (h *Hub) EncodeAttestation(session *Session) (string, error) { + + attestation, err := h.attestations.Encode("attestation", session.Id) + if err == nil { + session.UpdateAttestation(attestation) + } + return attestation, err + +} + +func (h *Hub) DecodeAttestation(token string) (string, error) { + + var id string + err := h.attestations.Decode("attestation", token, &id) + return id, err + +}*/ + func (h *Hub) EncodeSessionToken(st *SessionToken) (string, error) { return h.tickets.Encode(h.tokenName, st) @@ -448,13 +472,27 @@ func (h *Hub) sessionsHandler(c *Connection, srq *DataSessionsRequest, iid strin } // Find foreign user. h.mutex.RLock() - defer h.mutex.RUnlock() user, ok := h.userTable[userid] + h.mutex.RUnlock() if !ok { return } // Add sessions for forein user. users = user.SessionsData() + case "session": + id, err := c.Session.attestation.Decode(srq.Token) + if err != nil { + log.Println("Failed to decode incoming attestation", err, srq.Token) + return + } + h.mutex.RLock() + session, ok := h.sessionTable[id] + h.mutex.RUnlock() + if !ok { + return + } + users = make([]*DataSession, 1, 1) + users[0] = session.Data() default: log.Println("Unkown incoming sessions request type", srq.Type) } diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index 5e3577d0..8da4ef8a 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -180,7 +180,7 @@ func (s *Server) Unicast(c *Connection, to string, m interface{}) { b := c.h.buffers.New() encoder := json.NewEncoder(b) - err := encoder.Encode(&DataOutgoing{From: c.Id, To: to, Data: m}) + err := encoder.Encode(&DataOutgoing{From: c.Id, To: to, Data: m, A: c.Session.Attestation()}) if err != nil { b.Decref() log.Println("Unicast error while encoding JSON", err) @@ -192,6 +192,31 @@ func (s *Server) Unicast(c *Connection, to string, m interface{}) { b.Decref() } +func (s *Server) Broadcast(c *Connection, m interface{}) { + + b := c.h.buffers.New() + encoder := json.NewEncoder(b) + err := encoder.Encode(&DataOutgoing{From: c.Id, Data: m, A: c.Session.Attestation()}) + if err != nil { + b.Decref() + log.Println("Broadcast error while encoding JSON", err) + return + } + + if c.h.isGlobalRoomid(c.Roomid) { + c.h.RunForAllRooms(func(room *RoomWorker) { + var msg = &MessageRequest{From: c.Id, Message: b, Id: room.Id} + room.broadcastHandler(msg) + }) + } else { + var msg = &MessageRequest{From: c.Id, Message: b, Id: c.Roomid} + room := c.h.GetRoom(c.Roomid) + room.broadcastHandler(msg) + } + b.Decref() + +} + func (s *Server) Alive(c *Connection, alive *DataAlive, iid string) { c.h.aliveHandler(c, alive, iid) @@ -217,31 +242,6 @@ func (s *Server) ContactRequest(c *Connection, to string, cr *DataContactRequest } -func (s *Server) Broadcast(c *Connection, m interface{}) { - - b := c.h.buffers.New() - encoder := json.NewEncoder(b) - err := encoder.Encode(&DataOutgoing{From: c.Id, Data: m}) - if err != nil { - b.Decref() - log.Println("Broadcast error while encoding JSON", err) - return - } - - if c.h.isGlobalRoomid(c.Roomid) { - c.h.RunForAllRooms(func(room *RoomWorker) { - var msg = &MessageRequest{From: c.Id, Message: b, Id: room.Id} - room.broadcastHandler(msg) - }) - } else { - var msg = &MessageRequest{From: c.Id, Message: b, Id: c.Roomid} - room := c.h.GetRoom(c.Roomid) - room.broadcastHandler(msg) - } - b.Decref() - -} - func (s *Server) Users(c *Connection) { room := c.h.GetRoom(c.Roomid) diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index a2ccb07b..af68678b 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -32,26 +32,31 @@ import ( var sessionNonces *securecookie.SecureCookie type Session struct { - Id string - Sid string - Ua string - UpdateRev uint64 - Status interface{} - Nonce string - Prio int - mutex sync.RWMutex - userid string - stamp int64 + Id string + Sid string + Ua string + UpdateRev uint64 + Status interface{} + Nonce string + Prio int + mutex sync.RWMutex + userid string + stamp int64 + attestation *SessionAttestation + h *Hub } -func NewSession(id, sid string) *Session { +func NewSession(h *Hub, id, sid string) *Session { - return &Session{ + session := &Session{ Id: id, Sid: sid, Prio: 100, stamp: time.Now().Unix(), + h: h, } + session.NewAttestation() + return session } @@ -153,9 +158,12 @@ func (s *Session) Data() *DataSession { } -func (s *Session) Userid() string { +func (s *Session) Userid() (userid string) { - return s.userid + s.mutex.RLock() + userid = s.userid + s.mutex.RUnlock() + return } @@ -203,6 +211,32 @@ func (s *Session) DataSessionStatus() *DataSession { } +func (s *Session) NewAttestation() { + + s.attestation = &SessionAttestation{ + s: s, + } + s.attestation.Update() + +} + +func (s *Session) Attestation() (attestation string) { + + s.mutex.RLock() + attestation = s.attestation.Token() + s.mutex.RUnlock() + return + +} + +func (s *Session) UpdateAttestation() { + + s.mutex.Lock() + s.attestation.Update() + s.mutex.Unlock() + +} + type SessionUpdate struct { Id string Types []string @@ -219,6 +253,48 @@ type SessionToken struct { Nonce string `json:"Nonce,omitempty"` // User autentication nonce. } +type SessionAttestation struct { + refresh int64 + token string + s *Session +} + +func (sa *SessionAttestation) Update() (string, error) { + + token, err := sa.Encode() + if err == nil { + sa.token = token + sa.refresh = time.Now().Unix() + 180 // expires after 3 minutes + } + return token, err + +} + +func (sa *SessionAttestation) Token() (token string) { + + if sa.refresh < time.Now().Unix() { + token, _ = sa.Update() + } else { + token = sa.token + } + return + +} + +func (sa *SessionAttestation) Encode() (string, error) { + + return sa.s.h.attestations.Encode("attestation", sa.s.Id) + +} + +func (sa *SessionAttestation) Decode(token string) (string, error) { + + var id string + err := sa.s.h.attestations.Decode("attestation", token, &id) + return id, err + +} + func init() { // Create nonce generator. sessionNonces = securecookie.New(securecookie.GenerateRandomKey(64), nil) diff --git a/static/js/filters/buddyimagesrc.js b/static/js/filters/buddyimagesrc.js index 852a71c0..a3a9ec3f 100644 --- a/static/js/filters/buddyimagesrc.js +++ b/static/js/filters/buddyimagesrc.js @@ -77,7 +77,7 @@ define(["underscore"], function(_) { return function(id) { - var scope = buddyData.lookup(id); + var scope = buddyData.lookup(id, false, true); if (scope) { var display = scope.display; if (display) { diff --git a/static/js/filters/displayname.js b/static/js/filters/displayname.js index f3695968..a3a7a27f 100644 --- a/static/js/filters/displayname.js +++ b/static/js/filters/displayname.js @@ -33,7 +33,7 @@ define([], function() { if (id === group_chat_id) { return ""; } - var scope = buddyData.lookup(id); + var scope = buddyData.lookup(id, false, true); if (scope) { if (scope.display.displayName) { return scope.display.displayName; diff --git a/static/js/mediastream/api.js b/static/js/mediastream/api.js index c7f8b110..3c714e20 100644 --- a/static/js/mediastream/api.js +++ b/static/js/mediastream/api.js @@ -121,10 +121,16 @@ define(['jquery', 'underscore'], function($, _) { this.last_receive = now; this.last_receive_overdue = false; + var attestation = d.A; var iid = d.Iid; var data = d.Data; var dataType = data.Type; + if (attestation && d.From) { + // Trigger received attestations. + this.e.triggerHandler("received.attestation", [d.From, attestation]); + } + if (iid) { // Shortcut for iid registered responses. this.e.triggerHandler(iid+".request", [dataType, data]); diff --git a/static/js/services/buddydata.js b/static/js/services/buddydata.js index 26f68052..afec76bb 100644 --- a/static/js/services/buddydata.js +++ b/static/js/services/buddydata.js @@ -21,11 +21,13 @@ define(['underscore'], function(underscore) { // buddyData - return ["contactData", function(contactData) { + return ["contactData", "mediaStream", "$rootScope", function(contactData, mediaStream, $rootScope) { var scopes = {}; var brain = {}; var pushed = {}; + var attestations = {}; + var fakes = {}; var count = 0; var buddyData = { @@ -39,10 +41,15 @@ define(['underscore'], function(underscore) { push: function(id) { var entry = pushed[id]; if (!entry) { - entry = pushed[id] = { - count: 1, - scope: scopes[id] - }; + var scope = scopes[id]; + if (scope) { + entry = pushed[id] = { + count: 1, + scope: scopes[id] + }; + } else { + return 0; + } } else { entry.count++; } @@ -104,18 +111,48 @@ define(['underscore'], function(underscore) { } } }, - lookup: function(id, onlyactive) { - var scope = null; + lookup: function(id, onlyactive, withfakes) { + if (!id) { + return; + } if (scopes.hasOwnProperty(id)) { - scope = scopes[id]; + return scopes[id]; } else if (!onlyactive) { if (brain.hasOwnProperty(id)) { - scope = brain[id]; + return brain[id]; } else if (pushed.hasOwnProperty(id)) { - scope = pushed[id].scope; + return pushed[id].scope; + } + if (withfakes) { + var fake = fakes[id]; + //console.log("check fake", id, fake); + if (fake) { + //console.log("found fake", fake); + if (fake.display) { + return fake; + } + } else { + if (attestations.hasOwnProperty(id)) { + // Fetch with help of session attestation token. + fake = fakes[id] = {}; + var token = attestations[id].a; + console.log("attestation request", id); + mediaStream.api.sendSessions(token, "session", function(event, type, data) { + console.log("attestation session response", id, type, data); + if (data.Users && data.Users.length > 0) { + var s = data.Users[0]; + fake.display = { + displayName: s.Status.displayName + } + // TODO(longsleep): Find a better way to apply this than digest on root scope. + $rootScope.$digest(); + } + }); + } + } } } - return scope; + return null; }, del: function(id, hard) { var scope = scopes[id]; @@ -131,8 +168,55 @@ define(['underscore'], function(underscore) { }, set: function(id, scope) { scopes[id] = scope; + }, + attestation: function(id) { + var data = attestations[id]; + if (data) { + return data.a; + } + return null; } }; + + // attestation support + (function() { + + // Listen for attestation events. + mediaStream.api.e.on("received.attestation", function(event, from, attestation) { + + var current = attestations[from]; + var create = false; + if (!current) { + create = true; + } else { + if (current.a !== attestation) { + create = true; + } + } + if (create) { + console.log("Created attestation entry", from); + attestations[from] = { + a: attestation, + t: (new Date().getTime()) + } + } + + }); + + var expire = function() { + var expired = (new Date().getTime()) - 240000; + _.each(attestations, function(data, id) { + if (data.t < expired) { + delete attestations[id]; + //console.log("expired attestation", id); + } + }) + setTimeout(expire, 120000); + }; + expire(); + + })(); + return buddyData; }];