Browse Source

Merge branch 'attestation'

pull/51/head
Simon Eisenmann 12 years ago
parent
commit
e3a749e5d6
  1. 18
      doc/CHANNELING-API.txt
  2. 1
      src/app/spreed-webrtc-server/channeling.go
  3. 46
      src/app/spreed-webrtc-server/hub.go
  4. 52
      src/app/spreed-webrtc-server/server.go
  5. 84
      src/app/spreed-webrtc-server/session.go
  6. 2
      static/js/filters/buddyimagesrc.js
  7. 2
      static/js/filters/displayname.js
  8. 6
      static/js/mediastream/api.js
  9. 98
      static/js/services/buddydata.js

18
doc/CHANNELING-API.txt

@ -68,7 +68,8 @@ Sending vs receiving document data encapsulation
"From": "4", "From": "4",
"To": "5", "To": "5",
"Data": {}, "Data": {},
"Iid": "request-identifier-unique-to-client" "Iid": "request-identifier-unique-to-client",
"A": "attestation-session-token"
} }
The Data key contains the real Document. 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 To : The Id, the server send this Document to. Should be the same as
your current Self Id. your current Self Id.
Data : Contains the payload. 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 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 Special purpose documents for channling
@ -400,11 +403,16 @@ Information retrieval
"Type": "Sessions", "Type": "Sessions",
"Sessions": { "Sessions": {
"Type": "Token type", "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 Sessions (Response with Id, Token and Type from request and
populated Session list). populated Session list).

1
src/app/spreed-webrtc-server/channeling.go

