Skip to content

Commit

Permalink
Merge pull request #4059 from dseurotech/fix-rebuildSessionFilter
Browse files Browse the repository at this point in the history
🐛 RebuildSessionFilter incorrectly dirties the response
  • Loading branch information
Coduz authored Jun 19, 2024
2 parents 8e8cbfd + c27045d commit 6083dcf
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -37,10 +37,9 @@ public class KapuaSession implements Serializable {

// TODO to be moved inside configuration service or something like that "fully.qualified.classname.methodname" (<init> for the constructor)
static {
TRUSTED_CLASSES.add("org.eclipse.kapua.broker.core.plugin.KapuaSecurityContext.<init>");
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");
}

/**
Expand All @@ -59,8 +58,7 @@ public class KapuaSession implements Serializable {
private KapuaId userId;

/**
* Trusted mode.<br>
* If true every rights check will be skipped, in other word <b>the user is trusted so he is allowed to execute every operation</b> defined in the system.
* Trusted mode.<br> If true every rights check will be skipped, in other word <b>the user is trusted so he is allowed to execute every operation</b> defined in the system.
*/
private boolean trustedMode;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -221,16 +220,14 @@ public String getOpenIDidToken() {
}

/**
* Set the trusted mode status.<br>
* If true every rights check will be skipped, in other word <b>the user is trusted so he is allowed to execute every operation</b> defined in the system.
* Set the trusted mode status.<br> If true every rights check will be skipped, in other word <b>the user is trusted so he is allowed to execute every operation</b> defined in the system.
*/
final void setTrustedMode(boolean trustedMode) {
this.trustedMode = trustedMode;
}

/**
* Return the trusted mode status.<br>
* If true every rights check will be skipped, in other word <b>the user is trusted so he is allowed to execute every operation</b> defined in the system.
* Return the trusted mode status.<br> If true every rights check will be skipped, in other word <b>the user is trusted so he is allowed to execute every operation</b> defined in the system.
*
* @return
*/
Expand All @@ -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;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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);
}
}
}
4 changes: 2 additions & 2 deletions job-engine/app/web/src/main/resources/shiro.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 #
Expand Down Expand Up @@ -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
16 changes: 8 additions & 8 deletions job-engine/app/web/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@
<!-- Filters -->

<filter>
<filter-name>CORSResponseFilter</filter-name>
<filter-class>org.eclipse.kapua.commons.rest.filters.CORSResponseFilter</filter-class>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSResponseFilter</filter-name>
<url-pattern>/v1/*</url-pattern>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
<filter-name>CORSResponseFilter</filter-name>
<filter-class>org.eclipse.kapua.commons.rest.filters.CORSResponseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<filter-name>CORSResponseFilter</filter-name>
<url-pattern>/v1/*</url-pattern>
</filter-mapping>

<filter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down

0 comments on commit 6083dcf

Please sign in to comment.