@memberjunction/core 6.2.0-edge.5 → 6.2.0-edge.6

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.
Files changed (121) hide show
  1. package/dist/generic/BaseEntitySaveQueue.d.ts +15 -0
  2. package/dist/generic/BaseEntitySaveQueue.d.ts.map +1 -1
  3. package/dist/generic/BaseEntitySaveQueue.js +21 -0
  4. package/dist/generic/BaseEntitySaveQueue.js.map +1 -1
  5. package/dist/generic/alternateBaseViews.d.ts +119 -0
  6. package/dist/generic/alternateBaseViews.d.ts.map +1 -0
  7. package/dist/generic/alternateBaseViews.js +223 -0
  8. package/dist/generic/alternateBaseViews.js.map +1 -0
  9. package/dist/generic/baseEngine.d.ts +53 -9
  10. package/dist/generic/baseEngine.d.ts.map +1 -1
  11. package/dist/generic/baseEngine.js +194 -35
  12. package/dist/generic/baseEngine.js.map +1 -1
  13. package/dist/generic/baseEngineRegistry.d.ts.map +1 -1
  14. package/dist/generic/baseEngineRegistry.js +2 -1
  15. package/dist/generic/baseEngineRegistry.js.map +1 -1
  16. package/dist/generic/baseEntity.d.ts +153 -17
  17. package/dist/generic/baseEntity.d.ts.map +1 -1
  18. package/dist/generic/baseEntity.js +345 -46
  19. package/dist/generic/baseEntity.js.map +1 -1
  20. package/dist/generic/contextTokens.d.ts +136 -0
  21. package/dist/generic/contextTokens.d.ts.map +1 -0
  22. package/dist/generic/contextTokens.js +319 -0
  23. package/dist/generic/contextTokens.js.map +1 -0
  24. package/dist/generic/databaseProviderBase.d.ts +66 -5
  25. package/dist/generic/databaseProviderBase.d.ts.map +1 -1
  26. package/dist/generic/databaseProviderBase.js +159 -12
  27. package/dist/generic/databaseProviderBase.js.map +1 -1
  28. package/dist/generic/embeddedRecord.d.ts +6 -0
  29. package/dist/generic/embeddedRecord.d.ts.map +1 -1
  30. package/dist/generic/embeddedRecord.js +15 -1
  31. package/dist/generic/embeddedRecord.js.map +1 -1
  32. package/dist/generic/entityCompanion.d.ts +7 -0
  33. package/dist/generic/entityCompanion.d.ts.map +1 -1
  34. package/dist/generic/entityCompanion.js +8 -0
  35. package/dist/generic/entityCompanion.js.map +1 -1
  36. package/dist/generic/entityInfo.d.ts +228 -9
  37. package/dist/generic/entityInfo.d.ts.map +1 -1
  38. package/dist/generic/entityInfo.js +574 -65
  39. package/dist/generic/entityInfo.js.map +1 -1
  40. package/dist/generic/entityValueFieldSecurity.d.ts +139 -0
  41. package/dist/generic/entityValueFieldSecurity.d.ts.map +1 -0
  42. package/dist/generic/entityValueFieldSecurity.js +250 -0
  43. package/dist/generic/entityValueFieldSecurity.js.map +1 -0
  44. package/dist/generic/fieldSecuritySQLGate.d.ts +350 -0
  45. package/dist/generic/fieldSecuritySQLGate.d.ts.map +1 -0
  46. package/dist/generic/fieldSecuritySQLGate.js +898 -0
  47. package/dist/generic/fieldSecuritySQLGate.js.map +1 -0
  48. package/dist/generic/indexRestrictedFields.d.ts +47 -0
  49. package/dist/generic/indexRestrictedFields.d.ts.map +1 -0
  50. package/dist/generic/indexRestrictedFields.js +129 -0
  51. package/dist/generic/indexRestrictedFields.js.map +1 -0
  52. package/dist/generic/interfaces.d.ts +14 -1
  53. package/dist/generic/interfaces.d.ts.map +1 -1
  54. package/dist/generic/interfaces.js.map +1 -1
  55. package/dist/generic/localCacheManager.d.ts +80 -11
  56. package/dist/generic/localCacheManager.d.ts.map +1 -1
  57. package/dist/generic/localCacheManager.js +209 -38
  58. package/dist/generic/localCacheManager.js.map +1 -1
  59. package/dist/generic/logging.d.ts.map +1 -1
  60. package/dist/generic/logging.js +7 -1
  61. package/dist/generic/logging.js.map +1 -1
  62. package/dist/generic/metadata.d.ts +3 -2
  63. package/dist/generic/metadata.d.ts.map +1 -1
  64. package/dist/generic/metadata.js +5 -2
  65. package/dist/generic/metadata.js.map +1 -1
  66. package/dist/generic/platformSQL.d.ts +54 -0
  67. package/dist/generic/platformSQL.d.ts.map +1 -1
  68. package/dist/generic/platformSQL.js +55 -0
  69. package/dist/generic/platformSQL.js.map +1 -1
  70. package/dist/generic/providerBase.d.ts +254 -56
  71. package/dist/generic/providerBase.d.ts.map +1 -1
  72. package/dist/generic/providerBase.js +698 -195
  73. package/dist/generic/providerBase.js.map +1 -1
  74. package/dist/generic/queryInfo.d.ts +13 -0
  75. package/dist/generic/queryInfo.d.ts.map +1 -1
  76. package/dist/generic/queryInfo.js +13 -0
  77. package/dist/generic/queryInfo.js.map +1 -1
  78. package/dist/generic/queryInfoInterfaces.d.ts +8 -0
  79. package/dist/generic/queryInfoInterfaces.d.ts.map +1 -1
  80. package/dist/generic/queryTemplateContext.d.ts +49 -0
  81. package/dist/generic/queryTemplateContext.d.ts.map +1 -0
  82. package/dist/generic/queryTemplateContext.js +113 -0
  83. package/dist/generic/queryTemplateContext.js.map +1 -0
  84. package/dist/generic/recordChangeFieldSecurity.d.ts.map +1 -1
  85. package/dist/generic/recordChangeFieldSecurity.js +13 -2
  86. package/dist/generic/recordChangeFieldSecurity.js.map +1 -1
  87. package/dist/generic/relatedRecordCollection.d.ts +13 -0
  88. package/dist/generic/relatedRecordCollection.d.ts.map +1 -1
  89. package/dist/generic/relatedRecordCollection.js +44 -5
  90. package/dist/generic/relatedRecordCollection.js.map +1 -1
  91. package/dist/generic/runQuery.d.ts +42 -0
  92. package/dist/generic/runQuery.d.ts.map +1 -1
  93. package/dist/generic/runQuery.js +14 -0
  94. package/dist/generic/runQuery.js.map +1 -1
  95. package/dist/generic/runQuerySQLFilterImplementations.d.ts.map +1 -1
  96. package/dist/generic/runQuerySQLFilterImplementations.js +38 -2
  97. package/dist/generic/runQuerySQLFilterImplementations.js.map +1 -1
  98. package/dist/generic/saveEntityGraphOperation.d.ts.map +1 -1
  99. package/dist/generic/saveEntityGraphOperation.js +3 -0
  100. package/dist/generic/saveEntityGraphOperation.js.map +1 -1
  101. package/dist/generic/securityInfo.d.ts +11 -0
  102. package/dist/generic/securityInfo.d.ts.map +1 -1
  103. package/dist/generic/securityInfo.js +15 -0
  104. package/dist/generic/securityInfo.js.map +1 -1
  105. package/dist/generic/telemetryManager.d.ts +9 -0
  106. package/dist/generic/telemetryManager.d.ts.map +1 -1
  107. package/dist/generic/telemetryManager.js +5 -1
  108. package/dist/generic/telemetryManager.js.map +1 -1
  109. package/dist/index.d.ts +7 -0
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +7 -0
  112. package/dist/index.js.map +1 -1
  113. package/dist/views/runView.d.ts +89 -0
  114. package/dist/views/runView.d.ts.map +1 -1
  115. package/dist/views/runView.js +36 -0
  116. package/dist/views/runView.js.map +1 -1
  117. package/dist/views/viewProperties.d.ts +55 -0
  118. package/dist/views/viewProperties.d.ts.map +1 -0
  119. package/dist/views/viewProperties.js +258 -0
  120. package/dist/views/viewProperties.js.map +1 -0
  121. package/package.json +3 -3
