mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2024-05-02 22:03:09 +02:00
Add support for multiple Nextcloud backends.
This commit is contained in:
parent
2d21c98928
commit
eb6deff32b
|
@ -45,17 +45,21 @@ blockkey = -encryption-key-
|
||||||
internalsecret = the-shared-secret-for-internal-clients
|
internalsecret = the-shared-secret-for-internal-clients
|
||||||
|
|
||||||
[backend]
|
[backend]
|
||||||
# Comma-separated list of hostnames that are allowed to be used as backend
|
# Comma-separated list of backend ids from which clients are allowed to connect
|
||||||
# endpoints.
|
# from. Each backend will have isolated rooms, i.e. clients connecting to room
|
||||||
allowed = nextcloud.domain.invalid
|
# "abc12345" on backend 1 will be in a different room than clients connected to
|
||||||
|
# a room with the same name on backend 2. Also sessions connected from different
|
||||||
|
# backends will not be able to communicate with each other.
|
||||||
|
#backends = backend-id, another-backend
|
||||||
|
|
||||||
# Allow any hostname as backend endpoint. This is extremely insecure and should
|
# Allow any hostname as backend endpoint. This is extremely insecure and should
|
||||||
# only be used while running the benchmark client against the server.
|
# only be used while running the benchmark client against the server.
|
||||||
allowall = false
|
allowall = false
|
||||||
|
|
||||||
# Shared secret for requests from and to the backend servers. This must be the
|
# Common shared secret for requests from and to the backend servers if
|
||||||
# same value as configured in the Nextcloud admin ui.
|
# "allowall" is enabled. This must be the same value as configured in the
|
||||||
secret = the-shared-secret
|
# Nextcloud admin ui.
|
||||||
|
# secret = the-shared-secret
|
||||||
|
|
||||||
# Timeout in seconds for requests to the backend.
|
# Timeout in seconds for requests to the backend.
|
||||||
timeout = 10
|
timeout = 10
|
||||||
|
@ -68,6 +72,24 @@ connectionsperhost = 8
|
||||||
# certificates.
|
# certificates.
|
||||||
#skipverify = false
|
#skipverify = false
|
||||||
|
|
||||||
|
# Backend configurations as defined in the "[backend]" section above. The
|
||||||
|
# section names must match the ids used in "backends" above.
|
||||||
|
#[backend-id]
|
||||||
|
# URL of the Nextcloud instance
|
||||||
|
#url = https://cloud.domain.invalid
|
||||||
|
|
||||||
|
# Shared secret for requests from and to the backend servers. This must be the
|
||||||
|
# same value as configured in the Nextcloud admin ui.
|
||||||
|
#secret = the-shared-secret
|
||||||
|
|
||||||
|
#[another-backend]
|
||||||
|
# URL of the Nextcloud instance
|
||||||
|
#url = https://cloud.otherdomain.invalid
|
||||||
|
|
||||||
|
# Shared secret for requests from and to the backend servers. This must be the
|
||||||
|
# same value as configured in the Nextcloud admin ui.
|
||||||
|
#secret = the-shared-secret
|
||||||
|
|
||||||
[nats]
|
[nats]
|
||||||
# Url of NATS backend to use. This can also be a list of URLs to connect to
|
# Url of NATS backend to use. This can also be a list of URLs to connect to
|
||||||
# multiple backends. For local development, this can be set to ":loopback:"
|
# multiple backends. For local development, this can be set to ":loopback:"
|
||||||
|
|
|
@ -36,6 +36,7 @@ const (
|
||||||
|
|
||||||
HeaderBackendSignalingRandom = "Spreed-Signaling-Random"
|
HeaderBackendSignalingRandom = "Spreed-Signaling-Random"
|
||||||
HeaderBackendSignalingChecksum = "Spreed-Signaling-Checksum"
|
HeaderBackendSignalingChecksum = "Spreed-Signaling-Checksum"
|
||||||
|
HeaderBackendServer = "Spreed-Signaling-Backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRandomString(length int) string {
|
func newRandomString(length int) string {
|
||||||
|
|
|
@ -196,6 +196,20 @@ const (
|
||||||
type ClientTypeInternalAuthParams struct {
|
type ClientTypeInternalAuthParams struct {
|
||||||
Random string `json:"random"`
|
Random string `json:"random"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
|
||||||
|
Backend string `json:"backend"`
|
||||||
|
parsedBackend *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ClientTypeInternalAuthParams) CheckValid() error {
|
||||||
|
if p.Backend == "" {
|
||||||
|
return fmt.Errorf("backend missing")
|
||||||
|
} else if u, err := url.Parse(p.Backend); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
p.parsedBackend = u
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type HelloClientMessageAuth struct {
|
type HelloClientMessageAuth struct {
|
||||||
|
@ -247,6 +261,8 @@ func (m *HelloClientMessage) CheckValid() error {
|
||||||
case HelloClientTypeInternal:
|
case HelloClientTypeInternal:
|
||||||
if err := json.Unmarshal(*m.Auth.Params, &m.Auth.internalParams); err != nil {
|
if err := json.Unmarshal(*m.Auth.Params, &m.Auth.internalParams); err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if err := m.Auth.internalParams.CheckValid(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported auth type")
|
return fmt.Errorf("unsupported auth type")
|
||||||
|
|
|
@ -87,6 +87,7 @@ func TestClientMessage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelloClientMessage(t *testing.T) {
|
func TestHelloClientMessage(t *testing.T) {
|
||||||
|
internalAuthParams := []byte("{\"backend\":\"https://domain.invalid\"}")
|
||||||
valid_messages := []testCheckValid{
|
valid_messages := []testCheckValid{
|
||||||
&HelloClientMessage{
|
&HelloClientMessage{
|
||||||
Version: HelloVersion,
|
Version: HelloVersion,
|
||||||
|
@ -107,7 +108,7 @@ func TestHelloClientMessage(t *testing.T) {
|
||||||
Version: HelloVersion,
|
Version: HelloVersion,
|
||||||
Auth: HelloClientMessageAuth{
|
Auth: HelloClientMessageAuth{
|
||||||
Type: "internal",
|
Type: "internal",
|
||||||
Params: &json.RawMessage{'{', '}'},
|
Params: (*json.RawMessage)(&internalAuthParams),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&HelloClientMessage{
|
&HelloClientMessage{
|
||||||
|
@ -145,6 +146,13 @@ func TestHelloClientMessage(t *testing.T) {
|
||||||
Url: "invalid-url",
|
Url: "invalid-url",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&HelloClientMessage{
|
||||||
|
Version: HelloVersion,
|
||||||
|
Auth: HelloClientMessageAuth{
|
||||||
|
Type: "internal",
|
||||||
|
Params: &json.RawMessage{'{', '}'},
|
||||||
|
},
|
||||||
|
},
|
||||||
&HelloClientMessage{
|
&HelloClientMessage{
|
||||||
Version: HelloVersion,
|
Version: HelloVersion,
|
||||||
Auth: HelloClientMessageAuth{
|
Auth: HelloClientMessageAuth{
|
||||||
|
|
|
@ -44,11 +44,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendClient struct {
|
type BackendClient struct {
|
||||||
transport *http.Transport
|
transport *http.Transport
|
||||||
whitelist map[string]bool
|
version string
|
||||||
whitelistAll bool
|
backends *BackendConfiguration
|
||||||
secret []byte
|
|
||||||
version string
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
@ -57,31 +55,9 @@ type BackendClient struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string) (*BackendClient, error) {
|
func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost int, version string) (*BackendClient, error) {
|
||||||
whitelist := make(map[string]bool)
|
backends, err := NewBackendConfiguration(config)
|
||||||
whitelistAll, _ := config.GetBool("backend", "allowall")
|
if err != nil {
|
||||||
if whitelistAll {
|
return nil, err
|
||||||
log.Println("WARNING: All backend hostnames are allowed, only use for development!")
|
|
||||||
} else {
|
|
||||||
urls, _ := config.GetString("backend", "allowed")
|
|
||||||
for _, u := range strings.Split(urls, ",") {
|
|
||||||
u = strings.TrimSpace(u)
|
|
||||||
if idx := strings.IndexByte(u, '/'); idx != -1 {
|
|
||||||
log.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u)
|
|
||||||
u = u[:idx]
|
|
||||||
}
|
|
||||||
if u != "" {
|
|
||||||
whitelist[strings.ToLower(u)] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(whitelist) == 0 {
|
|
||||||
log.Println("WARNING: No backend hostnames are allowed, check your configuration!")
|
|
||||||
} else {
|
|
||||||
hosts := make([]string, 0, len(whitelist))
|
|
||||||
for u := range whitelist {
|
|
||||||
hosts = append(hosts, u)
|
|
||||||
}
|
|
||||||
log.Printf("Allowed backend hostnames: %s\n", hosts)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
skipverify, _ := config.GetBool("backend", "skipverify")
|
skipverify, _ := config.GetBool("backend", "skipverify")
|
||||||
|
@ -89,8 +65,6 @@ func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost in
|
||||||
log.Println("WARNING: Backend verification is disabled!")
|
log.Println("WARNING: Backend verification is disabled!")
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, _ := config.GetString("backend", "secret")
|
|
||||||
|
|
||||||
tlsconfig := &tls.Config{
|
tlsconfig := &tls.Config{
|
||||||
InsecureSkipVerify: skipverify,
|
InsecureSkipVerify: skipverify,
|
||||||
}
|
}
|
||||||
|
@ -100,11 +74,9 @@ func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost in
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BackendClient{
|
return &BackendClient{
|
||||||
transport: transport,
|
transport: transport,
|
||||||
whitelist: whitelist,
|
version: version,
|
||||||
whitelistAll: whitelistAll,
|
backends: backends,
|
||||||
secret: []byte(secret),
|
|
||||||
version: version,
|
|
||||||
|
|
||||||
maxConcurrentRequestsPerHost: maxConcurrentRequestsPerHost,
|
maxConcurrentRequestsPerHost: maxConcurrentRequestsPerHost,
|
||||||
clients: make(map[string]*HttpClientPool),
|
clients: make(map[string]*HttpClientPool),
|
||||||
|
@ -135,13 +107,24 @@ func (b *BackendClient) getPool(url *url.URL) (*HttpClientPool, error) {
|
||||||
return pool, nil
|
return pool, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendClient) IsUrlAllowed(u *url.URL) bool {
|
func (b *BackendClient) IsCompatBackend() bool {
|
||||||
if u == nil {
|
return b.backends.IsCompatBackend()
|
||||||
// Reject all invalid URLs.
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.whitelistAll || b.whitelist[u.Host]
|
func (b *BackendClient) GetCommonSecret() []byte {
|
||||||
|
return b.backends.GetCommonSecret()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendClient) GetBackend(u *url.URL) *Backend {
|
||||||
|
return b.backends.GetBackend(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendClient) GetBackends() []*Backend {
|
||||||
|
return b.backends.GetBackends()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendClient) IsUrlAllowed(u *url.URL) bool {
|
||||||
|
return b.backends.IsUrlAllowed(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOcsRequest(u *url.URL) bool {
|
func isOcsRequest(u *url.URL) bool {
|
||||||
|
@ -304,6 +287,11 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ
|
||||||
return fmt.Errorf("No url passed to perform JSON request %+v", request)
|
return fmt.Errorf("No url passed to perform JSON request %+v", request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secret := b.backends.GetSecret(u)
|
||||||
|
if secret == nil {
|
||||||
|
return fmt.Errorf("No backend secret configured for for %s", u)
|
||||||
|
}
|
||||||
|
|
||||||
pool, err := b.getPool(u)
|
pool, err := b.getPool(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Could not get client pool for host %s: %s\n", u.Host, err)
|
log.Printf("Could not get client pool for host %s: %s\n", u.Host, err)
|
||||||
|
@ -338,7 +326,7 @@ func (b *BackendClient) PerformJSONRequest(ctx context.Context, u *url.URL, requ
|
||||||
req.Header.Set("User-Agent", "nextcloud-spreed-signaling/"+b.version)
|
req.Header.Set("User-Agent", "nextcloud-spreed-signaling/"+b.version)
|
||||||
|
|
||||||
// Add checksum so the backend can validate the request.
|
// Add checksum so the backend can validate the request.
|
||||||
AddBackendChecksum(req, data, b.secret)
|
AddBackendChecksum(req, data, secret)
|
||||||
|
|
||||||
resp, err := performRequestWithRedirects(ctx, c, req, data)
|
resp, err := performRequestWithRedirects(ctx, c, req, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -35,71 +35,6 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testUrls(t *testing.T, client *BackendClient, valid_urls []string, invalid_urls []string) {
|
|
||||||
for _, u := range valid_urls {
|
|
||||||
parsed, err := url.ParseRequestURI(u)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("The url %s should be valid, got %s", u, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !client.IsUrlAllowed(parsed) {
|
|
||||||
t.Errorf("The url %s should be allowed", u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, u := range invalid_urls {
|
|
||||||
parsed, _ := url.ParseRequestURI(u)
|
|
||||||
if client.IsUrlAllowed(parsed) {
|
|
||||||
t.Errorf("The url %s should not be allowed", u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsUrlAllowed(t *testing.T) {
|
|
||||||
valid_urls := []string{
|
|
||||||
"http://domain.invalid",
|
|
||||||
"https://domain.invalid",
|
|
||||||
}
|
|
||||||
invalid_urls := []string{
|
|
||||||
"http://otherdomain.invalid",
|
|
||||||
"https://otherdomain.invalid",
|
|
||||||
"domain.invalid",
|
|
||||||
}
|
|
||||||
client := &BackendClient{
|
|
||||||
whitelistAll: false,
|
|
||||||
whitelist: map[string]bool{
|
|
||||||
"domain.invalid": true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testUrls(t, client, valid_urls, invalid_urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsUrlAllowed_EmptyWhitelist(t *testing.T) {
|
|
||||||
valid_urls := []string{}
|
|
||||||
invalid_urls := []string{
|
|
||||||
"http://domain.invalid",
|
|
||||||
"https://domain.invalid",
|
|
||||||
"domain.invalid",
|
|
||||||
}
|
|
||||||
client := &BackendClient{
|
|
||||||
whitelistAll: false,
|
|
||||||
}
|
|
||||||
testUrls(t, client, valid_urls, invalid_urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsUrlAllowed_WhitelistAll(t *testing.T) {
|
|
||||||
valid_urls := []string{
|
|
||||||
"http://domain.invalid",
|
|
||||||
"https://domain.invalid",
|
|
||||||
}
|
|
||||||
invalid_urls := []string{
|
|
||||||
"domain.invalid",
|
|
||||||
}
|
|
||||||
client := &BackendClient{
|
|
||||||
whitelistAll: true,
|
|
||||||
}
|
|
||||||
testUrls(t, client, valid_urls, invalid_urls)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPostOnRedirect(t *testing.T) {
|
func TestPostOnRedirect(t *testing.T) {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/ocs/v2.php/one", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/ocs/v2.php/one", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -142,17 +77,20 @@ func TestPostOnRedirect(t *testing.T) {
|
||||||
server := httptest.NewServer(r)
|
server := httptest.NewServer(r)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
config := &goconf.ConfigFile{}
|
u, err := url.Parse(server.URL + "/ocs/v2.php/one")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := goconf.NewConfigFile()
|
||||||
|
config.AddOption("backend", "allowed", u.Host)
|
||||||
|
config.AddOption("backend", "secret", string(testBackendSecret))
|
||||||
client, err := NewBackendClient(config, 1, "0.0")
|
client, err := NewBackendClient(config, 1, "0.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
u, err := url.Parse(server.URL + "/ocs/v2.php/one")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
request := map[string]string{
|
request := map[string]string{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
218
src/signaling/backend_configuration.go
Normal file
218
src/signaling/backend_configuration.go
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/**
|
||||||
|
* Standalone signaling server for the Nextcloud Spreed app.
|
||||||
|
* Copyright (C) 2020 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 (
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dlintw/goconf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Backend struct {
|
||||||
|
id string
|
||||||
|
url string
|
||||||
|
secret []byte
|
||||||
|
compat bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) Id() string {
|
||||||
|
return b.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) Secret() []byte {
|
||||||
|
return b.secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) IsCompat() bool {
|
||||||
|
return b.compat
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackendConfiguration struct {
|
||||||
|
backends map[string][]*Backend
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
whitelistAll bool
|
||||||
|
commonSecret []byte
|
||||||
|
compatBackend bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration, error) {
|
||||||
|
whitelistAll, _ := config.GetBool("backend", "allowall")
|
||||||
|
commonSecret, _ := config.GetString("backend", "secret")
|
||||||
|
backends := make(map[string][]*Backend)
|
||||||
|
compatBackend := commonSecret != ""
|
||||||
|
if whitelistAll {
|
||||||
|
log.Println("WARNING: All backend hostnames are allowed, only use for development!")
|
||||||
|
} else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
|
||||||
|
seenIds := make(map[string]bool)
|
||||||
|
compatBackend = false
|
||||||
|
for _, id := range strings.Split(backendIds, ",") {
|
||||||
|
id = strings.TrimSpace(id)
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if seenIds[id] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenIds[id] = true
|
||||||
|
|
||||||
|
u, _ := config.GetString(id, "url")
|
||||||
|
secret, _ := config.GetString(id, "secret")
|
||||||
|
if u == "" || secret == "" {
|
||||||
|
log.Printf("Backend %s is missing or incomplete, skipping", id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if u[len(u)-1] != '/' {
|
||||||
|
u += "/"
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
backends[parsed.Host] = append(backends[parsed.Host], &Backend{
|
||||||
|
id: id,
|
||||||
|
url: u,
|
||||||
|
secret: []byte(secret),
|
||||||
|
})
|
||||||
|
log.Printf("Backend %s added for %s", id, u)
|
||||||
|
}
|
||||||
|
} else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" {
|
||||||
|
// Old-style configuration, only hosts are configured and are using a common secret.
|
||||||
|
whitelist := make(map[string]bool)
|
||||||
|
for _, u := range strings.Split(allowedUrls, ",") {
|
||||||
|
u = strings.TrimSpace(u)
|
||||||
|
if idx := strings.IndexByte(u, '/'); idx != -1 {
|
||||||
|
log.Printf("WARNING: Removing path from allowed hostname \"%s\", check your configuration!", u)
|
||||||
|
u = u[:idx]
|
||||||
|
}
|
||||||
|
if u != "" {
|
||||||
|
whitelist[strings.ToLower(u)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(whitelist) == 0 {
|
||||||
|
log.Println("WARNING: No backend hostnames are allowed, check your configuration!")
|
||||||
|
} else {
|
||||||
|
hosts := make([]string, 0, len(whitelist))
|
||||||
|
for host := range whitelist {
|
||||||
|
hosts = append(hosts, host)
|
||||||
|
backends[host] = []*Backend{
|
||||||
|
&Backend{
|
||||||
|
id: "compat",
|
||||||
|
secret: []byte(commonSecret),
|
||||||
|
compat: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(hosts) > 1 {
|
||||||
|
log.Println("WARNING: Using deprecated backend configuration. Please migrate the \"allowed\" setting to the new \"backends\" configuration.")
|
||||||
|
}
|
||||||
|
log.Printf("Allowed backend hostnames: %s\n", hosts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BackendConfiguration{
|
||||||
|
backends: backends,
|
||||||
|
|
||||||
|
whitelistAll: whitelistAll,
|
||||||
|
commonSecret: []byte(commonSecret),
|
||||||
|
compatBackend: compatBackend,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendConfiguration) IsCompatBackend() bool {
|
||||||
|
return b.compatBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendConfiguration) GetCommonSecret() []byte {
|
||||||
|
return b.commonSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendConfiguration) GetBackend(u *url.URL) *Backend {
|
||||||
|
entries, found := b.backends[u.Host]
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := u.String()
|
||||||
|
if s[len(s)-1] != '/' {
|
||||||
|
s += "/"
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.url == "" {
|
||||||
|
// Old-style configuration, only hosts are configured.
|
||||||
|
return entry
|
||||||
|
} else if strings.HasPrefix(s, entry.url) {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendConfiguration) GetBackends() []*Backend {
|
||||||
|
var result []*Backend
|
||||||
|
for _, entries := range b.backends {
|
||||||
|
for _, entry := range entries {
|
||||||
|
result = append(result, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendConfiguration) IsUrlAllowed(u *url.URL) bool {
|
||||||
|
if u == nil {
|
||||||
|
// Reject all invalid URLs.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.whitelistAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := b.GetBackend(u)
|
||||||
|
return backend != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BackendConfiguration) GetSecret(u *url.URL) []byte {
|
||||||
|
if u == nil {
|
||||||
|
// Reject all invalid URLs.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.whitelistAll {
|
||||||
|
return b.commonSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := b.GetBackend(u)
|
||||||
|
if entry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.secret
|
||||||
|
}
|
165
src/signaling/backend_configuration_test.go
Normal file
165
src/signaling/backend_configuration_test.go
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* Standalone signaling server for the Nextcloud Spreed app.
|
||||||
|
* Copyright (C) 2020 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 (
|
||||||
|
"bytes"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dlintw/goconf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testUrls(t *testing.T, config *BackendConfiguration, valid_urls []string, invalid_urls []string) {
|
||||||
|
for _, u := range valid_urls {
|
||||||
|
parsed, err := url.ParseRequestURI(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("The url %s should be valid, got %s", u, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !config.IsUrlAllowed(parsed) {
|
||||||
|
t.Errorf("The url %s should be allowed", u)
|
||||||
|
}
|
||||||
|
if secret := config.GetSecret(parsed); !bytes.Equal(secret, testBackendSecret) {
|
||||||
|
t.Errorf("Expected secret %s for url %s, got %s", string(testBackendSecret), u, string(secret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, u := range invalid_urls {
|
||||||
|
parsed, _ := url.ParseRequestURI(u)
|
||||||
|
if config.IsUrlAllowed(parsed) {
|
||||||
|
t.Errorf("The url %s should not be allowed", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBackends(t *testing.T, config *BackendConfiguration, valid_urls [][]string, invalid_urls []string) {
|
||||||
|
for _, entry := range valid_urls {
|
||||||
|
u := entry[0]
|
||||||
|
parsed, err := url.ParseRequestURI(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("The url %s should be valid, got %s", u, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !config.IsUrlAllowed(parsed) {
|
||||||
|
t.Errorf("The url %s should be allowed", u)
|
||||||
|
}
|
||||||
|
s := entry[1]
|
||||||
|
if secret := config.GetSecret(parsed); !bytes.Equal(secret, []byte(s)) {
|
||||||
|
t.Errorf("Expected secret %s for url %s, got %s", string(s), u, string(secret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, u := range invalid_urls {
|
||||||
|
parsed, _ := url.ParseRequestURI(u)
|
||||||
|
if config.IsUrlAllowed(parsed) {
|
||||||
|
t.Errorf("The url %s should not be allowed", u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsUrlAllowed_Compat(t *testing.T) {
|
||||||
|
// Old-style configuration
|
||||||
|
valid_urls := []string{
|
||||||
|
"http://domain.invalid",
|
||||||
|
"https://domain.invalid",
|
||||||
|
}
|
||||||
|
invalid_urls := []string{
|
||||||
|
"http://otherdomain.invalid",
|
||||||
|
"https://otherdomain.invalid",
|
||||||
|
"domain.invalid",
|
||||||
|
}
|
||||||
|
config := goconf.NewConfigFile()
|
||||||
|
config.AddOption("backend", "allowed", "domain.invalid")
|
||||||
|
config.AddOption("backend", "secret", string(testBackendSecret))
|
||||||
|
cfg, err := NewBackendConfiguration(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testUrls(t, cfg, valid_urls, invalid_urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsUrlAllowed(t *testing.T) {
|
||||||
|
valid_urls := [][]string{
|
||||||
|
[]string{"https://domain.invalid/foo", string(testBackendSecret) + "-foo"},
|
||||||
|
[]string{"https://domain.invalid/foo/", string(testBackendSecret) + "-foo"},
|
||||||
|
[]string{"https://domain.invalid/foo/folder", string(testBackendSecret) + "-foo"},
|
||||||
|
[]string{"https://domain.invalid/bar", string(testBackendSecret) + "-bar"},
|
||||||
|
[]string{"https://domain.invalid/bar/", string(testBackendSecret) + "-bar"},
|
||||||
|
[]string{"https://domain.invalid/bar/folder/", string(testBackendSecret) + "-bar"},
|
||||||
|
[]string{"https://otherdomain.invalid/", string(testBackendSecret) + "-lala"},
|
||||||
|
[]string{"https://otherdomain.invalid/folder/", string(testBackendSecret) + "-lala"},
|
||||||
|
}
|
||||||
|
invalid_urls := []string{
|
||||||
|
"https://domain.invalid",
|
||||||
|
"https://domain.invalid/",
|
||||||
|
"https://www.domain.invalid/foo/",
|
||||||
|
"https://domain.invalid/baz/",
|
||||||
|
}
|
||||||
|
config := goconf.NewConfigFile()
|
||||||
|
config.AddOption("backend", "backends", "foo, bar, lala, missing")
|
||||||
|
config.AddOption("foo", "url", "https://domain.invalid/foo")
|
||||||
|
config.AddOption("foo", "secret", string(testBackendSecret)+"-foo")
|
||||||
|
config.AddOption("bar", "url", "https://domain.invalid/bar/")
|
||||||
|
config.AddOption("bar", "secret", string(testBackendSecret)+"-bar")
|
||||||
|
config.AddOption("lala", "url", "https://otherdomain.invalid/")
|
||||||
|
config.AddOption("lala", "secret", string(testBackendSecret)+"-lala")
|
||||||
|
cfg, err := NewBackendConfiguration(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testBackends(t, cfg, valid_urls, invalid_urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsUrlAllowed_EmptyWhitelist(t *testing.T) {
|
||||||
|
valid_urls := []string{}
|
||||||
|
invalid_urls := []string{
|
||||||
|
"http://domain.invalid",
|
||||||
|
"https://domain.invalid",
|
||||||
|
"domain.invalid",
|
||||||
|
}
|
||||||
|
config := goconf.NewConfigFile()
|
||||||
|
config.AddOption("backend", "allowed", "")
|
||||||
|
config.AddOption("backend", "secret", string(testBackendSecret))
|
||||||
|
cfg, err := NewBackendConfiguration(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testUrls(t, cfg, valid_urls, invalid_urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsUrlAllowed_WhitelistAll(t *testing.T) {
|
||||||
|
valid_urls := []string{
|
||||||
|
"http://domain.invalid",
|
||||||
|
"https://domain.invalid",
|
||||||
|
}
|
||||||
|
invalid_urls := []string{
|
||||||
|
"domain.invalid",
|
||||||
|
}
|
||||||
|
config := goconf.NewConfigFile()
|
||||||
|
config.AddOption("backend", "allowall", "true")
|
||||||
|
config.AddOption("backend", "allowed", "")
|
||||||
|
config.AddOption("backend", "secret", string(testBackendSecret))
|
||||||
|
cfg, err := NewBackendConfiguration(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testUrls(t, cfg, valid_urls, invalid_urls)
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ package signaling
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -32,6 +33,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -57,19 +59,16 @@ type BackendServer struct {
|
||||||
version string
|
version string
|
||||||
welcomeMessage string
|
welcomeMessage string
|
||||||
|
|
||||||
secret []byte
|
|
||||||
|
|
||||||
turnapikey string
|
turnapikey string
|
||||||
turnsecret []byte
|
turnsecret []byte
|
||||||
turnvalid time.Duration
|
turnvalid time.Duration
|
||||||
turnservers []string
|
turnservers []string
|
||||||
|
|
||||||
statsAllowedIps map[string]bool
|
statsAllowedIps map[string]bool
|
||||||
|
invalidSecret []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) {
|
func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*BackendServer, error) {
|
||||||
secret, _ := config.GetString("backend", "secret")
|
|
||||||
|
|
||||||
turnapikey, _ := config.GetString("turn", "apikey")
|
turnapikey, _ := config.GetString("turn", "apikey")
|
||||||
turnsecret, _ := config.GetString("turn", "secret")
|
turnsecret, _ := config.GetString("turn", "secret")
|
||||||
turnservers, _ := config.GetString("turn", "servers")
|
turnservers, _ := config.GetString("turn", "servers")
|
||||||
|
@ -117,21 +116,24 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalidSecret := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(invalidSecret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &BackendServer{
|
return &BackendServer{
|
||||||
hub: hub,
|
hub: hub,
|
||||||
nats: hub.nats,
|
nats: hub.nats,
|
||||||
roomSessions: hub.roomSessions,
|
roomSessions: hub.roomSessions,
|
||||||
version: version,
|
version: version,
|
||||||
|
|
||||||
secret: []byte(secret),
|
turnapikey: turnapikey,
|
||||||
|
|
||||||
turnapikey: turnapikey,
|
|
||||||
|
|
||||||
turnsecret: []byte(turnsecret),
|
turnsecret: []byte(turnsecret),
|
||||||
turnvalid: turnvalid,
|
turnvalid: turnvalid,
|
||||||
turnservers: turnserverslist,
|
turnservers: turnserverslist,
|
||||||
|
|
||||||
statsAllowedIps: statsAllowedIps,
|
statsAllowedIps: statsAllowedIps,
|
||||||
|
invalidSecret: invalidSecret,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +150,7 @@ func (b *BackendServer) Start(r *mux.Router) error {
|
||||||
}
|
}
|
||||||
s := r.PathPrefix("/api/v1").Subrouter()
|
s := r.PathPrefix("/api/v1").Subrouter()
|
||||||
s.HandleFunc("/welcome", b.setComonHeaders(b.welcomeFunc)).Methods("GET")
|
s.HandleFunc("/welcome", b.setComonHeaders(b.welcomeFunc)).Methods("GET")
|
||||||
s.HandleFunc("/room/{roomid}", b.setComonHeaders(b.validateBackendRequest(b.roomHandler))).Methods("POST")
|
s.HandleFunc("/room/{roomid}", b.setComonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST")
|
||||||
s.HandleFunc("/stats", b.setComonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET")
|
s.HandleFunc("/stats", b.setComonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET")
|
||||||
|
|
||||||
// Provide a REST service to get TURN credentials.
|
// Provide a REST service to get TURN credentials.
|
||||||
|
@ -236,7 +238,7 @@ func (b *BackendServer) getTurnCredentials(w http.ResponseWriter, r *http.Reques
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) validateBackendRequest(f func(http.ResponseWriter, *http.Request, []byte)) func(http.ResponseWriter, *http.Request) {
|
func (b *BackendServer) parseRequestBody(f func(http.ResponseWriter, *http.Request, []byte)) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
if r.ContentLength == -1 {
|
if r.ContentLength == -1 {
|
||||||
|
@ -265,16 +267,12 @@ func (b *BackendServer) validateBackendRequest(f func(http.ResponseWriter, *http
|
||||||
http.Error(w, "Could not read body", http.StatusBadRequest)
|
http.Error(w, "Could not read body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ValidateBackendChecksum(r, body, b.secret) {
|
|
||||||
http.Error(w, "Authentication check failed", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f(w, r, body)
|
f(w, r, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) sendRoomInvite(roomid string, userids []string, properties *json.RawMessage) {
|
func (b *BackendServer) sendRoomInvite(roomid string, backend *Backend, userids []string, properties *json.RawMessage) {
|
||||||
msg := &ServerMessage{
|
msg := &ServerMessage{
|
||||||
Type: "event",
|
Type: "event",
|
||||||
Event: &EventServerMessage{
|
Event: &EventServerMessage{
|
||||||
|
@ -287,11 +285,11 @@ func (b *BackendServer) sendRoomInvite(roomid string, userids []string, properti
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, userid := range userids {
|
for _, userid := range userids {
|
||||||
b.nats.PublishMessage(GetSubjectForUserId(userid), msg)
|
b.nats.PublishMessage(GetSubjectForUserId(userid, backend), msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) sendRoomDisinvite(roomid string, reason string, userids []string, sessionids []string) {
|
func (b *BackendServer) sendRoomDisinvite(roomid string, backend *Backend, reason string, userids []string, sessionids []string) {
|
||||||
msg := &ServerMessage{
|
msg := &ServerMessage{
|
||||||
Type: "event",
|
Type: "event",
|
||||||
Event: &EventServerMessage{
|
Event: &EventServerMessage{
|
||||||
|
@ -306,7 +304,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, reason string, userids
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, userid := range userids {
|
for _, userid := range userids {
|
||||||
b.nats.PublishMessage(GetSubjectForUserId(userid), msg)
|
b.nats.PublishMessage(GetSubjectForUserId(userid, backend), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := time.Second
|
timeout := time.Second
|
||||||
|
@ -330,7 +328,7 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, reason string, userids
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) sendRoomUpdate(roomid string, notified_userids []string, all_userids []string, properties *json.RawMessage) {
|
func (b *BackendServer) sendRoomUpdate(roomid string, backend *Backend, notified_userids []string, all_userids []string, properties *json.RawMessage) {
|
||||||
msg := &ServerMessage{
|
msg := &ServerMessage{
|
||||||
Type: "event",
|
Type: "event",
|
||||||
Event: &EventServerMessage{
|
Event: &EventServerMessage{
|
||||||
|
@ -352,7 +350,7 @@ func (b *BackendServer) sendRoomUpdate(roomid string, notified_userids []string,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b.nats.PublishMessage(GetSubjectForUserId(userid), msg)
|
b.nats.PublishMessage(GetSubjectForUserId(userid, backend), msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,7 +429,7 @@ func (b *BackendServer) fixupUserSessions(cache *ConcurrentStringStringMap, user
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) sendRoomIncall(roomid string, request *BackendServerRoomRequest) error {
|
func (b *BackendServer) sendRoomIncall(roomid string, backend *Backend, request *BackendServerRoomRequest) error {
|
||||||
timeout := time.Second
|
timeout := time.Second
|
||||||
|
|
||||||
var cache ConcurrentStringStringMap
|
var cache ConcurrentStringStringMap
|
||||||
|
@ -444,10 +442,10 @@ func (b *BackendServer) sendRoomIncall(roomid string, request *BackendServerRoom
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.nats.PublishBackendServerRoomRequest("backend.room."+roomid, request)
|
return b.nats.PublishBackendServerRoomRequest(GetSubjectForBackendRoomId(roomid, backend), request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) sendRoomParticipantsUpdate(roomid string, request *BackendServerRoomRequest) error {
|
func (b *BackendServer) sendRoomParticipantsUpdate(roomid string, backend *Backend, request *BackendServerRoomRequest) error {
|
||||||
timeout := time.Second
|
timeout := time.Second
|
||||||
|
|
||||||
// Convert (Nextcloud) session ids to signaling session ids.
|
// Convert (Nextcloud) session ids to signaling session ids.
|
||||||
|
@ -497,14 +495,62 @@ loop:
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return b.nats.PublishBackendServerRoomRequest("backend.room."+roomid, request)
|
return b.nats.PublishBackendServerRoomRequest(GetSubjectForBackendRoomId(roomid, backend), request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) sendRoomMessage(roomid string, request *BackendServerRoomRequest) error {
|
func (b *BackendServer) sendRoomMessage(roomid string, backend *Backend, request *BackendServerRoomRequest) error {
|
||||||
return b.nats.PublishBackendServerRoomRequest("backend.room."+roomid, request)
|
return b.nats.PublishBackendServerRoomRequest(GetSubjectForBackendRoomId(roomid, backend), request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body []byte) {
|
func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body []byte) {
|
||||||
|
v := mux.Vars(r)
|
||||||
|
roomid := v["roomid"]
|
||||||
|
|
||||||
|
var backend *Backend
|
||||||
|
backendUrl := r.Header.Get(HeaderBackendServer)
|
||||||
|
if backendUrl != "" {
|
||||||
|
if u, err := url.Parse(backendUrl); err == nil {
|
||||||
|
backend = b.hub.backend.GetBackend(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
if backend == nil {
|
||||||
|
// Unknown backend URL passed, return immediately.
|
||||||
|
http.Error(w, "Authentication check failed", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var secret []byte
|
||||||
|
if backend == nil {
|
||||||
|
if b.hub.backend.IsCompatBackend() {
|
||||||
|
// Old-style configuration using a single secret for all backends.
|
||||||
|
secret = b.hub.backend.GetCommonSecret()
|
||||||
|
} else {
|
||||||
|
// Old-style Talk, find backend that created the checksum.
|
||||||
|
// TODO(fancycode): Remove once all supported Talk versions send the backend header.
|
||||||
|
for _, b := range b.hub.backend.GetBackends() {
|
||||||
|
if ValidateBackendChecksum(r, body, b.Secret()) {
|
||||||
|
backend = b
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if backend == nil {
|
||||||
|
http.Error(w, "Authentication check failed", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = backend.Secret()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
secret = backend.Secret()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ValidateBackendChecksum(r, body, secret) {
|
||||||
|
http.Error(w, "Authentication check failed", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var request BackendServerRoomRequest
|
var request BackendServerRoomRequest
|
||||||
if err := json.Unmarshal(body, &request); err != nil {
|
if err := json.Unmarshal(body, &request); err != nil {
|
||||||
log.Printf("Error decoding body %s: %s\n", string(body), err)
|
log.Printf("Error decoding body %s: %s\n", string(body), err)
|
||||||
|
@ -514,28 +560,26 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body
|
||||||
|
|
||||||
request.ReceivedTime = time.Now().UnixNano()
|
request.ReceivedTime = time.Now().UnixNano()
|
||||||
|
|
||||||
v := mux.Vars(r)
|
|
||||||
roomid := v["roomid"]
|
|
||||||
var err error
|
var err error
|
||||||
switch request.Type {
|
switch request.Type {
|
||||||
case "invite":
|
case "invite":
|
||||||
b.sendRoomInvite(roomid, request.Invite.UserIds, request.Invite.Properties)
|
b.sendRoomInvite(roomid, backend, request.Invite.UserIds, request.Invite.Properties)
|
||||||
b.sendRoomUpdate(roomid, request.Invite.UserIds, request.Invite.AllUserIds, request.Invite.Properties)
|
b.sendRoomUpdate(roomid, backend, request.Invite.UserIds, request.Invite.AllUserIds, request.Invite.Properties)
|
||||||
case "disinvite":
|
case "disinvite":
|
||||||
b.sendRoomDisinvite(roomid, DisinviteReasonDisinvited, request.Disinvite.UserIds, request.Disinvite.SessionIds)
|
b.sendRoomDisinvite(roomid, backend, DisinviteReasonDisinvited, request.Disinvite.UserIds, request.Disinvite.SessionIds)
|
||||||
b.sendRoomUpdate(roomid, request.Disinvite.UserIds, request.Disinvite.AllUserIds, request.Disinvite.Properties)
|
b.sendRoomUpdate(roomid, backend, request.Disinvite.UserIds, request.Disinvite.AllUserIds, request.Disinvite.Properties)
|
||||||
case "update":
|
case "update":
|
||||||
err = b.nats.PublishBackendServerRoomRequest("backend.room."+roomid, &request)
|
err = b.nats.PublishBackendServerRoomRequest(GetSubjectForBackendRoomId(roomid, backend), &request)
|
||||||
b.sendRoomUpdate(roomid, nil, request.Update.UserIds, request.Update.Properties)
|
b.sendRoomUpdate(roomid, backend, nil, request.Update.UserIds, request.Update.Properties)
|
||||||
case "delete":
|
case "delete":
|
||||||
err = b.nats.PublishBackendServerRoomRequest("backend.room."+roomid, &request)
|
err = b.nats.PublishBackendServerRoomRequest(GetSubjectForBackendRoomId(roomid, backend), &request)
|
||||||
b.sendRoomDisinvite(roomid, DisinviteReasonDeleted, request.Delete.UserIds, nil)
|
b.sendRoomDisinvite(roomid, backend, DisinviteReasonDeleted, request.Delete.UserIds, nil)
|
||||||
case "incall":
|
case "incall":
|
||||||
err = b.sendRoomIncall(roomid, &request)
|
err = b.sendRoomIncall(roomid, backend, &request)
|
||||||
case "participants":
|
case "participants":
|
||||||
err = b.sendRoomParticipantsUpdate(roomid, &request)
|
err = b.sendRoomParticipantsUpdate(roomid, backend, &request)
|
||||||
case "message":
|
case "message":
|
||||||
err = b.sendRoomMessage(roomid, &request)
|
err = b.sendRoomMessage(roomid, backend, &request)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "Unsupported request type: "+request.Type, http.StatusBadRequest)
|
http.Error(w, "Unsupported request type: "+request.Type, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
|
|
@ -123,6 +123,7 @@ func performBackendRequest(url string, body []byte) (*http.Response, error) {
|
||||||
check := CalculateBackendChecksum(rnd, body, testBackendSecret)
|
check := CalculateBackendChecksum(rnd, body, testBackendSecret)
|
||||||
request.Header.Set("Spreed-Signaling-Random", rnd)
|
request.Header.Set("Spreed-Signaling-Random", rnd)
|
||||||
request.Header.Set("Spreed-Signaling-Checksum", check)
|
request.Header.Set("Spreed-Signaling-Checksum", check)
|
||||||
|
request.Header.Set("Spreed-Signaling-Backend", url)
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
return client.Do(request)
|
return client.Do(request)
|
||||||
}
|
}
|
||||||
|
@ -212,6 +213,56 @@ func TestBackendServer_InvalidAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBackendServer_OldCompatAuth(t *testing.T) {
|
||||||
|
_, _, _, _, _, server, shutdown := CreateBackendServerForTest(t)
|
||||||
|
defer shutdown()
|
||||||
|
|
||||||
|
roomId := "the-room-id"
|
||||||
|
userid := "the-user-id"
|
||||||
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
||||||
|
msg := &BackendServerRoomRequest{
|
||||||
|
Type: "invite",
|
||||||
|
Invite: &BackendRoomInviteRequest{
|
||||||
|
UserIds: []string{
|
||||||
|
userid,
|
||||||
|
},
|
||||||
|
AllUserIds: []string{
|
||||||
|
userid,
|
||||||
|
},
|
||||||
|
Properties: &roomProperties,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest("POST", server.URL+"/api/v1/room/"+roomId, bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
rnd := newRandomString(32)
|
||||||
|
check := CalculateBackendChecksum(rnd, data, testBackendSecret)
|
||||||
|
request.Header.Set("Spreed-Signaling-Random", rnd)
|
||||||
|
request.Header.Set("Spreed-Signaling-Checksum", check)
|
||||||
|
client := &http.Client{}
|
||||||
|
res, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Expected error response, got %s: %s", res.Status, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBackendServer_InvalidBody(t *testing.T) {
|
func TestBackendServer_InvalidBody(t *testing.T) {
|
||||||
_, _, _, _, _, server, shutdown := CreateBackendServerForTest(t)
|
_, _, _, _, _, server, shutdown := CreateBackendServerForTest(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
@ -260,14 +311,20 @@ func TestBackendServer_UnsupportedRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendServer_RoomInvite(t *testing.T) {
|
func TestBackendServer_RoomInvite(t *testing.T) {
|
||||||
_, _, n, _, _, server, shutdown := CreateBackendServerForTest(t)
|
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
|
||||||
|
u, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
userid := "test-userid"
|
userid := "test-userid"
|
||||||
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
||||||
|
backend := hub.backend.GetBackend(u)
|
||||||
|
|
||||||
natsChan := make(chan *nats.Msg, 1)
|
natsChan := make(chan *nats.Msg, 1)
|
||||||
subject := GetSubjectForUserId(userid)
|
subject := GetSubjectForUserId(userid, backend)
|
||||||
sub, err := n.Subscribe(subject, natsChan)
|
sub, err := n.Subscribe(subject, natsChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -321,6 +378,13 @@ func TestBackendServer_RoomDisinvite(t *testing.T) {
|
||||||
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
|
||||||
|
u, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := hub.backend.GetBackend(u)
|
||||||
|
|
||||||
client := NewTestClient(t, server, hub)
|
client := NewTestClient(t, server, hub)
|
||||||
defer client.CloseWithBye()
|
defer client.CloseWithBye()
|
||||||
if err := client.SendHello(testDefaultUserId); err != nil {
|
if err := client.SendHello(testDefaultUserId); err != nil {
|
||||||
|
@ -355,7 +419,7 @@ func TestBackendServer_RoomDisinvite(t *testing.T) {
|
||||||
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
||||||
|
|
||||||
natsChan := make(chan *nats.Msg, 1)
|
natsChan := make(chan *nats.Msg, 1)
|
||||||
subject := GetSubjectForUserId(testDefaultUserId)
|
subject := GetSubjectForUserId(testDefaultUserId, backend)
|
||||||
sub, err := n.Subscribe(subject, natsChan)
|
sub, err := n.Subscribe(subject, natsChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -556,9 +620,18 @@ func TestBackendServer_RoomUpdate(t *testing.T) {
|
||||||
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
|
||||||
|
u, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
roomId := "the-room-id"
|
roomId := "the-room-id"
|
||||||
emptyProperties := json.RawMessage("{}")
|
emptyProperties := json.RawMessage("{}")
|
||||||
room, err := hub.createRoom(roomId, &emptyProperties)
|
backend := hub.backend.GetBackend(u)
|
||||||
|
if backend == nil {
|
||||||
|
t.Fatalf("Did not find backend")
|
||||||
|
}
|
||||||
|
room, err := hub.createRoom(roomId, &emptyProperties, backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create room: %s", err)
|
t.Fatalf("Could not create room: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -568,7 +641,7 @@ func TestBackendServer_RoomUpdate(t *testing.T) {
|
||||||
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
|
||||||
|
|
||||||
natsChan := make(chan *nats.Msg, 1)
|
natsChan := make(chan *nats.Msg, 1)
|
||||||
subject := GetSubjectForUserId(userid)
|
subject := GetSubjectForUserId(userid, backend)
|
||||||
sub, err := n.Subscribe(subject, natsChan)
|
sub, err := n.Subscribe(subject, natsChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -629,16 +702,25 @@ func TestBackendServer_RoomDelete(t *testing.T) {
|
||||||
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
_, _, n, hub, _, server, shutdown := CreateBackendServerForTest(t)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
|
|
||||||
|
u, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
roomId := "the-room-id"
|
roomId := "the-room-id"
|
||||||
emptyProperties := json.RawMessage("{}")
|
emptyProperties := json.RawMessage("{}")
|
||||||
if _, err := hub.createRoom(roomId, &emptyProperties); err != nil {
|
backend := hub.backend.GetBackend(u)
|
||||||
|
if backend == nil {
|
||||||
|
t.Fatalf("Did not find backend")
|
||||||
|
}
|
||||||
|
if _, err := hub.createRoom(roomId, &emptyProperties, backend); err != nil {
|
||||||
t.Fatalf("Could not create room: %s", err)
|
t.Fatalf("Could not create room: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userid := "test-userid"
|
userid := "test-userid"
|
||||||
|
|
||||||
natsChan := make(chan *nats.Msg, 1)
|
natsChan := make(chan *nats.Msg, 1)
|
||||||
subject := GetSubjectForUserId(userid)
|
subject := GetSubjectForUserId(userid, backend)
|
||||||
sub, err := n.Subscribe(subject, natsChan)
|
sub, err := n.Subscribe(subject, natsChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
package signaling
|
package signaling
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -59,6 +58,7 @@ type ClientSession struct {
|
||||||
supportsPermissions bool
|
supportsPermissions bool
|
||||||
permissions map[Permission]bool
|
permissions map[Permission]bool
|
||||||
|
|
||||||
|
backend *Backend
|
||||||
backendUrl string
|
backendUrl string
|
||||||
parsedBackendUrl *url.URL
|
parsedBackendUrl *url.URL
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ type ClientSession struct {
|
||||||
pendingClientMessages []*NatsMessage
|
pendingClientMessages []*NatsMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientSession(hub *Hub, privateId string, publicId string, data *SessionIdData, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) {
|
func NewClientSession(hub *Hub, privateId string, publicId string, data *SessionIdData, backend *Backend, hello *HelloClientMessage, auth *BackendClientAuthResponse) (*ClientSession, error) {
|
||||||
s := &ClientSession{
|
s := &ClientSession{
|
||||||
hub: hub,
|
hub: hub,
|
||||||
privateId: privateId,
|
privateId: privateId,
|
||||||
|
@ -95,6 +95,7 @@ func NewClientSession(hub *Hub, privateId string, publicId string, data *Session
|
||||||
userId: auth.UserId,
|
userId: auth.UserId,
|
||||||
userData: auth.User,
|
userData: auth.User,
|
||||||
|
|
||||||
|
backend: backend,
|
||||||
backendUrl: hello.Auth.Url,
|
backendUrl: hello.Auth.Url,
|
||||||
parsedBackendUrl: hello.Auth.parsedUrl,
|
parsedBackendUrl: hello.Auth.parsedUrl,
|
||||||
|
|
||||||
|
@ -197,6 +198,10 @@ func (s *ClientSession) SetPermissions(permissions []Permission) {
|
||||||
log.Printf("Permissions of session %s changed: %s", s.PublicId(), permissions)
|
log.Printf("Permissions of session %s changed: %s", s.PublicId(), permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ClientSession) Backend() *Backend {
|
||||||
|
return s.backend
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ClientSession) BackendUrl() string {
|
func (s *ClientSession) BackendUrl() string {
|
||||||
return s.backendUrl
|
return s.backendUrl
|
||||||
}
|
}
|
||||||
|
@ -301,11 +306,12 @@ func (s *ClientSession) closeAndWait(wait bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSubjectForUserId(userId string) string {
|
func GetSubjectForUserId(userId string, backend *Backend) string {
|
||||||
// The NATS client doesn't work if a subject contains spaces. As the user id
|
if backend == nil || backend.IsCompat() {
|
||||||
// can have an arbitrary format, we need to make sure the subject is valid.
|
return GetEncodedSubject("user", userId)
|
||||||
// See "https://github.com/nats-io/nats.js/issues/158" for a similar report.
|
} else {
|
||||||
return "user." + base64.StdEncoding.EncodeToString([]byte(userId))
|
return GetEncodedSubject("user", userId+"|"+backend.Id())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientSession) SubscribeNats(n NatsClient) error {
|
func (s *ClientSession) SubscribeNats(n NatsClient) error {
|
||||||
|
@ -314,7 +320,7 @@ func (s *ClientSession) SubscribeNats(n NatsClient) error {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if s.userId != "" {
|
if s.userId != "" {
|
||||||
if s.userSubscription, err = n.Subscribe(GetSubjectForUserId(s.userId), s.natsReceiver); err != nil {
|
if s.userSubscription, err = n.Subscribe(GetSubjectForUserId(s.userId, s.backend), s.natsReceiver); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,7 +337,7 @@ func (s *ClientSession) SubscribeRoomNats(n NatsClient, roomid string, roomSessi
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if s.roomSubscription, err = n.Subscribe("room."+roomid, s.natsReceiver); err != nil {
|
if s.roomSubscription, err = n.Subscribe(GetSubjectForRoomId(roomid, s.Backend()), s.natsReceiver); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -565,7 +565,7 @@ func (h *Hub) processNewClient(client *Client) {
|
||||||
h.startExpectHello(client)
|
h.startExpectHello(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) processRegister(client *Client, message *ClientMessage, auth *BackendClientResponse) {
|
func (h *Hub) processRegister(client *Client, message *ClientMessage, backend *Backend, auth *BackendClientResponse) {
|
||||||
if !client.IsConnected() {
|
if !client.IsConnected() {
|
||||||
// Client disconnected while waiting for "hello" response.
|
// Client disconnected while waiting for "hello" response.
|
||||||
return
|
return
|
||||||
|
@ -584,8 +584,9 @@ func (h *Hub) processRegister(client *Client, message *ClientMessage, auth *Back
|
||||||
sid = atomic.AddUint64(&h.sid, 1)
|
sid = atomic.AddUint64(&h.sid, 1)
|
||||||
}
|
}
|
||||||
sessionIdData := &SessionIdData{
|
sessionIdData := &SessionIdData{
|
||||||
Sid: sid,
|
Sid: sid,
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
|
BackendId: backend.Id(),
|
||||||
}
|
}
|
||||||
privateSessionId, err := h.encodeSessionId(sessionIdData, privateSessionName)
|
privateSessionId, err := h.encodeSessionId(sessionIdData, privateSessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -600,14 +601,14 @@ func (h *Hub) processRegister(client *Client, message *ClientMessage, auth *Back
|
||||||
|
|
||||||
userId := auth.Auth.UserId
|
userId := auth.Auth.UserId
|
||||||
if userId != "" {
|
if userId != "" {
|
||||||
log.Printf("Register user %s from %s in %s (%s) %s (private=%s)", userId, client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId)
|
log.Printf("Register user %s@%s from %s in %s (%s) %s (private=%s)", userId, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId)
|
||||||
} else if message.Hello.Auth.Type != HelloClientTypeClient {
|
} else if message.Hello.Auth.Type != HelloClientTypeClient {
|
||||||
log.Printf("Register %s from %s in %s (%s) %s (private=%s)", message.Hello.Auth.Type, client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId)
|
log.Printf("Register %s@%s from %s in %s (%s) %s (private=%s)", message.Hello.Auth.Type, backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Register anonymous from %s in %s (%s) %s (private=%s)", client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId)
|
log.Printf("Register anonymous@%s from %s in %s (%s) %s (private=%s)", backend.Id(), client.RemoteAddr(), client.Country(), client.UserAgent(), publicSessionId, privateSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := NewClientSession(h, privateSessionId, publicSessionId, sessionIdData, message.Hello, auth.Auth)
|
session, err := NewClientSession(h, privateSessionId, publicSessionId, sessionIdData, backend, message.Hello, auth.Auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
||||||
return
|
return
|
||||||
|
@ -754,7 +755,8 @@ func (h *Hub) processHelloClient(client *Client, message *ClientMessage) {
|
||||||
defer h.startExpectHello(client)
|
defer h.startExpectHello(client)
|
||||||
|
|
||||||
url := message.Hello.Auth.parsedUrl
|
url := message.Hello.Auth.parsedUrl
|
||||||
if !h.backend.IsUrlAllowed(url) {
|
backend := h.backend.GetBackend(url)
|
||||||
|
if backend == nil {
|
||||||
client.SendMessage(message.NewErrorServerMessage(InvalidBackendUrl))
|
client.SendMessage(message.NewErrorServerMessage(InvalidBackendUrl))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -772,7 +774,7 @@ func (h *Hub) processHelloClient(client *Client, message *ClientMessage) {
|
||||||
|
|
||||||
// TODO(jojo): Validate response
|
// TODO(jojo): Validate response
|
||||||
|
|
||||||
h.processRegister(client, message, &auth)
|
h.processRegister(client, message, backend, &auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) processHelloInternal(client *Client, message *ClientMessage) {
|
func (h *Hub) processHelloInternal(client *Client, message *ClientMessage) {
|
||||||
|
@ -792,11 +794,17 @@ func (h *Hub) processHelloInternal(client *Client, message *ClientMessage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backend := h.backend.GetBackend(message.Hello.Auth.internalParams.parsedBackend)
|
||||||
|
if backend == nil {
|
||||||
|
client.SendMessage(message.NewErrorServerMessage(InvalidBackendUrl))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
auth := &BackendClientResponse{
|
auth := &BackendClientResponse{
|
||||||
Type: "auth",
|
Type: "auth",
|
||||||
Auth: &BackendClientAuthResponse{},
|
Auth: &BackendClientAuthResponse{},
|
||||||
}
|
}
|
||||||
h.processRegister(client, message, auth)
|
h.processRegister(client, message, backend, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) disconnectByRoomSessionId(roomSessionId string) {
|
func (h *Hub) disconnectByRoomSessionId(roomSessionId string) {
|
||||||
|
@ -852,7 +860,7 @@ func (h *Hub) processRoom(client *Client, message *ClientMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil {
|
if session != nil {
|
||||||
if room := h.getRoom(roomId); room != nil && room.HasSession(session) {
|
if room := h.getRoomForBackend(roomId, session.Backend()); room != nil && room.HasSession(session) {
|
||||||
// Session already is in that room, no action needed.
|
// Session already is in that room, no action needed.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -896,26 +904,43 @@ func (h *Hub) processRoom(client *Client, message *ClientMessage) {
|
||||||
h.processJoinRoom(client, message, &room)
|
h.processJoinRoom(client, message, &room)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hub) getRoomForBackend(id string, backend *Backend) *Room {
|
||||||
|
internalRoomId := getRoomIdForBackend(id, backend)
|
||||||
|
|
||||||
|
h.ru.RLock()
|
||||||
|
defer h.ru.RUnlock()
|
||||||
|
return h.rooms[internalRoomId]
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hub) getRoom(id string) *Room {
|
func (h *Hub) getRoom(id string) *Room {
|
||||||
h.ru.RLock()
|
h.ru.RLock()
|
||||||
defer h.ru.RUnlock()
|
defer h.ru.RUnlock()
|
||||||
return h.rooms[id]
|
// TODO: The same room might exist on different backends.
|
||||||
|
for _, room := range h.rooms {
|
||||||
|
if room.Id() == id {
|
||||||
|
return room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) removeRoom(room *Room) {
|
func (h *Hub) removeRoom(room *Room) {
|
||||||
|
internalRoomId := getRoomIdForBackend(room.Id(), room.Backend())
|
||||||
h.ru.Lock()
|
h.ru.Lock()
|
||||||
delete(h.rooms, room.Id())
|
delete(h.rooms, internalRoomId)
|
||||||
h.ru.Unlock()
|
h.ru.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hub) createRoom(id string, properties *json.RawMessage) (*Room, error) {
|
func (h *Hub) createRoom(id string, properties *json.RawMessage, backend *Backend) (*Room, error) {
|
||||||
// Note the write lock must be held.
|
// Note the write lock must be held.
|
||||||
room, err := NewRoom(id, properties, h, h.nats)
|
room, err := NewRoom(id, properties, h, h.nats, backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.rooms[id] = room
|
internalRoomId := getRoomIdForBackend(id, backend)
|
||||||
|
h.rooms[internalRoomId] = room
|
||||||
return room, nil
|
return room, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -937,6 +962,7 @@ func (h *Hub) processJoinRoom(client *Client, message *ClientMessage, room *Back
|
||||||
session.LeaveRoom(true)
|
session.LeaveRoom(true)
|
||||||
|
|
||||||
roomId := room.Room.RoomId
|
roomId := room.Room.RoomId
|
||||||
|
internalRoomId := getRoomIdForBackend(roomId, session.Backend())
|
||||||
if err := session.SubscribeRoomNats(h.nats, roomId, message.Room.SessionId); err != nil {
|
if err := session.SubscribeRoomNats(h.nats, roomId, message.Room.SessionId); err != nil {
|
||||||
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
||||||
// The client (implicitly) left the room due to an error.
|
// The client (implicitly) left the room due to an error.
|
||||||
|
@ -945,10 +971,10 @@ func (h *Hub) processJoinRoom(client *Client, message *ClientMessage, room *Back
|
||||||
}
|
}
|
||||||
|
|
||||||
h.ru.Lock()
|
h.ru.Lock()
|
||||||
r, found := h.rooms[roomId]
|
r, found := h.rooms[internalRoomId]
|
||||||
if !found {
|
if !found {
|
||||||
var err error
|
var err error
|
||||||
if r, err = h.createRoom(roomId, room.Room.Properties); err != nil {
|
if r, err = h.createRoom(roomId, room.Room.Properties, session.Backend()); err != nil {
|
||||||
h.ru.Unlock()
|
h.ru.Unlock()
|
||||||
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
||||||
// The client (implicitly) left the room due to an error.
|
// The client (implicitly) left the room due to an error.
|
||||||
|
@ -1011,6 +1037,11 @@ func (h *Hub) processMessageMsg(client *Client, message *ClientMessage) {
|
||||||
case RecipientTypeSession:
|
case RecipientTypeSession:
|
||||||
data := h.decodeSessionId(msg.Recipient.SessionId, publicSessionName)
|
data := h.decodeSessionId(msg.Recipient.SessionId, publicSessionName)
|
||||||
if data != nil {
|
if data != nil {
|
||||||
|
if data.BackendId != session.Backend().Id() {
|
||||||
|
// Clients are only allowed to send to sessions from the same backend.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if h.mcu != nil {
|
if h.mcu != nil {
|
||||||
// Maybe this is a message to be processed by the MCU.
|
// Maybe this is a message to be processed by the MCU.
|
||||||
var data MessageClientMessageData
|
var data MessageClientMessageData
|
||||||
|
@ -1054,12 +1085,12 @@ func (h *Hub) processMessageMsg(client *Client, message *ClientMessage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
subject = GetSubjectForUserId(msg.Recipient.UserId)
|
subject = GetSubjectForUserId(msg.Recipient.UserId, session.Backend())
|
||||||
}
|
}
|
||||||
case RecipientTypeRoom:
|
case RecipientTypeRoom:
|
||||||
if session != nil {
|
if session != nil {
|
||||||
if room := session.GetRoom(); room != nil {
|
if room := session.GetRoom(); room != nil {
|
||||||
subject = "room." + room.Id()
|
subject = GetSubjectForRoomId(room.Id(), room.Backend())
|
||||||
|
|
||||||
if h.mcu != nil {
|
if h.mcu != nil {
|
||||||
var data MessageClientMessageData
|
var data MessageClientMessageData
|
||||||
|
@ -1190,12 +1221,12 @@ func (h *Hub) processControlMsg(client *Client, message *ClientMessage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
subject = GetSubjectForUserId(msg.Recipient.UserId)
|
subject = GetSubjectForUserId(msg.Recipient.UserId, session.Backend())
|
||||||
}
|
}
|
||||||
case RecipientTypeRoom:
|
case RecipientTypeRoom:
|
||||||
if session != nil {
|
if session != nil {
|
||||||
if room := session.GetRoom(); room != nil {
|
if room := session.GetRoom(); room != nil {
|
||||||
subject = "room." + room.Id()
|
subject = GetSubjectForRoomId(room.Id(), room.Backend())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,25 @@ func getTestConfig(server *httptest.Server) (*goconf.ConfigFile, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateHubForTest(t *testing.T) (*Hub, NatsClient, *mux.Router, *httptest.Server, func()) {
|
func getTestConfigWithMultipleBackends(server *httptest.Server) (*goconf.ConfigFile, error) {
|
||||||
|
config, err := getTestConfig(server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.RemoveOption("backend", "allowed")
|
||||||
|
config.RemoveOption("backend", "secret")
|
||||||
|
config.AddOption("backend", "backends", "backend1, backend2")
|
||||||
|
|
||||||
|
config.AddOption("backend1", "url", server.URL+"/one")
|
||||||
|
config.AddOption("backend1", "secret", string(testBackendSecret))
|
||||||
|
|
||||||
|
config.AddOption("backend2", "url", server.URL+"/two/")
|
||||||
|
config.AddOption("backend2", "secret", string(testBackendSecret))
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateHubForTestWithConfig(t *testing.T, getConfigFunc func(*httptest.Server) (*goconf.ConfigFile, error)) (*Hub, NatsClient, *mux.Router, *httptest.Server, func()) {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
registerBackendHandler(t, r)
|
registerBackendHandler(t, r)
|
||||||
|
|
||||||
|
@ -71,7 +89,7 @@ func CreateHubForTest(t *testing.T) (*Hub, NatsClient, *mux.Router, *httptest.Se
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
config, err := getTestConfig(server)
|
config, err := getConfigFunc(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -94,6 +112,17 @@ func CreateHubForTest(t *testing.T) (*Hub, NatsClient, *mux.Router, *httptest.Se
|
||||||
return h, nats, r, server, shutdown
|
return h, nats, r, server, shutdown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateHubForTest(t *testing.T) (*Hub, NatsClient, *mux.Router, *httptest.Server, func()) {
|
||||||
|
return CreateHubForTestWithConfig(t, getTestConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateHubWithMultipleBackendsForTest(t *testing.T) (*Hub, NatsClient, *mux.Router, *httptest.Server, func()) {
|
||||||
|
h, nats, r, server, shutdown := CreateHubForTestWithConfig(t, getTestConfigWithMultipleBackends)
|
||||||
|
registerBackendHandlerUrl(t, r, "/one")
|
||||||
|
registerBackendHandlerUrl(t, r, "/two")
|
||||||
|
return h, nats, r, server, shutdown
|
||||||
|
}
|
||||||
|
|
||||||
func WaitForHub(ctx context.Context, t *testing.T, h *Hub) {
|
func WaitForHub(ctx context.Context, t *testing.T, h *Hub) {
|
||||||
h.Stop()
|
h.Stop()
|
||||||
for {
|
for {
|
||||||
|
@ -212,7 +241,11 @@ func processRoomRequest(t *testing.T, w http.ResponseWriter, r *http.Request, re
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerBackendHandler(t *testing.T, router *mux.Router) {
|
func registerBackendHandler(t *testing.T, router *mux.Router) {
|
||||||
router.HandleFunc("/", validateBackendChecksum(t, func(w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse {
|
registerBackendHandlerUrl(t, router, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerBackendHandlerUrl(t *testing.T, router *mux.Router, url string) {
|
||||||
|
router.HandleFunc(url, validateBackendChecksum(t, func(w http.ResponseWriter, r *http.Request, request *BackendClientRequest) *BackendClientResponse {
|
||||||
switch request.Type {
|
switch request.Type {
|
||||||
case "auth":
|
case "auth":
|
||||||
return processAuthRequest(t, w, r, request)
|
return processAuthRequest(t, w, r, request)
|
||||||
|
@ -1692,8 +1725,8 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) {
|
||||||
"inCall": 1,
|
"inCall": 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
room, found := hub.rooms[roomId]
|
room := hub.getRoom(roomId)
|
||||||
if !found {
|
if room == nil {
|
||||||
t.Fatalf("Could not find room %s", roomId)
|
t.Fatalf("Could not find room %s", roomId)
|
||||||
}
|
}
|
||||||
room.PublishUsersInCallChanged(users, users)
|
room.PublishUsersInCallChanged(users, users)
|
||||||
|
@ -2031,3 +2064,181 @@ func TestClientSendOfferPermissions(t *testing.T) {
|
||||||
t.Errorf("Expected no payload, got %+v", msg)
|
t.Errorf("Expected no payload, got %+v", msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) {
|
||||||
|
// Clients can't send messages to sessions connected from other backends.
|
||||||
|
hub, _, _, server, shutdown := CreateHubWithMultipleBackendsForTest(t)
|
||||||
|
defer shutdown()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client1 := NewTestClient(t, server, hub)
|
||||||
|
defer client1.CloseWithBye()
|
||||||
|
|
||||||
|
params1 := TestBackendClientAuthParams{
|
||||||
|
UserId: "user1",
|
||||||
|
}
|
||||||
|
if err := client1.SendHelloParams(server.URL+"/one", "client", params1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hello1, err := client1.RunUntilHello(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client2 := NewTestClient(t, server, hub)
|
||||||
|
defer client2.CloseWithBye()
|
||||||
|
|
||||||
|
params2 := TestBackendClientAuthParams{
|
||||||
|
UserId: "user2",
|
||||||
|
}
|
||||||
|
if err := client2.SendHelloParams(server.URL+"/two", "client", params2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hello2, err := client2.RunUntilHello(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipient1 := MessageClientMessageRecipient{
|
||||||
|
Type: "session",
|
||||||
|
SessionId: hello1.Hello.SessionId,
|
||||||
|
}
|
||||||
|
recipient2 := MessageClientMessageRecipient{
|
||||||
|
Type: "session",
|
||||||
|
SessionId: hello2.Hello.SessionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
data1 := "from-1-to-2"
|
||||||
|
client1.SendMessage(recipient2, data1)
|
||||||
|
data2 := "from-2-to-1"
|
||||||
|
client2.SendMessage(recipient1, data2)
|
||||||
|
|
||||||
|
var payload string
|
||||||
|
ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel2()
|
||||||
|
if err := checkReceiveClientMessage(ctx2, client1, "session", hello2.Hello, &payload); err != nil {
|
||||||
|
if err != NoMessageReceivedError {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Expected no payload, got %+v", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel3()
|
||||||
|
if err := checkReceiveClientMessage(ctx3, client2, "session", hello1.Hello, &payload); err != nil {
|
||||||
|
if err != NoMessageReceivedError {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Expected no payload, got %+v", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSameRoomOnDifferentBackends(t *testing.T) {
|
||||||
|
hub, _, _, server, shutdown := CreateHubWithMultipleBackendsForTest(t)
|
||||||
|
defer shutdown()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client1 := NewTestClient(t, server, hub)
|
||||||
|
defer client1.CloseWithBye()
|
||||||
|
|
||||||
|
params1 := TestBackendClientAuthParams{
|
||||||
|
UserId: "user1",
|
||||||
|
}
|
||||||
|
if err := client1.SendHelloParams(server.URL+"/one", "client", params1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hello1, err := client1.RunUntilHello(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client2 := NewTestClient(t, server, hub)
|
||||||
|
defer client2.CloseWithBye()
|
||||||
|
|
||||||
|
params2 := TestBackendClientAuthParams{
|
||||||
|
UserId: "user2",
|
||||||
|
}
|
||||||
|
if err := client2.SendHelloParams(server.URL+"/two", "client", params2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hello2, err := client2.RunUntilHello(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join room by id.
|
||||||
|
roomId := "test-room"
|
||||||
|
if room, err := client1.JoinRoom(ctx, roomId); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if room.Room.RoomId != roomId {
|
||||||
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
||||||
|
}
|
||||||
|
msg1, err := client1.RunUntilMessage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := client1.checkMessageJoined(msg1, hello1.Hello); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if room, err := client2.JoinRoom(ctx, roomId); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if room.Room.RoomId != roomId {
|
||||||
|
t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId)
|
||||||
|
}
|
||||||
|
msg2, err := client2.RunUntilMessage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := client2.checkMessageJoined(msg2, hello2.Hello); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hub.ru.RLock()
|
||||||
|
roomCount := 0
|
||||||
|
for _, room := range hub.rooms {
|
||||||
|
defer room.Close()
|
||||||
|
roomCount++
|
||||||
|
}
|
||||||
|
hub.ru.RUnlock()
|
||||||
|
|
||||||
|
if roomCount != 2 {
|
||||||
|
t.Errorf("Expected 2 rooms, got %d", roomCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipient := MessageClientMessageRecipient{
|
||||||
|
Type: "room",
|
||||||
|
}
|
||||||
|
|
||||||
|
data1 := "from-1-to-2"
|
||||||
|
client1.SendMessage(recipient, data1)
|
||||||
|
data2 := "from-2-to-1"
|
||||||
|
client2.SendMessage(recipient, data2)
|
||||||
|
|
||||||
|
var payload string
|
||||||
|
ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel2()
|
||||||
|
if err := checkReceiveClientMessage(ctx2, client1, "session", hello2.Hello, &payload); err != nil {
|
||||||
|
if err != NoMessageReceivedError {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Expected no payload, got %+v", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx3, cancel3 := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel3()
|
||||||
|
if err := checkReceiveClientMessage(ctx3, client2, "session", hello1.Hello, &payload); err != nil {
|
||||||
|
if err != NoMessageReceivedError {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Expected no payload, got %+v", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
package signaling
|
package signaling
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -63,6 +64,13 @@ type NatsClient interface {
|
||||||
Decode(msg *nats.Msg, v interface{}) error
|
Decode(msg *nats.Msg, v interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The NATS client doesn't work if a subject contains spaces. As the room id
|
||||||
|
// can have an arbitrary format, we need to make sure the subject is valid.
|
||||||
|
// See "https://github.com/nats-io/nats.js/issues/158" for a similar report.
|
||||||
|
func GetEncodedSubject(prefix string, suffix string) string {
|
||||||
|
return prefix + "." + base64.StdEncoding.EncodeToString([]byte(suffix))
|
||||||
|
}
|
||||||
|
|
||||||
type natsClient struct {
|
type natsClient struct {
|
||||||
nc *nats.Conn
|
nc *nats.Conn
|
||||||
conn *nats.EncodedConn
|
conn *nats.EncodedConn
|
||||||
|
|
|
@ -47,9 +47,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Room struct {
|
type Room struct {
|
||||||
id string
|
id string
|
||||||
hub *Hub
|
hub *Hub
|
||||||
nats NatsClient
|
nats NatsClient
|
||||||
|
backend *Backend
|
||||||
|
|
||||||
properties *json.RawMessage
|
properties *json.RawMessage
|
||||||
roomType int
|
roomType int
|
||||||
|
@ -72,18 +73,43 @@ type Room struct {
|
||||||
lastNatsRoomRequests map[string]int64
|
lastNatsRoomRequests map[string]int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoom(roomId string, properties *json.RawMessage, hub *Hub, n NatsClient) (*Room, error) {
|
func GetSubjectForRoomId(roomId string, backend *Backend) string {
|
||||||
|
if backend == nil || backend.IsCompat() {
|
||||||
|
return GetEncodedSubject("room", roomId)
|
||||||
|
} else {
|
||||||
|
return GetEncodedSubject("room", roomId+"|"+backend.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubjectForBackendRoomId(roomId string, backend *Backend) string {
|
||||||
|
if backend == nil || backend.IsCompat() {
|
||||||
|
return GetEncodedSubject("backend.room", roomId)
|
||||||
|
} else {
|
||||||
|
return GetEncodedSubject("backend.room", roomId+"|"+backend.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRoomIdForBackend(id string, backend *Backend) string {
|
||||||
|
if id == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return backend.Id() + "|" + id
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoom(roomId string, properties *json.RawMessage, hub *Hub, n NatsClient, backend *Backend) (*Room, error) {
|
||||||
natsReceiver := make(chan *nats.Msg, 64)
|
natsReceiver := make(chan *nats.Msg, 64)
|
||||||
backendSubscription, err := n.Subscribe("backend.room."+roomId, natsReceiver)
|
backendSubscription, err := n.Subscribe(GetSubjectForBackendRoomId(roomId, backend), natsReceiver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(natsReceiver)
|
close(natsReceiver)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
room := &Room{
|
room := &Room{
|
||||||
id: roomId,
|
id: roomId,
|
||||||
hub: hub,
|
hub: hub,
|
||||||
nats: n,
|
nats: n,
|
||||||
|
backend: backend,
|
||||||
|
|
||||||
properties: properties,
|
properties: properties,
|
||||||
|
|
||||||
|
@ -115,6 +141,10 @@ func (r *Room) Properties() *json.RawMessage {
|
||||||
return r.properties
|
return r.properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Room) Backend() *Backend {
|
||||||
|
return r.backend
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Room) run() {
|
func (r *Room) run() {
|
||||||
ticker := time.NewTicker(updateActiveSessionsInterval)
|
ticker := time.NewTicker(updateActiveSessionsInterval)
|
||||||
loop:
|
loop:
|
||||||
|
@ -278,7 +308,7 @@ func (r *Room) RemoveSession(session Session) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Room) publish(message *ServerMessage) {
|
func (r *Room) publish(message *ServerMessage) {
|
||||||
r.nats.PublishMessage("room."+r.id, message)
|
r.nats.PublishMessage(GetSubjectForRoomId(r.id, r.backend), message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Room) UpdateProperties(properties *json.RawMessage) {
|
func (r *Room) UpdateProperties(properties *json.RawMessage) {
|
||||||
|
|
|
@ -194,11 +194,7 @@ loop:
|
||||||
break loop
|
break loop
|
||||||
default:
|
default:
|
||||||
// The internal room has been updated with the new properties.
|
// The internal room has been updated with the new properties.
|
||||||
hub.ru.Lock()
|
if room := hub.getRoom(roomId); room == nil {
|
||||||
room, found := hub.rooms[roomId]
|
|
||||||
hub.ru.Unlock()
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
err = fmt.Errorf("Room %s not found in hub", roomId)
|
err = fmt.Errorf("Room %s not found in hub", roomId)
|
||||||
} else if room.Properties() == nil || !bytes.Equal(*room.Properties(), roomProperties) {
|
} else if room.Properties() == nil || !bytes.Equal(*room.Properties(), roomProperties) {
|
||||||
err = fmt.Errorf("Expected room properties %s, got %+v", string(roomProperties), room.Properties())
|
err = fmt.Errorf("Expected room properties %s, got %+v", string(roomProperties), room.Properties())
|
||||||
|
|
|
@ -56,6 +56,10 @@ func (s *DummySession) UserData() *json.RawMessage {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DummySession) Backend() *Backend {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DummySession) BackendUrl() string {
|
func (s *DummySession) BackendUrl() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionIdData struct {
|
type SessionIdData struct {
|
||||||
Sid uint64
|
Sid uint64
|
||||||
Created time.Time
|
Created time.Time
|
||||||
|
BackendId string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session interface {
|
type Session interface {
|
||||||
|
@ -49,6 +50,7 @@ type Session interface {
|
||||||
UserId() string
|
UserId() string
|
||||||
UserData() *json.RawMessage
|
UserData() *json.RawMessage
|
||||||
|
|
||||||
|
Backend() *Backend
|
||||||
BackendUrl() string
|
BackendUrl() string
|
||||||
ParsedBackendUrl() *url.URL
|
ParsedBackendUrl() *url.URL
|
||||||
|
|
||||||
|
|
|
@ -376,10 +376,12 @@ func (c *TestClient) SendHelloInternal() error {
|
||||||
mac := hmac.New(sha256.New, testInternalSecret)
|
mac := hmac.New(sha256.New, testInternalSecret)
|
||||||
mac.Write([]byte(random))
|
mac.Write([]byte(random))
|
||||||
token := hex.EncodeToString(mac.Sum(nil))
|
token := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
backend := c.server.URL
|
||||||
|
|
||||||
params := ClientTypeInternalAuthParams{
|
params := ClientTypeInternalAuthParams{
|
||||||
Random: random,
|
Random: random,
|
||||||
Token: token,
|
Token: token,
|
||||||
|
Backend: backend,
|
||||||
}
|
}
|
||||||
return c.SendHelloParams("", "internal", params)
|
return c.SendHelloParams("", "internal", params)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue