NPE in ApiServlet.skip2FAcheckForUser on SAML login when 2FA is disabled (4.22.0.0)
- Langage dominant
- Java
- Étoiles
- 3.1k
- Forks
- 1.4k
- Merge moyen
- 6 j 19 h
- PR mergées (30 j)
- 32
Description
### problem
##### COMPONENT NAME
API, SAML
##### SUMMARY
Every API request issued after a successful `samlSso` callback throws a `NullPointerException` in `ApiServlet.skip2FAcheckForUser`, because `session.getAttribute("2FAuthenticated")` returns `null` and is unboxed directly to `boolean`. The SAML login flow does not set this session attribute, and the 2FA-disabled global settings are not consulted before the unboxing.
Local username/password login is unaffected — only the SAML path triggers the NPE.
##### STEPS TO REPRODUCE
1. Enable SAML2 plugin and configure an IdP (Keycloak in our case).
2. Ensure `enable.user.2fa=false` and `mandate.user.2fa=false`.
3. Authorize a CloudStack user for SAML via `authorizeSamlSso`.
4. Click "Login with SSO" → authenticate at IdP → redirected back to `/client/api?command=samlSso`.
5. Browser immediately fires follow-up API call (e.g. `listIdps`, `login`, etc.).
##### EXPECTED RESULTS
The follow-up API request completes; user lands on the dashboard. A `null` value for the `2FAuthenticated` session attribute should be treated as "2FA not required / not completed" rather than dereferenced as a `boolean`.
##### ACTUAL RESULTS
Use cannot login via SAML:
```java
2026-05-17 10:22:39,650 ERROR [c.c.a.ApiServlet] (qtp1047478056-364:[ctx-50e47dca]) (logid:59e6408b) unknown exception writing api response java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "javax.servlet.http.HttpSession.getAttribute(String)" is null
at com.cloud.api.ApiServlet.skip2FAcheckForUser(ApiServlet.java:512)
at com.cloud.api.ApiServlet.processRequestInContext(ApiServlet.java:361)
at com.cloud.api.ApiServlet$1.run(ApiServlet.java:193)
at org.apache.cloudstack.managed.context.impl.DefaultManagedContext$1.call(DefaultManagedContext.java:56)
at org.apache.cloudstack.managed.context.impl.DefaultManagedContext.callWithContext(DefaultManagedContext.java:103)
at org.apache.cloudstack.managed.context.impl.DefaultManagedContext.runWithContext(DefaultManagedContext.java:53)
at com.cloud.api.ApiServlet.processRequest(ApiServlet.java:190)
at com.cloud.api.ApiServlet.doPost(ApiServlet.java:149)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
... (Jetty frames truncated)
```
Client receives the auth failure / error response, lands back on the login screen.
This is my first ticket here, so be gentle please!
### versions
4.22.0 on Debian 13.4 via shapeblue
i cloudstack-common 4.22.0.0-shapeblue0 all A common package which contains files which are shared by several CloudStack packages
ii cloudstack-management 4.22.0.0-shapeblue0 all CloudStack server library
ii cloudstack-usage 4.22.0.0-shapeblue0 all CloudStack usage monitor
And just auto installed java 21 (I know the docs says 17, but I haven't been able to downgrade).
ii default-jdk-headless 2:1.21-76 amd64 Standard Java or Java compatible Development Kit (headless)
ii openjdk-21-jdk-headless:amd64 21.0.11+10-1~deb13u2 amd64 OpenJDK Development Kit (JDK) (headless)
ii openjdk-21-jre-headless:amd64 21.0.11+10-1~deb13u2 amd64 OpenJDK Java runtime, using Hotspot JIT (headless)
##### CONFIGURATION
- SAML2 plugin enabled (`saml2.enabled=true`)
- IdP: Keycloak 26.x, realm `lluw`, SAML client with dedicated `uid` mapper (User Property: `email`)
- 2FA disabled globally: `enable.user.2fa=false`, `mandate.user.2fa=false`
- Single management server, MySQL backend
### The steps to reproduce the bug
1. Enable SAML2 plugin and configure an IdP (Keycloak in our case).
2. Ensure `enable.user.2fa=false` and `mandate.user.2fa=false`.
3. Authorize a CloudStack user for SAML via `authorizeSamlSso`.
4. Click "Login with SSO" → authenticate at IdP → redirected back to `/client/api?command=samlSso`.
5 browser response:
```xml
531
Your authenticated user is not authorized for SAML Single Sign-On, please contact your administrator
```
##### LOG response
```java
2026-05-17 10:22:39,650 ERROR [c.c.a.ApiServlet] (qtp1047478056-364:[ctx-50e47dca]) (logid:59e6408b) unknown exception writing api response java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "javax.servlet.http.HttpSession.getAttribute(String)" is null
at com.cloud.api.ApiServlet.skip2FAcheckForUser(ApiServlet.java:512)
at com.cloud.api.ApiServlet.processRequestInContext(ApiServlet.java:361)
at com.cloud.api.ApiServlet$1.run(ApiServlet.java:193)
at org.apache.cloudstack.managed.context.impl.DefaultManagedContext$1.call(DefaultManagedContext.java:56)
at org.apache.cloudstack.managed.context.impl.DefaultManagedContext.callWithContext(DefaultManagedContext.java:103)
at org.apache.cloudstack.managed.context.impl.DefaultManagedContext.runWithContext(DefaultManagedContext.java:53)
at com.cloud.api.ApiServlet.processRequest(ApiServlet.java:190)
at com.cloud.api.ApiServlet.doPost(ApiServlet.java:149)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
... (Jetty frames truncated)
```
### What to do about it?
### Expected result
The follow-up API request completes; user lands on the dashboard. A `null` value for the `2FAuthenticated` session attribute should be treated as "2FA not required / not completed" rather than dereferenced as a `boolean`.
- `ApiServlet.java:512` reads the `2FAuthenticated` attribute and unboxes without a null check.
- The SAML auth command (`SAML2LoginAPIAuthenticatorCmd`) creates the session but does not set `2FAuthenticated`.
- Suggested fix: replace direct unboxing with `Boolean.TRUE.equals(session.getAttribute("2FAuthenticated"))`, or short-circuit when both `enable.user.2fa` and `mandate.user.2fa` are false.
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
Commencez dans ApiServlet.java à la ligne 512 et examinez comment skip2FAcheckForUser lit l’attribut de session 2FAuthenticated. Suivez ensuite SAML2LoginAPIAuthenticatorCmd afin de comparer la configuration de la session avec le flux de connexion local. Reproduisez le problème avec SAML2 activé et les deux paramètres 2FA désactivés ; c’est terminé lorsque la requête API suivante réussit sans la NullPointerException et que l’utilisateur atteint le tableau de bord.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- java
- Domaine
- api, authentication, backend
- Type d'issue
- Bug
- Difficulté
- 2/5
- Temps estimé
- 1-3 heures
- Activité
- Active
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 78/100