feat(security): add guest user attributes and get_guest_user_attribute() macro (#33924)

Co-authored-by: Yash Janoria <yash.janoria@314ecorp.com>
Co-authored-by: Evan <evan@preset.io>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@rusackas.com>
This commit is contained in:
Yash Janoria
2026-08-08 16:56:07 -07:00
committed by GitHub
co-authored by Yash Janoria Evan Claude Opus 4.8 Evan Rusackas
parent 8181917f79
commit 42e4030104
10 changed files with 1743 additions and 17 deletions
@@ -315,6 +315,76 @@ Here's a concrete example:
WHERE country_code = 'US'
```
**Guest User Attributes**
The `{{ get_guest_user_attribute('attribute_name') }}` macro returns a specific attribute value from the guest user context.
This is useful when working with embedded Superset where guest tokens can contain custom attributes that need to be
accessed in SQL queries.
This macro only works when the current user is a guest user (authenticated via guest token). If the current user is
not a guest user, or if the specified attribute doesn't exist, the macro will return `None` or the provided default value.
If you have caching enabled in your Superset configuration, then by default the resolved value (whether it
came from the guest token, a null attribute, or the provided default) will be used by Superset when
calculating the cache key. A cache key is a unique identifier that determines if there's a cache hit in the
future and Superset can retrieve cached data. Including the resolved value on every branch ensures two guests
whose tokens render different SQL never share a cache entry.
You can disable the inclusion of the attribute value in the calculation of the
cache key by adding the following parameter to your Jinja code, but only do so
when the value cannot affect the query results:
```
{{ get_guest_user_attribute('department', add_to_cache_keys=False) }}
```
You can also provide a default value if the attribute is not found:
```
{{ get_guest_user_attribute('region', default='US') }}
```
Here's a concrete example of using guest user attributes in a query:
```sql
SELECT *
FROM sales_data
WHERE region = '{{ get_guest_user_attribute("user_region", default="global") }}'
AND department = '{{ get_guest_user_attribute("department") }}'
```
:::warning[Security Warning]
Guest token attributes come from the embedding application. By default,
`get_guest_user_attribute()` escapes string values — including strings nested inside
arrays and object values, and caller-supplied defaults — through the database dialect's
literal rendering (the same mechanism as `url_param()`). This covers dialect-specific
escape characters such as the backslash on MySQL/MariaDB, so the example above is safe
to interpolate directly. If you pass `escape_result=False`, or interpolate non-string
values (numbers, booleans), you are responsible for validating or allowlisting the
values, since they originate outside Superset.
If a guest attribute is an array and you plan to pipe it through the `|where_in` filter
(for example `full_name IN {{ get_guest_user_attribute('names')|where_in }}`), call
`get_guest_user_attribute('names', escape_result=False)`. `where_in` already applies its
own dialect-safe quoting, so escaping the values twice can corrupt them (a value such as
`O'Brien` would come back doubly escaped and match nothing).
Only individual string values are escaped as SQL literals. Interpolating an entire array
or object directly (rather than through `|where_in`, or by accessing a specific element)
renders Python's string form of that structure, which is not valid SQL, and object keys
are not escaped at all. Use `|where_in` for arrays, `|tojson` where you need a
JSON-stringified value, or read individual keys/elements out of the structure yourself.
The same double-escaping problem described above for `|where_in` applies to `|tojson`:
pass `escape_result=False` before piping to `|tojson` (for example
`{{ get_guest_user_attribute('profile', escape_result=False)|tojson }}`), since JSON
already handles its own quoting and re-escaping a value first would corrupt it (a nested
string such as `O'Brien` would come back as the altered `O''Brien` in the serialized
JSON).
:::
### Explicitly Including Values in Cache Key
The `{{ cache_key_wrapper() }}` function explicitly instructs Superset to add a value to the