From a1c1405c61bc280ec9d31461e3ff95904114ad20 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sat, 26 Apr 2014 23:23:50 +0200 Subject: [PATCH] Added realm to userids, tokens and nonces. --- doc/REST-API.txt | 1 + server.conf.in | 4 +++ src/app/spreed-speakfreely-server/hub.go | 12 +++++--- src/app/spreed-speakfreely-server/main.go | 24 ++++++++++----- src/app/spreed-speakfreely-server/server.go | 2 +- src/app/spreed-speakfreely-server/session.go | 9 +++--- src/app/spreed-speakfreely-server/sessions.go | 2 +- src/app/spreed-speakfreely-server/users.go | 17 +++++++---- .../js/controllers/mediastreamcontroller.js | 30 ++++++++++++------- 9 files changed, 67 insertions(+), 34 deletions(-) diff --git a/doc/REST-API.txt b/doc/REST-API.txt index 54cb4e54..62d541f5 100644 --- a/doc/REST-API.txt +++ b/doc/REST-API.txt @@ -92,6 +92,7 @@ Available end points with request methods and content-type: "success": true, "userid": "user-id", "useridcombo": "authorization-id", + "timestamp": 1430688014, "secret": "authorization-secret-for-authorization-id", "nonce": "authorization-nonce" } diff --git a/server.conf.in b/server.conf.in index ec1cd06d..17770e13 100644 --- a/server.conf.in +++ b/server.conf.in @@ -75,6 +75,10 @@ sessionSecret = the-default-secret-do-not-keep-me ; server generated security tokens. When the serverToken is changed all existing ; nonces become invalid. Use 32 or 64 byte random data. ;serverToken = i-did-not-change-the-public-token-boo +; The server realm is part of the validation chain of tokens and nonces and is +; added as suffix to server created user ids if user creation is enabled. When +; the realm is changed, all existing tokens and nonces become invalid. +;serverRealm = local ; Full path to an extra templates directory. Templates in this directory ending ; with .html will be parsed on startup and can be used to fill the supported ; extra-* template slots. If the extra folder has a sub folder "static", the diff --git a/src/app/spreed-speakfreely-server/hub.go b/src/app/spreed-speakfreely-server/hub.go index 4833f6b4..96ba40c1 100644 --- a/src/app/spreed-speakfreely-server/hub.go +++ b/src/app/spreed-speakfreely-server/hub.go @@ -79,9 +79,11 @@ type Hub struct { broadcastChatMessages uint64 unicastChatMessages uint64 buddyImages ImageCache + realm string + tokenName string } -func NewHub(version string, config *Config, sessionSecret, turnSecret string) *Hub { +func NewHub(version string, config *Config, sessionSecret, turnSecret, realm string) *Hub { h := &Hub{ connectionTable: make(map[string]*Connection), @@ -91,6 +93,7 @@ func NewHub(version string, config *Config, sessionSecret, turnSecret string) *H config: config, sessionSecret: []byte(sessionSecret), turnSecret: []byte(turnSecret), + realm: realm, } if len(h.sessionSecret) < 32 { @@ -100,6 +103,7 @@ func NewHub(version string, config *Config, sessionSecret, turnSecret string) *H h.tickets = securecookie.New(h.sessionSecret, nil) h.buffers = NewBufferCache(1024, bytes.MinRead) h.buddyImages = NewImageCache() + h.tokenName = fmt.Sprintf("token@%s", h.realm) return h } @@ -203,14 +207,14 @@ func (h *Hub) ValidateSession(id, sid string) bool { func (h *Hub) EncodeSessionToken(st *SessionToken) (string, error) { - return h.tickets.Encode("token", st) + return h.tickets.Encode(h.tokenName, st) } func (h *Hub) DecodeSessionToken(token string) (*SessionToken, error) { st := &SessionToken{} - err := h.tickets.Decode("token", token, st) + err := h.tickets.Decode(h.tokenName, token, st) return st, err } @@ -401,7 +405,7 @@ func (h *Hub) sessiontokenHandler(st *SessionToken) (string, error) { return "", errors.New("no such connection") } - nonce, err := c.Session.Authorize(st) + nonce, err := c.Session.Authorize(h.realm, st) if err != nil { return "", err } diff --git a/src/app/spreed-speakfreely-server/main.go b/src/app/spreed-speakfreely-server/main.go index 9e8ed7c0..1e7c1630 100644 --- a/src/app/spreed-speakfreely-server/main.go +++ b/src/app/spreed-speakfreely-server/main.go @@ -263,12 +263,6 @@ func runner(runtime phoenix.Runtime) error { defaultRoomEnabled = defaultRoomEnabledString == "true" } - serverToken, err := runtime.GetString("app", "serverToken") - if err == nil { - //TODO(longsleep): When we have a database, generate this once from random source and store it. - serverToken = "i-did-not-change-the-public-token-boo" - } - usersEnabled := false usersEnabledString, err := runtime.GetString("users", "enabled") if err == nil { @@ -281,6 +275,17 @@ func runner(runtime phoenix.Runtime) error { usersAllowRegistration = usersAllowRegistrationString == "true" } + serverToken, err := runtime.GetString("app", "serverToken") + if err != nil { + //TODO(longsleep): When we have a database, generate this once from random source and store it. + serverToken = "i-did-not-change-the-public-token-boo" + } + + serverRealm, err := runtime.GetString("app", "serverRealm") + if err != nil { + serverRealm = "local" + } + // Create token provider. var tokenProvider TokenProvider if tokenFile != "" { @@ -313,8 +318,11 @@ func runner(runtime phoenix.Runtime) error { log.Printf("Loaded extra templates from: %s", extraFolder) } + // Create realm string from config. + computedRealm := fmt.Sprintf("%s.%s", serverRealm, serverToken) + // Create our hub instance. - hub := NewHub(runtimeVersion, config, sessionSecret, turnSecret) + hub := NewHub(runtimeVersion, config, sessionSecret, turnSecret, computedRealm) // Set number of go routines if it is 1 if goruntime.GOMAXPROCS(0) == 1 { @@ -363,7 +371,7 @@ func runner(runtime phoenix.Runtime) error { api.AddResourceWithWrapper(&Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens") if usersEnabled { // Create Users handler. - users := NewUsers(hub, runtime) + users := NewUsers(hub, serverRealm, runtime) api.AddResource(&Sessions{hub: hub, users: users}, "/sessions/{id}/") if usersAllowRegistration { api.AddResource(users, "/users") diff --git a/src/app/spreed-speakfreely-server/server.go b/src/app/spreed-speakfreely-server/server.go index 040a109f..cb949985 100644 --- a/src/app/spreed-speakfreely-server/server.go +++ b/src/app/spreed-speakfreely-server/server.go @@ -228,7 +228,7 @@ func (s *Server) Users(c *Connection) { func (s *Server) Authenticate(c *Connection, st *SessionToken) bool { - err := c.Session.Authenticate(st) + err := c.Session.Authenticate(c.h.realm, st) if err == nil { log.Println("Authentication success", c.Id, c.Idx, st.Userid) return true diff --git a/src/app/spreed-speakfreely-server/session.go b/src/app/spreed-speakfreely-server/session.go index 8fdd53d4..889ae2f6 100644 --- a/src/app/spreed-speakfreely-server/session.go +++ b/src/app/spreed-speakfreely-server/session.go @@ -23,6 +23,7 @@ package main import ( "errors" + "fmt" "github.com/gorilla/securecookie" "sync" ) @@ -88,7 +89,7 @@ func (s *Session) Apply(st *SessionToken) uint64 { } -func (s *Session) Authorize(st *SessionToken) (string, error) { +func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -102,13 +103,13 @@ func (s *Session) Authorize(st *SessionToken) (string, error) { // Create authentication nonce. var err error - s.Nonce, err = sessionNonces.Encode(s.Sid, st.Userid) + s.Nonce, err = sessionNonces.Encode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Userid) return s.Nonce, err } -func (s *Session) Authenticate(st *SessionToken) error { +func (s *Session) Authenticate(realm string, st *SessionToken) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -120,7 +121,7 @@ func (s *Session) Authenticate(st *SessionToken) error { return errors.New("nonce validation failed") } var userid string - err := sessionNonces.Decode(s.Sid, st.Nonce, &userid) + err := sessionNonces.Decode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Nonce, &userid) if err != nil { return err } diff --git a/src/app/spreed-speakfreely-server/sessions.go b/src/app/spreed-speakfreely-server/sessions.go index 425fe2b5..0018c535 100644 --- a/src/app/spreed-speakfreely-server/sessions.go +++ b/src/app/spreed-speakfreely-server/sessions.go @@ -84,7 +84,7 @@ func (sessions *Sessions) Patch(request *http.Request) (int, interface{}, http.H } // Validate with users handler. - userid, err := sessions.users.Handler.Validate(&snr) + userid, err := sessions.users.handler.Validate(&snr) if err != nil { error = true log.Println("Session patch failed - users validation failed.", err) diff --git a/src/app/spreed-speakfreely-server/users.go b/src/app/spreed-speakfreely-server/users.go index e71b8cd0..e42616ec 100644 --- a/src/app/spreed-speakfreely-server/users.go +++ b/src/app/spreed-speakfreely-server/users.go @@ -83,8 +83,10 @@ func (uh *UsersSharedsecretHandler) Create(un *UserNonce) (*UserNonce, error) { // TODO(longsleep): Make this configureable - One year for now ... expiration := time.Now().Add(time.Duration(1) * time.Hour * 24 * 31 * 12) - un.UseridCombo = fmt.Sprintf("%d:%s", expiration.Unix(), un.Userid) + un.Timestamp = expiration.Unix() + un.UseridCombo = fmt.Sprintf("%d:%s", un.Timestamp, un.Userid) un.Secret = uh.createHMAC(un.UseridCombo) + return un, nil } @@ -94,15 +96,17 @@ type UserNonce struct { Userid string `json:"userid"` UseridCombo string `json:"useridcombo"` Secret string `json:"secret"` + Timestamp int64 `json:"timestamp"` Success bool `json:"success"` } type Users struct { hub *Hub - Handler UsersHandler + realm string + handler UsersHandler } -func NewUsers(hub *Hub, runtime phoenix.Runtime) *Users { +func NewUsers(hub *Hub, realm string, runtime phoenix.Runtime) *Users { var handler UsersHandler @@ -125,7 +129,8 @@ func NewUsers(hub *Hub, runtime phoenix.Runtime) *Users { return &Users{ hub: hub, - Handler: handler, + realm: realm, + handler: handler, } } @@ -146,7 +151,7 @@ func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) } // Do this before session validation to avoid timing information. - userid := uuid.NewV4().String() + userid := fmt.Sprintf("%s@%s", uuid.NewV4().String(), users.realm) // Make sure Sid matches session and is valid. if !users.hub.ValidateSession(snr.Id, snr.Sid) { @@ -158,7 +163,7 @@ func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) return 400, NewApiError("users_request_failed", fmt.Sprintf("Error: %q", err)), http.Header{"Content-Type": {"application/json"}} } - un, err := users.Handler.Create(&UserNonce{Nonce: nonce, Userid: userid, Success: true}) + un, err := users.handler.Create(&UserNonce{Nonce: nonce, Userid: userid, Success: true}) if err != nil { return 400, NewApiError("users_create_failed", fmt.Sprintf("Error: %q", err)), http.Header{"Content-Type": {"application/json"}} } diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index 3a055c7a..c60c2363 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -432,16 +432,26 @@ define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function if (!login && mediaStream.config.UsersAllowRegistration) { console.log("No userid - creating one ..."); mediaStream.users.register(function(data) { - var login = sjcl.encrypt(key, JSON.stringify({ - v: 1, - t: data.timestamp || "", - a: data.useridcombo, - b: data.secret, - })); - localStorage.setItem("mediastream-login", login); - console.info("Created new userid:", data.userid); - mediaStream.api.requestAuthentication(data.userid, data.nonce); - delete data.nonce; + console.info("Created new userid:", data.userid); + if (data.nonce) { + // If the server provided us a nonce, we can do everthing on our own. + // So we store the stuff in localStorage for later use and directly + // authenticate ourselves with the provided nonce. + var login = sjcl.encrypt(key, JSON.stringify({ + v: 1, + t: data.timestamp || "", + a: data.useridcombo, + b: data.secret, + })); + localStorage.setItem("mediastream-login", login); + mediaStream.api.requestAuthentication(data.userid, data.nonce); + delete data.nonce; + } else { + // No nonce received. So this means something we cannot do on our own. + // Make are GET request and retrieve nonce that way and let the + // browser/server do the rest. + // TODO(longsleep): Implement me. + } }, function(data, status) { console.error("Failed to create userid", status, data); });