From c27045d53715baa53f4f53f81ccec84344fbf1dc Mon Sep 17 00:00:00 2001 From: dseurotech Date: Tue, 18 Jun 2024 15:31:29 +0200 Subject: [PATCH] :fix: For trusted calls, Rebuild(Trusted)SessionFilter was polluting the Response even when passing, as it was triggering the login process even for trusted calls and correcting the situation too late. This resulted in subsequent filters failing (in this case: if CORSResponseFilter was executed after RebuildSessionFilter, as it should because it needs information about the Session (e.g. the Scope)) Signed-off-by: dseurotech --- .../kapua/commons/security/KapuaSession.java | 26 ++++----- .../app/core/filter/RebuildSessionFilter.java | 47 ---------------- .../filter/RebuildTrustedSessionFilter.java | 56 +++++++++++++++++++ .../app/web/src/main/resources/shiro.ini | 4 +- .../app/web/src/main/webapp/WEB-INF/web.xml | 16 +++--- .../client/filter/SessionInfoFilter.java | 5 +- .../client/filter/SessionInfoHttpHeaders.java | 2 +- .../auth/KapuaTokenAuthenticationFilter.java | 4 ++ .../realm/AccessTokenAuthenticatingRealm.java | 20 +++++-- 9 files changed, 101 insertions(+), 79 deletions(-) delete mode 100644 job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildSessionFilter.java create mode 100644 job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildTrustedSessionFilter.java diff --git a/commons/src/main/java/org/eclipse/kapua/commons/security/KapuaSession.java b/commons/src/main/java/org/eclipse/kapua/commons/security/KapuaSession.java index e465cf53da3..3bc9dc94cb6 100644 --- a/commons/src/main/java/org/eclipse/kapua/commons/security/KapuaSession.java +++ b/commons/src/main/java/org/eclipse/kapua/commons/security/KapuaSession.java @@ -12,15 +12,15 @@ *******************************************************************************/ package org.eclipse.kapua.commons.security; -import org.eclipse.kapua.model.id.KapuaId; -import org.eclipse.kapua.service.authentication.KapuaPrincipal; -import org.eclipse.kapua.service.authentication.token.AccessToken; - import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; +import org.eclipse.kapua.model.id.KapuaId; +import org.eclipse.kapua.service.authentication.KapuaPrincipal; +import org.eclipse.kapua.service.authentication.token.AccessToken; + /** * Kapua session * @@ -37,10 +37,9 @@ public class KapuaSession implements Serializable { // TODO to be moved inside configuration service or something like that "fully.qualified.classname.methodname" ( for the constructor) static { - TRUSTED_CLASSES.add("org.eclipse.kapua.broker.core.plugin.KapuaSecurityContext."); TRUSTED_CLASSES.add("org.eclipse.kapua.commons.security.KapuaSecurityUtils.doPrivileged"); TRUSTED_CLASSES.add("org.eclipse.kapua.commons.event.jms.JMSServiceEventBus.setSession"); - TRUSTED_CLASSES.add("org.eclipse.kapua.job.engine.app.core.filter.RebuildSessionFilter.onAccessDenied"); + TRUSTED_CLASSES.add("org.eclipse.kapua.job.engine.app.core.filter.RebuildTrustedSessionFilter.isAccessAllowed"); } /** @@ -59,8 +58,7 @@ public class KapuaSession implements Serializable { private KapuaId userId; /** - * Trusted mode.
- * If true every rights check will be skipped, in other word the user is trusted so he is allowed to execute every operation defined in the system. + * Trusted mode.
If true every rights check will be skipped, in other word the user is trusted so he is allowed to execute every operation defined in the system. */ private boolean trustedMode; @@ -163,7 +161,8 @@ public KapuaSession(AccessToken accessToken, * @param accessToken * @param scopeId * @param userId - * @param openIDidToken the idToken obtained with an OpenID Connect login, contains user information, used for the logout + * @param openIDidToken + * the idToken obtained with an OpenID Connect login, contains user information, used for the logout */ public KapuaSession(AccessToken accessToken, KapuaId scopeId, KapuaId userId, String openIDidToken) { this(); @@ -221,16 +220,14 @@ public String getOpenIDidToken() { } /** - * Set the trusted mode status.
- * If true every rights check will be skipped, in other word the user is trusted so he is allowed to execute every operation defined in the system. + * Set the trusted mode status.
If true every rights check will be skipped, in other word the user is trusted so he is allowed to execute every operation defined in the system. */ final void setTrustedMode(boolean trustedMode) { this.trustedMode = trustedMode; } /** - * Return the trusted mode status.
- * If true every rights check will be skipped, in other word the user is trusted so he is allowed to execute every operation defined in the system. + * Return the trusted mode status.
If true every rights check will be skipped, in other word the user is trusted so he is allowed to execute every operation defined in the system. * * @return */ @@ -250,7 +247,8 @@ public boolean isUserInitiatedLogout() { /** * Set the logout as 'user initiated'. This will allow to avoid logging out from an OpenID session by using the OpenIDLogoutListener. * - * @param userInitiatedLogout 'true' if user initiated logout, 'false' otherwise + * @param userInitiatedLogout + * 'true' if user initiated logout, 'false' otherwise */ public void setUserInitiatedLogout(boolean userInitiatedLogout) { this.userInitiatedLogout = userInitiatedLogout; diff --git a/job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildSessionFilter.java b/job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildSessionFilter.java deleted file mode 100644 index 78a06080562..00000000000 --- a/job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildSessionFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021, 2022 Eurotech and/or its affiliates and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Eurotech - initial API and implementation - *******************************************************************************/ -package org.eclipse.kapua.job.engine.app.core.filter; - -import org.eclipse.kapua.app.api.core.auth.KapuaTokenAuthenticationFilter; -import org.eclipse.kapua.commons.security.KapuaSecurityUtils; -import org.eclipse.kapua.commons.security.KapuaSession; -import org.eclipse.kapua.job.engine.client.filter.SessionInfoHttpHeaders; -import org.eclipse.kapua.locator.KapuaLocator; -import org.eclipse.kapua.model.id.KapuaId; -import org.eclipse.kapua.model.id.KapuaIdFactory; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -public class RebuildSessionFilter extends KapuaTokenAuthenticationFilter { - - private final KapuaIdFactory kapuaIdFactory = KapuaLocator.getInstance().getFactory(KapuaIdFactory.class); - - @Override - protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { - HttpServletRequest httpRequest = (HttpServletRequest) request; - String authMode = httpRequest.getHeader(SessionInfoHttpHeaders.AUTH_MODE); - switch (authMode) { - case "trusted": - KapuaId scopeId = kapuaIdFactory.newKapuaId(httpRequest.getHeader(SessionInfoHttpHeaders.SCOPE_ID_HTTP_HEADER)); - KapuaId userId = kapuaIdFactory.newKapuaId(httpRequest.getHeader(SessionInfoHttpHeaders.USER_ID_HTTP_HEADER)); - KapuaSecurityUtils.setSession(KapuaSession.createFrom(scopeId, userId)); - return true; - case "access_token": - default: - return super.onAccessDenied(request, response); - } - } - -} diff --git a/job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildTrustedSessionFilter.java b/job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildTrustedSessionFilter.java new file mode 100644 index 00000000000..1aefe0e371e --- /dev/null +++ b/job-engine/app/core/src/main/java/org/eclipse/kapua/job/engine/app/core/filter/RebuildTrustedSessionFilter.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2021, 2022 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech - initial API and implementation + *******************************************************************************/ +package org.eclipse.kapua.job.engine.app.core.filter; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.kapua.app.api.core.auth.KapuaTokenAuthenticationFilter; +import org.eclipse.kapua.commons.security.KapuaSecurityUtils; +import org.eclipse.kapua.commons.security.KapuaSession; +import org.eclipse.kapua.job.engine.client.filter.SessionInfoHttpHeaders; +import org.eclipse.kapua.locator.KapuaLocator; +import org.eclipse.kapua.model.id.KapuaId; +import org.eclipse.kapua.model.id.KapuaIdFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is meant to be used ONLY internally (as in: not in services exposed to the world) to reconstruct locally a privileged session (e.g.: KapuaSecurityUtils.doPrivileged). It works in + * conjunction with a SessionInfo filter on the client side that sets the trusted header when executing the call from a privileged context + */ +public class RebuildTrustedSessionFilter extends KapuaTokenAuthenticationFilter { + + private final KapuaIdFactory kapuaIdFactory = KapuaLocator.getInstance().getFactory(KapuaIdFactory.class); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + final HttpServletRequest httpRequest = (HttpServletRequest) request; + final String authMode = httpRequest.getHeader(SessionInfoHttpHeaders.AUTH_MODE); + logger.trace("Passing through RebuildTrustedSessionFilter.onAccessDenied, authMode: {}, request: {}", authMode, request); + switch (authMode) { + case "trusted": + logger.trace("Passing through RebuildTrustedSessionFilter.onAccessDenied, scopeId: {}, userId: {}", + httpRequest.getHeader(SessionInfoHttpHeaders.SCOPE_ID_HTTP_HEADER), httpRequest.getHeader(SessionInfoHttpHeaders.USER_ID_HTTP_HEADER)); + KapuaId scopeId = kapuaIdFactory.newKapuaId(httpRequest.getHeader(SessionInfoHttpHeaders.SCOPE_ID_HTTP_HEADER)); + KapuaId userId = kapuaIdFactory.newKapuaId(httpRequest.getHeader(SessionInfoHttpHeaders.USER_ID_HTTP_HEADER)); + KapuaSecurityUtils.setSession(KapuaSession.createFrom(scopeId, userId)); + return true; + case "access_token": + default: + return super.isAccessAllowed(request, response, mappedValue); + } + } +} diff --git a/job-engine/app/web/src/main/resources/shiro.ini b/job-engine/app/web/src/main/resources/shiro.ini index 02a3dd56717..011abcfc94a 100644 --- a/job-engine/app/web/src/main/resources/shiro.ini +++ b/job-engine/app/web/src/main/resources/shiro.ini @@ -13,7 +13,7 @@ securityManager.authenticator = $authenticator # # Auth filters -rebuildSessionFilter = org.eclipse.kapua.job.engine.app.core.filter.RebuildSessionFilter +rebuildTrustedSessionFilter = org.eclipse.kapua.job.engine.app.core.filter.RebuildTrustedSessionFilter ########## # Realms # @@ -50,4 +50,4 @@ securityManager.rememberMeManager.cookie.maxAge = 0 # in web applications. We'll discuss this section in the # Web documentation -/v1/** = rebuildSessionFilter +/v1/** = rebuildTrustedSessionFilter diff --git a/job-engine/app/web/src/main/webapp/WEB-INF/web.xml b/job-engine/app/web/src/main/webapp/WEB-INF/web.xml index 148d3cacf9d..297a6828df2 100644 --- a/job-engine/app/web/src/main/webapp/WEB-INF/web.xml +++ b/job-engine/app/web/src/main/webapp/WEB-INF/web.xml @@ -35,21 +35,21 @@ - CORSResponseFilter - org.eclipse.kapua.commons.rest.filters.CORSResponseFilter + ShiroFilter + org.apache.shiro.web.servlet.ShiroFilter - CORSResponseFilter - /v1/* + ShiroFilter + /* - ShiroFilter - org.apache.shiro.web.servlet.ShiroFilter + CORSResponseFilter + org.eclipse.kapua.commons.rest.filters.CORSResponseFilter - ShiroFilter - /* + CORSResponseFilter + /v1/* diff --git a/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoFilter.java b/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoFilter.java index 0ac80a56b01..534b166a734 100644 --- a/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoFilter.java +++ b/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoFilter.java @@ -18,6 +18,8 @@ import org.eclipse.kapua.job.engine.client.settings.JobEngineClientSetting; import org.eclipse.kapua.job.engine.client.settings.JobEngineClientSettingKeys; import org.eclipse.kapua.locator.KapuaLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; @@ -33,11 +35,12 @@ public class SessionInfoFilter implements ClientRequestFilter { private final JobEngineClientSetting jobEngineClientSetting = KapuaLocator.getInstance().getComponent(JobEngineClientSetting.class); private final String jobEngineClientSettingAuthMode = jobEngineClientSetting.getString(JobEngineClientSettingKeys.JOB_ENGINE_CLIENT_AUTH_MODE, "access_token"); private final boolean jobEngineClientAuthTrusted = "trusted".equals(jobEngineClientSettingAuthMode); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void filter(ClientRequestContext requestContext) throws IOException { KapuaSession kapuaSession = KapuaSecurityUtils.getSession(); - + logger.trace("SessionInfoFilter.filter, jobEngineClientAuthTrusted: {}, sessionTrusted: {}", jobEngineClientAuthTrusted, kapuaSession.isTrustedMode()); if (jobEngineClientAuthTrusted || kapuaSession.isTrustedMode()) { requestContext.getHeaders().putSingle(SessionInfoHttpHeaders.AUTH_MODE, "trusted"); } else { diff --git a/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoHttpHeaders.java b/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoHttpHeaders.java index 820ba258b97..7205914df5e 100644 --- a/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoHttpHeaders.java +++ b/job-engine/client/src/main/java/org/eclipse/kapua/job/engine/client/filter/SessionInfoHttpHeaders.java @@ -13,7 +13,7 @@ package org.eclipse.kapua.job.engine.client.filter; /** - * List of headers name used by the {@link SessionInfoFilter} + * List of headers name used by the job engine client and read by the job engine service to rebuild the session * * @since 1.5.0 */ diff --git a/rest-api/core/src/main/java/org/eclipse/kapua/app/api/core/auth/KapuaTokenAuthenticationFilter.java b/rest-api/core/src/main/java/org/eclipse/kapua/app/api/core/auth/KapuaTokenAuthenticationFilter.java index 49313d0cb21..59dfe9951e8 100644 --- a/rest-api/core/src/main/java/org/eclipse/kapua/app/api/core/auth/KapuaTokenAuthenticationFilter.java +++ b/rest-api/core/src/main/java/org/eclipse/kapua/app/api/core/auth/KapuaTokenAuthenticationFilter.java @@ -23,6 +23,8 @@ import org.eclipse.kapua.service.authentication.shiro.exceptions.ExpiredAccessTokenException; import org.eclipse.kapua.service.authentication.shiro.exceptions.InvalidatedAccessTokenException; import org.eclipse.kapua.service.authentication.shiro.exceptions.MalformedAccessTokenException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -35,6 +37,7 @@ public class KapuaTokenAuthenticationFilter extends AuthenticatingFilter { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER = "Bearer"; private final CredentialsFactory credentialsFactory; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); public KapuaTokenAuthenticationFilter() { KapuaLocator locator = KapuaLocator.getInstance(); @@ -43,6 +46,7 @@ public KapuaTokenAuthenticationFilter() { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + logger.trace("Passing through KapuaTokenAuthenticationFilter.isAccessAllowed, request: {}, response: {}, mappedValue: {}", request, response, mappedValue); if (OPTIONS.equals(((HttpServletRequest) request).getMethod())) { return true; } diff --git a/service/security/shiro/src/main/java/org/eclipse/kapua/service/authentication/shiro/realm/AccessTokenAuthenticatingRealm.java b/service/security/shiro/src/main/java/org/eclipse/kapua/service/authentication/shiro/realm/AccessTokenAuthenticatingRealm.java index ef7457fd944..a55af1e1f15 100644 --- a/service/security/shiro/src/main/java/org/eclipse/kapua/service/authentication/shiro/realm/AccessTokenAuthenticatingRealm.java +++ b/service/security/shiro/src/main/java/org/eclipse/kapua/service/authentication/shiro/realm/AccessTokenAuthenticatingRealm.java @@ -12,6 +12,9 @@ *******************************************************************************/ package org.eclipse.kapua.service.authentication.shiro.realm; +import java.util.Date; +import java.util.Optional; + import org.apache.shiro.SecurityUtils; import org.apache.shiro.ShiroException; import org.apache.shiro.authc.AuthenticationException; @@ -53,9 +56,8 @@ import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.jwt.consumer.JwtContext; - -import java.util.Date; -import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * {@link AccessTokenCredentials} based {@link AuthenticatingRealm} implementation. @@ -73,6 +75,7 @@ public class AccessTokenAuthenticatingRealm extends KapuaAuthenticatingRealm { private final AccessTokenService accessTokenService = KapuaLocator.getInstance().getService(AccessTokenService.class); private final UserService userService = KapuaLocator.getInstance().getService(UserService.class); private final KapuaAuthenticationSetting authenticationSetting = KapuaLocator.getInstance().getComponent(KapuaAuthenticationSetting.class); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * Constructor @@ -87,11 +90,12 @@ public AccessTokenAuthenticatingRealm() { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { + logger.trace("processing authenticationToken: {}", authenticationToken); // Extract credentials AccessTokenCredentialsImpl token = (AccessTokenCredentialsImpl) authenticationToken; // Token data String jwt = token.getTokenId(); - + logger.trace("processing jwt: {}", jwt); //verify validity of this token final JwtClaims jwtClaims; try { @@ -117,17 +121,21 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authent JwtConsumer jwtConsumer = new JwtConsumerBuilder() .setVerificationKey(CertificateUtils.stringToCertificate(certificateInfo.getCertificate()).getPublicKey()) // Set public key .setExpectedIssuer(issuer) // Set expected issuer - .setRequireIssuedAt() // Set require reserved claim: iat + .setRequireIssuedAt() // Set require reserved claim: iatp .setRequireExpirationTime() // Set require reserved claim: exp .setRequireSubject() // // Set require reserved claim: sub .build(); // This validates JWT final JwtContext jwtContext = jwtConsumer.process(jwt); jwtClaims = jwtContext.getJwtClaims(); - // FIXME: JWT cert. could be cached to speed-up validation process + // FIXME: JWT cert. could be cached to speed-up validation process } catch (KapuaException ke) { + //As we are swallowing the original exception, let's at least log it + logger.error("Error processing Auth Token(KapuaException)", ke); throw new AuthenticationException(); } catch (InvalidJwtException e) { + //As we are swallowing the original exception, let's at least log it + logger.error("Error processing Auth Token (InvalidJwtException)", e); if (e.hasErrorCode(ErrorCodes.EXPIRED)) { throw new ExpiredAccessTokenException(); } else {