@appsemble/utils 0.33.5 → 0.33.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/api/components/schemas/App.js +5 -1
- package/api/components/schemas/BlockVersion.js +6 -1
- package/api/components/schemas/index.d.ts +0 -118
- package/api/components/schemas/index.js +0 -118
- package/api/paths/appTemplates.js +12 -3
- package/appMessages.d.ts +2 -2
- package/appMessages.js +1 -1
- package/authorization.d.ts +1 -11
- package/authorization.js +1 -163
- package/constants/index.d.ts +0 -2
- package/constants/index.js +0 -2
- package/formatRequestAction.d.ts +1 -1
- package/index.d.ts +2 -14
- package/index.js +2 -14
- package/miscellaneous.d.ts +5 -13
- package/miscellaneous.js +6 -33
- package/package.json +3 -2
- package/theme.d.ts +1 -1
- package/theme.js +1 -1
- package/types.d.ts +1 -1
- package/validateAppMessages.d.ts +2 -1
- package/validateAppMessages.js +2 -2
- package/allActions.d.ts +0 -3
- package/allActions.js +0 -11
- package/api/components/schemas/ActionDefinition.d.ts +0 -2
- package/api/components/schemas/ActionDefinition.js +0 -95
- package/api/components/schemas/AnalyticsActionDefinition.d.ts +0 -1
- package/api/components/schemas/AnalyticsActionDefinition.js +0 -25
- package/api/components/schemas/AppDefinition.d.ts +0 -2
- package/api/components/schemas/AppDefinition.js +0 -126
- package/api/components/schemas/AppLayoutDefinition.d.ts +0 -2
- package/api/components/schemas/AppLayoutDefinition.js +0 -46
- package/api/components/schemas/AppMemberCurrentPatchActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberCurrentPatchActionDefinition.js +0 -30
- package/api/components/schemas/AppMemberDeleteActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberDeleteActionDefinition.js +0 -20
- package/api/components/schemas/AppMemberInviteActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberInviteActionDefinition.js +0 -24
- package/api/components/schemas/AppMemberLoginActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberLoginActionDefinition.js +0 -24
- package/api/components/schemas/AppMemberLogoutActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberLogoutActionDefinition.js +0 -14
- package/api/components/schemas/AppMemberPropertiesPatchActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberPropertiesPatchActionDefinition.js +0 -26
- package/api/components/schemas/AppMemberPropertyDefinition.d.ts +0 -2
- package/api/components/schemas/AppMemberPropertyDefinition.js +0 -49
- package/api/components/schemas/AppMemberQueryActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberQueryActionDefinition.js +0 -24
- package/api/components/schemas/AppMemberRegisterActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberRegisterActionDefinition.js +0 -43
- package/api/components/schemas/AppMemberRoleUpdateActionDefinition.d.ts +0 -1
- package/api/components/schemas/AppMemberRoleUpdateActionDefinition.js +0 -27
- package/api/components/schemas/AppMembersDefinition.d.ts +0 -2
- package/api/components/schemas/AppMembersDefinition.js +0 -17
- package/api/components/schemas/ArrayRemapperDefinition.d.ts +0 -2
- package/api/components/schemas/ArrayRemapperDefinition.js +0 -22
- package/api/components/schemas/BaseActionDefinition.d.ts +0 -2
- package/api/components/schemas/BaseActionDefinition.js +0 -10
- package/api/components/schemas/BaseJSONSchema.d.ts +0 -2
- package/api/components/schemas/BaseJSONSchema.js +0 -24
- package/api/components/schemas/BasePageDefinition.d.ts +0 -2
- package/api/components/schemas/BasePageDefinition.js +0 -92
- package/api/components/schemas/BlockDefinition.d.ts +0 -2
- package/api/components/schemas/BlockDefinition.js +0 -94
- package/api/components/schemas/ConditionActionDefinition.d.ts +0 -1
- package/api/components/schemas/ConditionActionDefinition.js +0 -30
- package/api/components/schemas/ContainerDefinition.d.ts +0 -2
- package/api/components/schemas/ContainerDefinition.js +0 -52
- package/api/components/schemas/ContainerPageDefinition.d.ts +0 -1
- package/api/components/schemas/ContainerPageDefinition.js +0 -56
- package/api/components/schemas/ControllerActionDefinition.d.ts +0 -1
- package/api/components/schemas/ControllerActionDefinition.js +0 -18
- package/api/components/schemas/ControllerDefinition.d.ts +0 -2
- package/api/components/schemas/ControllerDefinition.js +0 -17
- package/api/components/schemas/CronDefinition.d.ts +0 -2
- package/api/components/schemas/CronDefinition.js +0 -20
- package/api/components/schemas/CsvParseActionDefinition.d.ts +0 -1
- package/api/components/schemas/CsvParseActionDefinition.js +0 -22
- package/api/components/schemas/CustomFontDefinition.d.ts +0 -2
- package/api/components/schemas/CustomFontDefinition.js +0 -20
- package/api/components/schemas/DialogActionDefinition.d.ts +0 -1
- package/api/components/schemas/DialogActionDefinition.js +0 -40
- package/api/components/schemas/DialogErrorActionDefinition.d.ts +0 -1
- package/api/components/schemas/DialogErrorActionDefinition.js +0 -17
- package/api/components/schemas/DialogOkActionDefinition.d.ts +0 -1
- package/api/components/schemas/DialogOkActionDefinition.js +0 -17
- package/api/components/schemas/DownloadActionDefinition.d.ts +0 -1
- package/api/components/schemas/DownloadActionDefinition.js +0 -19
- package/api/components/schemas/EachActionDefinition.d.ts +0 -1
- package/api/components/schemas/EachActionDefinition.js +0 -26
- package/api/components/schemas/EmailActionDefinition.d.ts +0 -1
- package/api/components/schemas/EmailActionDefinition.js +0 -54
- package/api/components/schemas/EventActionDefinition.d.ts +0 -1
- package/api/components/schemas/EventActionDefinition.js +0 -27
- package/api/components/schemas/EventsDefinition.d.ts +0 -2
- package/api/components/schemas/EventsDefinition.js +0 -29
- package/api/components/schemas/FilterParametersDefinition.d.ts +0 -2
- package/api/components/schemas/FilterParametersDefinition.js +0 -15
- package/api/components/schemas/FlowBackActionDefinition.d.ts +0 -1
- package/api/components/schemas/FlowBackActionDefinition.js +0 -16
- package/api/components/schemas/FlowCancelActionDefinition.d.ts +0 -1
- package/api/components/schemas/FlowCancelActionDefinition.js +0 -14
- package/api/components/schemas/FlowFinishActionDefinition.d.ts +0 -1
- package/api/components/schemas/FlowFinishActionDefinition.js +0 -16
- package/api/components/schemas/FlowNextActionDefinition.d.ts +0 -1
- package/api/components/schemas/FlowNextActionDefinition.js +0 -17
- package/api/components/schemas/FlowPageActionsDefinition.d.ts +0 -2
- package/api/components/schemas/FlowPageActionsDefinition.js +0 -23
- package/api/components/schemas/FlowPageDefinition.d.ts +0 -1
- package/api/components/schemas/FlowPageDefinition.js +0 -37
- package/api/components/schemas/FlowToActionDefinition.d.ts +0 -1
- package/api/components/schemas/FlowToActionDefinition.js +0 -21
- package/api/components/schemas/GoogleFontDefinition.d.ts +0 -2
- package/api/components/schemas/GoogleFontDefinition.js +0 -22
- package/api/components/schemas/GroupMemberDeleteActionDefinition.d.ts +0 -1
- package/api/components/schemas/GroupMemberDeleteActionDefinition.js +0 -18
- package/api/components/schemas/GroupMemberInviteActionDefinition.d.ts +0 -1
- package/api/components/schemas/GroupMemberInviteActionDefinition.js +0 -26
- package/api/components/schemas/GroupMemberQueryActionDefinition.d.ts +0 -1
- package/api/components/schemas/GroupMemberQueryActionDefinition.js +0 -18
- package/api/components/schemas/GroupMemberRoleUpdateActionDefinition.d.ts +0 -1
- package/api/components/schemas/GroupMemberRoleUpdateActionDefinition.js +0 -22
- package/api/components/schemas/GroupQueryActionDefinition.d.ts +0 -1
- package/api/components/schemas/GroupQueryActionDefinition.js +0 -14
- package/api/components/schemas/JSONPointer.d.ts +0 -1
- package/api/components/schemas/JSONPointer.js +0 -20
- package/api/components/schemas/JSONSchema.d.ts +0 -2
- package/api/components/schemas/JSONSchema.js +0 -20
- package/api/components/schemas/JSONSchemaAnyOf.d.ts +0 -1
- package/api/components/schemas/JSONSchemaAnyOf.js +0 -26
- package/api/components/schemas/JSONSchemaArray.d.ts +0 -1
- package/api/components/schemas/JSONSchemaArray.js +0 -47
- package/api/components/schemas/JSONSchemaBoolean.d.ts +0 -1
- package/api/components/schemas/JSONSchemaBoolean.js +0 -37
- package/api/components/schemas/JSONSchemaConst.d.ts +0 -1
- package/api/components/schemas/JSONSchemaConst.js +0 -16
- package/api/components/schemas/JSONSchemaEnum.d.ts +0 -1
- package/api/components/schemas/JSONSchemaEnum.js +0 -36
- package/api/components/schemas/JSONSchemaInteger.d.ts +0 -1
- package/api/components/schemas/JSONSchemaInteger.js +0 -55
- package/api/components/schemas/JSONSchemaMultiType.d.ts +0 -1
- package/api/components/schemas/JSONSchemaMultiType.js +0 -48
- package/api/components/schemas/JSONSchemaNot.d.ts +0 -1
- package/api/components/schemas/JSONSchemaNot.js +0 -23
- package/api/components/schemas/JSONSchemaNull.d.ts +0 -1
- package/api/components/schemas/JSONSchemaNull.js +0 -16
- package/api/components/schemas/JSONSchemaNumber.d.ts +0 -1
- package/api/components/schemas/JSONSchemaNumber.js +0 -63
- package/api/components/schemas/JSONSchemaObject.d.ts +0 -1
- package/api/components/schemas/JSONSchemaObject.js +0 -74
- package/api/components/schemas/JSONSchemaOneOf.d.ts +0 -1
- package/api/components/schemas/JSONSchemaOneOf.js +0 -24
- package/api/components/schemas/JSONSchemaRemapper.d.ts +0 -1
- package/api/components/schemas/JSONSchemaRemapper.js +0 -19
- package/api/components/schemas/JSONSchemaRoot.d.ts +0 -1
- package/api/components/schemas/JSONSchemaRoot.js +0 -21
- package/api/components/schemas/JSONSchemaString.d.ts +0 -1
- package/api/components/schemas/JSONSchemaString.js +0 -107
- package/api/components/schemas/LinkActionDefinition.d.ts +0 -1
- package/api/components/schemas/LinkActionDefinition.js +0 -29
- package/api/components/schemas/LinkBackActionDefinition.d.ts +0 -1
- package/api/components/schemas/LinkBackActionDefinition.js +0 -14
- package/api/components/schemas/LinkNextActionDefinition.d.ts +0 -1
- package/api/components/schemas/LinkNextActionDefinition.js +0 -14
- package/api/components/schemas/LogActionDefinition.d.ts +0 -1
- package/api/components/schemas/LogActionDefinition.js +0 -22
- package/api/components/schemas/LoopPageActionsDefinition.d.ts +0 -2
- package/api/components/schemas/LoopPageActionsDefinition.js +0 -24
- package/api/components/schemas/LoopPageDefinition.d.ts +0 -1
- package/api/components/schemas/LoopPageDefinition.js +0 -32
- package/api/components/schemas/MatchActionDefinition.d.ts +0 -1
- package/api/components/schemas/MatchActionDefinition.js +0 -35
- package/api/components/schemas/MessageActionDefinition.d.ts +0 -1
- package/api/components/schemas/MessageActionDefinition.js +0 -40
- package/api/components/schemas/NoopActionDefinition.d.ts +0 -1
- package/api/components/schemas/NoopActionDefinition.js +0 -17
- package/api/components/schemas/NotificationHookDataDefinition.d.ts +0 -2
- package/api/components/schemas/NotificationHookDataDefinition.js +0 -24
- package/api/components/schemas/NotificationHookDefinition.d.ts +0 -2
- package/api/components/schemas/NotificationHookDefinition.js +0 -32
- package/api/components/schemas/NotifyActionDefinition.d.ts +0 -1
- package/api/components/schemas/NotifyActionDefinition.js +0 -32
- package/api/components/schemas/ObjectRemapperDefinition.d.ts +0 -2
- package/api/components/schemas/ObjectRemapperDefinition.js +0 -17
- package/api/components/schemas/PageActionsDefinition.d.ts +0 -2
- package/api/components/schemas/PageActionsDefinition.js +0 -12
- package/api/components/schemas/PageDefinition.d.ts +0 -1
- package/api/components/schemas/PageDefinition.js +0 -26
- package/api/components/schemas/RemapperDefinition.d.ts +0 -2
- package/api/components/schemas/RemapperDefinition.js +0 -27
- package/api/components/schemas/RequestActionDefinition.d.ts +0 -1
- package/api/components/schemas/RequestActionDefinition.js +0 -75
- package/api/components/schemas/ResourceCountActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceCountActionDefinition.js +0 -22
- package/api/components/schemas/ResourceCreateActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceCreateActionDefinition.js +0 -18
- package/api/components/schemas/ResourceDefinition.d.ts +0 -2
- package/api/components/schemas/ResourceDefinition.js +0 -263
- package/api/components/schemas/ResourceDeleteActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceDeleteActionDefinition.js +0 -18
- package/api/components/schemas/ResourceDeleteAllActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceDeleteAllActionDefinition.js +0 -21
- package/api/components/schemas/ResourceDeleteBulkActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceDeleteBulkActionDefinition.js +0 -18
- package/api/components/schemas/ResourceGetActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceGetActionDefinition.js +0 -25
- package/api/components/schemas/ResourceHistoryDefinition.d.ts +0 -2
- package/api/components/schemas/ResourceHistoryDefinition.js +0 -14
- package/api/components/schemas/ResourceHistoryGetActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceHistoryGetActionDefinition.js +0 -18
- package/api/components/schemas/ResourceHooksDefinition.d.ts +0 -2
- package/api/components/schemas/ResourceHooksDefinition.js +0 -10
- package/api/components/schemas/ResourcePatchActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourcePatchActionDefinition.js +0 -21
- package/api/components/schemas/ResourceQueryActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceQueryActionDefinition.js +0 -26
- package/api/components/schemas/ResourceSubscriptionStatusActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceSubscriptionStatusActionDefinition.js +0 -23
- package/api/components/schemas/ResourceSubscriptionSubscribeActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceSubscriptionSubscribeActionDefinition.js +0 -23
- package/api/components/schemas/ResourceSubscriptionToggleActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceSubscriptionToggleActionDefinition.js +0 -23
- package/api/components/schemas/ResourceSubscriptionUnsubscribeActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceSubscriptionUnsubscribeActionDefinition.js +0 -23
- package/api/components/schemas/ResourceUpdateActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceUpdateActionDefinition.js +0 -18
- package/api/components/schemas/ResourceUpdatePositionsActionDefinition.d.ts +0 -1
- package/api/components/schemas/ResourceUpdatePositionsActionDefinition.js +0 -21
- package/api/components/schemas/ResourceViewDefinition.d.ts +0 -2
- package/api/components/schemas/ResourceViewDefinition.js +0 -24
- package/api/components/schemas/SecurityCronDefinition.d.ts +0 -2
- package/api/components/schemas/SecurityCronDefinition.js +0 -26
- package/api/components/schemas/SecurityDefaultDefinition.d.ts +0 -2
- package/api/components/schemas/SecurityDefaultDefinition.js +0 -34
- package/api/components/schemas/SecurityDefinition.d.ts +0 -2
- package/api/components/schemas/SecurityDefinition.js +0 -17
- package/api/components/schemas/SecurityGuestDefinition.d.ts +0 -2
- package/api/components/schemas/SecurityGuestDefinition.js +0 -26
- package/api/components/schemas/SecurityRoleDefinition.d.ts +0 -2
- package/api/components/schemas/SecurityRoleDefinition.js +0 -35
- package/api/components/schemas/ShareActionDefinition.d.ts +0 -1
- package/api/components/schemas/ShareActionDefinition.js +0 -30
- package/api/components/schemas/StaticActionDefinition.d.ts +0 -1
- package/api/components/schemas/StaticActionDefinition.js +0 -20
- package/api/components/schemas/StorageAppendActionDefinition.d.ts +0 -1
- package/api/components/schemas/StorageAppendActionDefinition.js +0 -43
- package/api/components/schemas/StorageDeleteActionDefinition.d.ts +0 -1
- package/api/components/schemas/StorageDeleteActionDefinition.js +0 -31
- package/api/components/schemas/StorageReadActionDefinition.d.ts +0 -1
- package/api/components/schemas/StorageReadActionDefinition.js +0 -32
- package/api/components/schemas/StorageSubtractActionDefinition.d.ts +0 -1
- package/api/components/schemas/StorageSubtractActionDefinition.js +0 -35
- package/api/components/schemas/StorageUpdateActionDefinition.d.ts +0 -1
- package/api/components/schemas/StorageUpdateActionDefinition.js +0 -46
- package/api/components/schemas/StorageWriteActionDefinition.d.ts +0 -1
- package/api/components/schemas/StorageWriteActionDefinition.js +0 -46
- package/api/components/schemas/SubPage.d.ts +0 -2
- package/api/components/schemas/SubPage.js +0 -36
- package/api/components/schemas/TabsPageDefinition.d.ts +0 -1
- package/api/components/schemas/TabsPageDefinition.js +0 -46
- package/api/components/schemas/Theme.d.ts +0 -2
- package/api/components/schemas/Theme.js +0 -80
- package/api/components/schemas/ThrowActionDefinition.d.ts +0 -1
- package/api/components/schemas/ThrowActionDefinition.js +0 -17
- package/api/components/schemas/WebhookDefinition.d.ts +0 -2
- package/api/components/schemas/WebhookDefinition.js +0 -17
- package/api/components/schemas/utils.d.ts +0 -13
- package/api/components/schemas/utils.js +0 -34
- package/appMembers.d.ts +0 -1
- package/appMembers.js +0 -9
- package/blockUtils.d.ts +0 -31
- package/blockUtils.js +0 -65
- package/constants/baseTheme.d.ts +0 -2
- package/constants/baseTheme.js +0 -16
- package/constants/fonts.d.ts +0 -6
- package/constants/fonts.js +0 -1607
- package/examples.d.ts +0 -23
- package/examples.js +0 -975
- package/findPageByName.d.ts +0 -2
- package/findPageByName.js +0 -15
- package/has.d.ts +0 -8
- package/has.js +0 -11
- package/ics.d.ts +0 -3
- package/ics.js +0 -41
- package/iterApp.d.ts +0 -64
- package/iterApp.js +0 -150
- package/jsonschema.d.ts +0 -54
- package/jsonschema.js +0 -185
- package/reference-schemas/actions/appMember.d.ts +0 -2
- package/reference-schemas/actions/appMember.js +0 -21
- package/reference-schemas/actions/flow.d.ts +0 -2
- package/reference-schemas/actions/flow.js +0 -13
- package/reference-schemas/actions/group.d.ts +0 -2
- package/reference-schemas/actions/group.js +0 -13
- package/reference-schemas/actions/index.d.ts +0 -7
- package/reference-schemas/actions/index.js +0 -8
- package/reference-schemas/actions/link.d.ts +0 -2
- package/reference-schemas/actions/link.js +0 -9
- package/reference-schemas/actions/miscellaneous.d.ts +0 -2
- package/reference-schemas/actions/miscellaneous.js +0 -43
- package/reference-schemas/actions/resources.d.ts +0 -2
- package/reference-schemas/actions/resources.js +0 -19
- package/reference-schemas/actions/storage.d.ts +0 -2
- package/reference-schemas/actions/storage.js +0 -15
- package/reference-schemas/remappers/arrays.d.ts +0 -2
- package/reference-schemas/remappers/arrays.js +0 -297
- package/reference-schemas/remappers/conditionals.d.ts +0 -2
- package/reference-schemas/remappers/conditionals.js +0 -167
- package/reference-schemas/remappers/config.d.ts +0 -2
- package/reference-schemas/remappers/config.js +0 -13
- package/reference-schemas/remappers/data.d.ts +0 -2
- package/reference-schemas/remappers/data.js +0 -394
- package/reference-schemas/remappers/dates.d.ts +0 -2
- package/reference-schemas/remappers/dates.js +0 -94
- package/reference-schemas/remappers/history.d.ts +0 -2
- package/reference-schemas/remappers/history.js +0 -246
- package/reference-schemas/remappers/index.d.ts +0 -12
- package/reference-schemas/remappers/index.js +0 -13
- package/reference-schemas/remappers/numbers.d.ts +0 -2
- package/reference-schemas/remappers/numbers.js +0 -30
- package/reference-schemas/remappers/objects.d.ts +0 -2
- package/reference-schemas/remappers/objects.js +0 -127
- package/reference-schemas/remappers/odata.d.ts +0 -2
- package/reference-schemas/remappers/odata.js +0 -95
- package/reference-schemas/remappers/randoms.d.ts +0 -2
- package/reference-schemas/remappers/randoms.js +0 -82
- package/reference-schemas/remappers/strings.d.ts +0 -2
- package/reference-schemas/remappers/strings.js +0 -93
- package/reference-schemas/remappers/unsorted.d.ts +0 -2
- package/reference-schemas/remappers/unsorted.js +0 -276
- package/remap.d.ts +0 -74
- package/remap.js +0 -692
- package/serverActions.d.ts +0 -2
- package/serverActions.js +0 -21
- package/string.d.ts +0 -23
- package/string.js +0 -30
- package/validation.d.ts +0 -25
- package/validation.js +0 -1231
package/validation.js
DELETED
|
@@ -1,1231 +0,0 @@
|
|
|
1
|
-
import { PredefinedAppRole, predefinedAppRolePermissions, } from '@appsemble/types';
|
|
2
|
-
import cronParser from 'cron-parser';
|
|
3
|
-
import { ValidationError, Validator } from 'jsonschema';
|
|
4
|
-
import languageTags from 'language-tags';
|
|
5
|
-
import { getAppBlocks, normalizeBlockName } from './blockUtils.js';
|
|
6
|
-
import { has } from './has.js';
|
|
7
|
-
import { findPageByName, getAppInheritedRoles, getAppPossibleGuestPermissions, getAppPossiblePermissions, getAppRolePermissions, normalize, partialNormalized, } from './index.js';
|
|
8
|
-
import { iterApp } from './iterApp.js';
|
|
9
|
-
import { serverActions } from './serverActions.js';
|
|
10
|
-
const allResourcePermissionPattern = /^\$resource:all:(get|history:get|query|create|delete|patch|update)$/;
|
|
11
|
-
const resourcePermissionPattern = /^\$resource:[^:]+:(get|history:get|query|create|delete|patch|update)$/;
|
|
12
|
-
const allOwnResourcePermissionPattern = /^\$resource:all:own:(get|query|delete|patch|update)$/;
|
|
13
|
-
const ownResourcePermissionPattern = /^\$resource:[^:]+:own:(get|query|delete|patch|update)$/;
|
|
14
|
-
const allResourceViewPermissionPattern = /^\$resource:all:(get|query):[^:]+$/;
|
|
15
|
-
const resourceViewPermissionPattern = /^\$resource:[^:]+:(get|query):[^:]+$/;
|
|
16
|
-
/**
|
|
17
|
-
* Check whether or not the given link represents a link related to the Appsemble core.
|
|
18
|
-
*
|
|
19
|
-
* @param link The link to check
|
|
20
|
-
* @returns Whether or not the given link represents a link related to the Appsemble core.
|
|
21
|
-
*/
|
|
22
|
-
export function isAppLink(link) {
|
|
23
|
-
return link === '/Login' || link === '/Settings';
|
|
24
|
-
}
|
|
25
|
-
function validateJSONSchema(schema, prefix, report) {
|
|
26
|
-
if (schema.type === 'object') {
|
|
27
|
-
if ('properties' in schema) {
|
|
28
|
-
if (Array.isArray(schema.required)) {
|
|
29
|
-
for (const [index, name] of schema.required.entries()) {
|
|
30
|
-
if (!has(schema.properties, name)) {
|
|
31
|
-
report(name, 'is not defined in properties', [...prefix, 'required', index]);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
for (const [key, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
36
|
-
validateJSONSchema(propertySchema, [...prefix, 'properties', key], report);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
report(schema, 'is missing properties', prefix);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Validates the pages in the app definition to ensure there are no duplicate page names.
|
|
46
|
-
*
|
|
47
|
-
* @param definition The definition of the app
|
|
48
|
-
* @param report A function used to report a value.
|
|
49
|
-
*/
|
|
50
|
-
function validateUniquePageNames(definition, report) {
|
|
51
|
-
if (!definition.pages) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const pageNames = new Map();
|
|
55
|
-
function checkPages(pages, parentPath = []) {
|
|
56
|
-
for (const page of pages) {
|
|
57
|
-
const pageName = page.name;
|
|
58
|
-
const normalizedPageName = normalize(page.name);
|
|
59
|
-
const pagePath = [...parentPath, pageName];
|
|
60
|
-
if (pageNames.has(normalizedPageName)) {
|
|
61
|
-
const paths = pageNames.get(normalizedPageName);
|
|
62
|
-
paths.push(pagePath);
|
|
63
|
-
report(pageName, 'is a duplicate page name', pagePath);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
pageNames.set(normalizedPageName, [pagePath]);
|
|
67
|
-
}
|
|
68
|
-
if (page.type === 'container') {
|
|
69
|
-
checkPages(page.pages, pagePath);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
checkPages(definition.pages);
|
|
74
|
-
}
|
|
75
|
-
function validateMembersSchema(definition, report) {
|
|
76
|
-
if (!definition.members) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
for (const [propertyName, propertyDefinition] of Object.entries(definition.members.properties)) {
|
|
80
|
-
// Handled by schema validation
|
|
81
|
-
if (!propertyDefinition?.schema) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const { schema } = propertyDefinition;
|
|
85
|
-
const prefix = ['members', 'properties', propertyName, 'schema'];
|
|
86
|
-
validateJSONSchema(schema, prefix, report);
|
|
87
|
-
if (!('type' in schema) && !('enum' in schema)) {
|
|
88
|
-
report(schema, 'must define type or enum', prefix);
|
|
89
|
-
}
|
|
90
|
-
if ('reference' in propertyDefinition && propertyDefinition.reference) {
|
|
91
|
-
const { resource: resourceName } = propertyDefinition.reference;
|
|
92
|
-
const resourceDefinition = definition.resources?.[resourceName];
|
|
93
|
-
if (!resourceDefinition) {
|
|
94
|
-
report(resourceName, 'refers to a resource that doesn’t exist', [
|
|
95
|
-
'members',
|
|
96
|
-
'properties',
|
|
97
|
-
propertyName,
|
|
98
|
-
'reference',
|
|
99
|
-
resourceName,
|
|
100
|
-
]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function validateResourceSchemas(definition, report) {
|
|
106
|
-
if (!definition.resources) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
for (const [resourceName, resource] of Object.entries(definition.resources)) {
|
|
110
|
-
// Handled by schema validation
|
|
111
|
-
if (!resource?.schema) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const { enforceOrderingGroupByFields, positioning, schema } = resource;
|
|
115
|
-
const prefix = ['resources', resourceName, 'schema'];
|
|
116
|
-
if (!positioning && enforceOrderingGroupByFields?.length) {
|
|
117
|
-
report(enforceOrderingGroupByFields, 'must set positioning to true', [
|
|
118
|
-
'resources',
|
|
119
|
-
resourceName,
|
|
120
|
-
'enforceOrderingGroupByFields',
|
|
121
|
-
]);
|
|
122
|
-
}
|
|
123
|
-
if (enforceOrderingGroupByFields?.some((item) => !item.match('^[a-zA-Z0-9]*$'))) {
|
|
124
|
-
report(enforceOrderingGroupByFields, 'must be alphanumeric', [
|
|
125
|
-
'resources',
|
|
126
|
-
resourceName,
|
|
127
|
-
'enforceOrderingGroupByFields',
|
|
128
|
-
]);
|
|
129
|
-
}
|
|
130
|
-
const reservedKeywords = new Set([
|
|
131
|
-
'created',
|
|
132
|
-
'updated',
|
|
133
|
-
'author',
|
|
134
|
-
'editor',
|
|
135
|
-
'seed',
|
|
136
|
-
'ephemeral',
|
|
137
|
-
'clonable',
|
|
138
|
-
'expires',
|
|
139
|
-
]);
|
|
140
|
-
if (reservedKeywords.has(resourceName)) {
|
|
141
|
-
report(schema, 'is a reserved keyword', ['resources', resourceName]);
|
|
142
|
-
}
|
|
143
|
-
validateJSONSchema(schema, prefix, report);
|
|
144
|
-
if (!('type' in schema)) {
|
|
145
|
-
report(schema, 'must define type object', prefix);
|
|
146
|
-
}
|
|
147
|
-
else if (schema.type !== 'object') {
|
|
148
|
-
report(schema.type, 'must define type object', [...prefix, 'type']);
|
|
149
|
-
}
|
|
150
|
-
if ('properties' in schema) {
|
|
151
|
-
for (const [propertyName, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
152
|
-
if (propertyName === 'id') {
|
|
153
|
-
for (const [validatorKey, value] of Object.entries(propertySchema)) {
|
|
154
|
-
if (validatorKey === 'description' || validatorKey === 'title') {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (validatorKey === 'type') {
|
|
158
|
-
if (value !== 'integer' && value !== 'number') {
|
|
159
|
-
report(value, 'must be integer', [
|
|
160
|
-
...prefix,
|
|
161
|
-
'properties',
|
|
162
|
-
propertyName,
|
|
163
|
-
validatorKey,
|
|
164
|
-
]);
|
|
165
|
-
}
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
report(value, 'does not support custom validators', [
|
|
169
|
-
...prefix,
|
|
170
|
-
'properties',
|
|
171
|
-
propertyName,
|
|
172
|
-
validatorKey,
|
|
173
|
-
]);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else if (propertyName === '$created' || propertyName === '$updated') {
|
|
177
|
-
for (const [validatorKey, value] of Object.entries(propertySchema)) {
|
|
178
|
-
if (validatorKey === 'description' || validatorKey === 'title') {
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
if (validatorKey === 'type') {
|
|
182
|
-
if (value !== 'string') {
|
|
183
|
-
report(value, 'must be string', [
|
|
184
|
-
...prefix,
|
|
185
|
-
'properties',
|
|
186
|
-
propertyName,
|
|
187
|
-
validatorKey,
|
|
188
|
-
]);
|
|
189
|
-
}
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (validatorKey === 'format') {
|
|
193
|
-
if (value !== 'date-time') {
|
|
194
|
-
report(value, 'must be date-time', [
|
|
195
|
-
...prefix,
|
|
196
|
-
'properties',
|
|
197
|
-
propertyName,
|
|
198
|
-
validatorKey,
|
|
199
|
-
]);
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
report(value, 'does not support custom validators', [
|
|
204
|
-
...prefix,
|
|
205
|
-
'properties',
|
|
206
|
-
propertyName,
|
|
207
|
-
validatorKey,
|
|
208
|
-
]);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else if (propertyName.startsWith('$')) {
|
|
212
|
-
report(propertySchema, 'may not start with $', [...prefix, 'properties', propertyName]);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function validateController(definition, controllerImplementations, report) {
|
|
219
|
-
if (!definition.controller || !controllerImplementations) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
iterApp(definition, {
|
|
223
|
-
onController(controller, path) {
|
|
224
|
-
const actionParameters = new Set();
|
|
225
|
-
if (controller.actions) {
|
|
226
|
-
if (controllerImplementations.actions) {
|
|
227
|
-
for (const [key, action] of Object.entries(controller.actions)) {
|
|
228
|
-
if (action.type in
|
|
229
|
-
[
|
|
230
|
-
'link',
|
|
231
|
-
'link.back',
|
|
232
|
-
'link.next',
|
|
233
|
-
'dialog',
|
|
234
|
-
'dialog.ok',
|
|
235
|
-
'dialog.error',
|
|
236
|
-
'flow.back',
|
|
237
|
-
'flow.cancel',
|
|
238
|
-
'flow.finish',
|
|
239
|
-
'flow.next',
|
|
240
|
-
'flow.to',
|
|
241
|
-
]) {
|
|
242
|
-
report(action, 'cannot be used in controllers', [...path, 'actions', key]);
|
|
243
|
-
}
|
|
244
|
-
if (controllerImplementations.actions.$any) {
|
|
245
|
-
if (actionParameters.has(key)) {
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (!has(controllerImplementations.actions, key)) {
|
|
249
|
-
report(action, 'is unused', [...path, 'actions', key]);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else if (!has(controllerImplementations.actions, key)) {
|
|
253
|
-
report(action, 'is an unknown action for this controller', [...path, 'actions', key]);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
report(controller.actions, 'is not allowed on this controller', [...path, 'actions']);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (!controller.events) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (controller.events.emit) {
|
|
265
|
-
for (const [key, value] of Object.entries(controller.events.emit)) {
|
|
266
|
-
if (!controllerImplementations.events?.emit?.$any &&
|
|
267
|
-
!has(controllerImplementations.events?.emit, key)) {
|
|
268
|
-
report(value, 'is an unknown event emitter', [...path, 'events', 'emit', key]);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if (controller.events.listen) {
|
|
273
|
-
for (const [key, value] of Object.entries(controller.events.listen)) {
|
|
274
|
-
if (!controllerImplementations.events?.listen?.$any &&
|
|
275
|
-
!has(controllerImplementations.events?.listen, key)) {
|
|
276
|
-
report(value, 'is an unknown event listener', [...path, 'events', 'listen', key]);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
function validateBlocks(definition, blockVersions, report) {
|
|
284
|
-
iterApp(definition, {
|
|
285
|
-
onBlock(block, path) {
|
|
286
|
-
const type = normalizeBlockName(block.type);
|
|
287
|
-
const versions = blockVersions.get(type);
|
|
288
|
-
if (!versions) {
|
|
289
|
-
report(block.type, 'is not a known block type', [...path, 'type']);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const actionParameters = new Set();
|
|
293
|
-
const version = versions.get(block.version);
|
|
294
|
-
if (!version) {
|
|
295
|
-
report(block.version, 'is not a known version for this block type', [...path, 'version']);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (version.parameters) {
|
|
299
|
-
const validator = new Validator();
|
|
300
|
-
validator.customFormats.fontawesome = () => true;
|
|
301
|
-
validator.customFormats.remapper = () => true;
|
|
302
|
-
validator.customFormats.action = (property) => {
|
|
303
|
-
actionParameters.add(property);
|
|
304
|
-
return has(block.actions, property);
|
|
305
|
-
};
|
|
306
|
-
validator.customFormats['event-listener'] = (property) => has(block.events?.listen, property);
|
|
307
|
-
validator.customFormats['event-emitter'] = (property) => has(block.events?.emit, property);
|
|
308
|
-
const result = validator.validate(block.parameters || {}, version.parameters, {
|
|
309
|
-
nestedErrors: true,
|
|
310
|
-
});
|
|
311
|
-
if ('parameters' in block) {
|
|
312
|
-
for (const error of result.errors) {
|
|
313
|
-
report(error.instance, error.message, [...path, 'parameters', ...error.path]);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
else if (!result.valid) {
|
|
317
|
-
report(block, 'requires property "parameters"', path);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
else if (block.parameters) {
|
|
321
|
-
report(block.parameters, 'is not allowed on this block type', [...path, 'parameters']);
|
|
322
|
-
}
|
|
323
|
-
if (block.actions) {
|
|
324
|
-
if (version.actions) {
|
|
325
|
-
for (const [key, action] of Object.entries(block.actions)) {
|
|
326
|
-
if (version.actions.$any) {
|
|
327
|
-
if (actionParameters.has(key)) {
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (!has(version.actions, key) && !version.wildcardActions) {
|
|
331
|
-
report(action, 'is unused', [...path, 'actions', key]);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
else if (!has(version.actions, key)) {
|
|
335
|
-
report(action, 'is an unknown action for this block', [...path, 'actions', key]);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
report(block.actions, 'is not allowed on this block', [...path, 'actions']);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if (!block.events) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (block.events.emit) {
|
|
347
|
-
for (const [key, value] of Object.entries(block.events.emit)) {
|
|
348
|
-
if (!version.events?.emit?.$any && !has(version.events?.emit, key)) {
|
|
349
|
-
report(value, 'is an unknown event emitter', [...path, 'events', 'emit', key]);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (block.events.listen) {
|
|
354
|
-
for (const [key, value] of Object.entries(block.events.listen)) {
|
|
355
|
-
if (!version.events?.listen?.$any && !has(version.events?.listen, key)) {
|
|
356
|
-
report(value, 'is an unknown event listener', [...path, 'events', 'listen', key]);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
function validatePermissions(appDefinition, permissions, inheritedPermissions, possiblePermissions, report, path) {
|
|
364
|
-
const checked = [];
|
|
365
|
-
for (const [index, permission] of permissions.entries()) {
|
|
366
|
-
if (checked.includes(permission)) {
|
|
367
|
-
report(appDefinition, 'duplicate permission declaration', [...path, 'permissions', index]);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (!possiblePermissions.includes(permission)) {
|
|
371
|
-
if (resourcePermissionPattern.test(permission) ||
|
|
372
|
-
ownResourcePermissionPattern.test(permission)) {
|
|
373
|
-
const [, resourceName] = permission.split(':');
|
|
374
|
-
if (resourceName && resourceName !== 'all' && !appDefinition.resources?.[resourceName]) {
|
|
375
|
-
report(appDefinition, `resource ${resourceName} does not exist in the app's resources definition`, [...path, 'permissions', index]);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (resourceViewPermissionPattern.test(permission)) {
|
|
380
|
-
const [, resourceName, , resourceView] = permission.split(':');
|
|
381
|
-
if (resourceName === 'all') {
|
|
382
|
-
for (const [rName, resourceDefinition] of Object.entries(appDefinition.resources ?? {})) {
|
|
383
|
-
if (!resourceDefinition.views?.[resourceView]) {
|
|
384
|
-
report(appDefinition, `resource ${rName} is missing a definition for the ${resourceView} view`, [...path, 'permissions', index]);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
if (!appDefinition.resources?.[resourceName]?.views?.[resourceView]) {
|
|
391
|
-
report(appDefinition, `resource ${resourceName} is missing a definition for the ${resourceView} view`, [...path, 'permissions', index]);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
report(appDefinition, 'invalid permission', [...path, 'permissions', index]);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
if (inheritedPermissions.includes(permission)) {
|
|
400
|
-
report(appDefinition, 'permission is already inherited from another role', [
|
|
401
|
-
...path,
|
|
402
|
-
'permissions',
|
|
403
|
-
index,
|
|
404
|
-
]);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const otherPermissions = permissions.filter((p) => p !== permission);
|
|
408
|
-
if (resourcePermissionPattern.test(permission)) {
|
|
409
|
-
const [, , resourceAction] = permission.split(':');
|
|
410
|
-
if (otherPermissions.some((p) => {
|
|
411
|
-
if (allResourcePermissionPattern.test(p)) {
|
|
412
|
-
const [, , otherResourceAction] = p.split(':');
|
|
413
|
-
return otherResourceAction === resourceAction;
|
|
414
|
-
}
|
|
415
|
-
return false;
|
|
416
|
-
})) {
|
|
417
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all is already declared`, [...path, 'permissions', index]);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (inheritedPermissions.some((p) => {
|
|
421
|
-
if (allResourcePermissionPattern.test(p)) {
|
|
422
|
-
const [, , otherResourceAction] = p.split(':');
|
|
423
|
-
return otherResourceAction === resourceAction;
|
|
424
|
-
}
|
|
425
|
-
return false;
|
|
426
|
-
})) {
|
|
427
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all is already inherited from another role`, [...path, 'permissions', index]);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (ownResourcePermissionPattern.test(permission)) {
|
|
432
|
-
const [, resourceName, , resourceAction] = permission.split(':');
|
|
433
|
-
if (otherPermissions.some((p) => {
|
|
434
|
-
if (resourcePermissionPattern.test(p)) {
|
|
435
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
436
|
-
return (resourceName !== 'all' &&
|
|
437
|
-
otherResourceName === resourceName &&
|
|
438
|
-
otherResourceAction === resourceAction);
|
|
439
|
-
}
|
|
440
|
-
return false;
|
|
441
|
-
})) {
|
|
442
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action on resource ${resourceName} is already declared`, [...path, 'permissions', index]);
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
if (otherPermissions.some((p) => {
|
|
446
|
-
if (allOwnResourcePermissionPattern.test(p)) {
|
|
447
|
-
const [, , , otherResourceAction] = p.split(':');
|
|
448
|
-
return otherResourceAction === resourceAction;
|
|
449
|
-
}
|
|
450
|
-
return false;
|
|
451
|
-
})) {
|
|
452
|
-
report(appDefinition, `redundant permission. An own permission for the ${resourceAction} resource action with scope all is already declared`, [...path, 'permissions', index]);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (otherPermissions.some((p) => {
|
|
456
|
-
if (allResourcePermissionPattern.test(p)) {
|
|
457
|
-
const [, , otherResourceAction] = p.split(':');
|
|
458
|
-
return otherResourceAction === resourceAction;
|
|
459
|
-
}
|
|
460
|
-
return false;
|
|
461
|
-
})) {
|
|
462
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all is already declared`, [...path, 'permissions', index]);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
if (inheritedPermissions.some((p) => {
|
|
466
|
-
if (resourcePermissionPattern.test(p)) {
|
|
467
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
468
|
-
return (resourceName !== 'all' &&
|
|
469
|
-
otherResourceName === resourceName &&
|
|
470
|
-
otherResourceAction === resourceAction);
|
|
471
|
-
}
|
|
472
|
-
return false;
|
|
473
|
-
})) {
|
|
474
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action on resource ${resourceName} is already inherited from another role`, [...path, 'permissions', index]);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
if (inheritedPermissions.some((p) => {
|
|
478
|
-
if (allOwnResourcePermissionPattern.test(p)) {
|
|
479
|
-
const [, , , otherResourceAction] = p.split(':');
|
|
480
|
-
return otherResourceAction === resourceAction;
|
|
481
|
-
}
|
|
482
|
-
return false;
|
|
483
|
-
})) {
|
|
484
|
-
report(appDefinition, `redundant permission. An own permission for the ${resourceAction} resource action with scope all is already inherited from another role`, [...path, 'permissions', index]);
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
if (inheritedPermissions.some((p) => {
|
|
488
|
-
if (allResourcePermissionPattern.test(p)) {
|
|
489
|
-
const [, , otherResourceAction] = p.split(':');
|
|
490
|
-
return otherResourceAction === resourceAction;
|
|
491
|
-
}
|
|
492
|
-
return false;
|
|
493
|
-
})) {
|
|
494
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all is already inherited from another role`, [...path, 'permissions', index]);
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
if (resourceViewPermissionPattern.test(permission)) {
|
|
499
|
-
const [, resourceName, resourceAction, resourceView] = permission.split(':');
|
|
500
|
-
// $resource:type:query:public, $resource:type:query:private
|
|
501
|
-
if (otherPermissions.some((p) => {
|
|
502
|
-
if (resourceViewPermissionPattern.test(p)) {
|
|
503
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
504
|
-
return (otherResourceName !== 'all' &&
|
|
505
|
-
otherResourceName === resourceName &&
|
|
506
|
-
otherResourceAction === resourceAction);
|
|
507
|
-
}
|
|
508
|
-
return false;
|
|
509
|
-
})) {
|
|
510
|
-
report(appDefinition, `a view permission for the ${resourceAction} action on resource ${resourceName} is already declared`, [...path, 'permissions', index]);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
// $resource:type:query:public, $resource:all:query:private
|
|
514
|
-
if (otherPermissions.some((p) => {
|
|
515
|
-
if (allResourceViewPermissionPattern.test(p)) {
|
|
516
|
-
const [, , otherResourceAction, otherResourceView] = p.split(':');
|
|
517
|
-
return otherResourceAction === resourceAction && otherResourceView !== resourceView;
|
|
518
|
-
}
|
|
519
|
-
return false;
|
|
520
|
-
})) {
|
|
521
|
-
report(appDefinition, `a view permission for the ${resourceAction} action with scope all is already declared`, [...path, 'permissions', index]);
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
// $resource:type:query:public, $resource:type:query
|
|
525
|
-
if (otherPermissions.some((p) => {
|
|
526
|
-
if (resourcePermissionPattern.test(p)) {
|
|
527
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
528
|
-
return otherResourceName === resourceName && otherResourceAction === resourceAction;
|
|
529
|
-
}
|
|
530
|
-
return false;
|
|
531
|
-
})) {
|
|
532
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} action on resource ${resourceName} without a specific view is already declared`, [...path, 'permissions', index]);
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
// $resource:type:query:public, $resource:all:query
|
|
536
|
-
if (otherPermissions.some((p) => {
|
|
537
|
-
if (resourcePermissionPattern.test(p)) {
|
|
538
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
539
|
-
return otherResourceName === 'all' && otherResourceAction === resourceAction;
|
|
540
|
-
}
|
|
541
|
-
return false;
|
|
542
|
-
})) {
|
|
543
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all without a specific view is already declared`, [...path, 'permissions', index]);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
// $resource:type:query:public, $resource:all:query:public
|
|
547
|
-
if (otherPermissions.some((p) => {
|
|
548
|
-
if (allResourceViewPermissionPattern.test(p)) {
|
|
549
|
-
const [, otherResourceName, otherResourceAction, otherResourceView] = p.split(':');
|
|
550
|
-
return (otherResourceName === 'all' &&
|
|
551
|
-
otherResourceAction === resourceAction &&
|
|
552
|
-
otherResourceView === resourceView);
|
|
553
|
-
}
|
|
554
|
-
return false;
|
|
555
|
-
})) {
|
|
556
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all for this view is already declared`, [...path, 'permissions', index]);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
// $resource:type:query:private
|
|
560
|
-
// $resource:type:query:public
|
|
561
|
-
if (inheritedPermissions.some((p) => {
|
|
562
|
-
if (resourceViewPermissionPattern.test(p)) {
|
|
563
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
564
|
-
return (otherResourceName !== 'all' &&
|
|
565
|
-
otherResourceName === resourceName &&
|
|
566
|
-
otherResourceAction === resourceAction);
|
|
567
|
-
}
|
|
568
|
-
return false;
|
|
569
|
-
})) {
|
|
570
|
-
report(appDefinition, `a view permission for the ${resourceAction} action on resource ${resourceName} is already inherited from another role`, [...path, 'permissions', index]);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
// $resource:all:query:private
|
|
574
|
-
// $resource:type:query:public
|
|
575
|
-
if (inheritedPermissions.some((p) => {
|
|
576
|
-
if (allResourceViewPermissionPattern.test(p)) {
|
|
577
|
-
const [, , otherResourceAction, otherResourceView] = p.split(':');
|
|
578
|
-
return otherResourceAction === resourceAction && otherResourceView !== resourceView;
|
|
579
|
-
}
|
|
580
|
-
return false;
|
|
581
|
-
})) {
|
|
582
|
-
report(appDefinition, `a view permission for the ${resourceAction} action with scope all is already inherited from another role`, [...path, 'permissions', index]);
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
// $resource:type:query
|
|
586
|
-
// $resource:type:query:public
|
|
587
|
-
if (inheritedPermissions.some((p) => {
|
|
588
|
-
if (resourcePermissionPattern.test(p)) {
|
|
589
|
-
const [, otherResourceName, otherResourceAction] = p.split(':');
|
|
590
|
-
return otherResourceName === resourceName && otherResourceAction === resourceAction;
|
|
591
|
-
}
|
|
592
|
-
return false;
|
|
593
|
-
})) {
|
|
594
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} action on resource ${resourceName} without a specific view is already inherited from another role`, [...path, 'permissions', index]);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
// $resource:all:query
|
|
598
|
-
// $resource:type:query:public
|
|
599
|
-
if (inheritedPermissions.some((p) => {
|
|
600
|
-
if (allResourcePermissionPattern.test(p)) {
|
|
601
|
-
const [, , otherResourceAction] = p.split(':');
|
|
602
|
-
return otherResourceAction === resourceAction;
|
|
603
|
-
}
|
|
604
|
-
return false;
|
|
605
|
-
})) {
|
|
606
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all without a specific view is already inherited from another role`, [...path, 'permissions', index]);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
// $resource:all:query:public
|
|
610
|
-
// $resource:type:query:public
|
|
611
|
-
if (inheritedPermissions.some((p) => {
|
|
612
|
-
if (allResourceViewPermissionPattern.test(p)) {
|
|
613
|
-
const [, , otherResourceAction, otherResourceView] = p.split(':');
|
|
614
|
-
return otherResourceAction === resourceAction && otherResourceView === resourceView;
|
|
615
|
-
}
|
|
616
|
-
return false;
|
|
617
|
-
})) {
|
|
618
|
-
report(appDefinition, `redundant permission. A permission for the ${resourceAction} resource action with scope all for this view is already inherited from another role`, [...path, 'permissions', index]);
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
checked.push(permission);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
function checkCyclicRoleInheritance(roles, name, report) {
|
|
626
|
-
let lastChecked;
|
|
627
|
-
const stack = [];
|
|
628
|
-
const checkRoleRecursively = (role) => {
|
|
629
|
-
lastChecked = role;
|
|
630
|
-
if (stack.includes(role)) {
|
|
631
|
-
return true;
|
|
632
|
-
}
|
|
633
|
-
stack.push(role);
|
|
634
|
-
return Boolean(roles[role]?.inherits?.some(checkRoleRecursively));
|
|
635
|
-
};
|
|
636
|
-
const duplicate = checkRoleRecursively(name);
|
|
637
|
-
if (duplicate && lastChecked === name) {
|
|
638
|
-
report(roles[name], 'cyclically inherits itself', ['security', 'roles', name]);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Validate security related definitions within the app definition.
|
|
643
|
-
*
|
|
644
|
-
* @param definition The definition of the app
|
|
645
|
-
* @param report A function used to report a value.
|
|
646
|
-
*/
|
|
647
|
-
function validateSecurity(definition, report) {
|
|
648
|
-
const { notifications, security } = definition;
|
|
649
|
-
const predefinedRoles = Object.keys(PredefinedAppRole);
|
|
650
|
-
const checkRoleExists = (name, path, roles = predefinedRoles) => {
|
|
651
|
-
if (!has(security?.roles, name) && !roles.includes(name)) {
|
|
652
|
-
report(name, 'does not exist in this app’s roles', path);
|
|
653
|
-
return false;
|
|
654
|
-
}
|
|
655
|
-
return true;
|
|
656
|
-
};
|
|
657
|
-
const checkRoles = (object, path) => {
|
|
658
|
-
if (!object?.roles) {
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
for (const [index, role] of object.roles.entries()) {
|
|
662
|
-
checkRoleExists(role, [...path, 'roles', index], ['$guest', ...predefinedRoles]);
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
if (!security) {
|
|
666
|
-
if (notifications === 'login') {
|
|
667
|
-
report(notifications, 'only works if security is defined', ['notifications']);
|
|
668
|
-
}
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
if ((!security.default || !security.roles) && !security.guest && !security.cron) {
|
|
672
|
-
report(definition, 'invalid security definition. Must define either guest or cron or roles and default', ['security']);
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
if (security.guest) {
|
|
676
|
-
if (security.guest.inherits && security.guest.inherits.length && !security.roles) {
|
|
677
|
-
report(definition, 'guest can not inherit roles if the roles property is not defined', [
|
|
678
|
-
'security',
|
|
679
|
-
'guest',
|
|
680
|
-
'inherits',
|
|
681
|
-
]);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
const inheritedPermissions = getAppRolePermissions(security, security.guest.inherits || []);
|
|
685
|
-
const possibleGuestPermissions = getAppPossibleGuestPermissions(definition);
|
|
686
|
-
if (inheritedPermissions.some((ip) => !possibleGuestPermissions.includes(ip))) {
|
|
687
|
-
report(definition, 'invalid security definition. Guest cannot inherit roles that contain own resource permissions', ['security', 'guest', 'inherits']);
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
if (security.guest.permissions) {
|
|
691
|
-
validatePermissions(definition, security.guest.permissions, inheritedPermissions, possibleGuestPermissions, report, ['security', 'guest']);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
else if (security.cron) {
|
|
695
|
-
if (!definition.cron) {
|
|
696
|
-
report(definition, 'can not define cron definition without a cron job', ['security', 'cron']);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
if (security.cron.inherits && security.cron.inherits.length && !security.roles) {
|
|
700
|
-
report(definition, 'cron can not inherit roles if the roles property is not defined', [
|
|
701
|
-
'security',
|
|
702
|
-
'cron',
|
|
703
|
-
'inherits',
|
|
704
|
-
]);
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
const inheritedPermissions = getAppRolePermissions(security, security.cron.inherits || []);
|
|
708
|
-
const possibleCronPermissions = getAppPossibleGuestPermissions(definition);
|
|
709
|
-
if (inheritedPermissions.some((ip) => !possibleCronPermissions.includes(ip))) {
|
|
710
|
-
report(definition, 'invalid security definition. Guest cannot inherit roles that contain own resource permissions', ['security', 'cron', 'inherits']);
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
if (security.cron.permissions) {
|
|
714
|
-
validatePermissions(definition, security.cron.permissions, inheritedPermissions, possibleCronPermissions, report, ['security', 'cron']);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
719
|
-
// @ts-ignore 18048 variable is possibly undefined (strictNullChecks)
|
|
720
|
-
checkRoleExists(security.default.role, ['security', 'default', 'role']);
|
|
721
|
-
}
|
|
722
|
-
if (security.roles) {
|
|
723
|
-
const possibleAppPermissions = getAppPossiblePermissions(definition);
|
|
724
|
-
for (const [name, role] of Object.entries(security.roles)) {
|
|
725
|
-
if ([...predefinedRoles, 'cron'].includes(name)) {
|
|
726
|
-
report(definition, `not allowed to overwrite role ${name}`, ['security', 'roles', name]);
|
|
727
|
-
}
|
|
728
|
-
const inheritedPermissions = [];
|
|
729
|
-
if (role?.inherits) {
|
|
730
|
-
let found = false;
|
|
731
|
-
for (const [index, inherited] of (role.inherits || []).entries()) {
|
|
732
|
-
found || (found = checkRoleExists(inherited, ['security', 'roles', name, 'inherits', index]));
|
|
733
|
-
}
|
|
734
|
-
if (found) {
|
|
735
|
-
checkCyclicRoleInheritance(security.roles, name, report);
|
|
736
|
-
}
|
|
737
|
-
const inheritedRoles = getAppInheritedRoles(security, [name]).filter((r) => r !== name);
|
|
738
|
-
for (const inheritedRole of inheritedRoles) {
|
|
739
|
-
const roleDefinition = security.roles[inheritedRole];
|
|
740
|
-
if (roleDefinition) {
|
|
741
|
-
const rolePermissions = roleDefinition.permissions;
|
|
742
|
-
if (rolePermissions) {
|
|
743
|
-
inheritedPermissions.push(...rolePermissions);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
else {
|
|
747
|
-
const predefinedRolePermissions = predefinedAppRolePermissions[inheritedRole];
|
|
748
|
-
if (predefinedRolePermissions) {
|
|
749
|
-
inheritedPermissions.push(...predefinedRolePermissions);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
if (role.permissions) {
|
|
755
|
-
validatePermissions(definition, role.permissions, inheritedPermissions, possibleAppPermissions, report, ['security', 'roles', name]);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
iterApp(definition, { onBlock: checkRoles, onPage: checkRoles });
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Validates the hooks in resource definition to ensure its properties are valid.
|
|
763
|
-
*
|
|
764
|
-
* @param definition The definition of the app
|
|
765
|
-
* @param report A function used to report a value.
|
|
766
|
-
*/
|
|
767
|
-
function validateHooks(definition, report) {
|
|
768
|
-
if (!definition.resources) {
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
const actionTypes = ['create', 'update', 'delete'];
|
|
772
|
-
for (const [resourceKey, resource] of Object.entries(definition.resources)) {
|
|
773
|
-
for (const actionType of actionTypes) {
|
|
774
|
-
if (!has(resource, actionType)) {
|
|
775
|
-
continue;
|
|
776
|
-
}
|
|
777
|
-
const tos = resource[actionType]?.hooks?.notification?.to;
|
|
778
|
-
if (tos) {
|
|
779
|
-
for (const [index, to] of tos.entries()) {
|
|
780
|
-
if (to !== '$author' && !has(definition.security?.roles, to)) {
|
|
781
|
-
report(to, 'is an unknown role', [
|
|
782
|
-
'resources',
|
|
783
|
-
resourceKey,
|
|
784
|
-
actionType,
|
|
785
|
-
'hooks',
|
|
786
|
-
'notifications',
|
|
787
|
-
'to',
|
|
788
|
-
index,
|
|
789
|
-
]);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
function validateResourceReferences(definition, report) {
|
|
797
|
-
if (!definition.resources) {
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
for (const [resourceType, resource] of Object.entries(definition.resources)) {
|
|
801
|
-
if (!resource.references) {
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
for (const [field, reference] of Object.entries(resource.references)) {
|
|
805
|
-
if (!has(definition.resources, reference.resource)) {
|
|
806
|
-
report(reference.resource, 'is not an existing resource', [
|
|
807
|
-
'resources',
|
|
808
|
-
resourceType,
|
|
809
|
-
'references',
|
|
810
|
-
field,
|
|
811
|
-
'resource',
|
|
812
|
-
]);
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
if (!has(resource.schema.properties, field)) {
|
|
816
|
-
report(field, 'does not exist on this resource', [
|
|
817
|
-
'resources',
|
|
818
|
-
resourceType,
|
|
819
|
-
'references',
|
|
820
|
-
field,
|
|
821
|
-
]);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
function validateLanguage({ defaultLanguage }, report) {
|
|
827
|
-
if (defaultLanguage != null && !languageTags.check(defaultLanguage)) {
|
|
828
|
-
report(defaultLanguage, 'is not a valid language code', ['defaultLanguage']);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
function validateDefaultPage({ defaultPage, pages }, report) {
|
|
832
|
-
const page = findPageByName(pages, defaultPage);
|
|
833
|
-
if (!page) {
|
|
834
|
-
report(defaultPage, 'does not refer to an existing page', ['defaultPage']);
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (page.parameters) {
|
|
838
|
-
report(defaultPage, 'may not specify parameters', ['defaultPage']);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
function validateCronJobs({ cron }, report) {
|
|
842
|
-
if (!cron) {
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
for (const [id, job] of Object.entries(cron)) {
|
|
846
|
-
if (typeof job?.schedule !== 'string') {
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
try {
|
|
850
|
-
cronParser.parseExpression(job.schedule);
|
|
851
|
-
}
|
|
852
|
-
catch {
|
|
853
|
-
report(job.schedule, 'contains an invalid expression', ['cron', id, 'schedule']);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
function validateActions(definition, report) {
|
|
858
|
-
const urlRegex = new RegExp(`^${partialNormalized.source}:`);
|
|
859
|
-
iterApp(definition, {
|
|
860
|
-
onAction(action, path) {
|
|
861
|
-
if (path[0] === 'cron' && !serverActions.has(action.type)) {
|
|
862
|
-
report(action.type, 'action type is not supported for cron jobs', [...path, 'type']);
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
if (path[0] === 'webhooks' && !serverActions.has(action.type)) {
|
|
866
|
-
report(action.type, 'action type is not supported for webhooks', [...path, 'type']);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (action.type.startsWith('app.member.') && !definition.security) {
|
|
870
|
-
report(action.type, 'refers to an app member action but the app doesn’t have a security definition', [...path, 'type']);
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
if (['app.member.register', 'app.member.properties.patch', 'app.member.current.patch'].includes(action.type) &&
|
|
874
|
-
Object.values(action.properties ?? {})[0] &&
|
|
875
|
-
definition.members?.properties) {
|
|
876
|
-
for (const propertyName of Object.keys(Object.values(action.properties ?? {})[0])) {
|
|
877
|
-
if (!definition.members?.properties[propertyName]) {
|
|
878
|
-
report(action.type, 'contains a property that doesn’t exist in app member properties', [
|
|
879
|
-
...path,
|
|
880
|
-
'properties',
|
|
881
|
-
]);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
if (action.type.startsWith('resource.')) {
|
|
886
|
-
// All of the actions starting with `resource.` contain a property called `resource`.
|
|
887
|
-
const { resource: resourceName, view } = action;
|
|
888
|
-
const resource = definition.resources?.[resourceName];
|
|
889
|
-
const [, resourceAction] = action.type.split('.');
|
|
890
|
-
if (!resource) {
|
|
891
|
-
report(action.type, 'refers to a resource that doesn’t exist', [...path, 'resource']);
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
if (!action.type.startsWith('resource.subscription.')) {
|
|
895
|
-
if (!definition.security) {
|
|
896
|
-
report(action.type, 'missing security definition', [...path, 'resource']);
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
const allPermissions = definition.security.guest?.permissions || [];
|
|
900
|
-
if (definition.security.roles) {
|
|
901
|
-
const allRolePermissions = getAppRolePermissions(definition.security, Object.keys(definition.security.roles));
|
|
902
|
-
allPermissions.push(...allRolePermissions);
|
|
903
|
-
}
|
|
904
|
-
if (!allPermissions.some((permission) => {
|
|
905
|
-
if (resourcePermissionPattern.test(permission)) {
|
|
906
|
-
const [, permissionResourceName, permissionResourceAction] = permission.split(':');
|
|
907
|
-
return (['all', resourceName].includes(permissionResourceName) &&
|
|
908
|
-
(permissionResourceAction === resourceAction ||
|
|
909
|
-
(resourceAction === 'count' && permissionResourceAction === 'query')));
|
|
910
|
-
}
|
|
911
|
-
if (ownResourcePermissionPattern.test(permission)) {
|
|
912
|
-
const [, permissionResourceName, , permissionResourceAction] = permission.split(':');
|
|
913
|
-
return (['all', resourceName].includes(permissionResourceName) &&
|
|
914
|
-
(permissionResourceAction === resourceAction ||
|
|
915
|
-
(resourceAction === 'count' && permissionResourceAction === 'query')));
|
|
916
|
-
}
|
|
917
|
-
return false;
|
|
918
|
-
})) {
|
|
919
|
-
report(action.type, 'there is no-one in the app, who has permissions to use this action', [...path, 'resource']);
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
if (view &&
|
|
923
|
-
!allPermissions.some((permission) => {
|
|
924
|
-
if (resourceViewPermissionPattern.test(permission)) {
|
|
925
|
-
const [, permissionResourceName, permissionResourceAction, permissionResourceView] = permission.split(':');
|
|
926
|
-
return (['all', resourceName].includes(permissionResourceName) &&
|
|
927
|
-
permissionResourceAction === resourceAction &&
|
|
928
|
-
(!permissionResourceView || permissionResourceView === view));
|
|
929
|
-
}
|
|
930
|
-
return false;
|
|
931
|
-
})) {
|
|
932
|
-
report(action.type, 'there is no-one in the app, who has permissions to use this action', [...path, 'resource']);
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
if (action.type.startsWith('flow.')) {
|
|
938
|
-
const page = definition.pages?.[Number(path[1])];
|
|
939
|
-
if (page.type !== 'flow' && page.type !== 'loop') {
|
|
940
|
-
report(action.type, 'flow actions can only be used on pages with the type ‘flow’ or ‘loop’', [...path, 'type']);
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
if (action.type === 'flow.cancel' && !page.actions?.onFlowCancel) {
|
|
944
|
-
report(action.type, 'was defined but ‘onFlowCancel’ page action wasn’t defined', [
|
|
945
|
-
...path,
|
|
946
|
-
'type',
|
|
947
|
-
]);
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
if (action.type === 'flow.finish' && !page.actions?.onFlowFinish) {
|
|
951
|
-
report(action.type, 'was defined but ‘onFlowFinish’ page action wasn’t defined', [
|
|
952
|
-
...path,
|
|
953
|
-
'type',
|
|
954
|
-
]);
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
if (action.type === 'flow.back' && path[3] === 0) {
|
|
958
|
-
report(action.type, 'is not allowed on the first step in the flow', [...path, 'type']);
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
if (page.type === 'flow' &&
|
|
962
|
-
action.type === 'flow.next' &&
|
|
963
|
-
Number(path[3]) === page.steps.length - 1 &&
|
|
964
|
-
!page.actions?.onFlowFinish) {
|
|
965
|
-
report(action.type, 'was defined on the last step but ‘onFlowFinish’ page action wasn’t defined', [...path, 'type']);
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
if (page.type === 'flow' &&
|
|
969
|
-
action.type === 'flow.to' &&
|
|
970
|
-
!page.steps.some((step) => step.name === action.step)) {
|
|
971
|
-
report(action.type, 'refers to a step that doesn’t exist', [...path, 'step']);
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
if (action.type === 'link') {
|
|
976
|
-
const { to } = action;
|
|
977
|
-
if (typeof to === 'object' &&
|
|
978
|
-
(!Array.isArray(to) ||
|
|
979
|
-
(Array.isArray(to) && to.every((entry) => typeof entry === 'object')))) {
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
if (typeof to === 'string' && urlRegex.test(to)) {
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
if (isAppLink(to)) {
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
const [toBase, toSub] = [].concat(to);
|
|
989
|
-
// @ts-expect-error 2345 argument of type is not assignable to parameter of type
|
|
990
|
-
// (strictNullChecks) - Severe
|
|
991
|
-
const toPage = findPageByName(definition.pages, toBase);
|
|
992
|
-
if (!toPage) {
|
|
993
|
-
report(to, 'refers to a page that doesn’t exist', [...path, 'to']);
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
if (toPage.type !== 'tabs' && toSub) {
|
|
997
|
-
report(toSub, 'refers to a sub page on a page that isn’t of type ‘tabs’ or ‘flow’', [
|
|
998
|
-
...path,
|
|
999
|
-
'to',
|
|
1000
|
-
1,
|
|
1001
|
-
]);
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
if (toPage.type === 'tabs' && toSub && Array.isArray(toPage.tabs)) {
|
|
1005
|
-
const subPage = toPage.tabs.find(({ name }) => name === toSub);
|
|
1006
|
-
if (!subPage) {
|
|
1007
|
-
report(toSub, 'refers to a tab that doesn’t exist', [...path, 'to', 1]);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
},
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
function validateEvents(definition, blockVersions, report) {
|
|
1015
|
-
const indexMap = new Map();
|
|
1016
|
-
function collect(prefix, name, isEmitter) {
|
|
1017
|
-
const [firstKey, pageIndex] = prefix;
|
|
1018
|
-
let mapAtKey;
|
|
1019
|
-
// Ignore anything not part of controller or a page.
|
|
1020
|
-
// For example cron actions never support events.
|
|
1021
|
-
switch (firstKey) {
|
|
1022
|
-
case 'controller':
|
|
1023
|
-
if (!indexMap.has('controller')) {
|
|
1024
|
-
indexMap.set('controller', { emitters: new Map(), listeners: new Map() });
|
|
1025
|
-
}
|
|
1026
|
-
mapAtKey = indexMap.get('controller');
|
|
1027
|
-
break;
|
|
1028
|
-
case 'pages':
|
|
1029
|
-
if (typeof pageIndex !== 'number') {
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1032
|
-
if (!indexMap.has(pageIndex)) {
|
|
1033
|
-
indexMap.set(pageIndex, { emitters: new Map(), listeners: new Map() });
|
|
1034
|
-
}
|
|
1035
|
-
mapAtKey = indexMap.get(pageIndex);
|
|
1036
|
-
break;
|
|
1037
|
-
default:
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
const { emitters, listeners } = mapAtKey;
|
|
1041
|
-
const context = isEmitter ? emitters : listeners;
|
|
1042
|
-
if (!context.has(name)) {
|
|
1043
|
-
context.set(name, []);
|
|
1044
|
-
}
|
|
1045
|
-
const prefixes = context.get(name);
|
|
1046
|
-
prefixes.push(prefix);
|
|
1047
|
-
}
|
|
1048
|
-
iterApp(definition, {
|
|
1049
|
-
onController(controller, path) {
|
|
1050
|
-
if (!controller.events) {
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
if (controller.events.emit) {
|
|
1054
|
-
for (const [prefix, name] of Object.entries(controller.events.emit)) {
|
|
1055
|
-
collect([...path, 'events', 'emit', prefix], name, true);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
if (controller.events.listen) {
|
|
1059
|
-
for (const [prefix, name] of Object.entries(controller.events.listen)) {
|
|
1060
|
-
collect([...path, 'events', 'listen', prefix], name, false);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
},
|
|
1064
|
-
onAction(action, path) {
|
|
1065
|
-
if (action.type === 'dialog') {
|
|
1066
|
-
for (const block of action.blocks) {
|
|
1067
|
-
const versions = blockVersions.get(normalizeBlockName(block.type));
|
|
1068
|
-
const version = versions?.get(block.version);
|
|
1069
|
-
if (version?.layout === 'float') {
|
|
1070
|
-
report(block.version, 'block with layout type: "'
|
|
1071
|
-
.concat(version.layout)
|
|
1072
|
-
.concat('" is not allowed in a dialog action'), [...path, 'type']);
|
|
1073
|
-
}
|
|
1074
|
-
if (block.layout === 'float') {
|
|
1075
|
-
report(block, 'block with layout type: "'
|
|
1076
|
-
.concat(block.layout)
|
|
1077
|
-
.concat('" is not allowed in a dialog action'), [...path, 'type']);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
return;
|
|
1081
|
-
}
|
|
1082
|
-
if (action.type !== 'event') {
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
collect([...path, 'event'], action.event, true);
|
|
1086
|
-
if ('waitFor' in action && action.waitFor) {
|
|
1087
|
-
collect([...path, 'waitFor'], action.waitFor, false);
|
|
1088
|
-
}
|
|
1089
|
-
},
|
|
1090
|
-
onPage(page, path) {
|
|
1091
|
-
if (!(page.type === 'tabs') || page.tabs) {
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
|
-
if (page.definition?.events.emit) {
|
|
1095
|
-
for (const [prefix, name] of Object.entries(page.definition.events.emit)) {
|
|
1096
|
-
collect([...path, 'events', 'emit', prefix], name, true);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
if (page.definition?.events.listen) {
|
|
1100
|
-
for (const [prefix, name] of Object.entries(page.definition.events.listen)) {
|
|
1101
|
-
collect([...path, 'events', 'listen', prefix], name, false);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
},
|
|
1105
|
-
onBlock(block, path) {
|
|
1106
|
-
if (!block.events) {
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
if (block.events.emit) {
|
|
1110
|
-
for (const [prefix, name] of Object.entries(block.events.emit)) {
|
|
1111
|
-
collect([...path, 'events', 'emit', prefix], name, true);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
if (block.events.listen) {
|
|
1115
|
-
for (const [prefix, name] of Object.entries(block.events.listen)) {
|
|
1116
|
-
collect([...path, 'events', 'listen', prefix], name, false);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
},
|
|
1120
|
-
});
|
|
1121
|
-
let controllerEvents = {
|
|
1122
|
-
emitters: new Map(),
|
|
1123
|
-
listeners: new Map(),
|
|
1124
|
-
};
|
|
1125
|
-
if (indexMap.has('controller')) {
|
|
1126
|
-
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
1127
|
-
// @ts-ignore 2322 null is not assignable to type (strictNullChecks)
|
|
1128
|
-
controllerEvents = { ...indexMap.get('controller') };
|
|
1129
|
-
}
|
|
1130
|
-
indexMap.delete('controller');
|
|
1131
|
-
for (const [name, prefixes] of controllerEvents.emitters.entries()) {
|
|
1132
|
-
let found = false;
|
|
1133
|
-
for (const { listeners } of indexMap.values()) {
|
|
1134
|
-
if (listeners.has(name)) {
|
|
1135
|
-
found = true;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
if (!found) {
|
|
1139
|
-
for (const prefix of prefixes) {
|
|
1140
|
-
report(name, 'does not match any listeners', prefix);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
for (const [name, prefixes] of controllerEvents.listeners.entries()) {
|
|
1145
|
-
let found = false;
|
|
1146
|
-
for (const { emitters } of indexMap.values()) {
|
|
1147
|
-
if (emitters.has(name)) {
|
|
1148
|
-
found = true;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
if (!found) {
|
|
1152
|
-
for (const prefix of prefixes) {
|
|
1153
|
-
report(name, 'does not match any emitters', prefix);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
for (const { emitters, listeners } of indexMap.values()) {
|
|
1158
|
-
for (const [name, prefixes] of listeners.entries()) {
|
|
1159
|
-
if (!emitters.has(name) && !controllerEvents.emitters.has(name)) {
|
|
1160
|
-
for (const prefix of prefixes) {
|
|
1161
|
-
report(name, 'does not match any event emitters', prefix);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
for (const [name, prefixes] of emitters.entries()) {
|
|
1166
|
-
if (!listeners.has(name) && !controllerEvents.listeners.has(name)) {
|
|
1167
|
-
for (const prefix of prefixes) {
|
|
1168
|
-
report(name, 'does not match any event listeners', prefix);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
/**
|
|
1175
|
-
* Validate an app definition.
|
|
1176
|
-
*
|
|
1177
|
-
* This check various conditions which can’t be validated using basic JSON schema validation.
|
|
1178
|
-
*
|
|
1179
|
-
* @param definition The app validation to check.
|
|
1180
|
-
* @param getBlockVersions A function for getting block manifests from block versions.
|
|
1181
|
-
* @param controllerImplementations App controller implementations of interfaces.
|
|
1182
|
-
* @param validatorResult If specified, error messages will be applied to this existing validator
|
|
1183
|
-
* result.
|
|
1184
|
-
* @returns A validator result which contains all app validation violations.
|
|
1185
|
-
*/
|
|
1186
|
-
export async function validateAppDefinition(definition, getBlockVersions, controllerImplementations, validatorResult) {
|
|
1187
|
-
let result = validatorResult;
|
|
1188
|
-
if (!result) {
|
|
1189
|
-
const validator = new Validator();
|
|
1190
|
-
result = validator.validate(definition, {});
|
|
1191
|
-
}
|
|
1192
|
-
if (!definition) {
|
|
1193
|
-
result.addError('App definition can not be null');
|
|
1194
|
-
return result;
|
|
1195
|
-
}
|
|
1196
|
-
const blocks = getAppBlocks(definition);
|
|
1197
|
-
const blockVersions = await getBlockVersions(blocks);
|
|
1198
|
-
const blockVersionMap = new Map();
|
|
1199
|
-
for (const version of blockVersions) {
|
|
1200
|
-
if (!blockVersionMap.has(version.name)) {
|
|
1201
|
-
blockVersionMap.set(version.name, new Map());
|
|
1202
|
-
}
|
|
1203
|
-
blockVersionMap.get(version.name).set(version.version, version);
|
|
1204
|
-
}
|
|
1205
|
-
const report = (instance, message, path) => {
|
|
1206
|
-
result.errors.push(new ValidationError(message, instance, undefined, path));
|
|
1207
|
-
};
|
|
1208
|
-
try {
|
|
1209
|
-
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
|
1210
|
-
// @ts-ignore 2345 argument of type is not assignable to parameter of type
|
|
1211
|
-
// (strictNullChecks)
|
|
1212
|
-
validateController(definition, controllerImplementations, report);
|
|
1213
|
-
validateCronJobs(definition, report);
|
|
1214
|
-
validateDefaultPage(definition, report);
|
|
1215
|
-
validateHooks(definition, report);
|
|
1216
|
-
validateLanguage(definition, report);
|
|
1217
|
-
validateResourceReferences(definition, report);
|
|
1218
|
-
validateMembersSchema(definition, report);
|
|
1219
|
-
validateResourceSchemas(definition, report);
|
|
1220
|
-
validateSecurity(definition, report);
|
|
1221
|
-
validateBlocks(definition, blockVersionMap, report);
|
|
1222
|
-
validateActions(definition, report);
|
|
1223
|
-
validateEvents(definition, blockVersionMap, report);
|
|
1224
|
-
validateUniquePageNames(definition, report);
|
|
1225
|
-
}
|
|
1226
|
-
catch (error) {
|
|
1227
|
-
report(null, `Unexpected error: ${error instanceof Error ? error.message : error}`, []);
|
|
1228
|
-
}
|
|
1229
|
-
return result;
|
|
1230
|
-
}
|
|
1231
|
-
//# sourceMappingURL=validation.js.map
|