Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[incubator-kie-issues-994] kafka auth feature with message header records #3037

Merged
merged 4 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class JMSConstants {
public static final String TARGET_CAPABILITY_PROPERTY_NAME = "kie_target_capability";
public static final String USER_PROPERTY_NAME = "kie_user";
public static final String PASSWRD_PROPERTY_NAME = "kie_password";
public static final String ASSERTION_PROPERTY_NAME = "kie_token";

public static final String INTERACTION_PATTERN_PROPERTY_NAME = "kie_interaction_pattern";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import org.kie.server.services.api.KieServerExtension;
import org.kie.server.services.impl.KieServerImpl;
import org.kie.server.services.impl.KieServerLocator;
import org.kie.server.services.impl.security.adapters.JMSSecurityAdapter;
import org.kie.server.services.impl.security.adapters.BrokerSecurityAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -208,7 +208,7 @@ public void onMessage(Message message) {
logger.warn("Unable to retrieve user name and/or password, from message");
}
if (username != null && password != null) {
JMSSecurityAdapter.login(username, password);
BrokerSecurityAdapter.login(username, password);
} else {
logger.warn("Unable to login to JMSSecurityAdapter, user name and/or password missing");
}
Expand Down Expand Up @@ -320,10 +320,10 @@ public void onMessage(Message message) {
} catch (JMSRuntimeException runtimeException) {
logger.error("Error while attempting to close connection/session",runtimeException);
} finally {
JMSSecurityAdapter.logout();
BrokerSecurityAdapter.logout();
}
} else {
JMSSecurityAdapter.logout();
BrokerSecurityAdapter.logout();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>

<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.evidence.Evidence;

public class JMSSecurityAdapter implements SecurityAdapter {
private static final Logger logger = LoggerFactory.getLogger(JMSSecurityAdapter.class);
public class BrokerSecurityAdapter implements SecurityAdapter {

private static final Logger logger = LoggerFactory.getLogger(BrokerSecurityAdapter.class);

private static final ServiceLoader<SecurityAdapter> securityAdapters = ServiceLoader.load(SecurityAdapter.class);
private static List<SecurityAdapter> adapters = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.kie.server.services.impl.security.adapters;

import java.util.List;

import org.kie.server.api.security.SecurityAdapter;
import org.kie.server.services.impl.util.JwtUserDetails;

public class JwtSecurityAdaptor implements SecurityAdapter {

private static ThreadLocal<JwtUserDetails> threadLocal = new ThreadLocal<JwtUserDetails>() {
@Override
public JwtUserDetails initialValue() {
return new JwtUserDetails();
}
};

public static void login(JwtUserDetails userDetails) {
threadLocal.set(userDetails);
}

@Override
public String getUser(Object... params) {
JwtUserDetails userDetails = threadLocal.get();
if (!userDetails.isLogged()) {
return null;
}
return userDetails.getUser();
}

@Override
public List<String> getRoles(Object... params) {
return threadLocal.get().getRoles();
}

public static void logout() {
threadLocal.set(null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.kie.server.services.impl.util;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;

public class JwtService {

private JWTVerifier verifier;

private Algorithm algorithm;
private String issuer;

private JwtService() {
this(Algorithm.none());
}

private JwtService(Algorithm algorithm) {
this(algorithm, "jBPM");
}

private JwtService(Algorithm algorithm, String issuer) {
this.issuer = issuer;
this.algorithm = algorithm;
this.verifier = JWT.require(algorithm)
.withIssuer(issuer)
.build();
}

public String getIssuer() {
return issuer;
}

public String token(String user, String ...roles) {
return JWT.create().withIssuer(this.issuer).withSubject(user).withClaim("roles", Arrays.asList(roles)).sign(algorithm);
}

public static JwtServiceBuilder newJwtServiceBuilder() {
return new JwtServiceBuilder();
}

public static class JwtServiceBuilder {
Algorithm algorithm;
String issuer;

public JwtServiceBuilder keys(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
this.algorithm = Algorithm.RSA256(publicKey, privateKey);
return this;
}

public JwtServiceBuilder issuer(String issuer) {
this.issuer = issuer;
return this;
}

public JwtService build() {
return new JwtService(algorithm != null ? algorithm : Algorithm.none(), issuer != null ? issuer : "jBPM");
}

}

public JwtUserDetails decodeUserDetails(String token) {
try {
DecodedJWT decodedJWT = verifier.verify(token);
String user = decodedJWT.getSubject();
Claim rolesClaim = decodedJWT.getClaim("roles");
List<String> roles = rolesClaim.asList(String.class);
return new JwtUserDetails(user, roles != null ? roles : new ArrayList<>());
} catch (JWTVerificationException exception) {
throw new IllegalArgumentException(exception);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.server.services.impl.util;

import java.util.ArrayList;
import java.util.List;

public class JwtUserDetails {
String user;
List<String> roles;

public JwtUserDetails() {
this.user = null;
this.roles = new ArrayList<>();
}

public JwtUserDetails(String user, List<String> roles) {
this.user = user;
this.roles = roles;
}

public List<String> getRoles() {
return roles;
}

public String getUser() {
return user;
}

public boolean isLogged() {
return user != null;
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
org.kie.server.services.impl.security.adapters.TomcatSecurityAdapter
org.kie.server.services.impl.security.adapters.JMSSecurityAdapter
mareknovotny marked this conversation as resolved.
Show resolved Hide resolved
org.kie.server.services.impl.security.adapters.WeblogicSecurityAdapter
org.kie.server.services.impl.security.adapters.WebSphereSecurityAdapter
org.kie.server.services.impl.security.adapters.WebSphereSecurityAdapter
org.kie.server.services.impl.security.adapters.JwtSecurityAdaptor
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.kie.server.services.impl.util;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

public class JwtUtilTest {

private static Logger LOGGER = LoggerFactory.getLogger(JwtUtilTest.class);

@Test
public void testJwtSigned() throws Exception {

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = kpg.generateKeyPair();

JwtService service = JwtService.newJwtServiceBuilder()
.keys((RSAPublicKey) keyPair.getPublic(), (RSAPrivateKey) keyPair.getPrivate())
.issuer("jBPM")
.build();

String token = service.token("myUser", "role1", "role2");

JwtUserDetails user = service.decodeUserDetails(token);
Assertions.assertThat(user.getUser()).isEqualTo("myUser");
Assertions.assertThat(user.getRoles()).containsExactly("role1", "role2");
}

@Test(expected = IllegalArgumentException.class)
public void testJwtBadSigned() throws Exception {

KeyPairGenerator kpgIn = KeyPairGenerator.getInstance("RSA");
KeyPair keyPairIn = kpgIn.generateKeyPair();

JwtService serviceInput = JwtService.newJwtServiceBuilder()
.keys((RSAPublicKey) keyPairIn.getPublic(), (RSAPrivateKey) keyPairIn.getPrivate())
.issuer("jBPM")
.build();

String token = serviceInput.token("myUser", "role1", "role2");

KeyPairGenerator kpgOut = KeyPairGenerator.getInstance("RSA");
KeyPair keyPairOut = kpgOut.generateKeyPair();

JwtService serviceOutput = JwtService.newJwtServiceBuilder()
.keys((RSAPublicKey) keyPairOut.getPublic(), (RSAPrivateKey) keyPairOut.getPrivate())
.issuer("jBPM")
.build();

serviceOutput.decodeUserDetails(token);
}

@Test
public void testJwtNotSigned() throws Exception {
JwtService service = JwtService.newJwtServiceBuilder()
.issuer("jBPM")
.build();

String token = service.token("myUser", "role1", "role2");
LOGGER.info(token);

JwtUserDetails user = service.decodeUserDetails(token);
Assertions.assertThat(user.getUser()).isEqualTo("myUser");
Assertions.assertThat(user.getRoles()).containsExactly("role1", "role2");
}

@Test
public void testJwtMissingSubjectInfo() throws Exception {
String token = JWT.create().withIssuer("jBPM").withClaim("roles", Arrays.asList("role1")).sign(Algorithm.none());
LOGGER.info(token);

JwtService service = JwtService.newJwtServiceBuilder()
.issuer("jBPM")
.build();
JwtUserDetails user = service.decodeUserDetails(token);
Assertions.assertThat(user.getUser()).isNull();
Assertions.assertThat(user.getRoles()).containsExactly("role1");
}

@Test
public void testJwtMissingRolesInfo() throws Exception {
String token = JWT.create().withIssuer("jBPM").withSubject("myUser").sign(Algorithm.none());
LOGGER.info(token);

JwtService service = JwtService.newJwtServiceBuilder()
.issuer("jBPM")
.build();
JwtUserDetails user = service.decodeUserDetails(token);
Assertions.assertThat(user.getUser()).isEqualTo("myUser");
Assertions.assertThat(user.getRoles()).isEmpty();
}

@Test
public void testJwtEmptyToken() throws Exception {
String token = JWT.create().withIssuer("jBPM").sign(Algorithm.none());
LOGGER.info(token);

JwtService service = JwtService.newJwtServiceBuilder()
.issuer("jBPM")
.build();
JwtUserDetails user = service.decodeUserDetails(token);
Assertions.assertThat(user.getUser()).isNull();
Assertions.assertThat(user.getRoles()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
import org.kie.server.services.jbpm.jpa.PersistenceUnitInfoImpl;
import org.kie.server.services.jbpm.jpa.PersistenceUnitInfoLoader;
import org.kie.server.services.jbpm.security.ElytronUserGroupCallbackImpl;
import org.kie.server.services.jbpm.security.JMSUserGroupAdapter;
import org.kie.server.services.jbpm.security.BrokerUserGroupAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -226,11 +226,11 @@ protected void configureServices(KieServerImpl kieServer, KieServerRegistry regi
if (ElytronIdentityProvider.available()) {
System.setProperty(KieServerConstants.CFG_HT_CALLBACK, "custom");
String name = ElytronUserGroupCallbackImpl.class.getName();
ElytronUserGroupCallbackImpl.addExternalUserGroupAdapter(new JMSUserGroupAdapter());
ElytronUserGroupCallbackImpl.addExternalUserGroupAdapter(new BrokerUserGroupAdapter());
System.setProperty(KieServerConstants.CFG_HT_CALLBACK_CLASS, name);
} else {
System.setProperty(KieServerConstants.CFG_HT_CALLBACK, "jaas");
JAASUserGroupCallbackImpl.addExternalUserGroupAdapter(new JMSUserGroupAdapter());
JAASUserGroupCallbackImpl.addExternalUserGroupAdapter(new BrokerUserGroupAdapter());
}
}

Expand Down
Loading
Loading