diff --git a/hopsworks-api/src/main/webapp/WEB-INF/web.xml b/hopsworks-api/src/main/webapp/WEB-INF/web.xml
index d1b6727200..78329f7ab3 100644
--- a/hopsworks-api/src/main/webapp/WEB-INF/web.xml
+++ b/hopsworks-api/src/main/webapp/WEB-INF/web.xml
@@ -340,7 +340,7 @@
BASIC
- cauthRealm
+ hopsworksrealm
javax.faces.FACELETS_SKIP_COMMENTS
diff --git a/hopsworks-ca/src/main/webapp/WEB-INF/web.xml b/hopsworks-ca/src/main/webapp/WEB-INF/web.xml
index 1edd1dc777..8553b55a6e 100644
--- a/hopsworks-ca/src/main/webapp/WEB-INF/web.xml
+++ b/hopsworks-ca/src/main/webapp/WEB-INF/web.xml
@@ -76,7 +76,7 @@
BASIC
- cauthRealm
+ hopsworksrealm
javax.faces.FACELETS_SKIP_COMMENTS
diff --git a/hopsworks-realm/README.md b/hopsworks-realm/README.md
new file mode 100644
index 0000000000..8688f13743
--- /dev/null
+++ b/hopsworks-realm/README.md
@@ -0,0 +1,39 @@
+## Hopsworks jdbc realm
+Hopsworks JDBC realm is a security realm for Payara with a JDBC backend.
+
+Payara's JDBC realm assumes a data model with two tables.
+One for user with encoded password and another with pairs of usernames and group names
+to define to which groups a user belongs.
+
+Hopsworks JDBC realm accepts two queries one to determine the password of the
+user based on the username and one to determine the groups the user belongs to based
+on the username. This allows it to handle any data model.
+
+
+### Build hopsworks JDBC realm
+```sh
+ mvn clean compile assembly:single
+```
+
+### Use hopsworks JDBC realm
+
+Copy ```hopsworks-realm-jar-with-dependencies.jar``` to ```[payara home installation]/domains/domain1/lib/hopsworks-realm.jar```
+
+#### Create the realm
+
+```sh
+PASSWORD_QUERY="SELECT password FROM hopsworks.users WHERE email = ?"
+GROUP_QUERY ="SELECT G.group_name from hopsworks.bbc_group AS G, hopsworks.user_group AS UG, hopsworks.users AS U WHERE U.email=? AND UG.gid = G.gid AND UG.uid = U.uid"
+
+${PAYARA_DIR}/bin/asadmin create-auth-realm \
+--login-module=io.hops.hopsworks.realm.jdbc.HopsworksLoginModule \
+--classname=io.hops.hopsworks.realm.jdbc.HopsworksJDBCRealm \
+--property=jaas-context=hopsworksJdbcRealm:datasource-jndi=jdbc/hopsworks:password-query=${PASSWORD_QUERY}:group-query=${GROUP_QUERY}:digest-algorithm=SHA-256:encoding=Hex \
+hopsworksrealm
+```
+
+1. _password-query_ query used to determine the password of the user based on the username (login name). default
+ ```"SELECT password FROM hopsworks.users WHERE email = ?"```
+2. _group-query_ query used to determine the groups the user belongs to based on the username (login name). default
+ ```"SELECT G.group_name from hopsworks.bbc_group AS G, hopsworks.user_group AS UG, hopsworks.users AS U WHERE U.email=? AND UG.gid = G.gid AND UG.uid = U.uid"```
+3. _datasource-jndi_ the datasource used to access the database.
\ No newline at end of file
diff --git a/hopsworks-realm/pom.xml b/hopsworks-realm/pom.xml
new file mode 100644
index 0000000000..0c272b048a
--- /dev/null
+++ b/hopsworks-realm/pom.xml
@@ -0,0 +1,71 @@
+
+
+
+ 4.0.0
+
+ io.hops
+ hopsworks
+ 3.8.0-SNAPSHOT
+ ../pom.xml
+
+
+ io.hops.hopsworks
+ hopsworks-realm
+ Hopsworks - realm
+ Hopsworks custom realm
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.6.0
+
+
+ fish.payara.extras
+ payara-embedded-web
+
+
+
+ hopsworks-realm
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.6.0
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
diff --git a/hopsworks-realm/src/main/java/io/hops/hopsworks/realm/jdbc/HopsworksJDBCRealm.java b/hopsworks-realm/src/main/java/io/hops/hopsworks/realm/jdbc/HopsworksJDBCRealm.java
new file mode 100644
index 0000000000..1a779bb8cb
--- /dev/null
+++ b/hopsworks-realm/src/main/java/io/hops/hopsworks/realm/jdbc/HopsworksJDBCRealm.java
@@ -0,0 +1,502 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://oss.oracle.com/licenses/CDDL+GPL-1.1
+ * or LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+// Portions Copyright [2017-2019] [Payara Foundation and/or its affiliates]
+// Portions Copyright (C) 2023, Hopsworks AB. All rights reserved
+package io.hops.hopsworks.realm.jdbc;
+
+import com.sun.enterprise.connectors.ConnectorRuntime;
+import com.sun.enterprise.security.auth.digest.api.DigestAlgorithmParameter;
+import com.sun.enterprise.security.auth.digest.api.Password;
+import com.sun.enterprise.security.auth.realm.BadRealmException;
+import com.sun.enterprise.security.auth.realm.InvalidOperationException;
+import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
+import com.sun.enterprise.security.auth.realm.NoSuchUserException;
+import com.sun.enterprise.security.auth.realm.Realm;
+import com.sun.enterprise.security.ee.auth.realm.DigestRealmBase;
+import com.sun.enterprise.util.Utility;
+import org.glassfish.hk2.api.ActiveDescriptor;
+import org.glassfish.hk2.utilities.BuilderHelper;
+import org.glassfish.internal.api.Globals;
+import org.jvnet.hk2.annotations.Service;
+
+import javax.security.auth.login.LoginException;
+import javax.sql.DataSource;
+import java.io.Reader;
+import java.nio.charset.CharacterCodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.logging.Level;
+
+import static java.lang.Character.toLowerCase;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.logging.Level.FINE;
+import static java.util.logging.Level.SEVERE;
+
+@Service
+public class HopsworksJDBCRealm extends DigestRealmBase {
+ public static final String AUTH_TYPE = "hopsworks-jdbc-realm";
+ public static final String PRE_HASHED = "HASHED";
+ public static final String PARAM_DATASOURCE_JNDI = "datasource-jndi";
+ public static final String PARAM_DB_USER = "db-user";
+ public static final String PARAM_DB_PASSWORD = "db-password";
+
+
+ public static final String PARAM_DIGEST_ALGORITHM = "digest-algorithm";
+ public static final String NONE = "none";
+ public static final String PARAM_ENCODING = "encoding";
+ public static final String HEX = "hex";
+ public static final String BASE64 = "base64";
+ public static final String DEFAULT_ENCODING = HEX; // for digest only
+
+ public static final String PARAM_CHARSET = "charset";
+ public static final String PARAM_PASSWORD_QUERY = "password-query";
+ public static final String PARAM_GROUP_QUERY = "group-query";
+ public static final String DEFAULT_PASSWORD_QUERY = "SELECT password FROM hopsworks.users WHERE email = ?";
+ public static final String DEFAULT_GROUP_QUERY =
+ "SELECT G.group_name from hopsworks.bbc_group AS G, hopsworks.user_group AS UG, " +
+ "hopsworks.users AS U WHERE U.email=? AND UG.gid = G.gid AND UG.uid = U.uid";
+
+ private static final char[] HEXADECIMAL =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ private String passwordQuery;
+ private String groupQuery;
+ private Map> groupCache;
+ private Vector emptyVector;
+ private MessageDigest md = null;
+ private ActiveDescriptor connectorRuntimeDescriptor;
+
+
+ @Override
+ public synchronized void init(Properties props) throws BadRealmException, NoSuchRealmException {
+ super.init(props);
+ final String jaasCtx = props.getProperty(Realm.JAAS_CONTEXT_PARAM);
+ final String dbUser = props.getProperty(PARAM_DB_USER);
+ final String dbPassword = props.getProperty(PARAM_DB_PASSWORD);
+ final String dsJndi = props.getProperty(PARAM_DATASOURCE_JNDI);
+ final String digestAlgorithm = props.getProperty(PARAM_DIGEST_ALGORITHM, getDefaultDigestAlgorithm());
+ final String charset = props.getProperty(PARAM_CHARSET);
+ String encoding = props.getProperty(PARAM_ENCODING);
+
+ connectorRuntimeDescriptor = getConnectorRuntimeDescriptor();
+
+ passwordQuery = props.getProperty(PARAM_PASSWORD_QUERY, DEFAULT_PASSWORD_QUERY);
+ groupQuery = props.getProperty(PARAM_GROUP_QUERY, DEFAULT_GROUP_QUERY);
+
+ if (jaasCtx == null) {
+ throw new BadRealmException(sm.getString("realm.missingprop", JAAS_CONTEXT_PARAM, "JDBCRealm"));
+ }
+
+ if (dsJndi == null) {
+ throw new BadRealmException(sm.getString("realm.missingprop", PARAM_DATASOURCE_JNDI, "JDBCRealm"));
+ }
+
+ if (!NONE.equalsIgnoreCase(digestAlgorithm)) {
+ try {
+ md = MessageDigest.getInstance(digestAlgorithm);
+ } catch(NoSuchAlgorithmException e) {
+ throw new BadRealmException(sm.getString("jdbcrealm.notsupportdigestalg", digestAlgorithm));
+ }
+ }
+ if (md != null && encoding == null) {
+ encoding = DEFAULT_ENCODING;
+ }
+
+ this.setProperty(Realm.JAAS_CONTEXT_PARAM, jaasCtx);
+ if (dbUser != null && dbPassword != null) {
+ this.setProperty(PARAM_DB_USER, dbUser);
+ this.setProperty(PARAM_DB_PASSWORD, dbPassword);
+ }
+ this.setProperty(PARAM_DATASOURCE_JNDI, dsJndi);
+ this.setProperty(PARAM_DIGEST_ALGORITHM, digestAlgorithm);
+ if (encoding != null) {
+ this.setProperty(PARAM_ENCODING, encoding);
+ }
+ if (charset != null) {
+ this.setProperty(PARAM_CHARSET, charset);
+ }
+
+ if (_logger.isLoggable(Level.FINEST)) {
+ _logger.finest("JDBCRealm : " +
+ Realm.JAAS_CONTEXT_PARAM + "= " + jaasCtx + ", " +
+ PARAM_DATASOURCE_JNDI + " = " + dsJndi + ", " +
+ PARAM_DB_USER + " = " + dbUser + ", " +
+ PARAM_DIGEST_ALGORITHM + " = " + digestAlgorithm + ", " +
+ PARAM_ENCODING + " = " + encoding + ", " +
+ PARAM_CHARSET + " = " + charset);
+ }
+
+ groupCache = new HashMap<>();
+ emptyVector = new Vector<>();
+ }
+
+ @SuppressWarnings("unchecked")
+ private ActiveDescriptor getConnectorRuntimeDescriptor() {
+ return (ActiveDescriptor) Globals.getStaticHabitat()
+ .getBestDescriptor(BuilderHelper.createContractFilter(ConnectorRuntime.class.getName()));
+ }
+
+ @Override
+ public String getAuthType() {
+ return AUTH_TYPE;
+ }
+
+ /**
+ * Returns the name of all the groups that this user belongs to. It loads the result from groupCache first. This is
+ * called from web path group verification, though it should not be.
+ *
+ * @param username Name of the user in this realm whose group listing is needed.
+ * @return Enumeration of group names (strings).
+ * @exception InvalidOperationException thrown if the realm does not support this operation - e.g. Certificate realm
+ * does not support this operation.
+ */
+ @Override
+ public Enumeration getGroupNames(String username) throws InvalidOperationException, NoSuchUserException {
+ Vector vector = groupCache.get(username);
+ if (vector == null) {
+ String[] grps = findGroups(username);
+ setGroupNames(username, grps);
+ vector = groupCache.get(username);
+ }
+ return vector.elements();
+ }
+
+ private void setGroupNames(String username, String[] groups) {
+ Vector v = null;
+
+ if (groups == null) {
+ v = emptyVector;
+
+ } else {
+ v = new Vector<>(groups.length + 1);
+ for (String group : groups) {
+ v.add(group);
+ }
+ }
+
+ synchronized (this) {
+ groupCache.put(username, v);
+ }
+ }
+
+ /**
+ * Invoke the native authentication call.
+ *
+ * @param username User to authenticate.
+ * @param password Given password.
+ * @return groups of valid user or null.
+ */
+ public String[] authenticate(String username, char[] password) {
+ String[] groups = null;
+ if (isUserValid(username, password)) {
+ groups = findGroups(username);
+ groups = addAssignGroups(groups);
+ setGroupNames(username, groups);
+ }
+ return groups;
+ }
+
+ @Override
+ public boolean validate(String username, DigestAlgorithmParameter[] params) {
+ final Password pass = getPassword(username);
+ if (pass == null) {
+ return false;
+ }
+ return validate(pass, params);
+ }
+
+ private Password getPassword(String username) {
+
+ Connection connection = null;
+ PreparedStatement statement = null;
+ ResultSet resultSet = null;
+
+ try {
+ connection = getConnection();
+ statement = connection.prepareStatement(passwordQuery);
+ statement.setString(1, username);
+ resultSet = statement.executeQuery();
+
+ if (resultSet.next()) {
+ String password = resultSet.getString(1);
+
+ if (PRE_HASHED.equalsIgnoreCase(getProperty(PARAM_ENCODING))) {
+ return new Password() {
+
+ @Override
+ public byte[] getValue() {
+ return password.getBytes();
+ }
+
+ @Override
+ public int getType() {
+ return HASHED;
+ }
+ };
+ } else {
+ return new Password() {
+
+ @Override
+ public byte[] getValue() {
+ return password.getBytes();
+ }
+
+ @Override
+ public int getType() {
+ return PLAIN_TEXT;
+ }
+ };
+ }
+ }
+ } catch (Exception ex) {
+ _logger.log(SEVERE, "jdbcrealm.invaliduser", username);
+ _logger.log(SEVERE, "Cannot validate user", ex);
+ } finally {
+ close(connection, statement, resultSet);
+ }
+
+ return null;
+ }
+
+ /**
+ * Test if a user is valid
+ *
+ * @param user user's identifier
+ * @param userPassword user's password
+ * @return true if valid
+ */
+ private boolean isUserValid(String user, char[] userPassword) {
+ Connection connection = null;
+ PreparedStatement statement = null;
+ ResultSet resultSet = null;
+ boolean valid = false;
+
+ try {
+ char[] hashedUserPassword = hashPassword(userPassword);
+ connection = getConnection();
+ statement = connection.prepareStatement(passwordQuery);
+ statement.setString(1, user);
+ resultSet = statement.executeQuery();
+
+ if (resultSet.next()) {
+ // Obtain the password as a char[] with a max size of 50
+ try (Reader reader = resultSet.getCharacterStream(1)) {
+ char[] pwd = new char[1024];
+ int noOfChars = reader.read(pwd);
+
+ /*
+ * Since pwd contains 1024 elements arbitrarily initialized, construct a new char[] that has the right no of
+ * char elements to be used for equal comparison
+ */
+ if (noOfChars < 0) {
+ noOfChars = 0;
+ }
+
+ char[] dbPassword = new char[noOfChars];
+ System.arraycopy(pwd, 0, dbPassword, 0, noOfChars);
+ if (HEX.equalsIgnoreCase(getProperty(PARAM_ENCODING))) {
+ valid = true;
+ // Do a case-insensitive equals
+ for (int i = 0; i < noOfChars; i++) {
+ if (!(toLowerCase(dbPassword[i]) == toLowerCase(hashedUserPassword[i]))) {
+ valid = false;
+ break;
+ }
+ }
+ } else {
+ valid = Arrays.equals(dbPassword, hashedUserPassword);
+ }
+ if (!valid) {
+ _logger.finest(() -> "User '" + user + "' password mismatch!");
+ }
+ }
+ } else {
+ _logger.finest(() -> "User '" + user + "' not found in the database!");
+ }
+ } catch (SQLException ex) {
+ _logger.log(Level.SEVERE, "jdbcrealm.invaliduserreason", new String[] { user, ex.toString() });
+ _logger.log(FINE, "Cannot validate user", ex);
+ } catch (Exception ex) {
+ _logger.log(Level.SEVERE, "jdbcrealm.invaliduser", user);
+ _logger.log(FINE, "Cannot validate user", ex);
+ } finally {
+ close(connection, statement, resultSet);
+ }
+
+ return valid;
+ }
+
+ private char[] hashPassword(char[] password) throws CharacterCodingException {
+ byte[] bytes = null;
+ char[] result = null;
+ String charSet = getProperty(PARAM_CHARSET);
+ bytes = Utility.convertCharArrayToByteArray(password, charSet);
+
+ if (md != null) {
+ synchronized (md) {
+ md.reset();
+ bytes = md.digest(bytes);
+ }
+ }
+
+ String encoding = getProperty(PARAM_ENCODING);
+ if (HEX.equalsIgnoreCase(encoding)) {
+ result = hexEncode(bytes);
+ } else if (BASE64.equalsIgnoreCase(encoding)) {
+ result = base64Encode(bytes).toCharArray();
+ } else { // no encoding specified
+ result = Utility.convertByteArrayToCharArray(bytes, charSet);
+ }
+ return result;
+ }
+
+ private char[] hexEncode(byte[] bytes) {
+ StringBuilder sb = new StringBuilder(2 * bytes.length);
+ for (byte b : bytes) {
+ int low = b & 0x0f;
+ int high = (b & 0xf0) >> 4;
+ sb.append(HEXADECIMAL[high]);
+ sb.append(HEXADECIMAL[low]);
+ }
+ char[] result = new char[sb.length()];
+ sb.getChars(0, sb.length(), result, 0);
+ return result;
+ }
+
+ private String base64Encode(byte[] bytes) {
+ return new String(Base64.getMimeEncoder().encode(bytes), UTF_8);
+ }
+
+ /**
+ * Delegate method for retreiving users groups
+ *
+ * @param user user's identifier
+ * @return array of group key
+ */
+ private String[] findGroups(String user) {
+ Connection connection = null;
+ PreparedStatement statement = null;
+ ResultSet rs = null;
+ try {
+ connection = getConnection();
+ statement = connection.prepareStatement(groupQuery);
+ statement.setString(1, user);
+ rs = statement.executeQuery();
+ final List groups = new ArrayList<>();
+ while (rs.next()) {
+ groups.add(rs.getString(1));
+ }
+ final String[] groupArray = new String[groups.size()];
+ return groups.toArray(groupArray);
+ } catch (Exception ex) {
+ _logger.log(Level.SEVERE, "jdbcrealm.grouperror", user);
+ if (_logger.isLoggable(Level.FINE)) {
+ _logger.log(Level.FINE, "Cannot load group", ex);
+ }
+ return null;
+ } finally {
+ close(connection, statement, rs);
+ }
+ }
+
+ private void close(Connection conn, PreparedStatement stmt, ResultSet rs) {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (Exception ex) {
+ }
+ }
+
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (Exception ex) {
+ }
+ }
+
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (Exception ex) {
+ }
+ }
+ }
+
+
+ /**
+ * Return a connection from the properties configured
+ * @return a connection
+ */
+ private Connection getConnection() throws LoginException {
+
+ final String dsJndi = this.getProperty(PARAM_DATASOURCE_JNDI);
+ final String dbUser = this.getProperty(PARAM_DB_USER);
+ final String dbPassword = this.getProperty(PARAM_DB_PASSWORD);
+ try{
+ final ConnectorRuntime connectorRuntime = Globals.getStaticHabitat()
+ .getServiceHandle(connectorRuntimeDescriptor).getService();
+ final DataSource dataSource = (DataSource) connectorRuntime.lookupNonTxResource(dsJndi,false);
+ Connection connection;
+ if (dbUser != null && dbPassword != null) {
+ connection = dataSource.getConnection(dbUser, dbPassword);
+ } else {
+ connection = dataSource.getConnection();
+ }
+ return connection;
+ } catch(Exception ex) {
+ LoginException loginEx = new LoginException(sm.getString("jdbcrealm.cantconnect", dsJndi, dbUser));
+ loginEx.initCause(ex);
+ throw loginEx;
+ }
+ }
+}
diff --git a/hopsworks-realm/src/main/java/io/hops/hopsworks/realm/jdbc/HopsworksLoginModule.java b/hopsworks-realm/src/main/java/io/hops/hopsworks/realm/jdbc/HopsworksLoginModule.java
new file mode 100644
index 0000000000..f7d1dde4ae
--- /dev/null
+++ b/hopsworks-realm/src/main/java/io/hops/hopsworks/realm/jdbc/HopsworksLoginModule.java
@@ -0,0 +1,73 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+// Portions Copyright [2017-2018] [Payara Foundation and/or its affiliates]
+// Portions Copyright (C) 2023, Hopsworks AB. All rights reserved
+package io.hops.hopsworks.realm.jdbc;
+
+import com.sun.enterprise.security.BasePasswordLoginModule;
+
+import javax.security.auth.login.LoginException;
+
+import java.util.Arrays;
+
+import static java.util.logging.Level.FINEST;
+
+public class HopsworksLoginModule extends BasePasswordLoginModule {
+ @Override
+ protected void authenticateUser() throws LoginException {
+ HopsworksJDBCRealm customRealm = getRealm(HopsworksJDBCRealm.class, "Realm not found");
+
+ if (_username == null || _username.isEmpty()) {
+ throw new LoginException("No username set");
+ }
+
+ String[] groups = customRealm.authenticate(_username, getPasswordChar());
+
+ if (groups == null) { // JAAS behavior
+ throw new LoginException("Login failed for " + _username);
+ }
+
+ if (LOGGER.isLoggable(FINEST)) {
+ LOGGER.log(FINEST, "JDBC login succeeded for: {0} groups:{1}", new Object[]{_username, Arrays.toString(groups)});
+ }
+
+ commitUserAuthentication(groups);
+ }
+}
diff --git a/hopsworks-testing/src/main/webapp/WEB-INF/web.xml b/hopsworks-testing/src/main/webapp/WEB-INF/web.xml
index feab8d893c..06b2a58931 100644
--- a/hopsworks-testing/src/main/webapp/WEB-INF/web.xml
+++ b/hopsworks-testing/src/main/webapp/WEB-INF/web.xml
@@ -67,7 +67,7 @@
BASIC
- cauthRealm
+ hopsworksrealm
javax.faces.FACELETS_SKIP_COMMENTS
diff --git a/hopsworks-web/src/main/webapp/WEB-INF/web.xml b/hopsworks-web/src/main/webapp/WEB-INF/web.xml
index 5d09137da0..5fe3d9386b 100644
--- a/hopsworks-web/src/main/webapp/WEB-INF/web.xml
+++ b/hopsworks-web/src/main/webapp/WEB-INF/web.xml
@@ -54,7 +54,7 @@
BASIC
- cauthRealm
+ hopsworksrealm
diff --git a/pom.xml b/pom.xml
index 7a1420fb2c..7465eb96f1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,6 +62,7 @@
hopsworks-service-discovery
hopsworks-api-auth
vector-db
+ hopsworks-realm
diff --git a/project-suppression.xml b/project-suppression.xml
index 02d74c3cc8..909d0d5a89 100644
--- a/project-suppression.xml
+++ b/project-suppression.xml
@@ -238,4 +238,16 @@
^pkg:maven/org\.apache\.flink/flink\-shaded\-zookeeper\-3@.*$
CVE-2023-44981
+
+
+
+ ^pkg:maven/org\.codehaus\.plexus/plexus\-archiver@.*$
+ CVE-2023-37460
+
+
+
+
+ ^pkg:maven/org\.apache\.commons/commons\-compress@.*$
+ CVE-2024-25710
+
diff --git a/scripts/asadmin b/scripts/asadmin
index 86750ca7f8..c417164b7b 100755
--- a/scripts/asadmin
+++ b/scripts/asadmin
@@ -11,4 +11,4 @@ To create email service follow these steps. Please note you sould changes the pa
To creat custom auth realm Run
-./asadmin create-auth-realm --classname se.kth.bbc.crealm.CustomAuthRealm --property "jaas-context=cauthRealm:encoding=Hex:password-column=password:datasource-jndi=jdbc/hopsworks:group-table=hopsworks.users_groups:user-table=hopsworks.users:charset=UTF-8:group-name-column=group_name:user-name-column=email:otp-secret-column=secret:user-status-column=status:group-table-user-name-column=email:variables-table=hopsworks.variables" cauthRealm
\ No newline at end of file
+./asadmin create-auth-realm --login-module=io.hops.hopsworks.realm.jdbc.HopsworksLoginModule --classname=io.hops.hopsworks.realm.jdbc.HopsworksJDBCRealm --property=jaas-context=hopsworksJdbcRealm:datasource-jndi=jdbc/hopsworks:digest-algorithm=SHA-256:encoding=Hex hopsworksrealm
\ No newline at end of file