@expo/entity 0.37.0 → 0.38.0
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/build/EntityFieldDefinition.d.ts +35 -1
- package/build/EntityFieldDefinition.js +29 -1
- package/build/EntityFieldDefinition.js.map +1 -1
- package/build/testfixtures/TestEntity.d.ts +4 -4
- package/build/testfixtures/TestEntity.js +4 -4
- package/build/testfixtures/TestEntity.js.map +1 -1
- package/build/utils/EntityPrivacyUtils.js +28 -7
- package/build/utils/EntityPrivacyUtils.js.map +1 -1
- package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.d.ts +1 -0
- package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js +178 -0
- package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js.map +1 -0
- package/package.json +2 -2
- package/src/EntityFieldDefinition.ts +38 -1
- package/src/testfixtures/TestEntity.ts +7 -4
- package/src/utils/EntityPrivacyUtils.ts +45 -12
- package/src/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.ts +254 -0
|
@@ -31,6 +31,33 @@ export declare enum EntityEdgeDeletionBehavior {
|
|
|
31
31
|
*/
|
|
32
32
|
SET_NULL = 3
|
|
33
33
|
}
|
|
34
|
+
export declare enum EntityEdgeDeletionAuthorizationInferenceBehavior {
|
|
35
|
+
/**
|
|
36
|
+
* Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
|
|
37
|
+
* (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
|
|
38
|
+
* cannot be inferred from authorization of any single entity at the end of an edge of this type.
|
|
39
|
+
*
|
|
40
|
+
* To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must be called on all
|
|
41
|
+
* entities at the ends of all edges of this type.
|
|
42
|
+
*/
|
|
43
|
+
NONE = 0,
|
|
44
|
+
/**
|
|
45
|
+
* Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
|
|
46
|
+
* (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
|
|
47
|
+
* may be inferred from authorization of any single entity at the end of an edge of this type.
|
|
48
|
+
*
|
|
49
|
+
* To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must only be called on
|
|
50
|
+
* a single entity at the end of one edge of this type chosen at random.
|
|
51
|
+
*
|
|
52
|
+
* This should only be the case when the entity at the other end of this edge can be implicitly
|
|
53
|
+
* deleted/updated by virtue of the source entity deletion being authorized and a single authorization check
|
|
54
|
+
* on one edge of this type.
|
|
55
|
+
*
|
|
56
|
+
* Note that this is not used during actual deletions, only as an optimistic optimization during execution
|
|
57
|
+
* of canViewerDeleteAsync. Each entity being deleted will still check deletion privacy during actual deletion.
|
|
58
|
+
*/
|
|
59
|
+
ONE_IMPLIES_ALL = 1
|
|
60
|
+
}
|
|
34
61
|
/**
|
|
35
62
|
* Defines an association between entities. An association is primarily used to define cascading deletion behavior.
|
|
36
63
|
*/
|
|
@@ -45,7 +72,7 @@ export interface EntityAssociationDefinition<TViewerContext extends ViewerContex
|
|
|
45
72
|
*/
|
|
46
73
|
associatedEntityLookupByField?: keyof TAssociatedFields;
|
|
47
74
|
/**
|
|
48
|
-
* What action to perform on the
|
|
75
|
+
* What action to perform on the entity at the other end of this edge when the entity on the source end of
|
|
49
76
|
* this edge is deleted.
|
|
50
77
|
*
|
|
51
78
|
* @remarks
|
|
@@ -61,6 +88,13 @@ export interface EntityAssociationDefinition<TViewerContext extends ViewerContex
|
|
|
61
88
|
* integrity is recommended.
|
|
62
89
|
*/
|
|
63
90
|
edgeDeletionBehavior: EntityEdgeDeletionBehavior;
|
|
91
|
+
/**
|
|
92
|
+
* Optimization setting for evaluation of this edge in canViewerDeleteAsync. Can be used to optimize permission checks
|
|
93
|
+
* for edge cascading deletions based on application-specific design of the entity association. Not used during actual deletions.
|
|
94
|
+
*
|
|
95
|
+
* Defaults to EntityEdgeDeletionAuthorizationInferenceBehavior.NONE.
|
|
96
|
+
*/
|
|
97
|
+
edgeDeletionAuthorizationInferenceBehavior?: EntityEdgeDeletionAuthorizationInferenceBehavior;
|
|
64
98
|
}
|
|
65
99
|
/**
|
|
66
100
|
* Options for EntityFieldDefinition
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EntityFieldDefinition = exports.EntityEdgeDeletionBehavior = void 0;
|
|
3
|
+
exports.EntityFieldDefinition = exports.EntityEdgeDeletionAuthorizationInferenceBehavior = exports.EntityEdgeDeletionBehavior = void 0;
|
|
4
4
|
var EntityEdgeDeletionBehavior;
|
|
5
5
|
(function (EntityEdgeDeletionBehavior) {
|
|
6
6
|
/**
|
|
@@ -31,6 +31,34 @@ var EntityEdgeDeletionBehavior;
|
|
|
31
31
|
*/
|
|
32
32
|
EntityEdgeDeletionBehavior[EntityEdgeDeletionBehavior["SET_NULL"] = 3] = "SET_NULL";
|
|
33
33
|
})(EntityEdgeDeletionBehavior || (exports.EntityEdgeDeletionBehavior = EntityEdgeDeletionBehavior = {}));
|
|
34
|
+
var EntityEdgeDeletionAuthorizationInferenceBehavior;
|
|
35
|
+
(function (EntityEdgeDeletionAuthorizationInferenceBehavior) {
|
|
36
|
+
/**
|
|
37
|
+
* Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
|
|
38
|
+
* (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
|
|
39
|
+
* cannot be inferred from authorization of any single entity at the end of an edge of this type.
|
|
40
|
+
*
|
|
41
|
+
* To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must be called on all
|
|
42
|
+
* entities at the ends of all edges of this type.
|
|
43
|
+
*/
|
|
44
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior[EntityEdgeDeletionAuthorizationInferenceBehavior["NONE"] = 0] = "NONE";
|
|
45
|
+
/**
|
|
46
|
+
* Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
|
|
47
|
+
* (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
|
|
48
|
+
* may be inferred from authorization of any single entity at the end of an edge of this type.
|
|
49
|
+
*
|
|
50
|
+
* To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must only be called on
|
|
51
|
+
* a single entity at the end of one edge of this type chosen at random.
|
|
52
|
+
*
|
|
53
|
+
* This should only be the case when the entity at the other end of this edge can be implicitly
|
|
54
|
+
* deleted/updated by virtue of the source entity deletion being authorized and a single authorization check
|
|
55
|
+
* on one edge of this type.
|
|
56
|
+
*
|
|
57
|
+
* Note that this is not used during actual deletions, only as an optimistic optimization during execution
|
|
58
|
+
* of canViewerDeleteAsync. Each entity being deleted will still check deletion privacy during actual deletion.
|
|
59
|
+
*/
|
|
60
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior[EntityEdgeDeletionAuthorizationInferenceBehavior["ONE_IMPLIES_ALL"] = 1] = "ONE_IMPLIES_ALL";
|
|
61
|
+
})(EntityEdgeDeletionAuthorizationInferenceBehavior || (exports.EntityEdgeDeletionAuthorizationInferenceBehavior = EntityEdgeDeletionAuthorizationInferenceBehavior = {}));
|
|
34
62
|
/**
|
|
35
63
|
* Definition for a field referencing a column in the underlying database. Specifies things like
|
|
36
64
|
* cache behavior and associations, and handles input validation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityFieldDefinition.js","sourceRoot":"","sources":["../src/EntityFieldDefinition.ts"],"names":[],"mappings":";;;AAKA,IAAY,0BA+BX;AA/BD,WAAY,0BAA0B;IACpC;;;;;;OAMG;IACH,2IAAoC,CAAA;IAEpC;;;;;OAKG;IACH,+HAA8B,CAAA;IAE9B;;;;OAIG;IACH,+FAAc,CAAA;IAEd;;;;OAIG;IACH,mFAAQ,CAAA;AACV,CAAC,EA/BW,0BAA0B,0CAA1B,0BAA0B,QA+BrC;
|
|
1
|
+
{"version":3,"file":"EntityFieldDefinition.js","sourceRoot":"","sources":["../src/EntityFieldDefinition.ts"],"names":[],"mappings":";;;AAKA,IAAY,0BA+BX;AA/BD,WAAY,0BAA0B;IACpC;;;;;;OAMG;IACH,2IAAoC,CAAA;IAEpC;;;;;OAKG;IACH,+HAA8B,CAAA;IAE9B;;;;OAIG;IACH,+FAAc,CAAA;IAEd;;;;OAIG;IACH,mFAAQ,CAAA;AACV,CAAC,EA/BW,0BAA0B,0CAA1B,0BAA0B,QA+BrC;AAED,IAAY,gDA2BX;AA3BD,WAAY,gDAAgD;IAC1D;;;;;;;OAOG;IACH,uHAAI,CAAA;IAEJ;;;;;;;;;;;;;;OAcG;IACH,6IAAe,CAAA;AACjB,CAAC,EA3BW,gDAAgD,gEAAhD,gDAAgD,QA2B3D;AA2FD;;;GAGG;AACH,MAAsB,qBAAqB;IAChC,UAAU,CAAS;IACnB,KAAK,CAAU;IACf,WAAW,CAAwE;IAC5F;;;OAGG;IACH,YAAY,OAAqC;QAC/C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACI,kBAAkB,CAAC,KAA2B;QACnD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;CAEF;AA3BD,sDA2BC"}
|
|
@@ -23,8 +23,8 @@ export declare class TestEntityPrivacyPolicy extends EntityPrivacyPolicy<TestFie
|
|
|
23
23
|
export default class TestEntity extends Entity<TestFields, string, ViewerContext> {
|
|
24
24
|
static defineCompanionDefinition(): EntityCompanionDefinition<TestFields, string, ViewerContext, TestEntity, TestEntityPrivacyPolicy>;
|
|
25
25
|
getBlah(): string;
|
|
26
|
-
static
|
|
27
|
-
static
|
|
28
|
-
static
|
|
29
|
-
static
|
|
26
|
+
static helloAsync(viewerContext: ViewerContext, testValue: string): Promise<Result<TestEntity>>;
|
|
27
|
+
static returnErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>>;
|
|
28
|
+
static throwErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>>;
|
|
29
|
+
static nonResultAsync(_viewerContext: ViewerContext, testValue: string): Promise<string>;
|
|
30
30
|
}
|
|
@@ -63,7 +63,7 @@ class TestEntity extends Entity_1.default {
|
|
|
63
63
|
getBlah() {
|
|
64
64
|
return 'Hello World!';
|
|
65
65
|
}
|
|
66
|
-
static async
|
|
66
|
+
static async helloAsync(viewerContext, testValue) {
|
|
67
67
|
const fields = {
|
|
68
68
|
customIdField: testValue,
|
|
69
69
|
testIndexedField: 'hello',
|
|
@@ -79,13 +79,13 @@ class TestEntity extends Entity_1.default {
|
|
|
79
79
|
selectedFields: fields,
|
|
80
80
|
}));
|
|
81
81
|
}
|
|
82
|
-
static async
|
|
82
|
+
static async returnErrorAsync(_viewerContext) {
|
|
83
83
|
return (0, results_1.result)(new Error('return entity'));
|
|
84
84
|
}
|
|
85
|
-
static async
|
|
85
|
+
static async throwErrorAsync(_viewerContext) {
|
|
86
86
|
throw new Error('threw entity');
|
|
87
87
|
}
|
|
88
|
-
static async
|
|
88
|
+
static async nonResultAsync(_viewerContext, testValue) {
|
|
89
89
|
return testValue;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestEntity.js","sourceRoot":"","sources":["../../src/testfixtures/TestEntity.ts"],"names":[],"mappings":";;;;;;AAAA,2CAA+C;AAE/C,uDAA+B;AAE/B,iFAAyD;AACzD,kDAA8E;AAC9E,iFAAyD;AAEzD,yGAAiF;AAWpE,QAAA,uBAAuB,GAAG,IAAI,6BAAmB,CAAa;IACzE,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,oCAAoC;IAC/C,MAAM,EAAE;QACN,aAAa,EAAE,IAAI,wBAAS,CAAC;YAC3B,UAAU,EAAE,WAAW;SACxB,CAAC;QACF,gBAAgB,EAAE,IAAI,0BAAW,CAAC;YAChC,UAAU,EAAE,YAAY;YACxB,KAAK,EAAE,IAAI;SACZ,CAAC;QACF,WAAW,EAAE,IAAI,0BAAW,CAAC;YAC3B,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,QAAQ,EAAE,IAAI,uBAAQ,CAAC;YACrB,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,SAAS,EAAE,IAAI,wBAAS,CAAC;YACvB,UAAU,EAAE,YAAY;SACzB,CAAC;QACF,aAAa,EAAE,IAAI,0BAAW,CAAC;YAC7B,UAAU,EAAE,gBAAgB;SAC7B,CAAC;KACH;IACD,qBAAqB,EAAE,UAAU;IACjC,kBAAkB,EAAE,OAAO;CAC5B,CAAC,CAAC;AAEH,MAAa,uBAAwB,SAAQ,6BAK5C;IAC6B,SAAS,GAAG;QACtC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;CACH;AAlBD,0DAkBC;AAED,MAAqB,UAAW,SAAQ,gBAAyC;IAC/E,MAAM,CAAC,yBAAyB;QAO9B,OAAO;YACL,WAAW,EAAE,UAAU;YACvB,mBAAmB,EAAE,+BAAuB;YAC5C,kBAAkB,EAAE,uBAAuB;SAC5C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"TestEntity.js","sourceRoot":"","sources":["../../src/testfixtures/TestEntity.ts"],"names":[],"mappings":";;;;;;AAAA,2CAA+C;AAE/C,uDAA+B;AAE/B,iFAAyD;AACzD,kDAA8E;AAC9E,iFAAyD;AAEzD,yGAAiF;AAWpE,QAAA,uBAAuB,GAAG,IAAI,6BAAmB,CAAa;IACzE,OAAO,EAAE,eAAe;IACxB,SAAS,EAAE,oCAAoC;IAC/C,MAAM,EAAE;QACN,aAAa,EAAE,IAAI,wBAAS,CAAC;YAC3B,UAAU,EAAE,WAAW;SACxB,CAAC;QACF,gBAAgB,EAAE,IAAI,0BAAW,CAAC;YAChC,UAAU,EAAE,YAAY;YACxB,KAAK,EAAE,IAAI;SACZ,CAAC;QACF,WAAW,EAAE,IAAI,0BAAW,CAAC;YAC3B,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,QAAQ,EAAE,IAAI,uBAAQ,CAAC;YACrB,UAAU,EAAE,cAAc;SAC3B,CAAC;QACF,SAAS,EAAE,IAAI,wBAAS,CAAC;YACvB,UAAU,EAAE,YAAY;SACzB,CAAC;QACF,aAAa,EAAE,IAAI,0BAAW,CAAC;YAC7B,UAAU,EAAE,gBAAgB;SAC7B,CAAC;KACH;IACD,qBAAqB,EAAE,UAAU;IACjC,kBAAkB,EAAE,OAAO;CAC5B,CAAC,CAAC;AAEH,MAAa,uBAAwB,SAAQ,6BAK5C;IAC6B,SAAS,GAAG;QACtC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;IAC0B,WAAW,GAAG;QACxC,IAAI,sCAA4B,EAAiD;KAClF,CAAC;CACH;AAlBD,0DAkBC;AAED,MAAqB,UAAW,SAAQ,gBAAyC;IAC/E,MAAM,CAAC,yBAAyB;QAO9B,OAAO;YACL,WAAW,EAAE,UAAU;YACvB,mBAAmB,EAAE,+BAAuB;YAC5C,kBAAkB,EAAE,uBAAuB;SAC5C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,UAAU,CACrB,aAA4B,EAC5B,SAAiB;QAEjB,MAAM,MAAM,GAAG;YACb,aAAa,EAAE,SAAS;YACxB,gBAAgB,EAAE,OAAO;YACzB,WAAW,EAAE,OAAO;YACpB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,aAAa,EAAE,IAAI;SACpB,CAAC;QACF,OAAO,IAAA,gBAAM,EACX,IAAI,UAAU,CAAC;YACb,aAAa;YACb,EAAE,EAAE,SAAS;YACb,cAAc,EAAE,MAAM;YACtB,cAAc,EAAE,MAAM;SACvB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAA6B;QACzD,OAAO,IAAA,gBAAM,EAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,cAA6B;QACxD,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,cAA6B,EAAE,SAAiB;QAC1E,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AApDD,6BAoDC"}
|
|
@@ -119,12 +119,33 @@ async function canViewerDeleteInternalAsync(entityClass, sourceEntity, cascading
|
|
|
119
119
|
if (associatedConfiguration !== entityConfiguration) {
|
|
120
120
|
continue;
|
|
121
121
|
}
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
const edgeDeletionPermissionInferenceBehavior = association.edgeDeletionAuthorizationInferenceBehavior;
|
|
123
|
+
let entityResultsToCheckForInboundEdge;
|
|
124
|
+
if (edgeDeletionPermissionInferenceBehavior ===
|
|
125
|
+
EntityFieldDefinition_1.EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL) {
|
|
126
|
+
const singleEntityToTestForInboundEdge = await loader
|
|
127
|
+
.withAuthorizationResults()
|
|
128
|
+
.loadFirstByFieldEqualityConjunctionAsync([
|
|
129
|
+
{
|
|
130
|
+
fieldName,
|
|
131
|
+
fieldValue: association.associatedEntityLookupByField
|
|
132
|
+
? sourceEntity.getField(association.associatedEntityLookupByField)
|
|
133
|
+
: sourceEntity.getID(),
|
|
134
|
+
},
|
|
135
|
+
], { orderBy: [] });
|
|
136
|
+
entityResultsToCheckForInboundEdge = singleEntityToTestForInboundEdge
|
|
137
|
+
? [singleEntityToTestForInboundEdge]
|
|
138
|
+
: [];
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const entityResultsForInboundEdge = await loader
|
|
142
|
+
.withAuthorizationResults()
|
|
143
|
+
.loadManyByFieldEqualingAsync(fieldName, association.associatedEntityLookupByField
|
|
144
|
+
? sourceEntity.getField(association.associatedEntityLookupByField)
|
|
145
|
+
: sourceEntity.getID());
|
|
146
|
+
entityResultsToCheckForInboundEdge = entityResultsForInboundEdge;
|
|
147
|
+
}
|
|
148
|
+
const failedEntityLoadResults = (0, entityUtils_1.failedResults)(entityResultsToCheckForInboundEdge);
|
|
128
149
|
for (const failedResult of failedEntityLoadResults) {
|
|
129
150
|
if (failedResult.reason instanceof EntityNotAuthorizedError_1.default) {
|
|
130
151
|
return false;
|
|
@@ -134,7 +155,7 @@ async function canViewerDeleteInternalAsync(entityClass, sourceEntity, cascading
|
|
|
134
155
|
}
|
|
135
156
|
}
|
|
136
157
|
// all results should be success at this point due to check above
|
|
137
|
-
const entitiesForInboundEdge =
|
|
158
|
+
const entitiesForInboundEdge = entityResultsToCheckForInboundEdge.map((r) => r.enforceValue());
|
|
138
159
|
switch (association.edgeDeletionBehavior) {
|
|
139
160
|
case EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE:
|
|
140
161
|
case EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE_ONLY: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityPrivacyUtils.js","sourceRoot":"","sources":["../../src/utils/EntityPrivacyUtils.ts"],"names":[],"mappings":";;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"EntityPrivacyUtils.js","sourceRoot":"","sources":["../../src/utils/EntityPrivacyUtils.ts"],"names":[],"mappings":"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"}
|
package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const Entity_1 = __importDefault(require("../../Entity"));
|
|
7
|
+
const EntityConfiguration_1 = __importDefault(require("../../EntityConfiguration"));
|
|
8
|
+
const EntityFieldDefinition_1 = require("../../EntityFieldDefinition");
|
|
9
|
+
const EntityFields_1 = require("../../EntityFields");
|
|
10
|
+
const EntityPrivacyPolicy_1 = __importDefault(require("../../EntityPrivacyPolicy"));
|
|
11
|
+
const ViewerContext_1 = __importDefault(require("../../ViewerContext"));
|
|
12
|
+
const AlwaysAllowPrivacyPolicyRule_1 = __importDefault(require("../../rules/AlwaysAllowPrivacyPolicyRule"));
|
|
13
|
+
const AlwaysDenyPrivacyPolicyRule_1 = __importDefault(require("../../rules/AlwaysDenyPrivacyPolicyRule"));
|
|
14
|
+
const EntityPrivacyUtils_1 = require("../EntityPrivacyUtils");
|
|
15
|
+
const createUnitTestEntityCompanionProvider_1 = require("../testing/createUnitTestEntityCompanionProvider");
|
|
16
|
+
describe(EntityPrivacyUtils_1.canViewerDeleteAsync, () => {
|
|
17
|
+
describe('edgeDeletionPermissionInferenceBehavior', () => {
|
|
18
|
+
it('optimizes when EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL', async () => {
|
|
19
|
+
const companionProvider = (0, createUnitTestEntityCompanionProvider_1.createUnitTestEntityCompanionProvider)();
|
|
20
|
+
const viewerContext = new ViewerContext_1.default(companionProvider);
|
|
21
|
+
// create root
|
|
22
|
+
const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
|
|
23
|
+
// create a bunch of leaves referencing root with
|
|
24
|
+
// edgeDeletionPermissionInferenceBehavior = EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL
|
|
25
|
+
for (let i = 0; i < 10; i++) {
|
|
26
|
+
await TestLeafEntity.creator(viewerContext)
|
|
27
|
+
.setField('test_entity_id', testEntity.getID())
|
|
28
|
+
.enforceCreateAsync();
|
|
29
|
+
}
|
|
30
|
+
for (let i = 0; i < 10; i++) {
|
|
31
|
+
await TestLeafLookupByFieldEntity.creator(viewerContext)
|
|
32
|
+
.setField('test_entity_id', testEntity.getID())
|
|
33
|
+
.enforceCreateAsync();
|
|
34
|
+
}
|
|
35
|
+
const testLeafEntityCompanion = viewerContext.getViewerScopedEntityCompanionForClass(TestLeafEntity);
|
|
36
|
+
const testLeafEntityAuthorizeDeleteSpy = jest.spyOn(testLeafEntityCompanion.entityCompanion.privacyPolicy, 'authorizeDeleteAsync');
|
|
37
|
+
const testLeafLookupByFieldEntityCompanion = viewerContext.getViewerScopedEntityCompanionForClass(TestLeafLookupByFieldEntity);
|
|
38
|
+
const testLeafLookupByFieldEntityAuthorizeDeleteSpy = jest.spyOn(testLeafLookupByFieldEntityCompanion.entityCompanion.privacyPolicy, 'authorizeDeleteAsync');
|
|
39
|
+
const canViewerDelete = await (0, EntityPrivacyUtils_1.canViewerDeleteAsync)(TestEntity, testEntity);
|
|
40
|
+
expect(canViewerDelete).toBe(true);
|
|
41
|
+
expect(testLeafEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
|
|
42
|
+
expect(testLeafLookupByFieldEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
44
|
+
it('does not optimize when undefined', async () => {
|
|
45
|
+
const companionProvider = (0, createUnitTestEntityCompanionProvider_1.createUnitTestEntityCompanionProvider)();
|
|
46
|
+
const viewerContext = new ViewerContext_1.default(companionProvider);
|
|
47
|
+
// create root
|
|
48
|
+
const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
|
|
49
|
+
// create a bunch of leaves with no edgeDeletionPermissionInferenceBehavior
|
|
50
|
+
for (let i = 0; i < 10; i++) {
|
|
51
|
+
await TestLeafNoInferenceEntity.creator(viewerContext)
|
|
52
|
+
.setField('test_entity_id', testEntity.getID())
|
|
53
|
+
.enforceCreateAsync();
|
|
54
|
+
}
|
|
55
|
+
const companion = viewerContext.getViewerScopedEntityCompanionForClass(TestLeafNoInferenceEntity);
|
|
56
|
+
const authorizeDeleteSpy = jest.spyOn(companion.entityCompanion.privacyPolicy, 'authorizeDeleteAsync');
|
|
57
|
+
const canViewerDelete = await (0, EntityPrivacyUtils_1.canViewerDeleteAsync)(TestEntity, testEntity);
|
|
58
|
+
expect(canViewerDelete).toBe(true);
|
|
59
|
+
expect(authorizeDeleteSpy).toHaveBeenCalledTimes(10);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
class AlwaysAllowEntityPrivacyPolicy extends EntityPrivacyPolicy_1.default {
|
|
64
|
+
readRules = [
|
|
65
|
+
new AlwaysAllowPrivacyPolicyRule_1.default(),
|
|
66
|
+
];
|
|
67
|
+
createRules = [
|
|
68
|
+
new AlwaysAllowPrivacyPolicyRule_1.default(),
|
|
69
|
+
];
|
|
70
|
+
updateRules = [
|
|
71
|
+
new AlwaysDenyPrivacyPolicyRule_1.default(),
|
|
72
|
+
];
|
|
73
|
+
deleteRules = [
|
|
74
|
+
new AlwaysAllowPrivacyPolicyRule_1.default(),
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
class TestEntity extends Entity_1.default {
|
|
78
|
+
static defineCompanionDefinition() {
|
|
79
|
+
return {
|
|
80
|
+
entityClass: TestEntity,
|
|
81
|
+
entityConfiguration: new EntityConfiguration_1.default({
|
|
82
|
+
idField: 'id',
|
|
83
|
+
tableName: 'blah',
|
|
84
|
+
inboundEdges: [TestLeafEntity, TestLeafLookupByFieldEntity, TestLeafNoInferenceEntity],
|
|
85
|
+
schema: {
|
|
86
|
+
id: new EntityFields_1.UUIDField({
|
|
87
|
+
columnName: 'custom_id',
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
databaseAdapterFlavor: 'postgres',
|
|
91
|
+
cacheAdapterFlavor: 'redis',
|
|
92
|
+
}),
|
|
93
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
class TestLeafEntity extends Entity_1.default {
|
|
98
|
+
static defineCompanionDefinition() {
|
|
99
|
+
return {
|
|
100
|
+
entityClass: TestLeafEntity,
|
|
101
|
+
entityConfiguration: new EntityConfiguration_1.default({
|
|
102
|
+
idField: 'id',
|
|
103
|
+
tableName: 'blah_2',
|
|
104
|
+
schema: {
|
|
105
|
+
id: new EntityFields_1.UUIDField({
|
|
106
|
+
columnName: 'custom_id',
|
|
107
|
+
}),
|
|
108
|
+
test_entity_id: new EntityFields_1.UUIDField({
|
|
109
|
+
columnName: 'test_entity_id',
|
|
110
|
+
association: {
|
|
111
|
+
associatedEntityClass: TestEntity,
|
|
112
|
+
edgeDeletionBehavior: EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE,
|
|
113
|
+
edgeDeletionAuthorizationInferenceBehavior: EntityFieldDefinition_1.EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
databaseAdapterFlavor: 'postgres',
|
|
118
|
+
cacheAdapterFlavor: 'redis',
|
|
119
|
+
}),
|
|
120
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
class TestLeafLookupByFieldEntity extends Entity_1.default {
|
|
125
|
+
static defineCompanionDefinition() {
|
|
126
|
+
return {
|
|
127
|
+
entityClass: TestLeafEntity,
|
|
128
|
+
entityConfiguration: new EntityConfiguration_1.default({
|
|
129
|
+
idField: 'id',
|
|
130
|
+
tableName: 'blah_4',
|
|
131
|
+
schema: {
|
|
132
|
+
id: new EntityFields_1.UUIDField({
|
|
133
|
+
columnName: 'custom_id',
|
|
134
|
+
}),
|
|
135
|
+
test_entity_id: new EntityFields_1.UUIDField({
|
|
136
|
+
columnName: 'test_entity_id',
|
|
137
|
+
association: {
|
|
138
|
+
associatedEntityClass: TestEntity,
|
|
139
|
+
edgeDeletionBehavior: EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE,
|
|
140
|
+
associatedEntityLookupByField: 'id',
|
|
141
|
+
edgeDeletionAuthorizationInferenceBehavior: EntityFieldDefinition_1.EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
databaseAdapterFlavor: 'postgres',
|
|
146
|
+
cacheAdapterFlavor: 'redis',
|
|
147
|
+
}),
|
|
148
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
class TestLeafNoInferenceEntity extends Entity_1.default {
|
|
153
|
+
static defineCompanionDefinition() {
|
|
154
|
+
return {
|
|
155
|
+
entityClass: TestLeafNoInferenceEntity,
|
|
156
|
+
entityConfiguration: new EntityConfiguration_1.default({
|
|
157
|
+
idField: 'id',
|
|
158
|
+
tableName: 'blah_3',
|
|
159
|
+
schema: {
|
|
160
|
+
id: new EntityFields_1.UUIDField({
|
|
161
|
+
columnName: 'custom_id',
|
|
162
|
+
}),
|
|
163
|
+
test_entity_id: new EntityFields_1.UUIDField({
|
|
164
|
+
columnName: 'test_entity_id',
|
|
165
|
+
association: {
|
|
166
|
+
associatedEntityClass: TestEntity,
|
|
167
|
+
edgeDeletionBehavior: EntityFieldDefinition_1.EntityEdgeDeletionBehavior.CASCADE_DELETE,
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
databaseAdapterFlavor: 'postgres',
|
|
172
|
+
cacheAdapterFlavor: 'redis',
|
|
173
|
+
}),
|
|
174
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/entity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "A privacy-first data model",
|
|
5
5
|
"files": [
|
|
6
6
|
"build",
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"uuid": "^8.3.0",
|
|
35
35
|
"uuidv7": "^1.0.0"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "d7cc0c23b983eccca9ca4e7ca620a5aaef6846c4"
|
|
38
38
|
}
|
|
@@ -36,6 +36,35 @@ export enum EntityEdgeDeletionBehavior {
|
|
|
36
36
|
SET_NULL,
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export enum EntityEdgeDeletionAuthorizationInferenceBehavior {
|
|
40
|
+
/**
|
|
41
|
+
* Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
|
|
42
|
+
* (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
|
|
43
|
+
* cannot be inferred from authorization of any single entity at the end of an edge of this type.
|
|
44
|
+
*
|
|
45
|
+
* To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must be called on all
|
|
46
|
+
* entities at the ends of all edges of this type.
|
|
47
|
+
*/
|
|
48
|
+
NONE,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Authorization to delete (when CASCADE_DELETE_INVALIDATE_CACHE_ONLY or CASCADE_DELETE) or update
|
|
52
|
+
* (when SET_NULL_INVALIDATE_CACHE_ONLY or SET_NULL) all entities at the ends of edges of this type
|
|
53
|
+
* may be inferred from authorization of any single entity at the end of an edge of this type.
|
|
54
|
+
*
|
|
55
|
+
* To evaluate canViewerDeleteAsync for the source entity, canViewerDeleteAsync must only be called on
|
|
56
|
+
* a single entity at the end of one edge of this type chosen at random.
|
|
57
|
+
*
|
|
58
|
+
* This should only be the case when the entity at the other end of this edge can be implicitly
|
|
59
|
+
* deleted/updated by virtue of the source entity deletion being authorized and a single authorization check
|
|
60
|
+
* on one edge of this type.
|
|
61
|
+
*
|
|
62
|
+
* Note that this is not used during actual deletions, only as an optimistic optimization during execution
|
|
63
|
+
* of canViewerDeleteAsync. Each entity being deleted will still check deletion privacy during actual deletion.
|
|
64
|
+
*/
|
|
65
|
+
ONE_IMPLIES_ALL,
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
/**
|
|
40
69
|
* Defines an association between entities. An association is primarily used to define cascading deletion behavior.
|
|
41
70
|
*/
|
|
@@ -77,7 +106,7 @@ export interface EntityAssociationDefinition<
|
|
|
77
106
|
associatedEntityLookupByField?: keyof TAssociatedFields;
|
|
78
107
|
|
|
79
108
|
/**
|
|
80
|
-
* What action to perform on the
|
|
109
|
+
* What action to perform on the entity at the other end of this edge when the entity on the source end of
|
|
81
110
|
* this edge is deleted.
|
|
82
111
|
*
|
|
83
112
|
* @remarks
|
|
@@ -93,6 +122,14 @@ export interface EntityAssociationDefinition<
|
|
|
93
122
|
* integrity is recommended.
|
|
94
123
|
*/
|
|
95
124
|
edgeDeletionBehavior: EntityEdgeDeletionBehavior;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Optimization setting for evaluation of this edge in canViewerDeleteAsync. Can be used to optimize permission checks
|
|
128
|
+
* for edge cascading deletions based on application-specific design of the entity association. Not used during actual deletions.
|
|
129
|
+
*
|
|
130
|
+
* Defaults to EntityEdgeDeletionAuthorizationInferenceBehavior.NONE.
|
|
131
|
+
*/
|
|
132
|
+
edgeDeletionAuthorizationInferenceBehavior?: EntityEdgeDeletionAuthorizationInferenceBehavior;
|
|
96
133
|
}
|
|
97
134
|
|
|
98
135
|
/**
|
|
@@ -84,7 +84,10 @@ export default class TestEntity extends Entity<TestFields, string, ViewerContext
|
|
|
84
84
|
return 'Hello World!';
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
static async
|
|
87
|
+
static async helloAsync(
|
|
88
|
+
viewerContext: ViewerContext,
|
|
89
|
+
testValue: string,
|
|
90
|
+
): Promise<Result<TestEntity>> {
|
|
88
91
|
const fields = {
|
|
89
92
|
customIdField: testValue,
|
|
90
93
|
testIndexedField: 'hello',
|
|
@@ -103,15 +106,15 @@ export default class TestEntity extends Entity<TestFields, string, ViewerContext
|
|
|
103
106
|
);
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
static async
|
|
109
|
+
static async returnErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
|
|
107
110
|
return result(new Error('return entity'));
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
static async
|
|
113
|
+
static async throwErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
|
|
111
114
|
throw new Error('threw entity');
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
static async
|
|
117
|
+
static async nonResultAsync(_viewerContext: ViewerContext, testValue: string): Promise<string> {
|
|
115
118
|
return testValue;
|
|
116
119
|
}
|
|
117
120
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { asyncResult } from '@expo/results';
|
|
1
|
+
import { Result, asyncResult } from '@expo/results';
|
|
2
2
|
|
|
3
3
|
import Entity, { IEntityClass } from '../Entity';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
EntityEdgeDeletionBehavior,
|
|
6
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior,
|
|
7
|
+
} from '../EntityFieldDefinition';
|
|
5
8
|
import { EntityCascadingDeletionInfo } from '../EntityMutationInfo';
|
|
6
9
|
import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
|
|
7
10
|
import { EntityQueryContext } from '../EntityQueryContext';
|
|
@@ -254,16 +257,44 @@ async function canViewerDeleteInternalAsync<
|
|
|
254
257
|
continue;
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
const
|
|
258
|
-
.
|
|
259
|
-
.loadManyByFieldEqualingAsync(
|
|
260
|
-
fieldName,
|
|
261
|
-
association.associatedEntityLookupByField
|
|
262
|
-
? sourceEntity.getField(association.associatedEntityLookupByField as any)
|
|
263
|
-
: sourceEntity.getID(),
|
|
264
|
-
);
|
|
260
|
+
const edgeDeletionPermissionInferenceBehavior =
|
|
261
|
+
association.edgeDeletionAuthorizationInferenceBehavior;
|
|
265
262
|
|
|
266
|
-
|
|
263
|
+
let entityResultsToCheckForInboundEdge: readonly Result<any>[];
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
edgeDeletionPermissionInferenceBehavior ===
|
|
267
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL
|
|
268
|
+
) {
|
|
269
|
+
const singleEntityToTestForInboundEdge = await loader
|
|
270
|
+
.withAuthorizationResults()
|
|
271
|
+
.loadFirstByFieldEqualityConjunctionAsync(
|
|
272
|
+
[
|
|
273
|
+
{
|
|
274
|
+
fieldName,
|
|
275
|
+
fieldValue: association.associatedEntityLookupByField
|
|
276
|
+
? sourceEntity.getField(association.associatedEntityLookupByField as any)
|
|
277
|
+
: sourceEntity.getID(),
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
{ orderBy: [] },
|
|
281
|
+
);
|
|
282
|
+
entityResultsToCheckForInboundEdge = singleEntityToTestForInboundEdge
|
|
283
|
+
? [singleEntityToTestForInboundEdge]
|
|
284
|
+
: [];
|
|
285
|
+
} else {
|
|
286
|
+
const entityResultsForInboundEdge = await loader
|
|
287
|
+
.withAuthorizationResults()
|
|
288
|
+
.loadManyByFieldEqualingAsync(
|
|
289
|
+
fieldName,
|
|
290
|
+
association.associatedEntityLookupByField
|
|
291
|
+
? sourceEntity.getField(association.associatedEntityLookupByField as any)
|
|
292
|
+
: sourceEntity.getID(),
|
|
293
|
+
);
|
|
294
|
+
entityResultsToCheckForInboundEdge = entityResultsForInboundEdge;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const failedEntityLoadResults = failedResults(entityResultsToCheckForInboundEdge);
|
|
267
298
|
for (const failedResult of failedEntityLoadResults) {
|
|
268
299
|
if (failedResult.reason instanceof EntityNotAuthorizedError) {
|
|
269
300
|
return false;
|
|
@@ -273,7 +304,9 @@ async function canViewerDeleteInternalAsync<
|
|
|
273
304
|
}
|
|
274
305
|
|
|
275
306
|
// all results should be success at this point due to check above
|
|
276
|
-
const entitiesForInboundEdge =
|
|
307
|
+
const entitiesForInboundEdge = entityResultsToCheckForInboundEdge.map((r) =>
|
|
308
|
+
r.enforceValue(),
|
|
309
|
+
);
|
|
277
310
|
|
|
278
311
|
switch (association.edgeDeletionBehavior) {
|
|
279
312
|
case EntityEdgeDeletionBehavior.CASCADE_DELETE:
|
package/src/utils/__tests__/canViewerDeleteAsync-edgeDeletionPermissionInferenceBehavior-test.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import Entity from '../../Entity';
|
|
2
|
+
import { EntityCompanionDefinition } from '../../EntityCompanionProvider';
|
|
3
|
+
import EntityConfiguration from '../../EntityConfiguration';
|
|
4
|
+
import {
|
|
5
|
+
EntityEdgeDeletionBehavior,
|
|
6
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior,
|
|
7
|
+
} from '../../EntityFieldDefinition';
|
|
8
|
+
import { UUIDField } from '../../EntityFields';
|
|
9
|
+
import EntityPrivacyPolicy from '../../EntityPrivacyPolicy';
|
|
10
|
+
import ReadonlyEntity from '../../ReadonlyEntity';
|
|
11
|
+
import ViewerContext from '../../ViewerContext';
|
|
12
|
+
import AlwaysAllowPrivacyPolicyRule from '../../rules/AlwaysAllowPrivacyPolicyRule';
|
|
13
|
+
import AlwaysDenyPrivacyPolicyRule from '../../rules/AlwaysDenyPrivacyPolicyRule';
|
|
14
|
+
import { canViewerDeleteAsync } from '../EntityPrivacyUtils';
|
|
15
|
+
import { createUnitTestEntityCompanionProvider } from '../testing/createUnitTestEntityCompanionProvider';
|
|
16
|
+
|
|
17
|
+
describe(canViewerDeleteAsync, () => {
|
|
18
|
+
describe('edgeDeletionPermissionInferenceBehavior', () => {
|
|
19
|
+
it('optimizes when EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL', async () => {
|
|
20
|
+
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
21
|
+
const viewerContext = new ViewerContext(companionProvider);
|
|
22
|
+
|
|
23
|
+
// create root
|
|
24
|
+
const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
|
|
25
|
+
|
|
26
|
+
// create a bunch of leaves referencing root with
|
|
27
|
+
// edgeDeletionPermissionInferenceBehavior = EntityEdgeDeletionPermissionInferenceBehavior.ONE_IMPLIES_ALL
|
|
28
|
+
for (let i = 0; i < 10; i++) {
|
|
29
|
+
await TestLeafEntity.creator(viewerContext)
|
|
30
|
+
.setField('test_entity_id', testEntity.getID())
|
|
31
|
+
.enforceCreateAsync();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < 10; i++) {
|
|
35
|
+
await TestLeafLookupByFieldEntity.creator(viewerContext)
|
|
36
|
+
.setField('test_entity_id', testEntity.getID())
|
|
37
|
+
.enforceCreateAsync();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const testLeafEntityCompanion =
|
|
41
|
+
viewerContext.getViewerScopedEntityCompanionForClass(TestLeafEntity);
|
|
42
|
+
const testLeafEntityAuthorizeDeleteSpy = jest.spyOn(
|
|
43
|
+
testLeafEntityCompanion.entityCompanion.privacyPolicy,
|
|
44
|
+
'authorizeDeleteAsync',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const testLeafLookupByFieldEntityCompanion =
|
|
48
|
+
viewerContext.getViewerScopedEntityCompanionForClass(TestLeafLookupByFieldEntity);
|
|
49
|
+
const testLeafLookupByFieldEntityAuthorizeDeleteSpy = jest.spyOn(
|
|
50
|
+
testLeafLookupByFieldEntityCompanion.entityCompanion.privacyPolicy,
|
|
51
|
+
'authorizeDeleteAsync',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const canViewerDelete = await canViewerDeleteAsync(TestEntity, testEntity);
|
|
55
|
+
expect(canViewerDelete).toBe(true);
|
|
56
|
+
|
|
57
|
+
expect(testLeafEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(testLeafLookupByFieldEntityAuthorizeDeleteSpy).toHaveBeenCalledTimes(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('does not optimize when undefined', async () => {
|
|
62
|
+
const companionProvider = createUnitTestEntityCompanionProvider();
|
|
63
|
+
const viewerContext = new ViewerContext(companionProvider);
|
|
64
|
+
|
|
65
|
+
// create root
|
|
66
|
+
const testEntity = await TestEntity.creator(viewerContext).enforceCreateAsync();
|
|
67
|
+
|
|
68
|
+
// create a bunch of leaves with no edgeDeletionPermissionInferenceBehavior
|
|
69
|
+
for (let i = 0; i < 10; i++) {
|
|
70
|
+
await TestLeafNoInferenceEntity.creator(viewerContext)
|
|
71
|
+
.setField('test_entity_id', testEntity.getID())
|
|
72
|
+
.enforceCreateAsync();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const companion =
|
|
76
|
+
viewerContext.getViewerScopedEntityCompanionForClass(TestLeafNoInferenceEntity);
|
|
77
|
+
const authorizeDeleteSpy = jest.spyOn(
|
|
78
|
+
companion.entityCompanion.privacyPolicy,
|
|
79
|
+
'authorizeDeleteAsync',
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const canViewerDelete = await canViewerDeleteAsync(TestEntity, testEntity);
|
|
83
|
+
expect(canViewerDelete).toBe(true);
|
|
84
|
+
|
|
85
|
+
expect(authorizeDeleteSpy).toHaveBeenCalledTimes(10);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
type TestEntityFields = {
|
|
91
|
+
id: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type TestLeafEntityFields = {
|
|
95
|
+
id: string;
|
|
96
|
+
test_entity_id: string | null;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
class AlwaysAllowEntityPrivacyPolicy<
|
|
100
|
+
TFields extends object,
|
|
101
|
+
TID extends NonNullable<TFields[TSelectedFields]>,
|
|
102
|
+
TViewerContext extends ViewerContext,
|
|
103
|
+
TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
|
|
104
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
105
|
+
> extends EntityPrivacyPolicy<TFields, TID, TViewerContext, TEntity, TSelectedFields> {
|
|
106
|
+
protected override readonly readRules = [
|
|
107
|
+
new AlwaysAllowPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
|
|
108
|
+
];
|
|
109
|
+
protected override readonly createRules = [
|
|
110
|
+
new AlwaysAllowPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
|
|
111
|
+
];
|
|
112
|
+
protected override readonly updateRules = [
|
|
113
|
+
new AlwaysDenyPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
|
|
114
|
+
];
|
|
115
|
+
protected override readonly deleteRules = [
|
|
116
|
+
new AlwaysAllowPrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>(),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class TestEntity extends Entity<TestEntityFields, string, ViewerContext> {
|
|
121
|
+
static defineCompanionDefinition(): EntityCompanionDefinition<
|
|
122
|
+
TestEntityFields,
|
|
123
|
+
string,
|
|
124
|
+
ViewerContext,
|
|
125
|
+
TestEntity,
|
|
126
|
+
AlwaysAllowEntityPrivacyPolicy<TestEntityFields, string, ViewerContext, TestEntity>
|
|
127
|
+
> {
|
|
128
|
+
return {
|
|
129
|
+
entityClass: TestEntity,
|
|
130
|
+
entityConfiguration: new EntityConfiguration<TestEntityFields>({
|
|
131
|
+
idField: 'id',
|
|
132
|
+
tableName: 'blah',
|
|
133
|
+
inboundEdges: [TestLeafEntity, TestLeafLookupByFieldEntity, TestLeafNoInferenceEntity],
|
|
134
|
+
schema: {
|
|
135
|
+
id: new UUIDField({
|
|
136
|
+
columnName: 'custom_id',
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
databaseAdapterFlavor: 'postgres',
|
|
140
|
+
cacheAdapterFlavor: 'redis',
|
|
141
|
+
}),
|
|
142
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class TestLeafEntity extends Entity<TestLeafEntityFields, string, ViewerContext> {
|
|
148
|
+
static defineCompanionDefinition(): EntityCompanionDefinition<
|
|
149
|
+
TestLeafEntityFields,
|
|
150
|
+
string,
|
|
151
|
+
ViewerContext,
|
|
152
|
+
TestLeafEntity,
|
|
153
|
+
AlwaysAllowEntityPrivacyPolicy<TestLeafEntityFields, string, ViewerContext, TestLeafEntity>
|
|
154
|
+
> {
|
|
155
|
+
return {
|
|
156
|
+
entityClass: TestLeafEntity,
|
|
157
|
+
entityConfiguration: new EntityConfiguration<TestLeafEntityFields>({
|
|
158
|
+
idField: 'id',
|
|
159
|
+
tableName: 'blah_2',
|
|
160
|
+
schema: {
|
|
161
|
+
id: new UUIDField({
|
|
162
|
+
columnName: 'custom_id',
|
|
163
|
+
}),
|
|
164
|
+
test_entity_id: new UUIDField({
|
|
165
|
+
columnName: 'test_entity_id',
|
|
166
|
+
association: {
|
|
167
|
+
associatedEntityClass: TestEntity,
|
|
168
|
+
edgeDeletionBehavior: EntityEdgeDeletionBehavior.CASCADE_DELETE,
|
|
169
|
+
edgeDeletionAuthorizationInferenceBehavior:
|
|
170
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
174
|
+
databaseAdapterFlavor: 'postgres',
|
|
175
|
+
cacheAdapterFlavor: 'redis',
|
|
176
|
+
}),
|
|
177
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class TestLeafLookupByFieldEntity extends Entity<TestLeafEntityFields, string, ViewerContext> {
|
|
183
|
+
static defineCompanionDefinition(): EntityCompanionDefinition<
|
|
184
|
+
TestLeafEntityFields,
|
|
185
|
+
string,
|
|
186
|
+
ViewerContext,
|
|
187
|
+
TestLeafEntity,
|
|
188
|
+
AlwaysAllowEntityPrivacyPolicy<TestLeafEntityFields, string, ViewerContext, TestLeafEntity>
|
|
189
|
+
> {
|
|
190
|
+
return {
|
|
191
|
+
entityClass: TestLeafEntity,
|
|
192
|
+
entityConfiguration: new EntityConfiguration<TestLeafEntityFields>({
|
|
193
|
+
idField: 'id',
|
|
194
|
+
tableName: 'blah_4',
|
|
195
|
+
schema: {
|
|
196
|
+
id: new UUIDField({
|
|
197
|
+
columnName: 'custom_id',
|
|
198
|
+
}),
|
|
199
|
+
test_entity_id: new UUIDField({
|
|
200
|
+
columnName: 'test_entity_id',
|
|
201
|
+
association: {
|
|
202
|
+
associatedEntityClass: TestEntity,
|
|
203
|
+
edgeDeletionBehavior: EntityEdgeDeletionBehavior.CASCADE_DELETE,
|
|
204
|
+
associatedEntityLookupByField: 'id',
|
|
205
|
+
edgeDeletionAuthorizationInferenceBehavior:
|
|
206
|
+
EntityEdgeDeletionAuthorizationInferenceBehavior.ONE_IMPLIES_ALL,
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
databaseAdapterFlavor: 'postgres',
|
|
211
|
+
cacheAdapterFlavor: 'redis',
|
|
212
|
+
}),
|
|
213
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
class TestLeafNoInferenceEntity extends Entity<TestLeafEntityFields, string, ViewerContext> {
|
|
219
|
+
static defineCompanionDefinition(): EntityCompanionDefinition<
|
|
220
|
+
TestLeafEntityFields,
|
|
221
|
+
string,
|
|
222
|
+
ViewerContext,
|
|
223
|
+
TestLeafNoInferenceEntity,
|
|
224
|
+
AlwaysAllowEntityPrivacyPolicy<
|
|
225
|
+
TestLeafEntityFields,
|
|
226
|
+
string,
|
|
227
|
+
ViewerContext,
|
|
228
|
+
TestLeafNoInferenceEntity
|
|
229
|
+
>
|
|
230
|
+
> {
|
|
231
|
+
return {
|
|
232
|
+
entityClass: TestLeafNoInferenceEntity,
|
|
233
|
+
entityConfiguration: new EntityConfiguration<TestLeafEntityFields>({
|
|
234
|
+
idField: 'id',
|
|
235
|
+
tableName: 'blah_3',
|
|
236
|
+
schema: {
|
|
237
|
+
id: new UUIDField({
|
|
238
|
+
columnName: 'custom_id',
|
|
239
|
+
}),
|
|
240
|
+
test_entity_id: new UUIDField({
|
|
241
|
+
columnName: 'test_entity_id',
|
|
242
|
+
association: {
|
|
243
|
+
associatedEntityClass: TestEntity,
|
|
244
|
+
edgeDeletionBehavior: EntityEdgeDeletionBehavior.CASCADE_DELETE,
|
|
245
|
+
},
|
|
246
|
+
}),
|
|
247
|
+
},
|
|
248
|
+
databaseAdapterFlavor: 'postgres',
|
|
249
|
+
cacheAdapterFlavor: 'redis',
|
|
250
|
+
}),
|
|
251
|
+
privacyPolicyClass: AlwaysAllowEntityPrivacyPolicy,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|