mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-23 02:06:29 -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 { EditorSocketIOServer } from './ot/editor-socketio-server'
|
||||
import { mapToObject } from './utils/functions'
|
||||
import { getPermission, Permission } from './web/note/util'
|
||||
|
||||
export type SocketWithNoteId = Socket & { noteId: string }
|
||||
|
||||
|
@ -102,7 +103,7 @@ class NoteSession {
|
|||
id: string
|
||||
alias: string
|
||||
title: string
|
||||
owner: string
|
||||
ownerId: string
|
||||
ownerprofile: PhotoProfile | null
|
||||
permission: string
|
||||
lastchangeuser: string | null
|
||||
|
@ -377,7 +378,7 @@ function emitRefresh (socket: SocketWithNoteId): void {
|
|||
const out = {
|
||||
title: note.title,
|
||||
docmaxlength: config.documentMaxLength,
|
||||
owner: note.owner,
|
||||
owner: note.ownerId,
|
||||
ownerprofile: note.ownerprofile,
|
||||
lastchangeuser: note.lastchangeuser,
|
||||
lastchangeuserprofile: note.lastchangeuserprofile,
|
||||
|
@ -442,16 +443,6 @@ function interruptConnection (socket: Socket, noteId: string, socketId): void {
|
|||
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 {
|
||||
// if no valid info provided will drop the client
|
||||
if (!socket || !notes.get(noteId) || !users.get(socketId)) {
|
||||
|
@ -460,7 +451,7 @@ function finishConnection (socket: SocketWithNoteId, noteId: string, socketId: s
|
|||
// check view permission
|
||||
const note = notes.get(noteId)
|
||||
if (!note) return
|
||||
if (!checkViewPermission(socket.request, note)) {
|
||||
if (getPermission(socket.request.user, note) === Permission.None) {
|
||||
interruptConnection(socket, noteId, socketId)
|
||||
return failConnection(403, 'connection forbidden', socket)
|
||||
}
|
||||
|
@ -513,27 +504,7 @@ function ifMayEdit (socket: SocketWithNoteId, originIsOperation: boolean, callba
|
|||
if (!noteId) return
|
||||
const note = notes.get(noteId)
|
||||
if (!note) return
|
||||
let mayEdit = true
|
||||
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
|
||||
}
|
||||
const mayEdit = (getPermission(socket.request.user, note) >= Permission.Write)
|
||||
// if user may edit and this is a text operation
|
||||
if (originIsOperation && mayEdit) {
|
||||
// save for the last change user id
|
||||
|
@ -625,7 +596,7 @@ function startConnection (socket: SocketWithNoteId): void {
|
|||
if (!note) {
|
||||
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 lastchangeuser = note.lastchangeuserId
|
||||
|
@ -653,7 +624,7 @@ function startConnection (socket: SocketWithNoteId): void {
|
|||
id: noteId,
|
||||
alias: note.alias,
|
||||
title: note.title,
|
||||
owner: owner,
|
||||
ownerId: ownerId,
|
||||
ownerprofile: ownerprofile,
|
||||
permission: note.permission,
|
||||
lastchangeuser: lastchangeuser,
|
||||
|
@ -863,7 +834,7 @@ function connection (socket: SocketWithNoteId): void {
|
|||
const note = notes.get(noteId)
|
||||
if (!note) return
|
||||
// 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
|
||||
note.permission = permission
|
||||
Note.update({
|
||||
|
@ -884,7 +855,7 @@ function connection (socket: SocketWithNoteId): void {
|
|||
const sock = note.socks[i]
|
||||
if (typeof sock !== 'undefined' && sock) {
|
||||
// check view permission
|
||||
if (!checkViewPermission(sock.request, note)) {
|
||||
if (getPermission(sock.request.user, note) === Permission.None) {
|
||||
sock.emit('info', {
|
||||
code: 403
|
||||
})
|
||||
|
@ -910,7 +881,7 @@ function connection (socket: SocketWithNoteId): void {
|
|||
const note = notes.get(noteId)
|
||||
if (!note) return
|
||||
// Only owner can delete note
|
||||
if (note.owner && note.owner === socket.request.user.id) {
|
||||
if (getPermission(socket.request.user, note) === Permission.Owner) {
|
||||
Note.destroy({
|
||||
where: {
|
||||
id: noteId
|
||||
|
|
|
@ -33,13 +33,51 @@ export function newNote (req, res: Response, body: string | null): void {
|
|||
})
|
||||
}
|
||||
|
||||
export function checkViewPermission (req, note: Note): boolean {
|
||||
if (note.permission === 'private') {
|
||||
return req.isAuthenticated() && note.ownerId === req.user.id
|
||||
} else if (note.permission === 'limited' || note.permission === 'protected') {
|
||||
return req.isAuthenticated()
|
||||
export enum Permission {
|
||||
None,
|
||||
Read,
|
||||
Write,
|
||||
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 {
|
||||
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) {
|
||||
return newNote(req, res, '')
|
||||
}
|
||||
if (!checkViewPermission(req, note)) {
|
||||
if (getPermission(req.user, note) === Permission.None) {
|
||||
return errors.errorForbidden(res)
|
||||
} else {
|
||||
return callback(note)
|
||||
|
|
Loading…
Reference in a new issue