From 697fb2732c0ff3575a6450d21bb562a31e5c6d4e Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Mon, 5 Feb 2024 15:38:18 +0700 Subject: [PATCH] UBERF-5283 Validate token in collaborator (#4517) Signed-off-by: Alexander Onnikov --- .vscode/launch.json | 1 + dev/docker-compose.yaml | 1 + server/collaborator/src/account.ts | 36 +++++++++++++++++++++ server/collaborator/src/config.ts | 4 +++ server/collaborator/src/server.ts | 22 ++++++++----- server/collaborator/src/storage/minio.ts | 18 +++++------ server/collaborator/src/storage/mongodb.ts | 8 ++--- server/collaborator/src/storage/platform.ts | 14 ++++---- tests/docker-compose.yaml | 1 + 9 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 server/collaborator/src/account.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 6816788123..f5ac5f890a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -90,6 +90,7 @@ "env": { "SECRET": "secret", "METRICS_CONSOLE": "true", + "ACCOUNTS_URL": "http://localhost:3000", "TRANSACTOR_URL": "ws://localhost:3333", "UPLOAD_URL": "/files", "MONGO_URL": "mongodb://localhost:27017", diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index ab48a0ee05..1780d57835 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -101,6 +101,7 @@ services: environment: - COLLABORATOR_PORT=3078 - SECRET=secret + - ACCOUNTS_URL=http://account:3000 - TRANSACTOR_URL=ws://transactor:3333 - UPLOAD_URL=/files - MONGO_URL=mongodb://mongodb:27017 diff --git a/server/collaborator/src/account.ts b/server/collaborator/src/account.ts new file mode 100644 index 0000000000..ee350e3897 --- /dev/null +++ b/server/collaborator/src/account.ts @@ -0,0 +1,36 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { WorkspaceLoginInfo } from '@hcengineering/account' +import config from './config' + +export async function getWorkspaceInfo (token: string): Promise { + const accountsUrl = config.AccountsUrl + const workspaceInfo = await ( + await fetch(accountsUrl, { + method: 'POST', + headers: { + Authorization: 'Bearer ' + token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + method: 'getWorkspaceInfo', + params: [] + }) + }) + ).json() + + return workspaceInfo.result as WorkspaceLoginInfo +} diff --git a/server/collaborator/src/config.ts b/server/collaborator/src/config.ts index d35487a0bd..cf96cee822 100644 --- a/server/collaborator/src/config.ts +++ b/server/collaborator/src/config.ts @@ -24,6 +24,7 @@ export interface Config { Port: number + AccountsUrl: string TransactorUrl: string MongoUrl: string UploadUrl: string @@ -38,6 +39,7 @@ const envMap: { [key in keyof Config]: string } = { Secret: 'SECRET', Interval: 'INTERVAL', Port: 'COLLABORATOR_PORT', + AccountsUrl: 'ACCOUNTS_URL', TransactorUrl: 'TRANSACTOR_URL', MongoUrl: 'MONGO_URL', UploadUrl: 'UPLOAD_URL', @@ -50,6 +52,7 @@ const required: Array = [ 'Secret', 'ServiceID', 'Port', + 'AccountsUrl', 'TransactorUrl', 'MongoUrl', 'MinioEndpoint', @@ -63,6 +66,7 @@ const config: Config = (() => { ServiceID: process.env[envMap.ServiceID] ?? 'collaborator-service', Interval: parseInt(process.env[envMap.Interval] ?? '30000'), Port: parseInt(process.env[envMap.Port] ?? '3078'), + AccountsUrl: process.env[envMap.AccountsUrl], TransactorUrl: process.env[envMap.TransactorUrl], MongoUrl: process.env[envMap.MongoUrl], UploadUrl: process.env[envMap.UploadUrl] ?? '/files', diff --git a/server/collaborator/src/server.ts b/server/collaborator/src/server.ts index a2acfb942f..1b29be4927 100644 --- a/server/collaborator/src/server.ts +++ b/server/collaborator/src/server.ts @@ -27,6 +27,7 @@ import { MongoClient } from 'mongodb' import { WebSocket, WebSocketServer } from 'ws' import { applyUpdate, encodeStateAsUpdate } from 'yjs' +import { getWorkspaceInfo } from './account' import { Config } from './config' import { Context, buildContext } from './context' import { ActionsExtension } from './extensions/action' @@ -145,20 +146,25 @@ export async function start ( ctx.measure('authenticate', 1) const context = buildContext(data, controller) + // verify workspace can be accessed with the token + const workspaceInfo = await getWorkspaceInfo(data.token) + // verify document name let documentName = data.documentName if (documentName.includes('://')) { documentName = documentName.split('://', 2)[1] } - // if (documentName.includes('/')) { - // const [workspace] = documentName.split('/', 2) - // if (workspace !== context.workspaceId.name) { - // throw new Error('documentName must include workspace') - // } - // } else { - // throw new Error('documentName must include workspace') - // } + if (documentName.includes('/')) { + const [workspaceUrl] = documentName.split('/', 2) + + // verify workspace url in the document matches the token + if (workspaceInfo.workspace !== workspaceUrl) { + throw new Error('documentName must include workspace') + } + } else { + throw new Error('documentName must include workspace') + } return context }, diff --git a/server/collaborator/src/storage/minio.ts b/server/collaborator/src/storage/minio.ts index ad18e60176..1e76f0a8c2 100644 --- a/server/collaborator/src/storage/minio.ts +++ b/server/collaborator/src/storage/minio.ts @@ -23,20 +23,20 @@ import { Context } from '../context' import { StorageAdapter } from './adapter' interface MinioDocumentId { - workspace: string + workspaceUrl: string minioDocumentId: string } function parseDocumentId (documentId: string): MinioDocumentId { - const [workspace, minioDocumentId] = documentId.split('/') + const [workspaceUrl, minioDocumentId] = documentId.split('/') return { - workspace: workspace ?? '', + workspaceUrl: workspaceUrl ?? '', minioDocumentId: minioDocumentId ?? '' } } -function isValidDocumentId (documentId: MinioDocumentId, context: Context): boolean { - return documentId.minioDocumentId !== '' // && documentId.workspace === context.workspaceId.name +function isValidDocumentId (documentId: MinioDocumentId): boolean { + return documentId.minioDocumentId !== '' && documentId.workspaceUrl !== '' } function maybePlatformDocumentId (documentId: string): boolean { @@ -52,9 +52,9 @@ export class MinioStorageAdapter implements StorageAdapter { async loadDocument (documentId: string, context: Context): Promise { const { workspaceId } = context - const { workspace, minioDocumentId } = parseDocumentId(documentId) + const { workspaceUrl, minioDocumentId } = parseDocumentId(documentId) - if (!isValidDocumentId({ workspace, minioDocumentId }, context)) { + if (!isValidDocumentId({ workspaceUrl, minioDocumentId })) { console.warn('malformed document id', documentId) return undefined } @@ -91,9 +91,9 @@ export class MinioStorageAdapter implements StorageAdapter { async saveDocument (documentId: string, document: YDoc, context: Context): Promise { const { clientFactory, workspaceId } = context - const { workspace, minioDocumentId } = parseDocumentId(documentId) + const { workspaceUrl, minioDocumentId } = parseDocumentId(documentId) - if (!isValidDocumentId({ workspace, minioDocumentId }, context)) { + if (!isValidDocumentId({ workspaceUrl, minioDocumentId })) { console.warn('malformed document id', documentId) return undefined } diff --git a/server/collaborator/src/storage/mongodb.ts b/server/collaborator/src/storage/mongodb.ts index d9435f9a9d..608e570971 100644 --- a/server/collaborator/src/storage/mongodb.ts +++ b/server/collaborator/src/storage/mongodb.ts @@ -23,7 +23,7 @@ import { Context } from '../context' import { StorageAdapter } from './adapter' interface MongodbDocumentId { - workspace: string + workspaceUrl: string objectDomain: string objectId: string objectAttr: string @@ -32,7 +32,7 @@ interface MongodbDocumentId { function parseDocumentId (documentId: string): MongodbDocumentId { const [workspace, objectDomain, objectId, objectAttr] = documentId.split('/') return { - workspace: workspace ?? '', + workspaceUrl: workspace ?? '', objectId: objectId ?? '', objectDomain: objectDomain ?? '', objectAttr: objectAttr ?? '' @@ -54,9 +54,9 @@ export class MongodbStorageAdapter implements StorageAdapter { ) {} async loadDocument (documentId: string, context: Context): Promise { - const { workspace, objectId, objectDomain, objectAttr } = parseDocumentId(documentId) + const { workspaceUrl, objectId, objectDomain, objectAttr } = parseDocumentId(documentId) - if (!isValidDocumentId({ workspace, objectId, objectDomain, objectAttr }, context)) { + if (!isValidDocumentId({ workspaceUrl, objectId, objectDomain, objectAttr }, context)) { console.warn('malformed document id', documentId) return undefined } diff --git a/server/collaborator/src/storage/platform.ts b/server/collaborator/src/storage/platform.ts index e7b5a4cbf4..11961cb63b 100644 --- a/server/collaborator/src/storage/platform.ts +++ b/server/collaborator/src/storage/platform.ts @@ -22,16 +22,16 @@ import { Context } from '../context' import { StorageAdapter } from './adapter' interface PlatformDocumentId { - workspace: string + workspaceUrl: string objectClass: Ref> objectId: Ref objectAttr: string } function parseDocumentId (documentId: string): PlatformDocumentId { - const [workspace, objectClass, objectId, objectAttr] = documentId.split('/') + const [workspaceUrl, objectClass, objectId, objectAttr] = documentId.split('/') return { - workspace: workspace ?? '', + workspaceUrl: workspaceUrl ?? '', objectClass: (objectClass ?? '') as Ref>, objectId: (objectId ?? '') as Ref, objectAttr: objectAttr ?? '' @@ -53,9 +53,9 @@ export class PlatformStorageAdapter implements StorageAdapter { async loadDocument (documentId: string, context: Context): Promise { const { clientFactory } = context - const { workspace, objectId, objectClass, objectAttr } = parseDocumentId(documentId) + const { workspaceUrl, objectId, objectClass, objectAttr } = parseDocumentId(documentId) - if (!isValidDocumentId({ workspace, objectId, objectClass, objectAttr }, context)) { + if (!isValidDocumentId({ workspaceUrl, objectId, objectClass, objectAttr }, context)) { console.warn('malformed document id', documentId) return undefined } @@ -82,9 +82,9 @@ export class PlatformStorageAdapter implements StorageAdapter { async saveDocument (documentId: string, document: YDoc, context: Context): Promise { const { clientFactory } = context - const { workspace, objectId, objectClass, objectAttr } = parseDocumentId(documentId) + const { workspaceUrl, objectId, objectClass, objectAttr } = parseDocumentId(documentId) - if (!isValidDocumentId({ workspace, objectId, objectClass, objectAttr }, context)) { + if (!isValidDocumentId({ workspaceUrl, objectId, objectClass, objectAttr }, context)) { console.warn('malformed document id', documentId) return undefined } diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml index 916e0a7d33..050ac2fbb9 100644 --- a/tests/docker-compose.yaml +++ b/tests/docker-compose.yaml @@ -111,6 +111,7 @@ services: environment: - COLLABORATOR_PORT=3078 - SECRET=secret + - ACCOUNTS_URL=http://account:3003 - TRANSACTOR_URL=ws://transactor:3334 - UPLOAD_URL=/files - MONGO_URL=mongodb://mongodb:27018