diff --git a/packages/api/.env b/packages/api/.env
index c7746f945..8bea8426d 100644
--- a/packages/api/.env
+++ b/packages/api/.env
@@ -1,3 +1,4 @@
DEVMODE=1
+# PERMISSIONS=~widgets/app,~widgets/plugins
# DISABLE_SHELL=1
# HIDE_APP_EDITOR=1
\ No newline at end of file
diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js
index 32d385fb8..937898a60 100644
--- a/packages/api/src/controllers/config.js
+++ b/packages/api/src/controllers/config.js
@@ -32,7 +32,7 @@ module.exports = {
return {
runAsPortal: !!connections.portalConnections,
singleDatabase: connections.singleDatabase,
- hideAppEditor: !!process.env.HIDE_APP_EDITOR,
+ // hideAppEditor: !!process.env.HIDE_APP_EDITOR,
allowShellConnection: platformInfo.allowShellConnection,
allowShellScripting: platformInfo.allowShellConnection,
permissions,
diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js
index 350f72262..4116bd9e7 100644
--- a/packages/api/src/controllers/files.js
+++ b/packages/api/src/controllers/files.js
@@ -46,26 +46,29 @@ module.exports = {
delete_meta: true,
async delete({ folder, file }) {
- if (!hasPermission(`files/${folder}/write`)) return;
+ if (!hasPermission(`files/${folder}/write`)) return false;
await fs.unlink(path.join(filesdir(), folder, file));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`all-files-changed`);
+ return true;
},
rename_meta: true,
async rename({ folder, file, newFile }) {
- if (!hasPermission(`files/${folder}/write`)) return;
+ if (!hasPermission(`files/${folder}/write`)) return false;
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`all-files-changed`);
+ return true;
},
copy_meta: true,
async copy({ folder, file, newFile }) {
- if (!hasPermission(`files/${folder}/write`)) return;
+ if (!hasPermission(`files/${folder}/write`)) return false;
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed-${folder}`);
socket.emitChanged(`all-files-changed`);
+ return true;
},
load_meta: true,
@@ -90,11 +93,13 @@ module.exports = {
save_meta: true,
async save({ folder, file, data, format }) {
if (folder.startsWith('archive:')) {
+ if (!hasPermission(`archive/write`)) return false;
const dir = resolveArchiveFolder(folder.substring('archive:'.length));
await fs.writeFile(path.join(dir, file), serialize(format, data));
socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
return true;
} else if (folder.startsWith('app:')) {
+ if (!hasPermission(`apps/write`)) return false;
const app = folder.substring('app:'.length);
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
socket.emitChanged(`app-files-changed-${app}`);
diff --git a/packages/tools/src/testPermission.ts b/packages/tools/src/testPermission.ts
index 907d7169b..322476b3e 100644
--- a/packages/tools/src/testPermission.ts
+++ b/packages/tools/src/testPermission.ts
@@ -1,16 +1,38 @@
import _escapeRegExp from 'lodash/escapeRegExp';
import _isString from 'lodash/isString';
+import _compact from 'lodash/compact';
-export function compilePermissions(permissions: string[] | string) {
+interface CompiledPermissions {
+ revoke: RegExp;
+ allow: RegExp;
+}
+
+function compileRegexp(permissions) {
+ if (permissions.length == 0) return null;
+ return new RegExp(permissions.map(x => '^' + _escapeRegExp(x).replace(/\\\*/g, '.*') + '$').join('|'));
+}
+
+export function compilePermissions(permissions: string[] | string): CompiledPermissions {
if (!permissions) return null;
if (_isString(permissions)) permissions = permissions.split(',');
- return permissions.map(x => new RegExp('^' + _escapeRegExp(x).replace(/\\\*/g, '.*') + '$'));
+ permissions = _compact(permissions.map(x => x.trim()));
+ const revoke = permissions.filter(x => x.startsWith('~')).map(x => x.substring(1));
+ const allow = permissions.filter(x => !x.startsWith('~'));
+ return {
+ revoke: compileRegexp(revoke),
+ allow: compileRegexp(allow),
+ };
}
-export function testPermission(tested: string, permissions: RegExp[]) {
+export function testPermission(tested: string, permissions: CompiledPermissions) {
if (!permissions) return true;
- for (const permission of permissions) {
- if (tested.match(permission)) return true;
+ if (!permissions.revoke) return true;
+
+ if (tested.match(permissions.revoke)) {
+ if (!tested.match(permissions.allow)) {
+ return false;
+ }
}
- return false;
+
+ return true;
}
diff --git a/packages/web/src/widgets/WidgetIconPanel.svelte b/packages/web/src/widgets/WidgetIconPanel.svelte
index 0ac958bd8..7c0e68a5c 100644
--- a/packages/web/src/widgets/WidgetIconPanel.svelte
+++ b/packages/web/src/widgets/WidgetIconPanel.svelte
@@ -4,6 +4,7 @@
import { currentDropDownMenu, selectedWidget, visibleCommandPalette, visibleHamburgerMenuWidget } from '../stores';
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
import { useConfig } from '../utility/metadataLoaders';
+ import hasPermission from '../utility/hasPermission';
let domSettings;
let domMainMenu;
@@ -88,7 +89,7 @@