Skip to content

Commit

Permalink
add Subcription permissions ; update permission tests
Browse files Browse the repository at this point in the history
  • Loading branch information
getlarge committed May 12, 2020
1 parent 24b1ea8 commit 51cfdd2
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 6 deletions.
19 changes: 16 additions & 3 deletions src/engine/entity/Entity.ts
Expand Up @@ -390,7 +390,6 @@ export class Entity {
return this.subscriptions;
}

// this.getStates();
this.subscriptions = this._processSubscriptions();
return this.subscriptions;
}
Expand Down Expand Up @@ -799,7 +798,21 @@ export class Entity {
});
}

// todo subscription
if (this.permissions.subscriptions && this.subscriptions) {
this.subscriptions.map(subscription => {
const subscriptionName = subscription.name;
const permission = this.permissions.subscriptions[subscriptionName];

if (permission) {
const descriptionPermissions = generatePermissionDescription(
permission,
);
if (descriptionPermissions) {
subscription.description += descriptionPermissions;
}
}
});
}
}
}

Expand Down Expand Up @@ -828,7 +841,7 @@ export class Entity {
}

this.getMutations();
// this.getSubscriptions();
this.getSubscriptions();
this.permissions = this._processPermissions();
this._generatePermissionDescriptions();
return this.permissions;
Expand Down
44 changes: 43 additions & 1 deletion src/engine/permission/Permission.spec.ts
Expand Up @@ -906,9 +906,13 @@ describe('Permission', () => {
mutations: {
update: new Permission().role('manager'),
},
subscriptions: {
onUpdate: new Permission().role('manager'),
},
};

processEntityPermissions(entity, permissions);
const permissionMap = processEntityPermissions(entity, permissions);
expect(permissionMap).toMatchSnapshot();
});

