enhancement
major
minor
major
minor
critical
#29926
Derived attributes computed by a <query> storage evaluate to null for a user without Discover right on the object (regression of #29782 in 8.0.0-alpha11)
Scenario
An application model declares a computed attribute, e.g. the name of a CMS resource derived from its stored path (tl-releasenotes, tl.web.model.xml):
{{{#!xml <property name="name" type="tl.core:String"> <annotations> <storage-algorithm> <query expr="x -> $x.get(tl.web:Resource#path)"/> </storage-algorithm> </annotations> </property> }}}
A servlet without a logged-in user (NoContextServlet, anonymous request of the public website) resolves the requested page by comparing resource.getName() with a path segment.
- 8.0.0-alpha10: getName() returns the path, the public page /content/releases/ renders (200).
- 8.0.0-alpha11: getName(), tValueByName("name"), tValue(part) and AttributeOperations.getAttributeValue(obj, part) all return null for the same object and database, while tValueByName("path") returns releases. The public website redirects every request to the login page.
Verified by starting the same application with the same database on both engine versions.
Analysis
- AttributeByExpression.getAttributeValue evaluates the configured expression through executor(kb, model, getExpr()).execute(object) (static import from QueryExecutor). The executor of this storage keeps the default _securityEnabled == true; init only switches security off inside the expression tree (UpdateSecurityVisitor.disableSecurity(getExpr())) and on the separate ScriptTracer analyzer.
- Since 28fcd0735a (Ticket #29782, "Restrict the inputs of a secured script to discoverable objects"), QueryExecutor.executeWith maps every argument of a secured executor through SearchExpression.filterSecurity before the execution. For a user who may not discover the object, $x becomes null, $x.get(...) yields null, and the computed attribute is null.
- In alpha10, executeWith filtered only the result; a string result passed unchanged, so the storage's own definer's-rights decision (comment in AttributeByExpression.init: "a computed value must not apply the invoking user's model security ... access to the value is controlled by the read grant on the computed attribute itself") was effective.
- The application configures <class name="tl.model:TLObject" without-security="true"/> in its SecurityConfigurationService; the anonymous user of the NoContextServlet session (no Person) is nevertheless denied.
Expected
A computed attribute has the same value for every reader (definer's rights, as documented in AttributeByExpression.init): the storage executes its expression with security disabled on the executor as well (QueryExecutor.disableSecurity()), not only inside the expression tree, so that neither the inputs nor the intermediate results are filtered by the Discover right of the current user. The same check applies to AttributeValueLocatorByExpression and every other storage that evaluates a script with definer's rights.