@ -158,6 +158,7 @@ type DataOutgoing struct {
From string From string
To string To string
Iid string `json:",omitempty"` Iid string `json:",omitempty"`
A string `json:",omitempty"`
} }
type DataSessions struct { type DataSessions struct {

46
src/app/spreed-webrtc-server/hub.go

@ -46,6 +46,7 @@ const (
maxUsersLength = 5000 maxUsersLength = 5000
) )
// TODO(longsleep): Get rid of MessageRequest type.
type MessageRequest struct { type MessageRequest struct {
From string From string
To string To string
@ -79,6 +80,7 @@ type Hub struct {
encryptionSecret []byte encryptionSecret []byte
turnSecret []byte turnSecret []byte
tickets *securecookie.SecureCookie tickets *securecookie.SecureCookie
attestations *securecookie.SecureCookie
count uint64 count uint64
mutex sync.RWMutex mutex sync.RWMutex
buffers BufferCache buffers BufferCache
@ -114,11 +116,14 @@ func NewHub(version string, config *Config, sessionSecret, encryptionSecret, tur
h.tickets.MaxAge(86400 * 30) // 30 days h.tickets.MaxAge(86400 * 30) // 30 days
h.tickets.HashFunc(sha256.New) h.tickets.HashFunc(sha256.New)
h.tickets.BlockFunc(aes.NewCipher) 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.buffers = NewBufferCache(1024, bytes.MinRead)
h.buddyImages = NewImageCache() h.buddyImages = NewImageCache()
h.tokenName = fmt.Sprintf("token@%s", h.realm) h.tokenName = fmt.Sprintf("token@%s", h.realm)
h.contacts = securecookie.New(h.sessionSecret, h.encryptionSecret) h.contacts = securecookie.New(h.sessionSecret, h.encryptionSecret)
h.contacts.MaxAge(0) h.contacts.MaxAge(0) // Forever
h.contacts.HashFunc(sha256.New) h.contacts.HashFunc(sha256.New)
h.contacts.BlockFunc(aes.NewCipher) h.contacts.BlockFunc(aes.NewCipher)
return h return h
@ -214,7 +219,7 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session {
if st == nil { if st == nil {
sid := NewRandomString(32) sid := NewRandomString(32)
id, _ := h.tickets.Encode("id", sid) 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) log.Println("Created new session id", len(id), id, sid)
} else { } else {
if userid == "" { if userid == "" {
@ -223,7 +228,7 @@ func (h *Hub) CreateSession(request *http.Request, st *SessionToken) *Session {
if !usersEnabled { if !usersEnabled {
userid = "" userid = ""
} }
session = NewSession(st.Id, st.Sid) session = NewSession(h, st.Id, st.Sid)
} }
if userid != "" { 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) { func (h *Hub) EncodeSessionToken(st *SessionToken) (string, error) {
return h.tickets.Encode(h.tokenName, st) return h.tickets.Encode(h.tokenName, st)
@ -448,13 +472,27 @@ func (h *Hub) sessionsHandler(c *Connection, srq *DataSessionsRequest, iid strin
} }
// Find foreign user. // Find foreign user.
h.mutex.RLock() h.mutex.RLock()
defer h.mutex.RUnlock()
user, ok := h.userTable[userid] user, ok := h.userTable[userid]
h.mutex.RUnlock()
if !ok { if !ok {
return return
} }
// Add sessions for forein user. // Add sessions for forein user.
users = user.SessionsData() 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: default:
log.Println("Unkown incoming sessions request type", srq.Type) log.Println("Unkown incoming sessions request type", srq.Type)
} }

52
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() b := c.h.buffers.New()
encoder := json.NewEncoder(b) 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 { if err != nil {
b.Decref() b.Decref()
log.Println("Unicast error while encoding JSON", err) log.Println("Unicast error while encoding JSON", err)
@ -192,6 +192,31 @@ func (s *Server) Unicast(c *Connection, to string, m interface{}) {
b.Decref() 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) { func (s *Server) Alive(c *Connection, alive *DataAlive, iid string) {
c.h.aliveHandler(c, alive, iid) 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) { func (s *Server) Users(c *Connection) {
room := c.h.GetRoom(c.Roomid) room := c.h.GetRoom(c.Roomid)

84
src/app/spreed-webrtc-server/session.go

@ -42,16 +42,21 @@ type Session struct {
mutex sync.RWMutex mutex sync.RWMutex
userid string userid string
stamp int64 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, Id: id,
Sid: sid, Sid: sid,
Prio: 100, Prio: 100,
stamp: time.Now().Unix(), 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 { type SessionUpdate struct {
Id string Id string
Types []string Types []string
@ -219,6 +253,48 @@ type SessionToken struct {
Nonce string `json:"Nonce,omitempty"` // User autentication nonce. 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() { func init() {
// Create nonce generator. // Create nonce generator.
sessionNonces = securecookie.New(securecookie.GenerateRandomKey(64), nil) sessionNonces = securecookie.New(securecookie.GenerateRandomKey(64), nil)

2
static/js/filters/buddyimagesrc.js

@ -77,7 +77,7 @@ define(["underscore"], function(_) {
return function(id) { return function(id) {
var scope = buddyData.lookup(id); var scope = buddyData.lookup(id, false, true);
if (scope) { if (scope) {
var display = scope.display; var display = scope.display;
if (display) { if (display) {

2
static/js/filters/displayname.js

@ -33,7 +33,7 @@ define([], function() {
if (id === group_chat_id) { if (id === group_chat_id) {
return ""; return "";
} }
var scope = buddyData.lookup(id); var scope = buddyData.lookup(id, false, true);
if (scope) { if (scope) {
if (scope.display.displayName) { if (scope.display.displayName) {
return scope.display.displayName; return scope.display.displayName;

6
static/js/mediastream/api.js

@ -121,10 +121,16 @@ define(['jquery', 'underscore'], function($, _) {
this.last_receive = now; this.last_receive = now;
this.last_receive_overdue = false; this.last_receive_overdue = false;
var attestation = d.A;
var iid = d.Iid; var iid = d.Iid;
var data = d.Data; var data = d.Data;
var dataType = data.Type; var dataType = data.Type;
if (attestation && d.From) {
// Trigger received attestations.
this.e.triggerHandler("received.attestation", [d.From, attestation]);
}
if (iid) { if (iid) {
// Shortcut for iid registered responses. // Shortcut for iid registered responses.
this.e.triggerHandler(iid+".request", [dataType, data]); this.e.triggerHandler(iid+".request", [dataType, data]);

98
static/js/services/buddydata.js

@ -21,11 +21,13 @@
define(['underscore'], function(underscore) { define(['underscore'], function(underscore) {
// buddyData // buddyData
return ["contactData", function(contactData) { return ["contactData", "mediaStream", "$rootScope", function(contactData, mediaStream, $rootScope) {
var scopes = {}; var scopes = {};
var brain = {}; var brain = {};
var pushed = {}; var pushed = {};
var attestations = {};
var fakes = {};
var count = 0; var count = 0;
var buddyData = { var buddyData = {
@ -39,10 +41,15 @@ define(['underscore'], function(underscore) {
push: function(id) { push: function(id) {
var entry = pushed[id]; var entry = pushed[id];
if (!entry) { if (!entry) {
var scope = scopes[id];
if (scope) {
entry = pushed[id] = { entry = pushed[id] = {
count: 1, count: 1,
scope: scopes[id] scope: scopes[id]
}; };
} else {
return 0;
}
} else { } else {
entry.count++; entry.count++;
} }
@ -104,18 +111,48 @@ define(['underscore'], function(underscore) {
} }
} }
}, },
lookup: function(id, onlyactive) { lookup: function(id, onlyactive, withfakes) {
var scope = null; if (!id) {
return;
}
if (scopes.hasOwnProperty(id)) { if (scopes.hasOwnProperty(id)) {
scope = scopes[id]; return scopes[id];
} else if (!onlyactive) { } else if (!onlyactive) {
if (brain.hasOwnProperty(id)) { if (brain.hasOwnProperty(id)) {
scope = brain[id]; return brain[id];
} else if (pushed.hasOwnProperty(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;
} }
return scope; } 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 null;
}, },
del: function(id, hard) { del: function(id, hard) {
var scope = scopes[id]; var scope = scopes[id];
@ -131,8 +168,55 @@ define(['underscore'], function(underscore) {
}, },
set: function(id, scope) { set: function(id, scope) {
scopes[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; return buddyData;
}]; }];

Loading…
Cancel
Save