@@ -0,0 +1,250 @@
1
+ import { CurrentFieldSecurityGeneration, FieldSecurityError, ReadableFieldsTransportKey } from './entityInfo.js';
2
+ import { RecordChangeFieldSecurityProjector } from './recordChangeFieldSecurity.js';
3
+ /**
4
+ * Returns `values` without the keys `denied` names, matched case- and whitespace-insensitively.
5
+ *
6
+ * `denied` is a denied-field set as {@link EntityInfo.GetDeniedReadFields} builds it: lowercased,
7
+ * holding both the `Name` and the `CodeName` of every denied field. Because the match is against
8
+ * the row's OWN keys, a row keyed by either spelling, in any case, is narrowed.
9
+ *
10
+ * Returns a shallow COPY when a key was removed and the original object by reference when none
11
+ * was, so a row that is already narrow costs nothing. The input is never modified, which is what
12
+ * makes this safe on frozen, shared cache rows.
13
+ */
14
+ export function OmitDeniedFieldKeys(values, denied) {
15
+ if (!values || typeof values !== 'object' || !denied || denied.size === 0) {
16
+ return values;
17
+ }
18
+ const source = values;
19
+ let copy = null;
20
+ for (const key of Object.keys(source)) {
21
+ if (!denied.has(key.trim().toLowerCase())) {
22
+ continue;
23
+ }
24
+ copy ??= { ...source };
25
+ delete copy[key];
26
+ }
27
+ return (copy ?? values);
28
+ }
29
+ /**
30
+ * Returns `values` without the keys `entity`'s own user may not read, for a payload built from
31
+ * that entity and sent to another tier (a graph save, a companion's wire form).
32
+ *
33
+ * The object already holds no denied value (its hydration points and `NewRecord` leave such
34
+ * fields not loaded), so `GetAll()` normally omits them by itself. This is the backstop for a key
35
+ * put back on the object without the readability check, which `SetMany` permits: the receiving
36
+ * tier refuses a restricted caller's input that names a field they may not read, so sending one
37
+ * fails the whole save.
38
+ *
39
+ * Returns the original object when nothing was removed, and a shallow copy otherwise.
40
+ */
41
+ export function OmitFieldsUnreadableByActiveUser(entity, values) {
42
+ if (!values || typeof values !== 'object') {
43
+ return values;
44
+ }
45
+ const source = values;
46
+ let copy = null;
47
+ for (const key of Object.keys(source)) {
48
+ if (entity.IsFieldReadableByActiveUser(key)) {
49
+ continue;
50
+ }
51
+ copy ??= { ...source };
52
+ delete copy[key];
53
+ }
54
+ return (copy ?? values);
55
+ }
56
+ /**
57
+ * The denied-READ set for `user` on `entityInfo`, or null when field security does not apply:
58
+ * it applies to neither the entity nor an IS-A ancestor, there is no user, or the user is denied nothing.
59
+ */
60
+ function deniedReadFieldsFor(entityInfo, user) {
61
+ if (!entityInfo.FieldLevelSecurityApplies) {
62
+ return null;
63
+ }
64
+ const denied = entityInfo.GetDeniedReadFields(user);
65
+ return denied.size > 0 ? denied : null;
66
+ }
67
+ /** The names of the fields a caller with this denied set may read, for {@link ReadableFieldsTransportKey}. */
68
+ function readableFieldNames(entityInfo, denied) {
69
+ return entityInfo.Fields
70
+ .filter((f) => !denied.has(f.Name.trim().toLowerCase()))
71
+ .map((f) => f.Name);
72
+ }
73
+ /** Field-security narrowing for one row: omission plus the optional readable-fields marker. */
74
+ function narrowRow(row, denied, readable) {
75
+ const narrowed = OmitDeniedFieldKeys(row, denied);
76
+ if (!readable) {
77
+ return narrowed;
78
+ }
79
+ const withMarker = narrowed === row
80
+ ? { ...row }
81
+ : narrowed;
82
+ withMarker[ReadableFieldsTransportKey] = readable;
83
+ return withMarker;
84
+ }
85
+ /**
86
+ * Projects a plain object of entity field values down to what `user` may READ.
87
+ *
88
+ * **The one output projection every transport uses**, so that no transport can apply only half
89
+ * of it. Three things are applied, matching what the GraphQL resolvers' field mapping does:
90
+ *
91
+ * 1. **Denied read fields are omitted**, under both the `Name` and `CodeName` spellings and in any
92
+ * case, because callers reach here with rows keyed either way. Omitted rather than nulled:
93
+ * a null is indistinguishable from a real value.
94
+ * 2. **{@link ReadableFieldsTransportKey}** is appended when `includeReadableFields` is set and
95
+ * the caller is denied something (see the option).
96
+ * 3. **The Record Changes payload projection**, which is keyed on the entity each ROW is about
97
+ * rather than on the entity being queried. `MJ: Record Changes` normally has field security
98
+ * off, so step 1 is a no-op for it, while its payload columns carry other entities' values.
99
+ *
100
+ * An entity object built for a restricted user does not hold denied values in the first place,
101
+ * so on that path this is defence in depth. It is the primary control for values that did not
102
+ * come from such an object: a system-identity load, a raw provider row, or a cached payload.
103
+ *
104
+ * Returns a COPY whenever anything changed, and the original object by reference when nothing
105
+ * did, so unrestricted callers pay nothing and frozen cache rows are never written to.
106
+ *
107
+ * @param provider Metadata source, used to resolve the per-row target entity for Record Changes.
108
+ * @param entityInfo The entity the values belong to. When absent the values are returned unchanged.
109
+ * @param values The field values. Never modified.
110
+ * **Fails open on a missing user.** With no `user` the values are returned unchanged, full width.
111
+ * That is deliberate: a browser or other single-user client has no per-call user to pass, and
112
+ * server-internal code running without one is trusted with full values. It also means a
113
+ * server-side transport that forgets to pass the caller sends everything, so every request-path
114
+ * caller must pass the requesting user.
115
+ *
116
+ * @param user The caller. When absent the values are returned unchanged (see above).
117
+ * @param options See {@link ProjectEntityValuesOptions}.
118
+ */
119
+ export function ProjectEntityValuesForUser(provider, entityInfo, values, user, options) {
120
+ if (!values || typeof values !== 'object' || !entityInfo || !user) {
121
+ return values;
122
+ }
123
+ const projected = ProjectEntityValueRowsForUser(provider, entityInfo, [values], user, options);
124
+ return projected?.[0] ?? values;
125
+ }
126
+ /**
127
+ * Array form of {@link ProjectEntityValuesForUser}. The denied set, the readable-fields list and
128
+ * the Record Changes projector are each built ONCE for the whole set: the projector memoizes its
129
+ * per-entity lookups for its own lifetime, and a denied set walks every field of the entity, so
130
+ * building either per row turns a per-request cost into a per-row one.
131
+ *
132
+ * Returns the ORIGINAL array when no row needed changing. Fails open on a missing user, as
133
+ * {@link ProjectEntityValuesForUser} does.
134
+ */
135
+ export function ProjectEntityValueRowsForUser(provider, entityInfo, rows, user, options) {
136
+ if (!rows?.length || !entityInfo || !user) {
137
+ return rows;
138
+ }
139
+ const denied = deniedReadFieldsFor(entityInfo, user);
140
+ const isRecordChanges = RecordChangeFieldSecurityProjector.IsRecordChangesEntity(entityInfo);
141
+ if (!denied && !isRecordChanges) {
142
+ return rows;
143
+ }
144
+ let projected = rows;
145
+ if (denied) {
146
+ const readable = options?.includeReadableFields ? readableFieldNames(entityInfo, denied) : null;
147
+ projected = rows.map((row) => narrowRow(row, denied, readable));
148
+ }
149
+ if (isRecordChanges) {
150
+ projected = new RecordChangeFieldSecurityProjector(provider, user).ProjectRows(projected);
151
+ }
152
+ return projected.some((row, i) => row !== rows[i]) ? projected : rows;
153
+ }
154
+ /**
155
+ * Keys a transport adds to client input that are not entity fields, and that
156
+ * {@link AssertClientInputWritableForUser} therefore must not treat as unknown.
157
+ */
158
+ export const ClientInputTransportKeys = [
159
+ 'OldValues___',
160
+ ReadableFieldsTransportKey,
161
+ 'RestoreContext___',
162
+ 'CloneContext___',
163
+ // The companion payload key, spelled out rather than imported: the module that defines it
164
+ // imports BaseEntity, which imports this module, and this array is built at load time.
165
+ 'Companions___',
166
+ '__typename',
167
+ ];
168
+ /**
169
+ * The wording for refusing a key in client input. Deliberately the SAME for a key that names a
170
+ * field the caller cannot read and a key that names no field at all, so the refusal cannot be
171
+ * used to learn which columns exist.
172
+ */
173
+ export function FieldSecurityClientInputMessage(key, entityName) {
174
+ return `Field '${key}' cannot be written on entity '${entityName}'.`;
175
+ }
176
+ /** {@link ClientInputTransportKeys}, lowercased and trimmed, as {@link AssertClientInputWritableForUser} matches them. */
177
+ const NORMALIZED_CLIENT_INPUT_TRANSPORT_KEYS = new Set(ClientInputTransportKeys.map((k) => k.trim().toLowerCase()));
178
+ const knownFieldKeysCache = new WeakMap();
179
+ /** Every key that names a field of `entityInfo` or of an IS-A ancestor, lowercased, by Name and CodeName. */
180
+ function knownFieldKeys(entityInfo) {
181
+ const fields = entityInfo.Fields;
182
+ const chain = entityInfo.ParentChain ?? [];
183
+ const generation = CurrentFieldSecurityGeneration();
184
+ const memo = knownFieldKeysCache.get(entityInfo);
185
+ if (memo && memo.fields === fields && memo.chain === chain && memo.generation === generation) {
186
+ return memo.keys;
187
+ }
188
+ const keys = new Set();
189
+ for (const entity of [entityInfo, ...chain]) {
190
+ for (const f of entity.Fields) {
191
+ keys.add(f.Name.trim().toLowerCase());
192
+ keys.add(f.CodeName.trim().toLowerCase());
193
+ }
194
+ }
195
+ knownFieldKeysCache.set(entityInfo, { fields, chain, generation, keys });
196
+ return keys;
197
+ }
198
+ /** Whether `normalized` is a transport key, built in or one the caller added. */
199
+ function isAllowedTransportKey(normalized, additional) {
200
+ return NORMALIZED_CLIENT_INPUT_TRANSPORT_KEYS.has(normalized) || (additional?.has(normalized) ?? false);
201
+ }
202
+ /**
203
+ * The write-side gate for transports that apply client input with `SetMany`: throws a
204
+ * {@link FieldSecurityError} when the input carries a key the caller may not send.
205
+ *
206
+ * Applies only to a caller who is denied at least one field on the entity. For that caller, a key
207
+ * is refused when it names a field they cannot READ, or when it names no field at all; both get
208
+ * the identical {@link FieldSecurityClientInputMessage}. Treating unknown keys the same way is
209
+ * what keeps the refusal from revealing whether a column exists: refusing only denied keys would
210
+ * answer "does `Salary` exist?" for any key a caller cares to try.
211
+ *
212
+ * Rejection rather than silent removal: a client holding a denied value can only have fabricated
213
+ * it, because no response it received contained one, so there is no legitimate request to save.
214
+ *
215
+ * Keys in {@link ClientInputTransportKeys}, and any in `options.additionalAllowedKeys`, are
216
+ * accepted. Matching is case- and whitespace-insensitive, by Name and CodeName, like `SetMany`.
217
+ * Callers who are denied nothing, or input for an entity without field security, are unaffected,
218
+ * so `SetMany`'s ordinary handling of unknown keys still applies to them.
219
+ *
220
+ * @param entityInfo The entity the input will be applied to.
221
+ * **Fails open on a missing user.** With no `user` the gate does nothing, so server-internal
222
+ * code, and a client with no per-call user, are unaffected. A server-side transport applying a
223
+ * request's input must therefore pass the requesting user, or nothing is checked.
224
+ *
225
+ * @param values The client input. Never modified.
226
+ * @param user The caller. When absent the gate does nothing (see above).
227
+ * @param options See {@link ClientInputGateOptions}.
228
+ */
229
+ export function AssertClientInputWritableForUser(entityInfo, values, user, options) {
230
+ if (!values || typeof values !== 'object' || !entityInfo || !user) {
231
+ return;
232
+ }
233
+ const denied = deniedReadFieldsFor(entityInfo, user);
234
+ if (!denied) {
235
+ return;
236
+ }
237
+ const extra = options?.additionalAllowedKeys;
238
+ const additional = extra && extra.length > 0 ? new Set(extra.map((k) => k.trim().toLowerCase())) : null;
239
+ const known = knownFieldKeys(entityInfo);
240
+ for (const key of Object.keys(values)) {
241
+ const normalized = key.trim().toLowerCase();
242
+ if (isAllowedTransportKey(normalized, additional)) {
243
+ continue;
244
+ }
245
+ if (denied.has(normalized) || !known.has(normalized)) {
246
+ throw new FieldSecurityError(key, entityInfo.Name, FieldSecurityClientInputMessage(key, entityInfo.Name));
247
+ }
248
+ }
249
+ }
250
+ //# sourceMappingURL=entityValueFieldSecurity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entityValueFieldSecurity.js","sourceRoot":"","sources":["../../src/generic/entityValueFieldSecurity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,350 @@
1
+ import { SQLScreeningDialect } from '@memberjunction/global';
2
+ import { EntityInfo, FieldSecurityError } from './entityInfo.js';
3
+ import { UserInfo } from './securityInfo.js';
4
+ /** Why {@link FieldSecuritySQLGate} refuses a piece of SQL. */
5
+ export type FieldSecurityViolationKind = 'DeniedField' | 'WholeRow' | 'Unresolvable' | 'UnknownRelation';
6
+ /** One reason {@link FieldSecuritySQLGate.FindViolation} refuses a piece of SQL. */
7
+ export interface FieldSecurityViolation {
8
+ Kind: FieldSecurityViolationKind;
9
+ /**
10
+ * `DeniedField`: the reference as the database reads it (a quoted identifier's bare name; on
11
+ * PostgreSQL an unquoted name in lower case). `WholeRow`: the construct, such as `FOR JSON`.
12
+ * `Unresolvable`: why the text could not be read. `UnknownRelation`: the relation the catalog
13
+ * does not know.
14
+ */
15
+ Reference: string;
16
+ /** The entity whose denial the reference reaches; null when the text could not be read. */
17
+ Entity: EntityInfo | null;
18
+ }
19
+ /** What {@link FieldSecuritySQLGate} needs to judge one piece of SQL for one user. */
20
+ export interface FieldSecuritySQLContext {
21
+ /** The acting user. */
22
+ User: UserInfo;
23
+ /** The dialect whose lexical rules the database applies to the text. */
24
+ Dialect: SQLScreeningDialect;
25
+ /** Every entity the provider knows, so base views and tables named in the text are recognized. */
26
+ Catalog: readonly EntityInfo[];
27
+ /**
28
+ * For a filter, sort or aggregate fragment: the entity whose view the fragment runs against.
29
+ * Its columns can be named without a qualifier, so its denied fields are always checked.
30
+ */
31
+ TargetEntity?: EntityInfo | null;
32
+ /** Entities the SQL is known to read beyond those its text names, such as a query's declared entities. */
33
+ ReadEntities?: readonly EntityInfo[];
34
+ /**
35
+ * For a whole statement, such as a saved query's SQL: refuse, for a user carrying any field
36
+ * denial, a relation that is not a CTE and not the base view or base table of a catalog entity
37
+ * (a custom view, synonym, table-valued function, temp table, or a name in another schema or
38
+ * database), and `EXEC`/`CALL`. What such a relation reads cannot be checked.
39
+ */
40
+ RefuseUnknownRelations?: boolean;
41
+ }
42
+ /**
43
+ * SECURITY — the field-level security gate for SQL text a caller authors: RunView filters, sorts and
44
+ * aggregate expressions, a saved view's stored clauses, dataset item filters, and the rendered SQL
45
+ * of a saved query.
46
+ *
47
+ * Output projection alone is not enough. A user denied `Salary` who can write
48
+ * `ExtraFilter: "Salary > 200000"`, sort by it, or aggregate `MIN(Salary)` reconstructs its values
49
+ * from which rows come back. This gate refuses such text before it reaches the cache or the
50
+ * database, for every caller: client requests, agents and actions alike.
51
+ *
52
+ * ## How it reads the text
53
+ *
54
+ * The text is split into tokens with the database's own lexical rules
55
+ * (`ScanSQLForScreening`), so a name inside a string literal or a comment is not a reference, and a
56
+ * `[bracketed]` or `"quoted"` name is read as the bare name it is. Every unquoted word and quoted
57
+ * identifier is then compared with every denied field of every entity the SQL reads:
58
+ * - the entity the fragment filters (its columns can be named unqualified);
59
+ * - the entities whose base view or base table the text names, such as a subquery's FROM;
60
+ * - any entities the caller says the SQL reads.
61
+ *
62
+ * A name matches a denied field by the rule `IdentifierMatches` states, or when the two are equal
63
+ * after Unicode compatibility folding, accent stripping and case folding, the comparisons a
64
+ * database collation can make. So a look-alike spelling that the database would still resolve to
65
+ * the denied column is refused too. Each name is compared on its own, so one denied name inside
66
+ * another can never hide it.
67
+ *
68
+ * A reference qualified by an alias or view name that binds only to entities whose denied set
69
+ * does not hold that name is not a reference to a denied field. An unknown qualifier is checked
70
+ * against every restricted entity.
71
+ *
72
+ * ## Whole-row constructs
73
+ *
74
+ * For a user denied something on an entity the SQL reads, constructs that read a whole row without
75
+ * naming its columns are refused: `SELECT *` and `alias.*` (except directly inside `EXISTS (…)`
76
+ * whose body has no set operation or row-picking ORDER BY), `FOR JSON`, `FOR XML`, any function of
77
+ * `*` other than `COUNT(*)`/`COUNT_BIG(*)` (for example `CHECKSUM(*)`), `NATURAL JOIN`, a column
78
+ * alias list on such a relation, and a relation or its alias used as a value (PostgreSQL's
79
+ * whole-row reference, as in `row_to_json(e)` or `TABLE vw`). A user with no denials on the
80
+ * entities read is not affected; ordinary subqueries keep working.
81
+ *
82
+ * ## Failing closed
83
+ *
84
+ * Text the scanner cannot read the way the database does is refused for any user who carries a
85
+ * denial on an entity the SQL could read. So is a name holding a control, format or
86
+ * default-ignorable character (a zero-width joiner, a direction mark), which a collation may give
87
+ * no weight to.
88
+ *
89
+ * ## Unknown columns
90
+ *
91
+ * A reference to a column that does not exist reaches the database, which rejects it. For a user
92
+ * carrying denials, {@link FieldSecuritySQLGate.ErrorForInvalidColumn} turns that rejection into the
93
+ * same {@link FieldSecurityError} a denied column gets, so the two cannot be told apart.
94
+ */
95
+ export declare class FieldSecuritySQLGate {
96
+ /**
97
+ * The first reason `sql` must be refused for `context.User`, or null when it may run.
98
+ *
99
+ * @param sql A filter, sort or aggregate fragment, or a whole statement
100
+ * @param context The user, dialect, catalog and the entities the SQL is known to read
101
+ */
102
+ static FindViolation(sql: string | null | undefined, context: FieldSecuritySQLContext): FieldSecurityViolation | null;
103
+ /**
104
+ * The first reason the sort fragment `orderBy` must be refused for `context.User`, or null.
105
+ * Everything {@link FindViolation} refuses, and, for a user denied a field on
106
+ * `context.TargetEntity`, a sort item that is a column position (`3 DESC`). A position names a
107
+ * column of the select list without spelling it, and an entity-object read selects every
108
+ * column, so `ORDER BY 3` can sort by a denied field. It is refused as a whole-row construct.
109
+ */
110
+ static FindSortViolation(orderBy: string | null | undefined, context: FieldSecuritySQLContext): FieldSecurityViolation | null;
111
+ /**
112
+ * Throws a {@link FieldSecurityError} when {@link FindViolation} refuses `sql`. The error names
113
+ * `entityName`, the entity the caller is reading, never the entity a subquery reaches, so a
114
+ * refusal says nothing a "no such column" error would not.
115
+ */
116
+ static AssertAllowed(sql: string | null | undefined, context: FieldSecuritySQLContext, entityName: string): void;
117
+ /**
118
+ * Checks a whole statement, such as a saved query's rendered SQL, against the denied fields of
119
+ * every entity it reads. The entities come from the base views and tables the text names, plus
120
+ * `readEntities`. References are checked wherever they appear: the select list, WHERE, JOIN,
121
+ * GROUP BY, ORDER BY and HAVING. For a user carrying any field denial, a relation the catalog
122
+ * does not know is refused too ({@link FieldSecuritySQLContext.RefuseUnknownRelations}).
123
+ * Throws a {@link FieldSecurityError} on the first violation, naming the entity whose denial
124
+ * it reached (or `fallbackEntityName` when none).
125
+ */
126
+ static AssertStatementAllowed(sql: string, user: UserInfo, catalog: readonly EntityInfo[], dialect: SQLScreeningDialect, readEntities?: readonly EntityInfo[], fallbackEntityName?: string): void;
127
+ /** The error a violation raises, naming `entityName`. */
128
+ static ErrorForViolation(violation: FieldSecurityViolation, entityName: string): FieldSecurityError;
129
+ /**
130
+ * For a user carrying field denials on `entity`, the {@link FieldSecurityError} a database's
131
+ * "no such column" rejection becomes, so a column that does not exist and a column that is
132
+ * denied produce the same error. Null when the user carries no denials there, or the error is
133
+ * not a "no such column" rejection.
134
+ *
135
+ * Reads SQL Server's `Invalid column name 'x'` and `The multi-part identifier "a.x" could not be
136
+ * bound`, and PostgreSQL's `column "x" does not exist` and `column a.x does not exist`.
137
+ */
138
+ static ErrorForInvalidColumn(error: unknown, entity: EntityInfo | null | undefined, user: UserInfo | null | undefined): FieldSecurityError | null;
139
+ /**
140
+ * The entities whose base view or base table `sql` reads as a relation: a name after FROM, JOIN
141
+ * or APPLY, or in a FROM list, wherever it appears (subqueries and CTE bodies included), with
142
+ * schema qualifiers skipped. A name in a literal or a comment is not a relation. A bare name
143
+ * shared by entities in several schemas yields each of them, so the set never misses a read.
144
+ * A relation inside a parenthesized join and PostgreSQL's `FROM ONLY x` count too. Null when
145
+ * the text cannot be read the way the database reads it.
146
+ */
147
+ static RelationEntities(sql: string | null | undefined, catalog: readonly EntityInfo[], dialect: SQLScreeningDialect): EntityInfo[] | null;
148
+ /** True when `user` is denied at least one field on at least one of `entities`. */
149
+ static UserCarriesDenials(entities: Iterable<EntityInfo | null | undefined>, user: UserInfo): boolean;
150
+ /**
151
+ * True when `user` is denied at least one field on any entity of `catalog`.
152
+ *
153
+ * Memoized per (catalog, user): after the first call it costs a few comparisons. The memo is
154
+ * discarded when the catalog array changes length, when the field-security generation moves
155
+ * (an entity rebuilt, a flag assigned, a cache invalidated; see
156
+ * `CurrentFieldSecurityGeneration`) or when the user's roles change, which are everything the
157
+ * answer depends on. A catalog swapped for a new array starts a new memo.
158
+ */
159
+ static CatalogHasDenialsForUser(catalog: readonly EntityInfo[] | null | undefined, user: UserInfo): boolean;
160
+ /**
161
+ * Every field `user` may not read across `catalog`: one `entityID:field,field` entry per
162
+ * entity on which the user is denied something, sorted. '' for a user denied nothing.
163
+ * Memoized exactly as {@link CatalogHasDenialsForUser}.
164
+ */
165
+ static CatalogDenialProfileKey(catalog: readonly EntityInfo[] | null | undefined, user: UserInfo): string;
166
+ /** The current summary for (`catalog`, `user`), rebuilt when any part of its key moved. */
167
+ private static catalogDenialSummary;
168
+ /** The uncached walk behind {@link CatalogDenialProfileKey}. */
169
+ private static buildDenialProfileKey;
170
+ /**
171
+ * The column a database "no such column" message names, or null. A qualified name gives its
172
+ * last part, which is what the gate reports for the same reference.
173
+ */
174
+ private static invalidColumnName;
175
+ /** Longest prefix of a database error message scanned for a column name. */
176
+ private static readonly maxErrorMessageScanLength;
177
+ /** The last dot-separated part of an identifier path, unquoted and unescaped. */
178
+ private static lastIdentifierPart;
179
+ /**
180
+ * The identifier path in a PostgreSQL `column <path> does not exist` message (for example
181
+ * `e.salry` or `"E"."Salry"`), or null. Scanned by hand rather than with a regular expression:
182
+ * the path's parts may themselves contain dots, and a pattern that allows that backtracks
183
+ * exponentially on crafted input.
184
+ */
185
+ private static postgresColumnPath;
186
+ /**
187
+ * Index just past an identifier path starting at `start`: a run of non-space characters in
188
+ * which double-quoted segments (with `""` escapes) may contain spaces. Returns `start` when no
189
+ * path starts there, and -1 for an unterminated quote.
190
+ */
191
+ private static identifierPathEnd;
192
+ /**
193
+ * The first call of a function the dialect forbids in caller SQL (one that runs SQL given as a
194
+ * string, reads files or other databases), or null. What such a call reads cannot be checked.
195
+ */
196
+ private static findDynamicSQLCall;
197
+ /**
198
+ * True when the user carries a denial on any entity the SQL could read: the target, the
199
+ * declared read entities (checked directly, as they need not be catalog members), or any
200
+ * catalog entity, since the text can name any of them. The catalog answer is memoized
201
+ * ({@link CatalogHasDenialsForUser}).
202
+ *
203
+ * Deliberately catalog-wide rather than "the target and read entities only". Which other
204
+ * entities a fragment reads is only known once its text has been read the way the database
205
+ * reads it: a subquery, a parenthesized join, a schema-qualified or bracketed name, or a name
206
+ * spelled with look-alike characters all reach another entity's view. And text that cannot be
207
+ * read at all, a dynamic-SQL call or an invisible character must be refused for such a user
208
+ * whatever it appears to name. Every one of those checks needs the tokens, so a user with a
209
+ * denial anywhere has every fragment tokenized; {@link analyze} then derives the read set, and
210
+ * a fragment whose read set carries none of the user's denials passes without the
211
+ * denied-name and whole-row checks.
212
+ */
213
+ private static mayReadRestrictedEntity;
214
+ /** Builds the relation bindings, the restricted set and its denied names. */
215
+ private static analyze;
216
+ /** Adds `entity` to the restricted set when the user is denied a field on it. */
217
+ private static addIfRestricted;
218
+ /** Records each relation the text names, its alias, and which of them are restricted. */
219
+ private static bindRelations;
220
+ /** Binds a qualifier to entities. */
221
+ private static bind;
222
+ /**
223
+ * Whether the relation name at `i` declares a relation: it follows FROM, JOIN or APPLY, or a
224
+ * comma in a FROM list, with any `schema .` / `database . schema .` qualifiers, a PostgreSQL
225
+ * `ONLY`, and the parentheses of a parenthesized join skipped.
226
+ */
227
+ private static isRelationDeclaration;
228
+ /** Whether the token at `j` is a FROM, JOIN or APPLY keyword, or a comma in a FROM list. */
229
+ private static introducesRelation;
230
+ /** The index of the first part of the dotted name that ends at `i` (`db . schema . name`). */
231
+ private static qualifierStart;
232
+ /**
233
+ * The clause keyword that governs the token at `i` at its own parenthesis depth: the nearest
234
+ * preceding SELECT, FROM, JOIN, WHERE, ON, GROUP, ORDER or HAVING at that depth. Inside the
235
+ * parentheses of a parenthesized join (`FROM (a x, b y)`) with no such keyword, `from`.
236
+ */
237
+ private static clauseAt;
238
+ /** Whether the `(` at `j` opens a parenthesized join: it stands where a relation is declared. */
239
+ private static opensJoinGroup;
240
+ /**
241
+ * The first relation the statement reads that is not a CTE and not the base view or base table
242
+ * of a catalog entity, or a stored-procedure call; null when there is none. A name qualified by
243
+ * a schema no entity of that name lives in, or by a database, is unknown; so is a function in
244
+ * a relation position (`FROM f(…)`).
245
+ */
246
+ private static findUnknownRelation;
247
+ /**
248
+ * Whether the name at `i` is the last part of a relation the statement reads: after FROM (of a
249
+ * SELECT, not `EXTRACT(… FROM x)` or `IS DISTINCT FROM x`), JOIN, APPLY, a FROM-list comma or
250
+ * PostgreSQL's `TABLE`, past qualifiers, `ONLY`, `LATERAL` and join-group parentheses.
251
+ */
252
+ private static isRelationTarget;
253
+ /** Whether a token is `ONLY` or `LATERAL`. */
254
+ private static isRelationModifier;
255
+ /** Whether the FROM at `j` is a SELECT's FROM clause. */
256
+ private static isSelectFrom;
257
+ /**
258
+ * Whether the relation whose last name part is at `i` is a CTE or a catalog entity's base view
259
+ * or base table, in that entity's schema when qualified.
260
+ */
261
+ private static isKnownRelation;
262
+ /**
263
+ * The folded names of the CTEs a statement defines: a name after WITH, RECURSIVE or a comma
264
+ * that is followed by an optional column list, AS, an optional `[NOT] MATERIALIZED`, and `(`.
265
+ */
266
+ private static cteNames;
267
+ /** Whether the tokens from `j` are `[(columns)] AS [NOT] [MATERIALIZED] (`. */
268
+ private static opensCTEBody;
269
+ /** The index of the `)` that closes the `(` at `open`, or -1. */
270
+ private static closingParen;
271
+ /**
272
+ * Whether the body of the `EXISTS (` whose parenthesis is at `open` has, at its own depth, a
273
+ * word that makes a `SELECT *` there read column values ({@link EXISTS_ROW_READING_WORDS}).
274
+ * An unclosed body counts as one that does.
275
+ */
276
+ private static existsBodyReadsRows;
277
+ /** The index of the alias after the relation at `i` (`x a`, `x AS a`), or null. */
278
+ private static aliasIndexAfter;
279
+ /** The first reference to a denied field, honoring qualifiers that bind to known entities. */
280
+ private static findDeniedReference;
281
+ /**
282
+ * The denied names a reference at `i` could reach: those of the entities its qualifier binds to,
283
+ * or every restricted entity's when it is unqualified or its qualifier is unknown.
284
+ */
285
+ private static candidatesFor;
286
+ /**
287
+ * A sort item of `orderBy` that is a column position, as a whole-row violation, or null. Only
288
+ * for a user denied a field on the target entity. An item counts when its first token, inside
289
+ * any opening parentheses, is a number.
290
+ */
291
+ private static findSortByPosition;
292
+ /** The first whole-row construct, or null. */
293
+ private static findWholeRowConstruct;
294
+ /** The whole-row construct that starts at token `i`, or null. */
295
+ private static wholeRowConstructAt;
296
+ /**
297
+ * What a `*` at `i` reads: null for a multiplication, `COUNT(*)`, `EXISTS (SELECT *` whose body
298
+ * has no set operation or row-picking ORDER BY at its own depth, or `alias.*` whose alias binds
299
+ * only to entities the user is denied nothing on.
300
+ */
301
+ private static starConstruct;
302
+ /**
303
+ * A restricted relation name or alias used other than as a declaration or a qualifier (`x.`):
304
+ * a whole-row value on PostgreSQL, or a column alias list after the declaration.
305
+ */
306
+ private static relationValueConstruct;
307
+ /** Whether a token can end the left operand of a multiplication. */
308
+ private static endsOperand;
309
+ /** Whether a token can start the right operand of a multiplication. */
310
+ private static startsOperand;
311
+ /**
312
+ * The name a "no such column" error would give for `name` as `sql` writes it, so a denied
313
+ * column is reported exactly as an unknown one. On PostgreSQL, MJ quotes a capitalized or
314
+ * dot-qualified word before the database sees it (`AutoQuoteLeavesWordBare`), so the error
315
+ * names such a word as written; a word left bare is folded to lower case. A quoted name, and
316
+ * every name on SQL Server, is reported as written. The first token spelled `name` decides.
317
+ *
318
+ * @param sql The caller's SQL
319
+ * @param name The reference as the database reads it
320
+ * @param dialect The dialect the SQL runs under
321
+ */
322
+ static ReportedColumnName(sql: string, name: string, dialect: SQLScreeningDialect): string;
323
+ /** A reference's name as {@link ReportedColumnName} gives it. */
324
+ private static reportedName;
325
+ private static isName;
326
+ private static isWord;
327
+ private static isSymbol;
328
+ /** Folded base view and base table names of every entity in the catalog. */
329
+ private static relationIndex;
330
+ }
331
+ /**
332
+ * An identifier reduced to the form a database collation may compare it in: Unicode compatibility
333
+ * decomposition (a KELVIN SIGN reads as `K`, a full-width letter as its ASCII form), accents and
334
+ * other combining marks removed, control, format and default-ignorable characters (zero-width
335
+ * joiners, direction marks, a soft hyphen) removed, case folded through upper case (`ß` and `ẞ` both read `ss`, a
336
+ * final `ς` reads `σ`), and surrounding spaces trimmed.
337
+ */
338
+ export declare function FoldIdentifier(name: string): string;
339
+ /**
340
+ * Whether a reference as written may name `fieldName` on some database: by the rule
341
+ * `IdentifierMatches` states, or when both fold to the same form ({@link FoldIdentifier}). Looser
342
+ * than any collation, so a security check that refuses on it never lets through a reference the
343
+ * database would resolve to the field.
344
+ */
345
+ export declare function ReferenceMayNameField(reference: string, fieldName: string): boolean;
346
+ /** The message for a whole-row construct refused for a user carrying field denials. */
347
+ export declare function FieldSecurityWholeRowMessage(construct: string, entityName: string): string;
348
+ /** The message for SQL that could not be read well enough to check against field permissions. */
349
+ export declare function FieldSecurityUnresolvableSQLMessage(entityName: string): string;
350
+ //# sourceMappingURL=fieldSecuritySQLGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldSecuritySQLGate.d.ts","sourceRoot":"","sources":["../../src/generic/fieldSecuritySQLGate.ts"],"names":[],"mappings":"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"}