minor
#29774
OpenAPI server: API key authentication cannot resolve an account — Person.byName called without an InteractionContext
Component: tl-service-openapi-server · Version: 8.0.0-alpha9 · Type: defect · Severity: blocker for any API-key-authenticated endpoint that must run as an account
Summary
APIKeyAuthenticator.checkKey calls Person.byName(userId) without wrapping it in TLContext.inSystemContext. Person.byName reads through the KnowledgeBase cache, which requires an InteractionContext — and none exists yet while a request is being authenticated. Any API key whose user-id names a real account therefore fails with
java.lang.IllegalStateException: KnowledgeBase operations require a valid InteractionContext
Effect
API key authentication is unusable whenever the request has to run as an account:
- user-id empty (technical user) → returns null, works, but establishes no account
- user-id naming a real account → throws, every request fails
Only the branch that resolves no account functions. This matters for tl-ai 2.x, which refuses any agent run that cannot name an authenticated principal ("An agent run acts as an authenticated user, and this run has none"), so an API-key-authenticated REST endpoint can never invoke an agent.
The code
com.top_logic.service.openapi.server.authentication.apikey.APIKeyAuthenticator#checkKey:
Person result = Person.byName(userName); // no context wrapper
com.top_logic.service.openapi.server.authentication.http.basic.BasicUserAuthenticator does it correctly:
return TLContext.inSystemContext(BasicUserAuthenticator.class, () -> {
Person person = Person.byName(login.getUser());
...
});
Reproduce
- Configure an OpenAPI server with <api-key-authentication domain="d" parameter-name="X-Key" position="header"/> and <api-key-server-secret domain="d" api-key="..." user-id="someAccount"/>, where someAccount exists.
- Require that authentication on an operation.
- Call the operation with a correct key.
Result: 500 / authentication failure, with the stack below. Expected: the request runs as someAccount.
Stack
java.lang.IllegalStateException: KnowledgeBase operations require a valid InteractionContext.
at com.top_logic.knowledge.service.db2.DBKnowledgeBase.currentInteraction(DBKnowledgeBase.java:3468)
at com.top_logic.knowledge.service.db2.DBKnowledgeBase.lookupDBContext(DBKnowledgeBase.java:2818)
at com.top_logic.knowledge.service.db2.DBKnowledgeBase.getCurrentDBContext(DBKnowledgeBase.java:3106)
at com.top_logic.knowledge.service.db2.KBCache.adaptToTransaction(KBCache.java:100)
at com.top_logic.knowledge.service.db2.AbstractKBCache.getValue(AbstractKBCache.java:110)
at com.top_logic.knowledge.util.ItemByNameCache.lookup(ItemByNameCache.java:264)
at com.top_logic.knowledge.wrap.person.Person.fromCache(Person.java:714)
at com.top_logic.knowledge.wrap.person.Person.byName(Person.java:693)
at com.top_logic.service.openapi.server.authentication.apikey.APIKeyAuthenticator.checkKey(APIKeyAuthenticator.java:99)
at com.top_logic.service.openapi.server.authentication.apikey.APIKeyAuthenticator.authenticate(APIKeyAuthenticator.java:87)
at com.top_logic.service.openapi.server.PathHandler.handleRequest(PathHandler.java:64)
at com.top_logic.service.openapi.server.OpenApiServer.process(OpenApiServer.java:573)
Suggested fix
Wrap the lookup as BasicUserAuthenticator does:
return TLContext.inSystemContext(APIKeyAuthenticator.class, () -> {
Person result = Person.byName(userName);
if (result == null) {
throw new AuthenticationFailure(I18NConstants.ERROR_REQUEST_USER_DOES_NOT_EXIST__NAME.fill(userName));
}
return result;
});
Worth checking ClientCredentialsAuthenticator and TokenBasedAuthenticator for the same omission.
Second, probably separate issue
The <secrets> element is order-sensitive within <instance>. Declared *before* <paths> the keys are not picked up at all and every request is answered "Invalid API key" however the key is supplied; moved *after* <paths> they are found. No configuration error is logged either way. If that ordering is required it should be validated or documented; if not, it is a second defect.
Workaround
Use <basic-authentication in-user-context="true"/>, which resolves the account correctly. Unsuitable for a browser client, since it means shipping an account password to the client.
Lösung
- Die API-Key-Authentifizierung des OpenAPI-Servers ermittelt den Account eines API-Keys (user-id) jetzt im System-Kontext (TLContext.inSystemContext), wie die Basic-Authentifizierung. Ein Request mit einem API-Key, dessen user-id einen existierenden Account benennt, läuft damit als dieser Account; ein nicht existierender Account wird weiterhin mit "Benutzer existiert nicht" abgewiesen.
- Die OAuth-Authentifizierungen (TokenBasedAuthenticator, ClientCredentialsAuthenticator) waren nicht betroffen, sie lösen den Account bereits in einer System-Interaktion auf.
- Reihenfolge von <secrets> und <paths>: nicht reproduzierbar. Die Konfiguration liefert in beiden Reihenfolgen dieselben Secrets, und der daraus erzeugte Authenticator akzeptiert den Key in beiden Fällen (Regressionstest TestAPIKeySecretsOrder). Die Meldung "Invalid API key" entsteht nur, wenn Secrets für die Domain vorhanden sind, der gesendete Key aber keinem davon entspricht; fehlen Secrets ganz, ist die Operation mit einer anderen Meldung nicht authentifizierbar. Die Ursache im gemeldeten Fall liegt daher vermutlich in der konkreten Konfiguration (z.B. Overlay mehrerer Konfigurationsdateien oder abweichender Key-Wert).