@balena/pinejs 23.2.4 → 23.2.5-build-reduce-constrained-model-memory-243f046051d320f659c36f6ba28c4121d84e1c9c-1
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/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +30 -1
- package/CHANGELOG.md +6 -1
- package/VERSION +1 -1
- package/out/sbvr-api/permissions.js +36 -19
- package/out/sbvr-api/permissions.js.map +1 -1
- package/package.json +2 -2
- package/src/sbvr-api/permissions.ts +173 -159
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
Relationship,
|
|
8
8
|
RelationshipInternalNode,
|
|
9
9
|
RelationshipLeafNode,
|
|
10
|
-
RelationshipMapping,
|
|
11
10
|
SelectNode,
|
|
12
11
|
SelectQueryNode,
|
|
13
12
|
} from '@balena/abstract-sql-compiler';
|
|
@@ -769,175 +768,183 @@ const getAlias = (name: string) => {
|
|
|
769
768
|
return `permissions${permissionsJSON}`;
|
|
770
769
|
};
|
|
771
770
|
|
|
772
|
-
const rewriteRelationship =
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
771
|
+
const rewriteRelationship = (
|
|
772
|
+
value: Relationship,
|
|
773
|
+
name: string,
|
|
774
|
+
abstractSqlModel: AbstractSqlModel,
|
|
775
|
+
permissionsLookup: PermissionLookup,
|
|
776
|
+
vocabulary: string,
|
|
777
|
+
odata2AbstractSQL: OData2AbstractSQL,
|
|
778
|
+
) => {
|
|
779
|
+
let escapedName = sqlNameToODataName(name);
|
|
780
|
+
if (abstractSqlModel.tables[name]) {
|
|
781
|
+
escapedName = sqlNameToODataName(abstractSqlModel.tables[name].name);
|
|
782
|
+
}
|
|
785
783
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
784
|
+
const rewrite = (object: Relationship) => {
|
|
785
|
+
let ret = object;
|
|
786
|
+
if ('$' in ret && Array.isArray(ret.$)) {
|
|
787
|
+
// object is in the form of
|
|
788
|
+
// { "$": ["actor", ["actor", "id"]] } or { "$": ["device type"] }
|
|
789
|
+
// we are only interested in the first case, since this is a relationship
|
|
790
|
+
// to a different resource
|
|
791
|
+
const mapping = ret.$;
|
|
792
|
+
if (
|
|
793
|
+
mapping.length === 2 &&
|
|
794
|
+
Array.isArray(mapping[1]) &&
|
|
795
|
+
mapping[1].length === 2 &&
|
|
796
|
+
typeof mapping[1][0] === 'string'
|
|
797
|
+
) {
|
|
798
|
+
// now have ensured that mapping looks like ["actor", ["actor", "id"]]
|
|
799
|
+
// this relations ship means that:
|
|
800
|
+
// mapping[0] is the local field
|
|
801
|
+
// mapping[1] is the reference to the other resource, that joins this resource
|
|
802
|
+
// mapping[1][0] is the name of the other resource (actor in the example)
|
|
803
|
+
// mapping[1][1] is the name of the field on the other resource
|
|
804
|
+
//
|
|
805
|
+
// this therefore defines that the local field `actor` needs
|
|
806
|
+
// to match the `id` of the `actor` resources for the join
|
|
807
|
+
|
|
808
|
+
const possibleTargetResourceName = mapping[1][0];
|
|
809
|
+
|
|
810
|
+
// Skip this if we already shortcut this connection
|
|
811
|
+
if (possibleTargetResourceName.endsWith('$bypass')) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const targetResourceEscaped = sqlNameToODataName(
|
|
816
|
+
abstractSqlModel.tables[possibleTargetResourceName]?.name ??
|
|
817
|
+
possibleTargetResourceName,
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
// This is either a translated or bypassed resource we don't
|
|
821
|
+
// mess with these
|
|
822
|
+
if (targetResourceEscaped.includes('$')) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
let foundCanAccessLink = false;
|
|
815
827
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
828
|
+
try {
|
|
829
|
+
const odata = memoizedParseOdata(`/${targetResourceEscaped}`);
|
|
830
|
+
|
|
831
|
+
const collapsedPermissionFilters = buildODataPermission(
|
|
832
|
+
permissionsLookup,
|
|
833
|
+
methodPermissions.GET,
|
|
834
|
+
vocabulary,
|
|
835
|
+
targetResourceEscaped,
|
|
836
|
+
odata,
|
|
819
837
|
);
|
|
820
838
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
839
|
+
if (collapsedPermissionFilters == null) {
|
|
840
|
+
// If we have full access already then there's no need to
|
|
841
|
+
// check for/rewrite based on `canAccess`
|
|
824
842
|
return;
|
|
825
843
|
}
|
|
826
844
|
|
|
827
|
-
|
|
845
|
+
_.set(
|
|
846
|
+
odata,
|
|
847
|
+
['tree', 'options', '$filter'],
|
|
848
|
+
collapsedPermissionFilters,
|
|
849
|
+
);
|
|
828
850
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
methodPermissions.GET,
|
|
835
|
-
vocabulary,
|
|
836
|
-
targetResourceEscaped,
|
|
837
|
-
odata,
|
|
838
|
-
);
|
|
851
|
+
const canAccessFunction: ResourceFunction = function (
|
|
852
|
+
property: AnyObject,
|
|
853
|
+
) {
|
|
854
|
+
// remove method property so that we won't loop back here again at this point
|
|
855
|
+
delete property.method;
|
|
839
856
|
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
// check for/rewrite based on `canAccess`
|
|
843
|
-
return;
|
|
857
|
+
if (!this.defaultResource) {
|
|
858
|
+
throw new Error(`No resource selected in AST.`);
|
|
844
859
|
}
|
|
845
860
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
collapsedPermissionFilters,
|
|
861
|
+
const targetResourceAST = this.NavigateResources(
|
|
862
|
+
this.defaultResource,
|
|
863
|
+
property.name,
|
|
850
864
|
);
|
|
851
865
|
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
)
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
if (!this.defaultResource) {
|
|
859
|
-
throw new Error(`No resource selected in AST.`);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
const targetResourceAST = this.NavigateResources(
|
|
863
|
-
this.defaultResource,
|
|
864
|
-
property.name,
|
|
865
|
-
);
|
|
866
|
-
|
|
867
|
-
const targetResourceName = sqlNameToODataName(
|
|
868
|
-
targetResourceAST.resource.name,
|
|
869
|
-
);
|
|
870
|
-
const currentResourceName = sqlNameToODataName(
|
|
871
|
-
this.defaultResource.name,
|
|
872
|
-
);
|
|
866
|
+
const targetResourceName = sqlNameToODataName(
|
|
867
|
+
targetResourceAST.resource.name,
|
|
868
|
+
);
|
|
869
|
+
const currentResourceName = sqlNameToODataName(
|
|
870
|
+
this.defaultResource.name,
|
|
871
|
+
);
|
|
873
872
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
// return a true expression to not select the relationship, which might be virtual
|
|
881
|
-
return ['Boolean', true];
|
|
882
|
-
};
|
|
883
|
-
|
|
884
|
-
try {
|
|
885
|
-
// We need execute the abstract SQL compiler to traverse
|
|
886
|
-
// through the permissions for that resource, using a
|
|
887
|
-
// special canAccess callback.
|
|
888
|
-
odata2AbstractSQL.match(
|
|
889
|
-
odata.tree,
|
|
890
|
-
'GET',
|
|
891
|
-
[],
|
|
892
|
-
odata.binds.length,
|
|
893
|
-
{
|
|
894
|
-
canAccess: canAccessFunction,
|
|
895
|
-
},
|
|
896
|
-
);
|
|
897
|
-
} catch (e: any) {
|
|
898
|
-
throw new ODataParser.SyntaxError(e);
|
|
899
|
-
}
|
|
900
|
-
if (foundCanAccessLink) {
|
|
901
|
-
// store the resource name as it was with a $bypass
|
|
902
|
-
// suffix in this relationship, this means that the
|
|
903
|
-
// query generator will use the plain resource instead
|
|
904
|
-
// of the filtered resource.
|
|
905
|
-
mapping[1][0] = `${possibleTargetResourceName}$bypass`;
|
|
906
|
-
}
|
|
907
|
-
} catch (e) {
|
|
908
|
-
if (e === constrainedPermissionError) {
|
|
909
|
-
// ignore
|
|
910
|
-
return;
|
|
873
|
+
if (
|
|
874
|
+
currentResourceName === targetResourceEscaped &&
|
|
875
|
+
targetResourceName === escapedName
|
|
876
|
+
) {
|
|
877
|
+
foundCanAccessLink = true;
|
|
911
878
|
}
|
|
879
|
+
// return a true expression to not select the relationship, which might be virtual
|
|
880
|
+
return ['Boolean', true];
|
|
881
|
+
};
|
|
912
882
|
|
|
913
|
-
|
|
914
|
-
//
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
883
|
+
try {
|
|
884
|
+
// We need execute the abstract SQL compiler to traverse
|
|
885
|
+
// through the permissions for that resource, using a
|
|
886
|
+
// special canAccess callback.
|
|
887
|
+
odata2AbstractSQL.match(odata.tree, 'GET', [], odata.binds.length, {
|
|
888
|
+
canAccess: canAccessFunction,
|
|
889
|
+
});
|
|
890
|
+
} catch (e: any) {
|
|
891
|
+
throw new ODataParser.SyntaxError(e);
|
|
892
|
+
}
|
|
893
|
+
if (foundCanAccessLink) {
|
|
894
|
+
// store the resource name as it was with a $bypass
|
|
895
|
+
// suffix in this relationship, this means that the
|
|
896
|
+
// query generator will use the plain resource instead
|
|
897
|
+
// of the filtered resource.
|
|
898
|
+
ret = { ...ret };
|
|
899
|
+
const modifiedMapping = _.cloneDeep(mapping);
|
|
900
|
+
modifiedMapping[1]![0] = `${possibleTargetResourceName}$bypass`;
|
|
901
|
+
ret.$ = modifiedMapping;
|
|
902
|
+
}
|
|
903
|
+
} catch (e) {
|
|
904
|
+
if (e === constrainedPermissionError) {
|
|
905
|
+
// ignore
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
919
908
|
|
|
920
|
-
|
|
909
|
+
// TODO: We should investigate in detail why this error
|
|
910
|
+
// occurse. It might be able to get rid of this.
|
|
911
|
+
if (e instanceof ODataParser.SyntaxError) {
|
|
912
|
+
// ignore
|
|
913
|
+
return;
|
|
921
914
|
}
|
|
915
|
+
|
|
916
|
+
throw e;
|
|
922
917
|
}
|
|
923
918
|
}
|
|
919
|
+
}
|
|
924
920
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
921
|
+
if (typeof object === 'object') {
|
|
922
|
+
for (const key of Object.keys(object)) {
|
|
923
|
+
if (key === '$') {
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const v = (object as RelationshipInternalNode)[key];
|
|
927
|
+
// we want to recurse into the relationship path, but
|
|
928
|
+
// in case we hit a plain string, we don't need to bother
|
|
929
|
+
// checking it. This can happen since plain terms also have
|
|
930
|
+
// relationships to sbvr-types.
|
|
931
|
+
if (typeof v !== 'string') {
|
|
932
|
+
const changedEntry = rewrite(v);
|
|
933
|
+
if (changedEntry) {
|
|
934
|
+
ret = { ...ret };
|
|
935
|
+
(ret as RelationshipInternalNode)[key] = changedEntry;
|
|
933
936
|
}
|
|
934
|
-
}
|
|
937
|
+
}
|
|
935
938
|
}
|
|
936
|
-
}
|
|
939
|
+
}
|
|
937
940
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
+
if (ret !== object) {
|
|
942
|
+
return ret;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
return rewrite(value);
|
|
947
|
+
};
|
|
941
948
|
|
|
942
949
|
const rewriteRelationships = (
|
|
943
950
|
abstractSqlModel: AbstractSqlModel,
|
|
@@ -955,17 +962,20 @@ const rewriteRelationships = (
|
|
|
955
962
|
originalAbstractSQLModel,
|
|
956
963
|
);
|
|
957
964
|
|
|
958
|
-
const newRelationships =
|
|
959
|
-
|
|
960
|
-
rewriteRelationship(
|
|
961
|
-
|
|
965
|
+
const newRelationships = { ...relationships };
|
|
966
|
+
for (const name of Object.keys(newRelationships)) {
|
|
967
|
+
const changedEntry = rewriteRelationship(
|
|
968
|
+
newRelationships[name],
|
|
962
969
|
name,
|
|
963
970
|
abstractSqlModel,
|
|
964
971
|
permissionsLookup,
|
|
965
972
|
vocabulary,
|
|
966
973
|
odata2AbstractSQL,
|
|
967
974
|
);
|
|
968
|
-
|
|
975
|
+
if (changedEntry) {
|
|
976
|
+
newRelationships[name] = changedEntry as RelationshipInternalNode;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
969
979
|
|
|
970
980
|
return newRelationships;
|
|
971
981
|
};
|
|
@@ -985,14 +995,18 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
|
985
995
|
/** This is the final translated vocabulary that permissions get written against as that's what we will have to resolve permissions against */
|
|
986
996
|
finalVocabulary: string,
|
|
987
997
|
): AbstractSqlModel => {
|
|
988
|
-
const constrainedAbstractSqlModel: Omit<
|
|
998
|
+
const constrainedAbstractSqlModel: Omit<
|
|
999
|
+
AbstractSqlModel,
|
|
1000
|
+
'tables' | 'relationships'
|
|
1001
|
+
> & {
|
|
989
1002
|
tables: {
|
|
990
1003
|
[resourceName: string]: ShallowWritableOnly<AbstractSqlTable>;
|
|
991
1004
|
};
|
|
1005
|
+
relationships: ShallowWritableOnly<AbstractSqlModel['relationships']>;
|
|
992
1006
|
} = {
|
|
993
1007
|
...abstractSqlModel,
|
|
994
1008
|
synonyms: _.cloneDeep(abstractSqlModel.synonyms),
|
|
995
|
-
relationships:
|
|
1009
|
+
relationships: {},
|
|
996
1010
|
tables: {},
|
|
997
1011
|
};
|
|
998
1012
|
const baseTables = abstractSqlModel.tables;
|
|
@@ -1022,9 +1036,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
|
1022
1036
|
},
|
|
1023
1037
|
);
|
|
1024
1038
|
|
|
1025
|
-
const origRelationships = Object.keys(
|
|
1026
|
-
constrainedAbstractSqlModel.relationships,
|
|
1027
|
-
);
|
|
1039
|
+
const origRelationships = Object.keys(abstractSqlModel.relationships);
|
|
1028
1040
|
|
|
1029
1041
|
const alreadyConstrainedTables = new Map<
|
|
1030
1042
|
ShallowWritableOnly<AbstractSqlTable>,
|
|
@@ -1163,7 +1175,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
|
1163
1175
|
// expands and filters to unconstraint resources
|
|
1164
1176
|
constrainedAbstractSqlModel.relationships = rewriteRelationships(
|
|
1165
1177
|
constrainedAbstractSqlModel as AbstractSqlModel,
|
|
1166
|
-
|
|
1178
|
+
abstractSqlModel.relationships,
|
|
1167
1179
|
permissionsLookup,
|
|
1168
1180
|
finalVocabulary,
|
|
1169
1181
|
);
|
|
@@ -1187,8 +1199,10 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
|
1187
1199
|
return;
|
|
1188
1200
|
}
|
|
1189
1201
|
for (const relationship of origRelationships) {
|
|
1190
|
-
|
|
1191
|
-
|
|
1202
|
+
// TODO: Avoid this cloneDeep when not necessary
|
|
1203
|
+
relationships[`${relationship}$${alias}`] = relationships[
|
|
1204
|
+
relationship
|
|
1205
|
+
] = _.cloneDeep(relationships[relationship]);
|
|
1192
1206
|
namespaceRelationships(relationships[relationship], alias);
|
|
1193
1207
|
}
|
|
1194
1208
|
return relationships[permissionResourceName];
|