it('should throw if provided with an invalid map of permissions', () => {
Expand All @@ -933,6 +937,18 @@ describe('Permission', () => {
expect(fn).toThrowErrorMatchingSnapshot();
});

it('should throw if provided with an invalid map of subscription permissions', () => {
const permissions = {
subscriptions: ['bad'],
};

function fn() {
processEntityPermissions(entity, permissions);
}

expect(fn).toThrowErrorMatchingSnapshot();
});

it('should throw if provided with an invalid permissions', () => {
const permissions1 = {
read: ['bad'],
Expand Down Expand Up @@ -1001,6 +1017,18 @@ describe('Permission', () => {
}

expect(fn3).toThrowErrorMatchingSnapshot();

const permissions4 = {
subscriptions: {
onUpdate: new Permission().userAttribute('notHere'),
},
};

function fn4() {
processEntityPermissions(entity, permissions4);
}

expect(fn4).toThrowErrorMatchingSnapshot();
});

it('should throw if permissions have invalid attributes defined', () => {
Expand Down Expand Up @@ -1029,6 +1057,20 @@ describe('Permission', () => {
expect(fn).toThrowErrorMatchingSnapshot();
});

it('should throw if permissions are assigned to unknown subscriptions', () => {
const permissions = {
subscriptions: {
noSuchSubscription: new Permission().userAttribute('someAttribute'),
},
};

function fn() {
processEntityPermissions(entity, permissions);
}

expect(fn).toThrowErrorMatchingSnapshot();
});

it('should throw if permission is used on a create type mutation and using data-bound permission types', () => {
const permissions1 = {
mutations: {
Expand Down
93 changes: 93 additions & 0 deletions src/engine/permission/Permission.ts
Expand Up @@ -10,6 +10,11 @@ import {
isMutation,
Mutation,
} from '../mutation/Mutation';
import {
SUBSCRIPTION_TYPE_CREATE,
// isSubscription,
Subscription,
} from '../subscription/Subscription';
import { isDataTypeState } from '../datatype/DataTypeState';

/*
Expand All @@ -29,6 +34,7 @@ export type PermissionMap = {
read?: Permission | Permission[];
find?: Permission | Permission[];
mutations?: {} | Permission | Permission[];
subscriptions?: {} | Permission | Permission[];
};

export class Permission {
Expand Down Expand Up @@ -973,6 +979,28 @@ const validatePermissionMutationTypes = (
}
};

const validatePermissionSubscriptionTypes = (
entity: Entity,
permissions: Permission | Permission[],
subscription: Subscription,
): void => {
if (subscription.type === SUBSCRIPTION_TYPE_CREATE) {
const permissionsArray = isArray(permissions as Permission[])
? (permissions as Permission[])
: ([permissions] as Permission[]);

permissionsArray.map(permission => {
passOrThrow(
!permission.userAttributes.length &&
!permission.states.length &&
!permission.values.length,
() =>
`Create type subscription permission '${subscription.name}' in '${entity.name}.permissions' can only be of type 'authenticated', 'everyone', 'role' or 'lookup'`,
);
});
}
};

export const hasEmptyPermissions = (
_permissions: Permission | Permission[],
): boolean => {
Expand Down Expand Up @@ -1075,6 +1103,41 @@ export const processEntityPermissions = (
}
}

const entitySubscriptions = entity.getSubscriptions();

if (!permissions.subscriptions && defaultPermissions) {
permissions.subscriptions = {};
}

if (permissions.subscriptions) {
passOrThrow(
isMap(permissions.subscriptions),
() =>
`Entity '${entity.name}' permissions definition for subscriptions needs to be a map of subscriptions and permissions`,
);

const subscriptionNames = Object.keys(permissions.subscriptions);
subscriptionNames.map((subscriptionName, idx) => {
passOrThrow(
isPermission(permissions.subscriptions[subscriptionName]) ||
isPermissionsArray(permissions.subscriptions[subscriptionName]),
() =>
`Invalid subscription permission definition for entity '${entity.name}' at position '${idx}'`,
);
});

if (defaultPermissions) {
entitySubscriptions.map(({ name: subscriptionName }) => {
if (defaultPermissions.subscriptions) {
permissions.subscriptions[subscriptionName] =
permissions.subscriptions[subscriptionName] ||
defaultPermissions.subscriptions[subscriptionName] ||
defaultPermissions.subscriptions._default;
}
});
}
}

if (permissions.find) {
validatePermissionAttributesAndStates(entity, permissions.find, 'find');
}
Expand Down Expand Up @@ -1107,6 +1170,36 @@ export const processEntityPermissions = (
});
}

if (permissions.subscriptions && entitySubscriptions) {
const permissionSubscriptionNames = Object.keys(permissions.subscriptions);

const subscriptionNames = entitySubscriptions.map(
subscription => subscription.name,
);

permissionSubscriptionNames.map(permissionSubscriptionName => {
passOrThrow(
subscriptionNames.includes(permissionSubscriptionName),
() =>
`Unknown subscription '${permissionSubscriptionName}' used for permissions in entity '${entity.name}'`,
);
});

entitySubscriptions.map(subscription => {
const subscriptionName = subscription.name;
const permission = permissions.subscriptions[subscriptionName];
if (permission) {
// not sure it's needed for subscription
validatePermissionSubscriptionTypes(entity, permission, subscription);
validatePermissionAttributesAndStates(
entity,
permission,
subscription.type,
);
}
});
}

const emptyPermissionsIn = findEmptyEntityPermissions(permissions);

passOrThrow(
Expand Down
63 changes: 63 additions & 0 deletions src/engine/permission/__snapshots__/Permission.spec.ts.snap
Expand Up @@ -487,6 +487,63 @@ exports[`Permission processActionPermissions should throw if provided with inval

exports[`Permission processActionPermissions should throw if provided with invalid permissions 2`] = `"Invalid permission definition for action 'SomeActionName'"`;

exports[`Permission processEntityPermissions should accept a correct permissions setup 1`] = `
Object {
"mutations": Object {
"update": Permission {
"authenticatedCanAccess": false,
"everyoneCanAccess": false,
"isEmpty": false,
"lookups": Array [],
"roles": Array [
"manager",
],
"states": Array [],
"types": Object {
"role": true,
},
"userAttributes": Array [],
"values": Array [],
},
},
"read": Permission {
"authenticatedCanAccess": false,
"everyoneCanAccess": false,
"isEmpty": false,
"lookups": Array [],
"roles": Array [],
"states": Array [],
"types": Object {
"value": true,
},
"userAttributes": Array [],
"values": Array [
Object {
"attributeName": "someAttribute",
"value": 123,
},
],
},
"subscriptions": Object {
"onUpdate": Permission {
"authenticatedCanAccess": false,
"everyoneCanAccess": false,
"isEmpty": false,
"lookups": Array [],
"roles": Array [
"manager",
],
"states": Array [],
"types": Object {
"role": true,
},
"userAttributes": Array [],
"values": Array [],
},
},
}
`;

exports[`Permission processEntityPermissions should throw if permission is used on a create type mutation and using data-bound permission types 1`] = `"Create type mutation permission 'create' in 'SomeEntityName.permissions' can only be of type 'authenticated', 'everyone', 'role' or 'lookup'"`;

exports[`Permission processEntityPermissions should throw if permission is used on a create type mutation and using data-bound permission types 2`] = `"Create type mutation permission 'create' in 'SomeEntityName.permissions' can only be of type 'authenticated', 'everyone', 'role' or 'lookup'"`;
Expand All @@ -495,6 +552,8 @@ exports[`Permission processEntityPermissions should throw if permission is used

exports[`Permission processEntityPermissions should throw if permissions are assigned to unknown mutations 1`] = `"Unknown mutation 'noSuchMutation' used for permissions in entity 'SomeEntityName'"`;

exports[`Permission processEntityPermissions should throw if permissions are assigned to unknown subscriptions 1`] = `"Unknown subscription 'noSuchSubscription' used for permissions in entity 'SomeEntityName'"`;

exports[`Permission processEntityPermissions should throw if permissions have invalid attributes defined 1`] = `"Cannot use attribute 'someAttribute' in 'SomeEntityName.permissions' as 'userAttribute' as it is not a reference to the User entity"`;

exports[`Permission processEntityPermissions should throw if permissions have unknown attributes defined 1`] = `"Cannot use attribute 'notHere' in 'SomeEntityName.permissions' for 'read' as it does not exist"`;
Expand All @@ -503,10 +562,14 @@ exports[`Permission processEntityPermissions should throw if permissions have un

exports[`Permission processEntityPermissions should throw if permissions have unknown attributes defined 3`] = `"Cannot use attribute 'notHere' in 'SomeEntityName.permissions' for 'update' as it does not exist"`;

exports[`Permission processEntityPermissions should throw if permissions have unknown attributes defined 4`] = `"Cannot use attribute 'notHere' in 'SomeEntityName.permissions' for 'onUpdate' as it does not exist"`;

exports[`Permission processEntityPermissions should throw if provided with an invalid map of mutation permissions 1`] = `"Entity 'SomeEntityName' permissions definition for mutations needs to be a map of mutations and permissions"`;

exports[`Permission processEntityPermissions should throw if provided with an invalid map of permissions 1`] = `"Entity 'SomeEntityName' permissions definition needs to be an object"`;

exports[`Permission processEntityPermissions should throw if provided with an invalid map of subscription permissions 1`] = `"Entity 'SomeEntityName' permissions definition for subscriptions needs to be a map of subscriptions and permissions"`;

exports[`Permission processEntityPermissions should throw if provided with an invalid permissions 1`] = `"Invalid 'read' permission definition for entity 'SomeEntityName'"`;

exports[`Permission processEntityPermissions should throw if provided with an invalid permissions 2`] = `"Invalid 'find' permission definition for entity 'SomeEntityName'"`;
Expand Down
15 changes: 13 additions & 2 deletions src/engine/schema/Schema.ts
Expand Up @@ -4,7 +4,11 @@ import { Entity, isEntity } from '../entity/Entity';
import { Action, isAction } from '../action/Action';
import { isDataTypeUser } from '../datatype/DataTypeUser';
import { StorageType, isStorageType } from '../storage/StorageType';
import { isPermission, isPermissionsArray } from '../permission/Permission';
import {
isPermission,
isPermissionsArray,
Permission,
} from '../permission/Permission';
import { isViewEntity, ViewEntity } from '../entity/ViewEntity';
import { isShadowEntity } from '../entity/ShadowEntity';

Expand Down Expand Up @@ -220,11 +224,14 @@ export class Schema {
const entityDefaultPermissions =
this.permissionsMap.entities[entity.name] || {};
entityDefaultPermissions.mutations =
entityDefaultPermissions.mutations || ({} as Entity);
entityDefaultPermissions.mutations || ({} as Permission);
entityDefaultPermissions.subscriptions =
entityDefaultPermissions.subscriptions || ({} as Permission);

const defaultPermissions = this.permissionsMap.entities
._defaultPermissions;
defaultPermissions.mutations = defaultPermissions.mutations || {};
defaultPermissions.subscriptions = defaultPermissions.subscriptions || {};

const newDefaultPermissions = {
read: entityDefaultPermissions.read || defaultPermissions.read,
Expand All @@ -233,6 +240,10 @@ export class Schema {
...defaultPermissions.mutations,
...entityDefaultPermissions.mutations,
},
subscriptions: {
...defaultPermissions.subscriptions,
...entityDefaultPermissions.subscriptions,
},
};

if (isEntity(entity) || isViewEntity(entity)) {
Expand Down

0 comments on commit 51cfdd2

Please sign in to comment.