mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-22 01:36:29 -05:00
feat(config): add config vars for default permissions for special groups
Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
7dd093a44f
commit
df976b5fe1
10 changed files with 518 additions and 36 deletions
|
@ -20,7 +20,7 @@ We also provide an `.env.example` file containing a minimal configuration in the
|
|||
## General
|
||||
|
||||
| environment variable | default | example | description |
|
||||
|--------------------------|-----------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|--------------------------|-----------|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `HD_DOMAIN` | - | `https://md.example.com` | The URL the HedgeDoc instance runs on. |
|
||||
| `PORT` | 3000 | | The port the HedgeDoc instance runs on. |
|
||||
| `HD_RENDERER_ORIGIN` | HD_DOMAIN | | The URL the renderer runs on. If omitted this will be same as `HD_DOMAIN`. |
|
||||
|
@ -33,6 +33,16 @@ We also provide an `.env.example` file containing a minimal configuration in the
|
|||
|
||||
If the renderer is provided by another domain, it's way harder to manipulate HedgeDoc or steal credentials from the rendered note content, because renderer and editor are more isolated. This increases the security of the software and greatly mitigates [XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting). However, you can run HedgeDoc without this extra security, but we recommend using it if possible.
|
||||
|
||||
## Notes
|
||||
|
||||
| environment variable | default | example | description |
|
||||
|------------------------------------------|---------|-----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `HD_FORBIDDEN_NOTE_IDS` | - | `notAllowed, alsoNotAllowed` | A list of note ids (separated by `,`), that are not allowed to be created or requested by anyone. |
|
||||
| `HD_MAX_DOCUMENT_LENGTH` | 100000 | | The maximum length of any one document. Changes to this will impact performance for your users. |
|
||||
| `HD_GUEST_ACCESS` | `write` | `deny`, `read`, `write`, `create` | Defines the maximum access level for guest users to the instance. If guest access is set lower than the "everyone" permission of a note then the note permission will be overridden. |
|
||||
| `HD_PERMISSION_LOGGED_IN_DEFAULT_ACCESS` | `write` | `none, read, write` | The default permission for the "logged-in" group that is set on new notes. |
|
||||
| `HD_PERMISSION_EVERYONE_DEFAULT_ACCESS` | `read` | `none, read, write` | The default permission for the "everyone" group (logged-in & guest users), that is set on new notes created by logged-in users. Notes created by guests always set this to "write". |
|
||||
|
||||
## Authentication
|
||||
|
||||
**ToDo:** Add Authentication docs
|
||||
|
|
|
@ -8,7 +8,7 @@ NestJS - the framework we use - is reading the variables from the environment an
|
|||
|
||||
## How the config code works
|
||||
|
||||
The config of HedgeDoc is split up into **eight** different modules:
|
||||
The config of HedgeDoc is split up into **nine** different modules:
|
||||
|
||||
`app.config.ts`
|
||||
: General configuration of the app
|
||||
|
@ -34,6 +34,9 @@ The config of HedgeDoc is split up into **eight** different modules:
|
|||
`media.config.ts`
|
||||
: Where media files are being stored
|
||||
|
||||
`note.config.ts`
|
||||
: Configuration for notes
|
||||
|
||||
Each of those files (except `auth.config.ts` which is discussed later) consists of three parts:
|
||||
|
||||
1. An interface
|
||||
|
|
26
src/config/default-access-permission.enum.ts
Normal file
26
src/config/default-access-permission.enum.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export enum DefaultAccessPermission {
|
||||
NONE = 'none',
|
||||
READ = 'read',
|
||||
WRITE = 'write',
|
||||
}
|
||||
|
||||
export function getDefaultAccessPermissionOrdinal(
|
||||
permission: DefaultAccessPermission,
|
||||
): number {
|
||||
switch (permission) {
|
||||
case DefaultAccessPermission.NONE:
|
||||
return 0;
|
||||
case DefaultAccessPermission.READ:
|
||||
return 1;
|
||||
case DefaultAccessPermission.WRITE:
|
||||
return 2;
|
||||
default:
|
||||
throw Error('Unknown permission');
|
||||
}
|
||||
}
|
29
src/config/guest_access.enum.ts
Normal file
29
src/config/guest_access.enum.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export enum GuestAccess {
|
||||
DENY = 'deny',
|
||||
READ = 'read',
|
||||
WRITE = 'write',
|
||||
CREATE = 'create',
|
||||
}
|
||||
|
||||
export function getGuestAccessOrdinal(
|
||||
guestAccess: GuestAccess,
|
||||
): number {
|
||||
switch (guestAccess) {
|
||||
case GuestAccess.DENY:
|
||||
return 0;
|
||||
case GuestAccess.READ:
|
||||
return 1;
|
||||
case GuestAccess.WRITE:
|
||||
return 2;
|
||||
case GuestAccess.CREATE:
|
||||
return 3;
|
||||
default:
|
||||
throw Error('Unknown permission');
|
||||
}
|
||||
}
|
|
@ -6,12 +6,21 @@
|
|||
import { ConfigFactoryKeyHost, registerAs } from '@nestjs/config';
|
||||
import { ConfigFactory } from '@nestjs/config/dist/interfaces';
|
||||
|
||||
import { DefaultAccessPermission } from '../default-access-permission.enum';
|
||||
import { DefaultAccessPermission } from '../default-access-permission.enum';
|
||||
import { NoteConfig } from '../note.config';
|
||||
import { GuestAccess } from '../guest_access.enum';
|
||||
|
||||
export function createDefaultMockNoteConfig(): NoteConfig {
|
||||
return {
|
||||
maxDocumentLength: 100000,
|
||||
forbiddenNoteIds: ['forbiddenNoteId'],
|
||||
permissions: {
|
||||
default: {
|
||||
everyone: DefaultAccessPermission.READ,
|
||||
loggedIn: DefaultAccessPermission.WRITE,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
import mockedEnv from 'mocked-env';
|
||||
|
||||
import { DefaultAccessPermission } from './default-access-permission.enum';
|
||||
import { GuestAccess } from './guest_access.enum';
|
||||
import noteConfig from './note.config';
|
||||
|
||||
describe('noteConfig', () => {
|
||||
|
@ -15,14 +17,19 @@ describe('noteConfig', () => {
|
|||
const negativeMaxDocumentLength = -123;
|
||||
const floatMaxDocumentLength = 2.71;
|
||||
const invalidMaxDocumentLength = 'not-a-max-document-length';
|
||||
const guestAccess = GuestAccess.CREATE;
|
||||
const wrongDefaultPermission = 'wrong';
|
||||
|
||||
describe('correctly parses config', () => {
|
||||
it('when given correct and complete environment variables', async () => {
|
||||
it('when given correct and complete environment variables', () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -33,6 +40,13 @@ describe('noteConfig', () => {
|
|||
expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
|
||||
expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
|
||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.guestAccess).toEqual(guestAccess);
|
||||
restore();
|
||||
});
|
||||
|
||||
|
@ -41,6 +55,9 @@ describe('noteConfig', () => {
|
|||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -50,24 +67,13 @@ describe('noteConfig', () => {
|
|||
const config = noteConfig();
|
||||
expect(config.forbiddenNoteIds).toHaveLength(0);
|
||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when no HD_MAX_DOCUMENT_LENGTH is set', () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
const config = noteConfig();
|
||||
expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
|
||||
expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
|
||||
expect(config.maxDocumentLength).toEqual(100000);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.guestAccess).toEqual(guestAccess);
|
||||
restore();
|
||||
});
|
||||
|
||||
|
@ -77,6 +83,9 @@ describe('noteConfig', () => {
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteId,
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -87,9 +96,133 @@ describe('noteConfig', () => {
|
|||
expect(config.forbiddenNoteIds).toHaveLength(1);
|
||||
expect(config.forbiddenNoteIds[0]).toEqual(forbiddenNoteId);
|
||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
|
||||
expect(config.guestAccess).toEqual(guestAccess);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when no HD_MAX_DOCUMENT_LENGTH is set', () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
const config = noteConfig();
|
||||
expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
|
||||
expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
|
||||
expect(config.maxDocumentLength).toEqual(100000);
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
|
||||
expect(config.guestAccess).toEqual(guestAccess);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when no HD_PERMISSION_DEFAULT_EVERYONE is set', () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
const config = noteConfig();
|
||||
expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
|
||||
expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
|
||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
|
||||
expect(config.guestAccess).toEqual(guestAccess);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when no HD_PERMISSION_DEFAULT_LOGGED_IN is set', () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
const config = noteConfig();
|
||||
expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
|
||||
expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
|
||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.WRITE,
|
||||
);
|
||||
|
||||
expect(config.guestAccess).toEqual(guestAccess);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when no HD_GUEST_ACCESS is set', () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
const config = noteConfig();
|
||||
expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
|
||||
expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
|
||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||
expect(config.permissions.default.everyone).toEqual(
|
||||
DefaultAccessPermission.READ,
|
||||
);
|
||||
expect(config.permissions.default.loggedIn).toEqual(
|
||||
DefaultAccessPermission.WRITE,
|
||||
);
|
||||
|
||||
expect(config.guestAccess).toEqual(GuestAccess.WRITE);
|
||||
restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('throws error', () => {
|
||||
it('when given a non-valid HD_FORBIDDEN_NOTE_IDS', async () => {
|
||||
const restore = mockedEnv(
|
||||
|
@ -97,6 +230,9 @@ describe('noteConfig', () => {
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: invalidforbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -115,6 +251,9 @@ describe('noteConfig', () => {
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: negativeMaxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -133,6 +272,9 @@ describe('noteConfig', () => {
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: floatMaxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -151,6 +293,9 @@ describe('noteConfig', () => {
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: invalidMaxDocumentLength,
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
|
@ -162,5 +307,152 @@ describe('noteConfig', () => {
|
|||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when given a non-valid HD_PERMISSION_DEFAULT_EVERYONE', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: wrongDefaultPermission,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
'"HD_PERMISSION_DEFAULT_EVERYONE" must be one of [none, read, write]',
|
||||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when given a non-valid HD_PERMISSION_DEFAULT_LOGGED_IN', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: wrongDefaultPermission,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
'"HD_PERMISSION_DEFAULT_LOGGED_IN" must be one of [none, read, write]',
|
||||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when given a non-valid HD_GUEST_ACCESS', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: wrongDefaultPermission,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
'"HD_GUEST_ACCESS" must be one of [deny, read, write, create]',
|
||||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when HD_GUEST_ACCESS is set to deny and HD_PERMISSION_DEFAULT_EVERYONE is set', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: 'deny',
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
`'HD_GUEST_ACCESS' is set to 'deny', but 'HD_PERMISSION_DEFAULT_EVERYONE' is also configured. Please remove 'HD_PERMISSION_DEFAULT_EVERYONE'.`,
|
||||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when HD_PERMISSION_DEFAULT_EVERYONE is set to write, but HD_PERMISSION_DEFAULT_LOGGED_IN is set to read', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.WRITE,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.READ,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
`'HD_PERMISSION_DEFAULT_EVERYONE' is set to '${DefaultAccessPermission.WRITE}', but 'HD_PERMISSION_DEFAULT_LOGGED_IN' is set to '${DefaultAccessPermission.READ}'. This gives everyone greater permissions than logged-in users which is not allowed.`,
|
||||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when HD_PERMISSION_DEFAULT_EVERYONE is set to write, but HD_PERMISSION_DEFAULT_LOGGED_IN is set to none', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.WRITE,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.NONE,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
`'HD_PERMISSION_DEFAULT_EVERYONE' is set to '${DefaultAccessPermission.WRITE}', but 'HD_PERMISSION_DEFAULT_LOGGED_IN' is set to '${DefaultAccessPermission.NONE}'. This gives everyone greater permissions than logged-in users which is not allowed.`,
|
||||
);
|
||||
restore();
|
||||
});
|
||||
|
||||
it('when HD_PERMISSION_DEFAULT_EVERYONE is set to read, but HD_PERMISSION_DEFAULT_LOGGED_IN is set to none', async () => {
|
||||
const restore = mockedEnv(
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
|
||||
HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
|
||||
HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessPermission.READ,
|
||||
HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessPermission.NONE,
|
||||
HD_GUEST_ACCESS: guestAccess,
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
},
|
||||
{
|
||||
clear: true,
|
||||
},
|
||||
);
|
||||
expect(() => noteConfig()).toThrow(
|
||||
`'HD_PERMISSION_DEFAULT_EVERYONE' is set to '${DefaultAccessPermission.READ}', but 'HD_PERMISSION_DEFAULT_LOGGED_IN' is set to '${DefaultAccessPermission.NONE}'. This gives everyone greater permissions than logged-in users which is not allowed.`,
|
||||
);
|
||||
restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,14 +6,26 @@
|
|||
import { registerAs } from '@nestjs/config';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import {
|
||||
DefaultAccessPermission,
|
||||
getDefaultAccessPermissionOrdinal,
|
||||
} from './default-access-permission.enum';
|
||||
import { GuestAccess } from './guest_access.enum';
|
||||
import { buildErrorMessage, parseOptionalNumber, toArrayConfig } from './utils';
|
||||
|
||||
export interface NoteConfig {
|
||||
forbiddenNoteIds: string[];
|
||||
maxDocumentLength: number;
|
||||
guestAccess: GuestAccess;
|
||||
permissions: {
|
||||
default: {
|
||||
everyone: DefaultAccessPermission;
|
||||
loggedIn: DefaultAccessPermission;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
const schema = Joi.object<NoteConfig>({
|
||||
forbiddenNoteIds: Joi.array()
|
||||
.items(Joi.string())
|
||||
.optional()
|
||||
|
@ -25,8 +37,52 @@ const schema = Joi.object({
|
|||
.integer()
|
||||
.optional()
|
||||
.label('HD_MAX_DOCUMENT_LENGTH'),
|
||||
guestAccess: Joi.string()
|
||||
.valid(...Object.values(GuestAccess))
|
||||
.optional()
|
||||
.default(GuestAccess.WRITE)
|
||||
.label('HD_GUEST_ACCESS'),
|
||||
permissions: {
|
||||
default: {
|
||||
everyone: Joi.string()
|
||||
.valid(...Object.values(DefaultAccessPermission))
|
||||
.optional()
|
||||
.default(DefaultAccessPermission.READ)
|
||||
.label('HD_PERMISSION_DEFAULT_EVERYONE'),
|
||||
loggedIn: Joi.string()
|
||||
.valid(...Object.values(DefaultAccessPermission))
|
||||
.optional()
|
||||
.default(DefaultAccessPermission.WRITE)
|
||||
.label('HD_PERMISSION_DEFAULT_LOGGED_IN'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function checkEveryoneConfigIsConsistent(config: NoteConfig): void {
|
||||
const everyoneDefaultSet =
|
||||
process.env.HD_PERMISSION_DEFAULT_EVERYONE !== undefined;
|
||||
if (config.guestAccess === GuestAccess.DENY && everyoneDefaultSet) {
|
||||
throw new Error(
|
||||
`'HD_GUEST_ACCESS' is set to '${config.guestAccess}', but 'HD_PERMISSION_DEFAULT_EVERYONE' is also configured. Please remove 'HD_PERMISSION_DEFAULT_EVERYONE'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkLoggedInUsersHaveHigherDefaultPermissionsThanGuests(
|
||||
config: NoteConfig,
|
||||
): void {
|
||||
const everyone = config.permissions.default.everyone;
|
||||
const loggedIn = config.permissions.default.loggedIn;
|
||||
if (
|
||||
getDefaultAccessPermissionOrdinal(everyone) >
|
||||
getDefaultAccessPermissionOrdinal(loggedIn)
|
||||
) {
|
||||
throw new Error(
|
||||
`'HD_PERMISSION_DEFAULT_EVERYONE' is set to '${everyone}', but 'HD_PERMISSION_DEFAULT_LOGGED_IN' is set to '${loggedIn}'. This gives everyone greater permissions than logged-in users which is not allowed.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default registerAs('noteConfig', () => {
|
||||
const noteConfig = schema.validate(
|
||||
{
|
||||
|
@ -34,7 +90,14 @@ export default registerAs('noteConfig', () => {
|
|||
maxDocumentLength: parseOptionalNumber(
|
||||
process.env.HD_MAX_DOCUMENT_LENGTH,
|
||||
),
|
||||
guestAccess: process.env.HD_GUEST_ACCESS,
|
||||
permissions: {
|
||||
default: {
|
||||
everyone: process.env.HD_PERMISSION_DEFAULT_EVERYONE,
|
||||
loggedIn: process.env.HD_PERMISSION_DEFAULT_LOGGED_IN,
|
||||
},
|
||||
},
|
||||
} as NoteConfig,
|
||||
{
|
||||
abortEarly: false,
|
||||
presence: 'required',
|
||||
|
@ -46,5 +109,8 @@ export default registerAs('noteConfig', () => {
|
|||
);
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return noteConfig.value as NoteConfig;
|
||||
const config = noteConfig.value;
|
||||
checkEveryoneConfigIsConsistent(config);
|
||||
checkLoggedInUsersHaveHigherDefaultPermissionsThanGuests(config);
|
||||
return config;
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { URL } from 'url';
|
|||
import { AppConfig } from '../config/app.config';
|
||||
import { AuthConfig } from '../config/auth.config';
|
||||
import { CustomizationConfig } from '../config/customization.config';
|
||||
import { DefaultAccessPermission } from '../config/default-access-permission.enum';
|
||||
import { ExternalServicesConfig } from '../config/external-services.config';
|
||||
import { GitlabScope, GitlabVersion } from '../config/gitlab.enum';
|
||||
import { Loglevel } from '../config/loglevel.enum';
|
||||
|
@ -191,7 +192,14 @@ describe('FrontendConfigService', () => {
|
|||
return {
|
||||
forbiddenNoteIds: [],
|
||||
maxDocumentLength: 200,
|
||||
};
|
||||
guestAccess: true,
|
||||
permissions: {
|
||||
default: {
|
||||
everyone: DefaultAccessPermission.READ,
|
||||
loggedIn: DefaultAccessPermission.WRITE,
|
||||
},
|
||||
},
|
||||
} as NoteConfig;
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
@ -350,6 +358,13 @@ describe('FrontendConfigService', () => {
|
|||
const noteConfig: NoteConfig = {
|
||||
forbiddenNoteIds: [],
|
||||
maxDocumentLength: maxDocumentLength,
|
||||
guestAccess: true,
|
||||
permissions: {
|
||||
default: {
|
||||
everyone: DefaultAccessPermission.READ,
|
||||
loggedIn: DefaultAccessPermission.WRITE,
|
||||
},
|
||||
},
|
||||
};
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
|
@ -377,8 +392,7 @@ describe('FrontendConfigService', () => {
|
|||
const service = module.get(FrontendConfigService);
|
||||
const config = await service.getFrontendConfig();
|
||||
expect(config.allowRegister).toEqual(enableRegister);
|
||||
|
||||
expect(config.allowAnonymous).toEqual(false);
|
||||
expect(config.allowAnonymous).toEqual(noteConfig.guestAccess);
|
||||
expect(config.branding.name).toEqual(customName);
|
||||
expect(config.branding.logo).toEqual(
|
||||
customLogo ? new URL(customLogo) : undefined,
|
||||
|
|
|
@ -46,8 +46,7 @@ export class FrontendConfigService {
|
|||
|
||||
async getFrontendConfig(): Promise<FrontendConfigDto> {
|
||||
return {
|
||||
// ToDo: use actual value here
|
||||
allowAnonymous: false,
|
||||
allowAnonymous: this.noteConfig.guestAccess,
|
||||
allowRegister: this.authConfig.local.enableRegister,
|
||||
authProviders: this.getAuthProviders(),
|
||||
branding: this.getBranding(),
|
||||
|
|
|
@ -96,6 +96,26 @@ export class NotesService {
|
|||
HistoryEntry.create(owner, newNote as Note) as HistoryEntry,
|
||||
]);
|
||||
}
|
||||
|
||||
const everyonePermission = this.createGroupPermission(
|
||||
newNote as Note,
|
||||
await this.groupsService.getEveryoneGroup(),
|
||||
owner === null
|
||||
? DefaultAccessPermission.WRITE
|
||||
: this.noteConfig.permissions.default.everyone,
|
||||
);
|
||||
|
||||
const loggedInPermission = this.createGroupPermission(
|
||||
newNote as Note,
|
||||
await this.groupsService.getLoggedInGroup(),
|
||||
this.noteConfig.permissions.default.loggedIn,
|
||||
);
|
||||
|
||||
newNote.groupPermissions = Promise.resolve([
|
||||
...Optional.ofNullable(everyonePermission).wrapInArray(),
|
||||
...Optional.ofNullable(loggedInPermission).wrapInArray(),
|
||||
]);
|
||||
|
||||
try {
|
||||
return await this.noteRepository.save(newNote);
|
||||
} catch (e) {
|
||||
|
@ -113,6 +133,20 @@ export class NotesService {
|
|||
}
|
||||
}
|
||||
|
||||
private createGroupPermission(
|
||||
note: Note,
|
||||
group: Group,
|
||||
permission: DefaultAccessPermission,
|
||||
): NoteGroupPermission | null {
|
||||
return permission === DefaultAccessPermission.NONE
|
||||
? null
|
||||
: NoteGroupPermission.create(
|
||||
group,
|
||||
note,
|
||||
permission === DefaultAccessPermission.WRITE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @async
|
||||
* Get the current content of the note.
|
||||
|
|
Loading…
Reference in a new issue