From 79b79b35bc0204867311de913a86abad2656bb39 Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Tue, 2 Feb 2021 18:15:03 -0800 Subject: [PATCH] Auth readToken in websocket --- server/api/api.go | 10 ++---- server/app/auth.go | 5 +++ server/auth/auth.go | 23 ++++++++++++++ server/ws/websockets.go | 57 ++++++++++++++++++++++++---------- webapp/src/octoListener.ts | 17 ++++++++-- webapp/src/pages/boardPage.tsx | 10 +++++- 6 files changed, 95 insertions(+), 27 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index 99a4d80ca..7e7dbcab0 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -234,19 +234,13 @@ func (a *API) handleGetSubTree(w http.ResponseWriter, r *http.Request) { return } - rootID, err := a.app().GetRootID(blockID) + isValid, err := a.app().IsValidReadToken(blockID, readToken) if err != nil { errorResponse(w, http.StatusInternalServerError, nil, err) return } - sharing, err := a.app().GetSharing(rootID) - if err != nil { - errorResponse(w, http.StatusInternalServerError, nil, err) - return - } - - if sharing == nil || !(sharing.ID == rootID && sharing.Enabled && sharing.Token == readToken) { + if !isValid { errorResponse(w, http.StatusUnauthorized, nil, nil) return } diff --git a/server/app/auth.go b/server/app/auth.go index 6e6760c6a..bab17a22c 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -15,6 +15,11 @@ func (a *App) GetSession(token string) (*model.Session, error) { return a.auth.GetSession(token) } +// IsValidReadToken validates the read token for a block +func (a *App) IsValidReadToken(blockID string, readToken string) (bool, error) { + return a.auth.IsValidReadToken(blockID, readToken) +} + // GetRegisteredUserCount returns the number of registered users func (a *App) GetRegisteredUserCount() (int, error) { return a.store.GetRegisteredUserCount() diff --git a/server/auth/auth.go b/server/auth/auth.go index 7ed1fb9ba..2531a1070 100644 --- a/server/auth/auth.go +++ b/server/auth/auth.go @@ -1,6 +1,7 @@ package auth import ( + "database/sql" "time" "github.com/mattermost/focalboard/server/model" @@ -35,3 +36,25 @@ func (a *Auth) GetSession(token string) (*model.Session, error) { } return session, nil } + +// IsValidReadToken validates the read token for a block +func (a *Auth) IsValidReadToken(blockID string, readToken string) (bool, error) { + rootID, err := a.store.GetRootID(blockID) + if err != nil { + return false, err + } + + sharing, err := a.store.GetSharing(rootID) + if err == sql.ErrNoRows { + return false, nil + } + if err != nil { + return false, err + } + + if sharing != nil && (sharing.ID == rootID && sharing.Enabled && sharing.Token == readToken) { + return true, nil + } + + return false, nil +} diff --git a/server/ws/websockets.go b/server/ws/websockets.go index ac90073e9..b00044c49 100644 --- a/server/ws/websockets.go +++ b/server/ws/websockets.go @@ -38,9 +38,10 @@ type ErrorMsg struct { // WebsocketCommand is an incoming command from the client. type WebsocketCommand struct { - Action string `json:"action"` - Token string `json:"token"` - BlockIDs []string `json:"blockIds"` + Action string `json:"action"` + Token string `json:"token"` + ReadToken string `json:"readToken"` + BlockIDs []string `json:"blockIds"` } type websocketSession struct { @@ -116,15 +117,15 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request switch command.Action { case "AUTH": log.Printf(`Command: AUTH, client: %s`, client.RemoteAddr()) - ws.authenticateListener(&wsSession, command.Token) + ws.authenticateListener(&wsSession, command.Token, command.ReadToken) case "ADD": log.Printf(`Command: Add blockID: %v, client: %s`, command.BlockIDs, client.RemoteAddr()) - ws.addListener(&wsSession, command.BlockIDs) + ws.addListener(&wsSession, &command) case "REMOVE": log.Printf(`Command: Remove blockID: %v, client: %s`, command.BlockIDs, client.RemoteAddr()) - ws.removeListenerFromBlocks(&wsSession, command.BlockIDs) + ws.removeListenerFromBlocks(&wsSession, &command) default: log.Printf(`ERROR webSocket command, invalid action: %v`, command.Action) @@ -138,14 +139,15 @@ func (ws *Server) isValidSessionToken(token string) bool { } session, err := ws.auth.GetSession(token) - if session == nil || err != nil { - return false + if session != nil && err == nil { + return true } - return true + return false } -func (ws *Server) authenticateListener(wsSession *websocketSession, token string) { +func (ws *Server) authenticateListener(wsSession *websocketSession, token string, readToken string) { + // Authenticate session isValidSession := ws.isValidSessionToken(token) if !isValidSession { wsSession.client.Close() @@ -157,16 +159,39 @@ func (ws *Server) authenticateListener(wsSession *websocketSession, token string log.Printf("authenticateListener: Authenticated") } +func (ws *Server) checkAuthentication(wsSession *websocketSession, command *WebsocketCommand) bool { + if ws.singleUser { + return true + } + + if wsSession.isAuthenticated { + return true + } + + if len(command.ReadToken) > 0 { + // Read token must be valid for all block IDs + for _, blockID := range command.BlockIDs { + isValid, _ := ws.auth.IsValidReadToken(blockID, command.ReadToken) + if !isValid { + return false + } + } + return true + } + + return false +} + // addListener adds a listener for a block's change. -func (ws *Server) addListener(wsSession *websocketSession, blockIDs []string) { - if !wsSession.isAuthenticated { +func (ws *Server) addListener(wsSession *websocketSession, command *WebsocketCommand) { + if !ws.checkAuthentication(wsSession, command) { log.Printf("addListener: NOT AUTHENTICATED") sendError(wsSession.client, "not authenticated") return } ws.mu.Lock() - for _, blockID := range blockIDs { + for _, blockID := range command.BlockIDs { if ws.listeners[blockID] == nil { ws.listeners[blockID] = []*websocket.Conn{} } @@ -194,8 +219,8 @@ func (ws *Server) removeListener(client *websocket.Conn) { } // removeListenerFromBlocks removes a webSocket listener from a set of block. -func (ws *Server) removeListenerFromBlocks(wsSession *websocketSession, blockIDs []string) { - if !wsSession.isAuthenticated { +func (ws *Server) removeListenerFromBlocks(wsSession *websocketSession, command *WebsocketCommand) { + if !ws.checkAuthentication(wsSession, command) { log.Printf("removeListenerFromBlocks: NOT AUTHENTICATED") sendError(wsSession.client, "not authenticated") return @@ -203,7 +228,7 @@ func (ws *Server) removeListenerFromBlocks(wsSession *websocketSession, blockIDs ws.mu.Lock() - for _, blockID := range blockIDs { + for _, blockID := range command.BlockIDs { listeners := ws.listeners[blockID] if listeners == nil { return diff --git a/webapp/src/octoListener.ts b/webapp/src/octoListener.ts index 335c922a2..175a2d799 100644 --- a/webapp/src/octoListener.ts +++ b/webapp/src/octoListener.ts @@ -6,6 +6,7 @@ import {Utils} from './utils' // These are outgoing commands to the server type WSCommand = { action: string + readToken?: string blockIds: string[] } @@ -28,6 +29,7 @@ class OctoListener { readonly serverUrl: string private token: string + private readToken: string private ws?: WebSocket private blockIds: string[] = [] private isInitialized = false @@ -39,12 +41,19 @@ class OctoListener { notificationDelay = 100 reopenDelay = 3000 - constructor(serverUrl?: string, token?: string) { + constructor(serverUrl?: string, token?: string, readToken?: string) { this.serverUrl = serverUrl || window.location.origin this.token = token || localStorage.getItem('sessionId') || '' + this.readToken = readToken || OctoListener.getReadToken() Utils.log(`OctoListener serverUrl: ${this.serverUrl}`) } + static getReadToken(): string { + const queryString = new URLSearchParams(window.location.search) + const readToken = queryString.get('r') || '' + return readToken + } + open(blockIds: string[], onChange: OnChangeHandler, onReconnect: () => void): void { if (this.ws) { this.close() @@ -133,11 +142,13 @@ class OctoListener { return } + if (!this.token) { + return + } const command = { action: 'AUTH', token: this.token, } - this.ws.send(JSON.stringify(command)) } @@ -150,6 +161,7 @@ class OctoListener { const command: WSCommand = { action: 'ADD', blockIds, + readToken: this.readToken, } this.ws.send(JSON.stringify(command)) @@ -165,6 +177,7 @@ class OctoListener { const command: WSCommand = { action: 'REMOVE', blockIds, + readToken: this.readToken, } this.ws.send(JSON.stringify(command)) diff --git a/webapp/src/pages/boardPage.tsx b/webapp/src/pages/boardPage.tsx index be4b51e7e..3f270b34f 100644 --- a/webapp/src/pages/boardPage.tsx +++ b/webapp/src/pages/boardPage.tsx @@ -185,9 +185,17 @@ class BoardPage extends React.Component { const boardIds = [...workspaceTree.boards.map((o) => o.id), ...workspaceTree.boardTemplates.map((o) => o.id)] this.setState({workspaceTree}) + let boardIdsToListen: string[] + if (boardIds.length > 0) { + boardIdsToListen = ['', ...boardIds] + } else { + // Read-only view + boardIdsToListen = [this.state.boardId] + } + // Listen to boards plus all blocks at root (Empty string for parentId) this.workspaceListener.open( - ['', ...boardIds], + boardIdsToListen, async (blocks) => { Utils.log(`workspaceListener.onChanged: ${blocks.length}`) this.incrementalUpdate(blocks)