enhancement
major
minor
major
minor
major
#29639
TL-Script objectResolve(id, typeOrTable): resolution via a type including its subtypes or via a table name, objectTable delivers the table of an object, and an object the current user may not read is not resolved
Problem
objectId($x) (#29530, ObjectFunctions) delivers the table-local identifier of an object, short enough for a URL segment, and objectResolve(type, id) finds the object again: it looks in the one table TLModelUtil.getTable(type) assigns to the given type (CompatibilityService.getTableFor) and checks the hit with isCompatibleInstance. #29603 added objectKey($x) / objectResolveKey(text) for the complete key (Table:id, with branch and revision) as the purge functions need it. Three gaps remain:
1. Polymorphic display: the table of an instance is not known
A view displaying instances of a supertype (a table over my:Item whose subtypes my:Book and my:Film are stored in tables of their own) knows the supertype only. objectResolve(`my:Item, $id)` looks into the single table assigned to my:Item - none, when the abstract type has no table of its own - and misses every instance stored elsewhere. There is no function delivering the table of an object, and objectResolve takes no table.
2. Access: identifiers can be guessed
A table-local identifier is a small number and guessable, and a key is easily assembled from one. Neither objectResolve nor objectResolveKey checks whether the current user may read the object it resolves. The executor filters the final result of a script by the user's read rights (QueryExecutor.executeWith -> SearchExpression.filterSecurity). But:
- a value derived from the resolved object that is no business object passes the filter: objectResolve(...) != null (or a label, a count) tells whether an identifier exists, an existence oracle over the whole table;
- in the route-parameter use of #29530 (reverse="id -> objectResolve(...)") the resolved object is written into a channel of the view and reaches the display over that channel, not as a script result.
3. Chaining
The identifier is the varying argument; as second argument it cannot be the receiver of a chained call ($id.objectResolve(...)).
Lösung
- objectResolve(id, typeOrTable): the identifier comes first, so an incoming identifier is resolved in a chain: $id.objectResolve(`tl.accounts:Person)`. The second argument is
- a type (the normal case): every table storing the type or one of its subtypes is searched (TLModelUtil.potentialTables), a hit counts only if it is an instance of the type. For a type stored in one table this is a single lookup; a view over a supertype whose subtypes are stored in different tables resolves its rows through the supertype. Identifiers are allocated from one sequence for all tables, so the first compatible hit is the only one.
- a table name: exactly that table is searched - for type-agnostic routes that carry objectTable($x) next to objectId($x), e.g. item/:table/:id with $id.objectResolve($table).
- An unknown table, an invalid identifier, a type the object is no instance of, or anything else gives nothing. The function is not yet part of a released version, so the changed signature needs no migration outside the engine; every use in the engine (React demo views, the tile-stack test view, the JavaDoc examples of <frame>/`<param>`/`<tile-stack>`, docs/faq/react-view-layer.md) is converted to the chain form.
- objectTable($x): the name of the table storing the object; nothing for no object and for a transient one (like objectId).
- Access check: objectResolve gives nothing for an object the current user may not read (ModelAccessRights.isReadAllowed), exactly as for an identifier that names nothing - unless the script is evaluated with security disabled. That closes the existence oracle and makes a resolved object safe wherever it goes, channel or result.
- Security flag for TLScriptFunctions: a function defined as a static method of a TLScriptFunctions class can learn whether the script is evaluated with the user's access rights: a boolean parameter annotated @UsesSecurity is no script argument, the framework fills it with the security flag of the call (TLScriptMethod is a GenericMethodWithSecurity, so disabling the security of a script switches it off like every other security-aware expression). Such a function is never evaluated at compile time; the TLDoclet leaves the parameter out of the function documentation.
- objectResolveKey (#29603, not yet on master) gets the same check through @UsesSecurity in #29603 or after its merge.
Test
- test.com.top_logic.model.search.expr.config.operations.TestUsesSecurity - the flag is filled by the framework, switched off with the script's security, and is no script argument.
- test.com.top_logic.model.search.expr.config.operations.TestObjectFunctions - round trip via type, supertype spanning two tables and table name, chain calls, type mismatch, unknown table/identifier, unreadable object with security on and off.