You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

313 lines
9.0 KiB

/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2017 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"net/http"
)
const (
BackendVersion = "1.0"
HeaderBackendSignalingRandom = "Spreed-Signaling-Random"
HeaderBackendSignalingChecksum = "Spreed-Signaling-Checksum"
HeaderBackendServer = "Spreed-Signaling-Backend"
)
func newRandomString(length int) string {
b := make([]byte, length/2)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return hex.EncodeToString(b)
}
func CalculateBackendChecksum(random string, body []byte, secret []byte) string {
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(random)) // nolint
mac.Write(body) // nolint
return hex.EncodeToString(mac.Sum(nil))
}
func AddBackendChecksum(r *http.Request, body []byte, secret []byte) {
// Add checksum so the backend can validate the request.
rnd := newRandomString(64)
checksum := CalculateBackendChecksum(rnd, body, secret)
r.Header.Set(HeaderBackendSignalingRandom, rnd)
r.Header.Set(HeaderBackendSignalingChecksum, checksum)
}
func ValidateBackendChecksum(r *http.Request, body []byte, secret []byte) bool {
rnd := r.Header.Get(HeaderBackendSignalingRandom)
checksum := r.Header.Get(HeaderBackendSignalingChecksum)
return ValidateBackendChecksumValue(checksum, rnd, body, secret)
}
func ValidateBackendChecksumValue(checksum string, random string, body []byte, secret []byte) bool {
verify := CalculateBackendChecksum(random, body, secret)
return subtle.ConstantTimeCompare([]byte(verify), []byte(checksum)) == 1
}
// Requests from Nextcloud to the signaling server.
type BackendServerRoomRequest struct {
room *Room
Type string `json:"type"`
Invite *BackendRoomInviteRequest `json:"invite,omitempty"`
Disinvite *BackendRoomDisinviteRequest `json:"disinvite,omitempty"`
Update *BackendRoomUpdateRequest `json:"update,omitempty"`
Delete *BackendRoomDeleteRequest `json:"delete,omitempty"`
InCall *BackendRoomInCallRequest `json:"incall,omitempty"`
Participants *BackendRoomParticipantsRequest `json:"participants,omitempty"`
Message *BackendRoomMessageRequest `json:"message,omitempty"`
// Internal properties
ReceivedTime int64 `json:"received,omitempty"`
}
type BackendRoomInviteRequest struct {
UserIds []string `json:"userids,omitempty"`
// TODO(jojo): We should get rid of "AllUserIds" and find a better way to
// notify existing users the room has changed and they need to update it.
AllUserIds []string `json:"alluserids,omitempty"`
Properties *json.RawMessage `json:"properties,omitempty"`
}
type BackendRoomDisinviteRequest struct {
UserIds []string `json:"userids,omitempty"`
SessionIds []string `json:"sessionids,omitempty"`
// TODO(jojo): We should get rid of "AllUserIds" and find a better way to
// notify existing users the room has changed and they need to update it.
AllUserIds []string `json:"alluserids,omitempty"`
Properties *json.RawMessage `json:"properties,omitempty"`
}
type BackendRoomUpdateRequest struct {
UserIds []string `json:"userids,omitempty"`
Properties *json.RawMessage `json:"properties,omitempty"`
}
type BackendRoomDeleteRequest struct {
UserIds []string `json:"userids,omitempty"`
}
type BackendRoomInCallRequest struct {
// TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk.
InCall json.RawMessage `json:"incall,omitempty"`
Changed []map[string]interface{} `json:"changed,omitempty"`
Users []map[string]interface{} `json:"users,omitempty"`
}
type BackendRoomParticipantsRequest struct {
Changed []map[string]interface{} `json:"changed,omitempty"`
Users []map[string]interface{} `json:"users,omitempty"`
}
type BackendRoomMessageRequest struct {
Data *json.RawMessage `json:"data,omitempty"`
}
// Requests from the signaling server to the Nextcloud backend.
type BackendClientAuthRequest struct {
Version string `json:"version"`
Params *json.RawMessage `json:"params"`
}
type BackendClientRequest struct {
Type string `json:"type"`
Auth *BackendClientAuthRequest `json:"auth,omitempty"`
Room *BackendClientRoomRequest `json:"room,omitempty"`
Ping *BackendClientPingRequest `json:"ping,omitempty"`
Session *BackendClientSessionRequest `json:"session,omitempty"`
}
func NewBackendClientAuthRequest(params *json.RawMessage) *BackendClientRequest {
return &BackendClientRequest{
Type: "auth",
Auth: &BackendClientAuthRequest{
Version: BackendVersion,
Params: params,
},
}
}
type BackendClientResponse struct {
Type string `json:"type"`
Error *Error `json:"error,omitempty"`
Auth *BackendClientAuthResponse `json:"auth,omitempty"`
Room *BackendClientRoomResponse `json:"room,omitempty"`
Ping *BackendClientRingResponse `json:"ping,omitempty"`
Session *BackendClientSessionResponse `json:"session,omitempty"`
}
type BackendClientAuthResponse struct {
Version string `json:"version"`
UserId string `json:"userid"`
User *json.RawMessage `json:"user"`
}
type BackendClientRoomRequest struct {
Version string `json:"version"`
RoomId string `json:"roomid"`
Action string `json:"action,omitempty"`
UserId string `json:"userid"`
SessionId string `json:"sessionid"`
// For Nextcloud Talk with SIP support.
ActorId string `json:"actorid,omitempty"`
ActorType string `json:"actortype,omitempty"`
InCall int `json:"incall,omitempty"`
}
func NewBackendClientRoomRequest(roomid string, userid string, sessionid string) *BackendClientRequest {
return &BackendClientRequest{
Type: "room",
Room: &BackendClientRoomRequest{
Version: BackendVersion,
RoomId: roomid,
UserId: userid,
SessionId: sessionid,
},
}
}
type BackendClientRoomResponse struct {
Version string `json:"version"`
RoomId string `json:"roomid"`
Properties *json.RawMessage `json:"properties"`
// Optional information about the Nextcloud Talk session. Can be used for
// example to define a "userid" for otherwise anonymous users.
// See "RoomSessionData" for a possible content.
Session *json.RawMessage `json:"session,omitempty"`
Permissions *[]Permission `json:"permissions,omitempty"`
}
type RoomSessionData struct {
UserId string `json:"userid,omitempty"`
}
type BackendPingEntry struct {
UserId string `json:"userid,omitempty"`
SessionId string `json:"sessionid"`
}
type BackendClientPingRequest struct {
Version string `json:"version"`
RoomId string `json:"roomid"`
Entries []BackendPingEntry `json:"entries"`
}
func NewBackendClientPingRequest(roomid string, entries []BackendPingEntry) *BackendClientRequest {
return &BackendClientRequest{
Type: "ping",
Ping: &BackendClientPingRequest{
Version: BackendVersion,
RoomId: roomid,
Entries: entries,
},
}
}
type BackendClientRingResponse struct {
Version string `json:"version"`
RoomId string `json:"roomid"`
}
type BackendClientSessionRequest struct {
Version string `json:"version"`
RoomId string `json:"roomid"`
Action string `json:"action"`
SessionId string `json:"sessionid"`
UserId string `json:"userid,omitempty"`
User *json.RawMessage `json:"user,omitempty"`
}
type BackendClientSessionResponse struct {
Version string `json:"version"`
RoomId string `json:"roomid"`
}
func NewBackendClientSessionRequest(roomid string, action string, sessionid string, msg *AddSessionInternalClientMessage) *BackendClientRequest {
request := &BackendClientRequest{
Type: "session",
Session: &BackendClientSessionRequest{
Version: BackendVersion,
RoomId: roomid,
Action: action,
SessionId: sessionid,
},
}
if msg != nil {
request.Session.UserId = msg.UserId
request.Session.User = msg.User
}
return request
}
type OcsMeta struct {
Status string `json:"status"`
StatusCode int `json:"statuscode"`
Message string `json:"message"`
}
type OcsBody struct {
Meta OcsMeta `json:"meta"`
Data *json.RawMessage `json:"data"`
}
type OcsResponse struct {
Ocs *OcsBody `json:"ocs"`
}
// See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
type TurnCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
TTL int64 `json:"ttl"`
URIs []string `json:"uris"`
}