mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-23 10:16:32 -05:00
Centralize permission checking
This makes it more convenient to modify the permission model, both for future modifications and for custom installations. This changes the `owner` property of NoteSession to `ownerId`, which is a more accurate description anyway. Signed-off-by: Dexter Chua <dec41@srcf.net>
This commit is contained in:
parent
04a652c3a6
commit
c47520b09e
2 changed files with 55 additions and 46 deletions
|
@ -14,6 +14,7 @@ import { NoteAuthorship } from './models/note'
|
||||||
import { PhotoProfile } from './utils/PhotoProfile'
|
import { PhotoProfile } from './utils/PhotoProfile'
|
||||||
import { EditorSocketIOServer } from './ot/editor-socketio-server'
|
import { EditorSocketIOServer } from './ot/editor-socketio-server'
|
||||||
import { mapToObject } from './utils/functions'
|
import { mapToObject } from './utils/functions'
|
||||||
|
import { getPermission, Permission } from './web/note/util'
|
||||||
|
|
||||||
export type SocketWithNoteId = Socket & { noteId: string }
|
export type SocketWithNoteId = Socket & { noteId: string }
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ class NoteSession {
|
||||||
id: string
|
id: string
|
||||||
alias: string
|
alias: string
|
||||||
title: string
|
title: string
|
||||||
owner: string
|
ownerId: string
|
||||||
ownerprofile: PhotoProfile | null
|
ownerprofile: PhotoProfile | null
|
||||||
permission: string
|
permission: string
|
||||||
lastchangeuser: string | null
|
lastchangeuser: string | null
|
||||||
|
@ -377,7 +378,7 @@ function emitRefresh (socket: SocketWithNoteId): void {
|
||||||
const out = {
|
const out = {
|
||||||
title: note.title,
|
title: note.title,
|
||||||
docmaxlength: config.documentMaxLength,
|
docmaxlength: config.documentMaxLength,
|
||||||
owner: note.owner,
|
owner: note.ownerId,
|
||||||
ownerprofile: note.ownerprofile,
|
ownerprofile: note.ownerprofile,
|
||||||
lastchangeuser: note.lastchangeuser,
|
lastchangeuser: note.lastchangeuser,
|
||||||
lastchangeuserprofile: note.lastchangeuserprofile,
|
lastchangeuserprofile: note.lastchangeuserprofile,
|
||||||
|
@ -442,16 +443,6 @@ function interruptConnection (socket: Socket, noteId: string, socketId): void {
|
||||||
connectNextSocket()
|
connectNextSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkViewPermission (req, note: NoteSession): boolean {
|
|
||||||
if (note.permission === 'private') {
|
|
||||||
return !!(req.user?.logged_in && req.user.id === note.owner)
|
|
||||||
} else if (note.permission === 'limited' || note.permission === 'protected') {
|
|
||||||
return !!(req.user?.logged_in)
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: string): void {
|
function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: string): void {
|
||||||
// if no valid info provided will drop the client
|
// if no valid info provided will drop the client
|
||||||
if (!socket || !notes.get(noteId) || !users.get(socketId)) {
|
if (!socket || !notes.get(noteId) || !users.get(socketId)) {
|
||||||
|
@ -460,7 +451,7 @@ function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: s
|
||||||
// check view permission
|
// check view permission
|
||||||
const note = notes.get(noteId)
|
const note = notes.get(noteId)
|
||||||
if (!note) return
|
if (!note) return
|
||||||
if (!checkViewPermission(socket.request, note)) {
|
if (getPermission(socket.request.user, note) === Permission.None) {
|
||||||
interruptConnection(socket, noteId, socketId)
|
interruptConnection(socket, noteId, socketId)
|
||||||
return failConnection(403, 'connection forbidden', socket)
|
return failConnection(403, 'connection forbidden', socket)
|
||||||
}
|
}
|
||||||
|
@ -513,27 +504,7 @@ function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callba
|
||||||
if (!noteId) return
|
if (!noteId) return
|
||||||
const note = notes.get(noteId)
|
const note = notes.get(noteId)
|
||||||
if (!note) return
|
if (!note) return
|
||||||
let mayEdit = true
|
const mayEdit = (getPermission(socket.request.user, note) >= Permission.Write)
|
||||||
switch (note.permission) {
|
|
||||||
case 'freely':
|
|
||||||
// not blocking anyone
|
|
||||||
break
|
|
||||||
case 'editable':
|
|
||||||
case 'limited':
|
|
||||||
// only login user can change
|
|
||||||
if (!socket.request.user || !socket.request.user.logged_in) {
|
|
||||||
mayEdit = false
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'locked':
|
|
||||||
case 'private':
|
|
||||||
case 'protected':
|
|
||||||
// only owner can change
|
|
||||||
if (!note.owner || note.owner !== socket.request.user.id) {
|
|
||||||
mayEdit = false
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// if user may edit and this is a text operation
|
// if user may edit and this is a text operation
|
||||||
if (originIsOperation && mayEdit) {
|
if (originIsOperation && mayEdit) {
|
||||||
// save for the last change user id
|
// save for the last change user id
|
||||||
|
@ -625,7 +596,7 @@ function startConnection (socket: SocketWithNoteId): void {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return failConnection(404, 'note not found', socket)
|
return failConnection(404, 'note not found', socket)
|
||||||
}
|
}
|
||||||
const owner = note.ownerId
|
const ownerId = note.ownerId
|
||||||
const ownerprofile = note.owner ? PhotoProfile.fromUser(note.owner) : null
|
const ownerprofile = note.owner ? PhotoProfile.fromUser(note.owner) : null
|
||||||
|
|
||||||
const lastchangeuser = note.lastchangeuserId
|
const lastchangeuser = note.lastchangeuserId
|
||||||
|
@ -653,7 +624,7 @@ function startConnection (socket: SocketWithNoteId): void {
|
||||||
id: noteId,
|
id: noteId,
|
||||||
alias: note.alias,
|
alias: note.alias,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
owner: owner,
|
ownerId: ownerId,
|
||||||
ownerprofile: ownerprofile,
|
ownerprofile: ownerprofile,
|
||||||
permission: note.permission,
|
permission: note.permission,
|
||||||
lastchangeuser: lastchangeuser,
|
lastchangeuser: lastchangeuser,
|
||||||
|
@ -863,7 +834,7 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
const note = notes.get(noteId)
|
const note = notes.get(noteId)
|
||||||
if (!note) return
|
if (!note) return
|
||||||
// Only owner can change permission
|
// Only owner can change permission
|
||||||
if (note.owner && note.owner === socket.request.user.id) {
|
if (getPermission(socket.request.user, note) === Permission.Owner) {
|
||||||
if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return
|
if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return
|
||||||
note.permission = permission
|
note.permission = permission
|
||||||
Note.update({
|
Note.update({
|
||||||
|
@ -884,7 +855,7 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
const sock = note.socks[i]
|
const sock = note.socks[i]
|
||||||
if (typeof sock !== 'undefined' && sock) {
|
if (typeof sock !== 'undefined' && sock) {
|
||||||
// check view permission
|
// check view permission
|
||||||
if (!checkViewPermission(sock.request, note)) {
|
if (getPermission(sock.request.user, note) === Permission.None) {
|
||||||
sock.emit('info', {
|
sock.emit('info', {
|
||||||
code: 403
|
code: 403
|
||||||
})
|
})
|
||||||
|
@ -910,7 +881,7 @@ function connection (socket: SocketWithNoteId): void {
|
||||||
const note = notes.get(noteId)
|
const note = notes.get(noteId)
|
||||||
if (!note) return
|
if (!note) return
|
||||||
// Only owner can delete note
|
// Only owner can delete note
|
||||||
if (note.owner && note.owner === socket.request.user.id) {
|
if (getPermission(socket.request.user, note) === Permission.Owner) {
|
||||||
Note.destroy({
|
Note.destroy({
|
||||||
where: {
|
where: {
|
||||||
id: noteId
|
id: noteId
|
||||||
|
|
|
@ -33,13 +33,51 @@ export function newNote (req, res: Response, body: string | null): void {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkViewPermission (req, note: Note): boolean {
|
export enum Permission {
|
||||||
if (note.permission === 'private') {
|
None,
|
||||||
return req.isAuthenticated() && note.ownerId === req.user.id
|
Read,
|
||||||
} else if (note.permission === 'limited' || note.permission === 'protected') {
|
Write,
|
||||||
return req.isAuthenticated()
|
Owner
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NoteObject {
|
||||||
|
ownerId?: string;
|
||||||
|
permission: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermission (user, note: NoteObject): Permission {
|
||||||
|
// There are two possible User objects we get passed. One is from socket.io
|
||||||
|
// and the other is from passport directly. The former sets the logged_in
|
||||||
|
// parameter to either true or false, whereas for the latter, the logged_in
|
||||||
|
// parameter is always undefined, and the existence of user itself means the
|
||||||
|
// user is logged in.
|
||||||
|
if (!user || user.logged_in === false) {
|
||||||
|
// Anonymous
|
||||||
|
switch (note.permission) {
|
||||||
|
case 'freely':
|
||||||
|
return Permission.Write
|
||||||
|
case 'editable':
|
||||||
|
case 'locked':
|
||||||
|
return Permission.Read
|
||||||
|
default:
|
||||||
|
return Permission.None
|
||||||
|
}
|
||||||
|
} else if (note.ownerId === user.id) {
|
||||||
|
// Owner
|
||||||
|
return Permission.Owner
|
||||||
} else {
|
} else {
|
||||||
return true
|
// Registered user
|
||||||
|
switch (note.permission) {
|
||||||
|
case 'editable':
|
||||||
|
case 'limited':
|
||||||
|
case 'freely':
|
||||||
|
return Permission.Write
|
||||||
|
case 'locked':
|
||||||
|
case 'protected':
|
||||||
|
return Permission.Read
|
||||||
|
default:
|
||||||
|
return Permission.None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +96,7 @@ export function findNoteOrCreate (req: Request, res: Response, callback: (note:
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return newNote(req, res, '')
|
return newNote(req, res, '')
|
||||||
}
|
}
|
||||||
if (!checkViewPermission(req, note)) {
|
if (getPermission(req.user, note) === Permission.None) {
|
||||||
return errors.errorForbidden(res)
|
return errors.errorForbidden(res)
|
||||||
} else {
|
} else {
|
||||||
return callback(note)
|
return callback(note)
|
||||||
|
|
Loading…
Reference in a new issue