Problem/Motivation
Exposure is decided once, for every consumer. The settings form lists the config objects to expose and the keys to exclude, and each consumer then receives that same set, differing only in the values it overrides.
A public marketing frontend and an internal tool read the same endpoint. Anything the internal tool needs has to be exposed to the public one as well, because the list is shared. There is no way to say that one consumer receives less.
Proposed resolution
Store an optional exclude list on the consumer, next to the overrides that already live there. Keys on it are removed from what that consumer receives, applied after the global exclusion list.
Exclusion only, and deliberately so. A per-consumer include was considered and rejected: it would have to reverse the global exclusion list, and that list is part of the module's security boundary. Today an administrator reads one screen and knows what leaves the site. If a consumer could restore an excluded key, answering "what is exposed" would mean reading every consumer, and a key excluded on purpose would no longer stay excluded. Narrowing carries no such risk.
This costs something. The site-wide list has to hold everything any consumer needs, so the most permissive consumer sets it and every other one narrows from there. A new consumer starts with everything the site exposes until it is narrowed. On a site where every consumer authenticates, that solves the whole of the "one app receives more than another" problem. Where the read permission is granted to anonymous, a request that does not name a consumer still receives the full site-wide set, so a key that must never reach an anonymous reader belongs on the global exclusion list rather than here.
Storage is additive: the consumer already stores its overrides in a map field, and the same shape holds a list of keys. Existing consumers with no list behave exactly as they do now.
A worked example
A frontend decides whether to show a create-account link by reading user.settings:register. Exposing user.settings to reach it also publishes anonymous, verify_mail, cancel_method, password_reset_timeout, password_strength and seven notify.* flags, because everything exposed reaches the client. Fifteen keys to answer one question.
The global exclusion list already solves that per site. A per-consumer list is what lets an internal tool keep a key the public frontend should not receive, and user.settings is the case to write the README section and the test fixtures around.
Remaining tasks
- Agree whether an entry names one key or a whole object's keys. One key at a time matches the global exclusion list it extends, and reads the same way on the form.
- Extend the per-consumer form: the overrides table already lists every exposed key, so the column fits there rather than on a third screen.
- Merge order in the resolver: exposed objects, global exclusions, this consumer's exclusions, then its overrides.
- Decide what an override on an excluded key means. Dropping the key from delivery while keeping the stored override is the least surprising: the exclusion decides what is sent, and the override reappears if the exclusion is lifted.
- Document that this narrows delivery and is not an access control. See the separate issue on per-consumer access.
API changes
None to the response shape. The document holds fewer keys for a consumer that configures a list.
Data model changes
One optional base field on the consumer entity, with an update hook for existing installs.
Comments