From a935d03c98913afc410ae2a8e50042afa61f9084 Mon Sep 17 00:00:00 2001 From: Ethaniel Billon Date: Tue, 22 Oct 2024 14:32:16 +0200 Subject: [PATCH 01/21] isomorphic gwt update to 13 --- pom.xml | 6 +++--- .../creatis/vip/core/client/view/common/LabelButton.java | 4 +++- .../client/view/monitor/GateLabDownloadButtonLayout.java | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index b9814e52c..407daff8a 100644 --- a/pom.xml +++ b/pom.xml @@ -49,8 +49,8 @@ knowledge of the CeCILL-B license and that you accept its terms. UTF-8 - 11 - 11 + 21 + 21 @@ -110,7 +110,7 @@ knowledge of the CeCILL-B license and that you accept its terms. com.isomorphic.smartgwt.lgpl smartgwt-lgpl - 12.0p + 13.0p @@ -63,6 +62,12 @@ knowledge of the CeCILL-B license and that you accept its terms. + + org.gwtproject + gwt-user + 2.11.0 + + com.isomorphic.smartgwt.lgpl smartgwt-lgpl @@ -131,14 +136,14 @@ knowledge of the CeCILL-B license and that you accept its terms. - javax.mail - mail - 1.4.5 + jakarta.mail + jakarta.mail-api + 2.1.3 - javax.annotation - javax.annotation-api - 1.3.2 + jakarta.annotation + jakarta.annotation-api + 3.0.0 @@ -169,7 +174,6 @@ knowledge of the CeCILL-B license and that you accept its terms. jackson-datatype-jsr310 ${jackson.version} - diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java index 89144129b..10cc7e452 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java @@ -31,7 +31,6 @@ */ package fr.insalyon.creatis.vip.core.server.auth; -import fr.insalyon.creatis.vip.core.client.bean.Group; import fr.insalyon.creatis.vip.core.client.bean.User; import fr.insalyon.creatis.vip.core.client.view.CoreException; import fr.insalyon.creatis.vip.core.server.business.BusinessException; @@ -41,12 +40,12 @@ import java.io.IOException; import java.io.PrintWriter; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.InternetAddress; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/SamlAuthenticationService.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/SamlAuthenticationService.java index 6db3cbaec..c9e7b7d19 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/SamlAuthenticationService.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/SamlAuthenticationService.java @@ -41,8 +41,8 @@ import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; import java.util.List; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/ConfigurationBusiness.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/ConfigurationBusiness.java index 3df0369b5..9b8af0fc7 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/ConfigurationBusiness.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/ConfigurationBusiness.java @@ -50,8 +50,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; +import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.InternetAddress; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.URL; diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/SpringConfigServer.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/SpringConfigServer.java index 51efb66f7..1bf0fb9b3 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/SpringConfigServer.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/SpringConfigServer.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.stream.Stream; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/VipSessionBusiness.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/VipSessionBusiness.java index 01778e24a..81028f58c 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/VipSessionBusiness.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/business/VipSessionBusiness.java @@ -1,6 +1,5 @@ package fr.insalyon.creatis.vip.core.server.business; -import fr.insalyon.creatis.vip.core.client.CoreModule; import fr.insalyon.creatis.vip.core.client.bean.Group; import fr.insalyon.creatis.vip.core.client.bean.User; import fr.insalyon.creatis.vip.core.client.view.CoreConstants; @@ -12,10 +11,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @@ -25,8 +24,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; /* Manages the reads/writes of vip core information in the web session diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/AbstractRemoteServiceServlet.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/AbstractRemoteServiceServlet.java index 13b1a4f4a..70cae109b 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/AbstractRemoteServiceServlet.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/AbstractRemoteServiceServlet.java @@ -31,7 +31,6 @@ */ package fr.insalyon.creatis.vip.core.server.rpc; -import com.google.gwt.user.server.rpc.RemoteServiceServlet; import fr.insalyon.creatis.vip.core.client.bean.Group; import fr.insalyon.creatis.vip.core.client.bean.User; import fr.insalyon.creatis.vip.core.client.view.CoreConstants.GROUP_ROLE; @@ -42,10 +41,12 @@ import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; +import com.google.gwt.user.server.rpc.jakarta.RemoteServiceServlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import java.util.Map; /** diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/ConfigurationServiceImpl.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/ConfigurationServiceImpl.java index 6fed0680a..86bed0b5c 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/ConfigurationServiceImpl.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/ConfigurationServiceImpl.java @@ -47,8 +47,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; import java.net.MalformedURLException; import java.net.URL; import java.sql.Timestamp; diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/GetFileServiceImpl.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/GetFileServiceImpl.java index 81fcfed15..5354acd47 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/GetFileServiceImpl.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/rpc/GetFileServiceImpl.java @@ -42,12 +42,12 @@ import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; diff --git a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/DataManagerServiceImpl.java b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/DataManagerServiceImpl.java index b868ed4f6..165c2a79b 100644 --- a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/DataManagerServiceImpl.java +++ b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/DataManagerServiceImpl.java @@ -45,10 +45,8 @@ import fr.insalyon.creatis.vip.datamanager.server.business.TransferPoolBusiness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletException; +import jakarta.servlet.ServletException; import java.io.File; import java.util.ArrayList; import java.util.Date; diff --git a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileDownloadServiceImpl.java b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileDownloadServiceImpl.java index 5d227bc6f..4daa16731 100644 --- a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileDownloadServiceImpl.java +++ b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileDownloadServiceImpl.java @@ -46,12 +46,12 @@ import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; diff --git a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileUploadServiceImpl.java b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileUploadServiceImpl.java index 3be2fa471..3a6830de3 100644 --- a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileUploadServiceImpl.java +++ b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/server/rpc/FileUploadServiceImpl.java @@ -41,19 +41,19 @@ import fr.insalyon.creatis.vip.datamanager.server.DataManagerUtil; import fr.insalyon.creatis.vip.datamanager.server.business.DataManagerBusiness; import fr.insalyon.creatis.vip.datamanager.server.business.LfcPathsBusiness; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileItemFactory; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload2.core.FileItem; +import org.apache.commons.fileupload2.core.FileItemFactory; +import org.apache.commons.fileupload2.core.DiskFileItemFactory; +import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.File; import java.io.PrintWriter; import java.text.Normalizer; @@ -91,10 +91,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) try { User user = (User) request.getSession().getAttribute(CoreConstants.SESSION_USER); logger.info("upload received from " + user.getEmail()); - if (user != null && ServletFileUpload.isMultipartContent(request)) { + if (user != null && JakartaServletFileUpload.isMultipartContent(request)) { - FileItemFactory factory = new DiskFileItemFactory(); - ServletFileUpload upload = new ServletFileUpload(factory); + FileItemFactory factory = DiskFileItemFactory.builder().get(); + JakartaServletFileUpload upload = new JakartaServletFileUpload(factory); List items = upload.parseRequest(request); Iterator iter = items.iterator(); String fileName = null; @@ -143,7 +143,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) File uploadedFile = new File(rootDirectory + fileName); try { - fileItem.write(uploadedFile); + fileItem.write(uploadedFile.toPath()); response.getWriter().write(fileName); if (!local) { diff --git a/vip-gatelab/pom.xml b/vip-gatelab/pom.xml index 08759aa4b..364b83679 100644 --- a/vip-gatelab/pom.xml +++ b/vip-gatelab/pom.xml @@ -43,26 +43,27 @@ knowledge of the CeCILL-B license and that you accept its terms. jar VIP-GateLab - + + jakarta.servlet + jakarta.servlet-api + fr.insalyon.creatis vip-core ${project.version} - fr.insalyon.creatis vip-datamanager ${project.version} - fr.insalyon.creatis vip-application ${project.version} - + diff --git a/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/business/GateLabInputs.java b/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/business/GateLabInputs.java index 57c0c8026..a668bff52 100644 --- a/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/business/GateLabInputs.java +++ b/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/business/GateLabInputs.java @@ -46,7 +46,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; /** * This stores data in fields and this is not threadsafe. So it cannot be used diff --git a/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/rpc/GateLabServiceImpl.java b/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/rpc/GateLabServiceImpl.java index e93a54d8a..d77b91d74 100644 --- a/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/rpc/GateLabServiceImpl.java +++ b/vip-gatelab/src/main/java/fr/insalyon/creatis/vip/gatelab/server/rpc/GateLabServiceImpl.java @@ -40,10 +40,8 @@ import fr.insalyon.creatis.vip.gatelab.server.business.GateLabBusiness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletException; +import jakarta.servlet.ServletException; import java.util.Map; /** diff --git a/vip-local/src/main/java/fr/insalyon/creatis/vip/local/GridaClientLocal.java b/vip-local/src/main/java/fr/insalyon/creatis/vip/local/GridaClientLocal.java index ed2c1cbb1..9cdc27666 100644 --- a/vip-local/src/main/java/fr/insalyon/creatis/vip/local/GridaClientLocal.java +++ b/vip-local/src/main/java/fr/insalyon/creatis/vip/local/GridaClientLocal.java @@ -8,14 +8,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.DependsOn; -import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; -import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.io.IOException; import java.nio.file.*; import java.util.List; diff --git a/vip-local/src/main/java/fr/insalyon/creatis/vip/local/WorkflowsDBLocalConfiguration.java b/vip-local/src/main/java/fr/insalyon/creatis/vip/local/WorkflowsDBLocalConfiguration.java index 8629f22e1..26ded2e7e 100644 --- a/vip-local/src/main/java/fr/insalyon/creatis/vip/local/WorkflowsDBLocalConfiguration.java +++ b/vip-local/src/main/java/fr/insalyon/creatis/vip/local/WorkflowsDBLocalConfiguration.java @@ -3,9 +3,6 @@ import fr.insalyon.creatis.moteur.plugins.workflowsdb.bean.*; import fr.insalyon.creatis.moteur.plugins.workflowsdb.dao.*; import fr.insalyon.creatis.moteur.plugins.workflowsdb.hibernate.*; -import fr.insalyon.creatis.vip.application.server.dao.SimulationStatsDAO; -import fr.insalyon.creatis.vip.application.server.dao.hibernate.SimulationStatsData; -import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.dialect.H2Dialect; import org.hibernate.internal.SessionFactoryImpl; @@ -17,15 +14,13 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.io.IOException; -import java.util.concurrent.TimeUnit; /** * overrides workflowsdb dao by others configured with a h2 database diff --git a/vip-publication/pom.xml b/vip-publication/pom.xml index b64363093..ce5337179 100644 --- a/vip-publication/pom.xml +++ b/vip-publication/pom.xml @@ -46,7 +46,10 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-Publication - + + jakarta.servlet + jakarta.servlet-api + fr.insalyon.creatis vip-core diff --git a/vip-publication/src/main/java/fr/insalyon/creatis/vip/publication/server/rpc/PublicationServiceImpl.java b/vip-publication/src/main/java/fr/insalyon/creatis/vip/publication/server/rpc/PublicationServiceImpl.java index 335c6cf25..de9e89dca 100644 --- a/vip-publication/src/main/java/fr/insalyon/creatis/vip/publication/server/rpc/PublicationServiceImpl.java +++ b/vip-publication/src/main/java/fr/insalyon/creatis/vip/publication/server/rpc/PublicationServiceImpl.java @@ -43,10 +43,8 @@ import org.jbibtex.TokenMgrException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletException; +import jakarta.servlet.ServletException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; diff --git a/vip-social/pom.xml b/vip-social/pom.xml index 83fc38007..9ccfdb4a9 100644 --- a/vip-social/pom.xml +++ b/vip-social/pom.xml @@ -47,6 +47,10 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-Social + + jakarta.servlet + jakarta.servlet-api + fr.insalyon.creatis diff --git a/vip-social/src/main/java/fr/insalyon/creatis/vip/social/server/rpc/SocialServiceImpl.java b/vip-social/src/main/java/fr/insalyon/creatis/vip/social/server/rpc/SocialServiceImpl.java index bfc6e9f52..5d542af9a 100644 --- a/vip-social/src/main/java/fr/insalyon/creatis/vip/social/server/rpc/SocialServiceImpl.java +++ b/vip-social/src/main/java/fr/insalyon/creatis/vip/social/server/rpc/SocialServiceImpl.java @@ -43,10 +43,8 @@ import fr.insalyon.creatis.vip.social.server.business.MessageBusiness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; -import javax.servlet.ServletException; +import jakarta.servlet.ServletException; import java.util.Arrays; import java.util.Date; import java.util.List; diff --git a/vip-visualization/pom.xml b/vip-visualization/pom.xml index a894efe47..e027f7a9c 100644 --- a/vip-visualization/pom.xml +++ b/vip-visualization/pom.xml @@ -57,7 +57,10 @@ knowledge of the CeCILL-B license and that you accept its terms. vip-datamanager ${project.version} - + + jakarta.servlet + jakarta.servlet-api + diff --git a/vip-visualization/src/main/java/fr/insalyon/creatis/vip/visualization/server/rpc/VisualizationServiceImpl.java b/vip-visualization/src/main/java/fr/insalyon/creatis/vip/visualization/server/rpc/VisualizationServiceImpl.java index b4bcde80a..bfa6b3825 100644 --- a/vip-visualization/src/main/java/fr/insalyon/creatis/vip/visualization/server/rpc/VisualizationServiceImpl.java +++ b/vip-visualization/src/main/java/fr/insalyon/creatis/vip/visualization/server/rpc/VisualizationServiceImpl.java @@ -42,10 +42,7 @@ import fr.insalyon.creatis.vip.visualization.server.business.VisualizationBusiness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; - -import javax.servlet.ServletException; +import jakarta.servlet.ServletException; public class VisualizationServiceImpl extends AbstractRemoteServiceServlet implements VisualizationService { From 6f97b3386f4c6c0f13f37938b03b792b3586f960 Mon Sep 17 00:00:00 2001 From: Ethaniel Billon Date: Wed, 23 Oct 2024 12:59:13 +0200 Subject: [PATCH 04/21] apache common & fileupload update --- vip-datamanagement/pom.xml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/vip-datamanagement/pom.xml b/vip-datamanagement/pom.xml index 7a73c4c8d..22d6efe09 100644 --- a/vip-datamanagement/pom.xml +++ b/vip-datamanagement/pom.xml @@ -43,6 +43,10 @@ knowledge of the CeCILL-B license and that you accept its terms. jar VIP-DataManager + + jakarta.servlet + jakarta.servlet-api + fr.insalyon.creatis @@ -51,9 +55,15 @@ knowledge of the CeCILL-B license and that you accept its terms. - commons-fileupload - commons-fileupload - 1.5 + org.apache.commons + commons-fileupload2-jakarta-servlet6 + 2.0.0-M2 + + + + commons-io + commons-io + 2.17.0 From 9f6424a4d6f33c4d2fbb9686b25262e2349432a8 Mon Sep 17 00:00:00 2001 From: Ethaniel Billon Date: Wed, 23 Oct 2024 13:00:39 +0200 Subject: [PATCH 05/21] unused imports --- .../insalyon/creatis/vip/api/business/ApiBusiness.java | 1 - .../creatis/vip/api/business/ApiUserBusiness.java | 1 - .../vip/api/controller/ExternalPlatformController.java | 2 -- .../creatis/vip/api/controller/PlatformController.java | 7 ------- .../api/controller/processing/PublicationController.java | 1 - .../vip/api/controller/stats/StatsController.java | 3 --- .../insalyon/creatis/vip/api/model/stats/StatUser.java | 1 - .../creatis/vip/api/model/stats/UsersNumber.java | 1 - .../api/security/apikey/ApikeyAuthenticationToken.java | 3 --- .../keycloak/VipKeycloakAuthenticationProvider.java | 1 - .../application/server/business/BoutiquesBusiness.java | 5 ----- .../creatis/vip/core/server/SpringCoreConfig.java | 9 --------- 12 files changed, 35 deletions(-) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiBusiness.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiBusiness.java index e5fce4016..de6d6ffc0 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiBusiness.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiBusiness.java @@ -11,7 +11,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.stereotype.Service; @Service diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiUserBusiness.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiUserBusiness.java index 2929da18b..e6c8f594b 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiUserBusiness.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/ApiUserBusiness.java @@ -12,7 +12,6 @@ import org.springframework.stereotype.Service; import java.util.*; -import java.util.stream.Collectors; /** * @author khalilkes service to signup a user in VIP diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ExternalPlatformController.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ExternalPlatformController.java index 320eb64be..60d03bea7 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ExternalPlatformController.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ExternalPlatformController.java @@ -32,7 +32,6 @@ package fr.insalyon.creatis.vip.api.controller; import fr.insalyon.creatis.vip.api.exception.ApiException; -import fr.insalyon.creatis.vip.core.client.bean.User; import fr.insalyon.creatis.vip.core.server.business.BusinessException; import fr.insalyon.creatis.vip.datamanager.client.bean.ExternalPlatform; import fr.insalyon.creatis.vip.datamanager.server.business.ExternalPlatformBusiness; @@ -44,7 +43,6 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -import java.util.function.Supplier; /** * Created by abonnet on 7/4/19. diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/PlatformController.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/PlatformController.java index d6c887715..ac215d145 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/PlatformController.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/PlatformController.java @@ -40,21 +40,14 @@ import fr.insalyon.creatis.vip.application.client.view.ApplicationException.ApplicationError; import fr.insalyon.creatis.vip.core.client.VipException; import fr.insalyon.creatis.vip.core.client.VipException.VipError; -import fr.insalyon.creatis.vip.core.client.bean.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.function.Supplier; import static fr.insalyon.creatis.vip.api.CarminProperties.*; diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/processing/PublicationController.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/processing/PublicationController.java index f2b55a39e..a67cf2057 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/processing/PublicationController.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/processing/PublicationController.java @@ -1,7 +1,6 @@ package fr.insalyon.creatis.vip.api.controller.processing; import fr.insalyon.creatis.vip.api.controller.ApiController; -import fr.insalyon.creatis.vip.api.business.PipelineBusiness; import fr.insalyon.creatis.vip.api.exception.ApiException; import fr.insalyon.creatis.vip.core.server.business.BusinessException; import fr.insalyon.creatis.vip.publication.client.bean.Publication; diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/stats/StatsController.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/stats/StatsController.java index f9bb14e55..0203398d1 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/stats/StatsController.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/stats/StatsController.java @@ -6,7 +6,6 @@ import fr.insalyon.creatis.vip.api.exception.ApiException.ApiError; import fr.insalyon.creatis.vip.api.model.stats.UsersList; import fr.insalyon.creatis.vip.api.model.stats.UsersNumber; -import fr.insalyon.creatis.vip.core.client.bean.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -14,8 +13,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.function.Supplier; - @RestController @RequestMapping("/statistics") public class StatsController extends ApiController { diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/StatUser.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/StatUser.java index f79c10de1..769405694 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/StatUser.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/StatUser.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import fr.insalyon.creatis.vip.api.CarminProperties; -import java.time.LocalDate; import java.time.LocalDateTime; @JsonInclude(Include.NON_NULL) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/UsersNumber.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/UsersNumber.java index 485878070..d9ff66016 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/UsersNumber.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/model/stats/UsersNumber.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonFormat; import fr.insalyon.creatis.vip.api.CarminProperties; -import java.time.LocalDate; import java.time.LocalDateTime; public class UsersNumber { diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationToken.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationToken.java index 8f89f0103..235c4b3a2 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationToken.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationToken.java @@ -35,11 +35,8 @@ import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collections; - /** * Created by abonnet on 10/6/16. * diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java index ff876e7a3..6f03d55d2 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java @@ -1,6 +1,5 @@ package fr.insalyon.creatis.vip.api.security.keycloak; -import fr.insalyon.creatis.vip.api.security.keycloak.SpringKeycloakPrincipal; import fr.insalyon.creatis.vip.core.client.bean.User; import fr.insalyon.creatis.vip.core.server.business.BusinessException; import fr.insalyon.creatis.vip.core.server.business.ConfigurationBusiness; diff --git a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java index 1da7addb0..dbbcc18e9 100644 --- a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java +++ b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/server/business/BoutiquesBusiness.java @@ -37,23 +37,18 @@ import fr.insalyon.creatis.vip.core.client.bean.User; import fr.insalyon.creatis.vip.core.server.business.BusinessException; import fr.insalyon.creatis.vip.core.server.business.Server; -import fr.insalyon.creatis.vip.core.server.dao.DAOException; import fr.insalyon.creatis.vip.datamanager.server.business.DataManagerBusiness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.xml.sax.SAXException; import java.io.*; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Scanner; -import static fr.insalyon.creatis.vip.application.client.view.ApplicationException.ApplicationError.WRONG_APPLICATION_DESCRIPTOR; - /** * Created by abonnet on 2/21/19. */ diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/SpringCoreConfig.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/SpringCoreConfig.java index f4cc0de5e..c77b55159 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/SpringCoreConfig.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/SpringCoreConfig.java @@ -20,23 +20,14 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; -import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; -import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.util.ResourceUtils; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; import javax.sql.DataSource; import java.io.IOException; -import java.util.function.Consumer; import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX; From 8bb47e68a6f1ffdee104192c549ad6019629e0a5 Mon Sep 17 00:00:00 2001 From: Ethaniel Billon Date: Wed, 23 Oct 2024 13:02:17 +0200 Subject: [PATCH 06/21] desactivate keycloak + remove WebSecurityAdapter to SecurityFilterChain --- .../creatis/vip/api/SpringWebConfig.java | 22 +---- .../api/controller/RestExceptionHandler.java | 5 +- .../vip/api/security/ApiSecurityConfig.java | 94 +++++++++---------- .../vip/api/security/EgiSecurityConfig.java | 10 +- .../boutiquesParsing/AbstractJsonParser.java | 9 +- 5 files changed, 66 insertions(+), 74 deletions(-) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java index 162ce3860..6961c28de 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java @@ -31,26 +31,12 @@ */ package fr.insalyon.creatis.vip.api; -import com.fasterxml.jackson.databind.ObjectMapper; import fr.insalyon.creatis.vip.api.business.VipConfigurer; -import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory; -import org.keycloak.adapters.springsecurity.client.KeycloakRestTemplate; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.context.annotation.DependsOn; -import org.springframework.context.annotation.PropertySource; -import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.core.env.PropertiesPropertySource; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.support.ResourcePropertySource; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.web.servlet.config.annotation.*; -import java.io.IOException; import java.util.Collections; import static fr.insalyon.creatis.vip.api.CarminProperties.CORS_AUTHORIZED_DOMAINS; @@ -78,10 +64,10 @@ public SpringWebConfig(Environment env, VipConfigurer vipConfigurer) { } //implements rest template to send requests with tokens - @Bean - public KeycloakRestTemplate keycloakRestTemplate(KeycloakClientRequestFactory keycloakClientRequestFactory) { - return new KeycloakRestTemplate(keycloakClientRequestFactory); - } + // @Bean + // public KeycloakRestTemplate keycloakRestTemplate(KeycloakClientRequestFactory keycloakClientRequestFactory) { + // return new KeycloakRestTemplate(keycloakClientRequestFactory); + // } @Override public void configurePathMatch(PathMatchConfigurer configurer) { diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RestExceptionHandler.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RestExceptionHandler.java index 6e2ec8bb0..ed5ecdecb 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RestExceptionHandler.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RestExceptionHandler.java @@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.lang.NonNull; import org.springframework.validation.FieldError; @@ -102,7 +103,7 @@ private String cleanExceptionMessage(VipException vipException) { @NonNull protected ResponseEntity handleExceptionInternal( @NonNull Exception ex, Object body, HttpHeaders headers, - HttpStatus status, @NonNull WebRequest request) { + HttpStatusCode status, @NonNull WebRequest request) { logger.error("Internal spring exception catched", ex); ErrorCodeAndMessage codeAndmessage = new ErrorCodeAndMessage( ApiError.GENERIC_API_ERROR.getCode(), @@ -116,7 +117,7 @@ protected ResponseEntity handleExceptionInternal( protected ResponseEntity handleMethodArgumentNotValid( @NonNull MethodArgumentNotValidException ex, @NonNull HttpHeaders headers, - @NonNull HttpStatus status, + @NonNull HttpStatusCode status, @NonNull WebRequest request) { // only handle field error if (ex.getBindingResult().getFieldError() != null) { diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java index 08707bb7d..caff2dc39 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java @@ -86,7 +86,7 @@ @KeycloakConfiguration @EnableWebSecurity @Order(1) -public class ApiSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { +public class ApiSecurityConfig { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -110,30 +110,30 @@ protected boolean isKeycloakActive() { return env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); } - @Override + // @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { // required for bearer only applications return new NullAuthenticatedSessionStrategy(); } - @Override + // @Override protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception { return vipAuthenticationEntryPoint; } - @Override - protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception { - KeycloakAuthenticationProcessingFilter f = super.keycloakAuthenticationProcessingFilter(); - f.setAuthenticationFailureHandler(vipAuthenticationEntryPoint); - return f; - } + // @Override + // protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception { + // KeycloakAuthenticationProcessingFilter f = super.keycloakAuthenticationProcessingFilter(); + // f.setAuthenticationFailureHandler(vipAuthenticationEntryPoint); + // return f; + // } - @Override + // @Override protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() { return vipKeycloakAuthenticationProvider; } - @Override + // @Override public void configure(AuthenticationManagerBuilder auth) { if (isKeycloakActive()) { KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); @@ -143,42 +143,42 @@ public void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(apikeyAuthenticationProvider); } - @Override - protected void configure(HttpSecurity http) throws Exception { - if (isKeycloakActive()) { - super.configure(http); - } - http.antMatcher("/rest/**") - .authorizeRequests() - .antMatchers("/rest/platform").permitAll() - .antMatchers("/rest/authenticate").permitAll() - .antMatchers("/rest/session").permitAll() - .regexMatchers("/rest/pipelines\\?public").permitAll() - .antMatchers("/rest/publications").permitAll() - .antMatchers("/rest/reset-password").permitAll() - .antMatchers("/rest/register").permitAll() - .antMatchers("/rest/executions/{executionId}/summary").hasAnyRole("SERVICE") - .antMatchers("/rest/simulate-refresh").authenticated() - .antMatchers("/rest/statistics/**").hasAnyRole("ADVANCED", "ADMINISTRATOR") - .antMatchers("/rest/**").authenticated() - .anyRequest().permitAll() - .and() - .addFilterBefore(apikeyAuthenticationFilter(), BasicAuthenticationFilter.class) - .exceptionHandling().authenticationEntryPoint(vipAuthenticationEntryPoint)// also done in parent but needed here when keycloak is not active. It can be done twice without harm. - // session must be activated otherwise OIDC auth info will be lost when accessing /loginEgi - //.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .cors().and() - .headers().frameOptions().sameOrigin().and() - .csrf().disable(); - } - - @Bean - public ApikeyAuthenticationFilter apikeyAuthenticationFilter() throws Exception { - return new ApikeyAuthenticationFilter( - env.getRequiredProperty(CarminProperties.APIKEY_HEADER_NAME), - vipAuthenticationEntryPoint, authenticationManager()); - } + // @Override + // protected void configure(HttpSecurity http) throws Exception { + // if (isKeycloakActive()) { + // super.configure(http); + // } + // http.antMatcher("/rest/**") + // .authorizeRequests() + // .antMatchers("/rest/platform").permitAll() + // .antMatchers("/rest/authenticate").permitAll() + // .antMatchers("/rest/session").permitAll() + // .regexMatchers("/rest/pipelines\\?public").permitAll() + // .antMatchers("/rest/publications").permitAll() + // .antMatchers("/rest/reset-password").permitAll() + // .antMatchers("/rest/register").permitAll() + // .antMatchers("/rest/executions/{executionId}/summary").hasAnyRole("SERVICE") + // .antMatchers("/rest/simulate-refresh").authenticated() + // .antMatchers("/rest/statistics/**").hasAnyRole("ADVANCED", "ADMINISTRATOR") + // .antMatchers("/rest/**").authenticated() + // .anyRequest().permitAll() + // .and() + // .addFilterBefore(apikeyAuthenticationFilter(), BasicAuthenticationFilter.class) + // .exceptionHandling().authenticationEntryPoint(vipAuthenticationEntryPoint)// also done in parent but needed here when keycloak is not active. It can be done twice without harm. + // // session must be activated otherwise OIDC auth info will be lost when accessing /loginEgi + // //.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + // .and() + // .cors().and() + // .headers().frameOptions().sameOrigin().and() + // .csrf().disable(); + // } + + // @Bean + // public ApikeyAuthenticationFilter apikeyAuthenticationFilter() throws Exception { + // return new ApikeyAuthenticationFilter( + // env.getRequiredProperty(CarminProperties.APIKEY_HEADER_NAME), + // vipAuthenticationEntryPoint, authenticationManager()); + // } @Service public static class CurrentUserProvider implements Supplier { diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java index 7231115f8..ce1ac3e51 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java @@ -35,13 +35,13 @@ import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.firewall.DefaultHttpFirewall; /** @@ -56,11 +56,10 @@ */ @EnableWebSecurity @Order(2) -public class EgiSecurityConfig extends WebSecurityConfigurerAdapter { +public class EgiSecurityConfig { - - @Override - protected void configure(HttpSecurity http) throws Exception { + @Bean + protected SecurityFilterChain configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().permitAll() @@ -79,6 +78,7 @@ protected void configure(HttpSecurity http) throws Exception { .cors().and() .headers().frameOptions().sameOrigin().and() .csrf().disable(); + return http.build(); } @Bean diff --git a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/client/view/boutiquesParsing/AbstractJsonParser.java b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/client/view/boutiquesParsing/AbstractJsonParser.java index 01eeb0050..1ddd098f2 100644 --- a/vip-application/src/main/java/fr/insalyon/creatis/vip/application/client/view/boutiquesParsing/AbstractJsonParser.java +++ b/vip-application/src/main/java/fr/insalyon/creatis/vip/application/client/view/boutiquesParsing/AbstractJsonParser.java @@ -1,10 +1,15 @@ package fr.insalyon.creatis.vip.application.client.view.boutiquesParsing; -import com.google.gwt.json.client.*; - import java.util.*; import java.util.function.Function; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONBoolean; +import com.google.gwt.json.client.JSONNumber; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONString; +import com.google.gwt.json.client.JSONValue; + /** * Helper class for parsing JSON objects * From fb42922faf11d357448262adfb08a6eb37147b57 Mon Sep 17 00:00:00 2001 From: Ethaniel Billon Date: Wed, 23 Oct 2024 13:03:12 +0200 Subject: [PATCH 07/21] update spring to 6.1.14, spring security & auth to 6.3.4 and jackson to 2.18.0 --- pom.xml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 973bf915d..6abbb1fee 100644 --- a/pom.xml +++ b/pom.xml @@ -60,10 +60,10 @@ knowledge of the CeCILL-B license and that you accept its terms. 5.6.2 - 5.3.15 - 5.6.1 - 5.6.1 - 2.11.0 + 6.1.14 + 6.3.4 + 6.3.4 + 2.18.0 1.3.173 @@ -101,9 +101,9 @@ knowledge of the CeCILL-B license and that you accept its terms. - com.google.gwt - gwt - 2.8.2 + org.gwtproject + gwt-servlet-jakarta + 2.11.0 pom import @@ -112,6 +112,12 @@ knowledge of the CeCILL-B license and that you accept its terms. smartgwt-lgpl 13.0p + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + provided + - 5.6.2 + 5.11.3 6.1.14 6.3.4 6.3.4 diff --git a/vip-core/pom.xml b/vip-core/pom.xml index 4a6982265..320e14fcc 100644 --- a/vip-core/pom.xml +++ b/vip-core/pom.xml @@ -136,9 +136,9 @@ knowledge of the CeCILL-B license and that you accept its terms. - jakarta.mail - jakarta.mail-api - 2.1.3 + com.sun.mail + jakarta.mail + 2.0.1 jakarta.annotation diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java index 10cc7e452..d141cbf5e 100644 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/server/auth/AbstractAuthenticationService.java @@ -40,6 +40,7 @@ import java.io.IOException; import java.io.PrintWriter; + import jakarta.mail.internet.AddressException; import jakarta.mail.internet.InternetAddress; import jakarta.servlet.ServletException; From e62c1c07f19132f757603a23d9bc1a0128c98cae Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Mon, 4 Nov 2024 15:29:22 +0100 Subject: [PATCH 09/21] Java21/Spring6 upgrade + Keycloak removal - Upgrade API key and EGI auth to Spring Security 6.3. - Add -parameters compilation flag - Add @Configuration annotation to @EnableWebSecurity classes - Move @Order annotations to SecurityFilterChain methods - Port API key and EGI authentications to use the new Spring6 API, removing use of deprecated stuff. - Fully remove org.keycloak connector: this temporarily drops support for Keycloak authentication in VIP API, as this connector is deprecated and doesn't supprot Java21/Spring6. It will be replaced with a new Spring-based OIDC connector in an upcoming patch. - Also remove unused keycloak refreshtoken stuff, and the three related keycloak parameters in vip-api.conf --- pom.xml | 11 ++ vip-api/pom.xml | 12 -- .../vip/api/ApiPropertiesInitializer.java | 7 +- .../creatis/vip/api/CarminProperties.java | 5 +- .../creatis/vip/api/SpringWebConfig.java | 6 - .../vip/api/business/KeycloakBusiness.java | 71 -------- .../vip/api/controller/ApiController.java | 6 - .../controller/RefreshTokenController.java | 40 ----- .../vip/api/security/ApiSecurityConfig.java | 168 +++++++----------- .../vip/api/security/EgiSecurityConfig.java | 37 ++-- .../security/VipAuthenticationEntryPoint.java | 3 - .../PathBasedKeycloakConfigResolver.java | 46 ----- .../keycloak/SpringKeycloakPrincipal.java | 19 -- .../VipKeycloakAuthenticationProvider.java | 75 -------- 14 files changed, 94 insertions(+), 412 deletions(-) delete mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/KeycloakBusiness.java delete mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RefreshTokenController.java delete mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/PathBasedKeycloakConfigResolver.java delete mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/SpringKeycloakPrincipal.java delete mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java diff --git a/pom.xml b/pom.xml index 33d032c43..19ce20d88 100644 --- a/pom.xml +++ b/pom.xml @@ -235,6 +235,17 @@ knowledge of the CeCILL-B license and that you accept its terms. + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + -parameters + + + org.codehaus.mojo diff --git a/vip-api/pom.xml b/vip-api/pom.xml index 38a511685..f225f2cc0 100644 --- a/vip-api/pom.xml +++ b/vip-api/pom.xml @@ -48,7 +48,6 @@ knowledge of the CeCILL-B license and that you accept its terms. true 2.2 3.3.3 - 25.0.3 @@ -111,17 +110,6 @@ knowledge of the CeCILL-B license and that you accept its terms. ${springsecurity.version} - - org.keycloak - keycloak-spring-security-adapter - ${keycloak.version} - - - org.keycloak - keycloak-admin-client - ${keycloak.version} - - org.hibernate diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java index d2c2eb637..e52b972fb 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java @@ -96,12 +96,9 @@ private void verifyProperties() { if (env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE)) { - logger.info("Keycloak activated"); - verifyPropertyNotNull(KEYCLOAK_CLIENT_ID, String.class); - verifyPropertyNotNull(KEYCLOAK_CLIENT_SECRET, String.class); - verifyPropertyNotNull(KEYCLOAK_REALM_URL, String.class); + logger.info("Keycloak/OIDC activated, but this has no effect yet"); } else { - logger.info("Keycloak NOT active"); + logger.info("Keycloak/OIDC NOT active"); } // due to arrays and generics, this verification aren't easy to factorize diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/CarminProperties.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/CarminProperties.java index e8f196991..0ba02591c 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/CarminProperties.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/CarminProperties.java @@ -73,10 +73,7 @@ public interface CarminProperties { String SHANOIR_HOST_IP = "shanoir.host.ip"; String KEYCLOAK_ACTIVATED = "keycloak.active"; - String KEYCLOAK_REALM_URL = "keycloak.realm.url"; - String KEYCLOAK_CLIENT_ID = "keycloak.client.id"; - String KEYCLOAK_CLIENT_SECRET = "keycloak.client.secret"; - + // Client secret EGI String EGI_CLIENT_ID = "oidc.egi.client_id"; String EGI_CLIENT_SECRET = "oidc.egi.client_secret"; diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java index 6961c28de..da9d8f103 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/SpringWebConfig.java @@ -63,12 +63,6 @@ public SpringWebConfig(Environment env, VipConfigurer vipConfigurer) { this.vipConfigurer = vipConfigurer; } - //implements rest template to send requests with tokens - // @Bean - // public KeycloakRestTemplate keycloakRestTemplate(KeycloakClientRequestFactory keycloakClientRequestFactory) { - // return new KeycloakRestTemplate(keycloakClientRequestFactory); - // } - @Override public void configurePathMatch(PathMatchConfigurer configurer) { // Otherwise all that follow a dot in an URL is considered an extension and removed diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/KeycloakBusiness.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/KeycloakBusiness.java deleted file mode 100644 index fcdfe9665..000000000 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/business/KeycloakBusiness.java +++ /dev/null @@ -1,71 +0,0 @@ -package fr.insalyon.creatis.vip.api.business; - -import fr.insalyon.creatis.vip.api.CarminProperties; -import fr.insalyon.creatis.vip.api.exception.ApiException; -import org.keycloak.representations.AccessTokenResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; -import org.springframework.http.*; -import org.springframework.stereotype.Component; -import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; - -/** - * @author alaeessaki this class is only for testing the refresh mechanism - */ -@Service -public class KeycloakBusiness { - - private final Environment env; - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final byte ALLOWED_ATTEMPTS = 2; - - @Autowired - KeycloakBusiness(Environment env){ - this.env = env; - } - - public ResponseEntity refreshToken(String offlineToken) throws ApiException { - int attempt = 1; // 2 attempts max - RestTemplate restTemplate = new RestTemplate(); - - /** - * - * Creating the http headers with the values needed for the refreshing action - */ - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - MultiValueMap map = new LinkedMultiValueMap<>(); - - map.add("client_id", env.getRequiredProperty(CarminProperties.KEYCLOAK_CLIENT_ID)); - map.add("grant_type", "refresh_token"); - map.add("refresh_token", offlineToken); - map.add("client_secret", env.getRequiredProperty(CarminProperties.KEYCLOAK_CLIENT_SECRET)); - - HttpEntity entity = new HttpEntity(map, httpHeaders); - ResponseEntity responseEntity = null; - while (attempt <= ALLOWED_ATTEMPTS) { - try { - responseEntity = restTemplate.exchange( - env.getRequiredProperty(CarminProperties.KEYCLOAK_REALM_URL), HttpMethod.POST, - entity, AccessTokenResponse.class); - break; - } catch (HttpClientErrorException httpClientErrorException) { - logger.info(httpClientErrorException.getMessage()); - attempt++; - } - } - - if (responseEntity == null){ - throw new ApiException("token not refreshed after " + attempt + " attempts"); - } - return responseEntity; - } - -} diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ApiController.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ApiController.java index 39456506a..5abb183cc 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ApiController.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/ApiController.java @@ -7,8 +7,6 @@ import java.util.function.Supplier; -import static fr.insalyon.creatis.vip.api.CarminProperties.KEYCLOAK_ACTIVATED; - public abstract class ApiController { protected Supplier currentUserSupplier; @@ -24,10 +22,6 @@ protected User currentUser() { return currentUserSupplier.get(); } - protected boolean isKeycloakActive() { - return env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); - } - protected void logMethodInvocation(Logger logger, String methodName, Object... parameters) { Object user = currentUser() != null ? currentUser() : "Anonymous"; logger.debug( "({}) Calling API method {}} ({})", user, methodName, parameters); diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RefreshTokenController.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RefreshTokenController.java deleted file mode 100644 index 1a1c2a970..000000000 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/controller/RefreshTokenController.java +++ /dev/null @@ -1,40 +0,0 @@ -package fr.insalyon.creatis.vip.api.controller; - -import fr.insalyon.creatis.vip.api.exception.ApiException; -import fr.insalyon.creatis.vip.api.business.KeycloakBusiness; -import org.keycloak.representations.AccessTokenResponse; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import jakarta.servlet.http.HttpServletRequest; - -/** - * @author alaeessaki - */ -@RestController -@RequestMapping("/simulate-refresh") -public class RefreshTokenController extends ApiController { - - private final KeycloakBusiness refreshUtils; - - @Autowired - public RefreshTokenController(KeycloakBusiness refreshUtils ) { - this.refreshUtils = refreshUtils; - } - - @RequestMapping(method = RequestMethod.GET, produces = "application/json;charset=utf-8") - @ResponseBody - public String simulatingRefreshToken(final HttpServletRequest request) throws ApiException { - if ( ! isKeycloakActive()) { - throw new ApiException("Keycloak is not activated on the VIP server"); - } - String offline_token = request.getHeader("offline_token"); //getting the offline token from header sent by the client. - ResponseEntity tokenResponseEntity = refreshUtils.refreshToken(offline_token); //refreshing the token - return tokenResponseEntity.getBody().getToken(); - } -} diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java index caff2dc39..0e32bf6d5 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java @@ -36,150 +36,114 @@ import fr.insalyon.creatis.vip.api.security.apikey.SpringApiPrincipal; import fr.insalyon.creatis.vip.api.security.apikey.ApikeyAuthenticationFilter; import fr.insalyon.creatis.vip.api.security.apikey.ApikeyAuthenticationProvider; -import fr.insalyon.creatis.vip.api.security.keycloak.SpringKeycloakPrincipal; -import fr.insalyon.creatis.vip.api.security.keycloak.VipKeycloakAuthenticationProvider; import fr.insalyon.creatis.vip.core.client.bean.User; -import org.keycloak.adapters.springsecurity.KeycloakConfiguration; -import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents; -import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; -import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; - -import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; - -import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.config.Customizer; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.firewall.DefaultHttpFirewall; -import org.springframework.stereotype.Service; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RegexRequestMatcher; import java.util.function.Supplier; +import java.util.ArrayList; import static fr.insalyon.creatis.vip.api.CarminProperties.KEYCLOAK_ACTIVATED; /** - * Keycloaksecurity configuration. - * - * It secures by api key all rest requests (except platform and authenticate) - * - * Modified by khalilkes to implement keycloak adapter + * VIP API configuration for API key and OIDC authentications. * + * Authenticates /rest requests with either: + * - a static per-user API key (ApikeyAuthenticationFilter) + * - or an OIDC Bearer token (ex-Keycloak). This part is currently work-in-progress, + * with org.keycloak currently removed, and proper OIDC connector not implemented yet. */ -@ComponentScan(basePackageClasses = {KeycloakSecurityComponents.class}) -@KeycloakConfiguration +@Configuration @EnableWebSecurity -@Order(1) public class ApiSecurityConfig { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Environment env; - private final ApikeyAuthenticationProvider apikeyAuthenticationProvider; private final VipAuthenticationEntryPoint vipAuthenticationEntryPoint; - private final VipKeycloakAuthenticationProvider vipKeycloakAuthenticationProvider; + private final AuthenticationManager vipAuthenticationManager; @Autowired public ApiSecurityConfig( Environment env, ApikeyAuthenticationProvider apikeyAuthenticationProvider, - VipAuthenticationEntryPoint vipAuthenticationEntryPoint, - VipKeycloakAuthenticationProvider vipKeycloakAuthenticationProvider) { + VipAuthenticationEntryPoint vipAuthenticationEntryPoint) { this.env = env; - this.apikeyAuthenticationProvider = apikeyAuthenticationProvider; this.vipAuthenticationEntryPoint = vipAuthenticationEntryPoint; - this.vipKeycloakAuthenticationProvider = vipKeycloakAuthenticationProvider; + // Build our AuthenticationManager instance, with one provider for each authentication method + ArrayList providers = new ArrayList<>(); + providers.add(apikeyAuthenticationProvider); + // providers.add(oidcAuthenticationProvider); + this.vipAuthenticationManager = new ProviderManager(providers); } - protected boolean isKeycloakActive() { - return env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); - } - - // @Override - protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { - // required for bearer only applications - return new NullAuthenticatedSessionStrategy(); + @Bean + public AuthenticationManager authenticationManager() { + return vipAuthenticationManager; } - // @Override - protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception { - return vipAuthenticationEntryPoint; + protected boolean isOIDCActive() { + return env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); } - // @Override - // protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception { - // KeycloakAuthenticationProcessingFilter f = super.keycloakAuthenticationProcessingFilter(); - // f.setAuthenticationFailureHandler(vipAuthenticationEntryPoint); - // return f; - // } - - // @Override - protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() { - return vipKeycloakAuthenticationProvider; + @Bean + @Order(1) + public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { + http + .securityMatcher(AntPathRequestMatcher.antMatcher("/rest/**")) + .authorizeHttpRequests((authorize) -> authorize + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/platform")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/authenticate")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/session")).permitAll() + .requestMatchers(new RegexRequestMatcher("/rest/pipelines\\?public", "GET")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/publications")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/reset-password")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/register")).permitAll() + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/executions/{executionId}/summary")).hasAnyRole("SERVICE") + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/statistics/**")).hasAnyRole("ADVANCED", "ADMINISTRATOR") + .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/**")).authenticated() + .anyRequest().permitAll() + ) + .addFilterBefore(apikeyAuthenticationFilter(), BasicAuthenticationFilter.class) + //.addFilterBefore(oidcAuthenticationFilter(), BasicAuthenticationFilter.class) + .exceptionHandling((exceptionHandling) -> exceptionHandling.authenticationEntryPoint(vipAuthenticationEntryPoint)) + // session must be activated otherwise OIDC auth info will be lost when accessing /loginEgi + // .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .cors(Customizer.withDefaults()) + .headers((headers) -> headers.frameOptions((frameOptions) -> frameOptions.sameOrigin())) + .csrf((csrf) -> csrf.disable()); + return http.build(); } - // @Override - public void configure(AuthenticationManagerBuilder auth) { - if (isKeycloakActive()) { - KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); - keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper()); - auth.authenticationProvider(keycloakAuthenticationProvider); - } - auth.authenticationProvider(apikeyAuthenticationProvider); + @Bean + public ApikeyAuthenticationFilter apikeyAuthenticationFilter() throws Exception { + return new ApikeyAuthenticationFilter( + env.getRequiredProperty(CarminProperties.APIKEY_HEADER_NAME), + vipAuthenticationEntryPoint, authenticationManager()); } - // @Override - // protected void configure(HttpSecurity http) throws Exception { - // if (isKeycloakActive()) { - // super.configure(http); - // } - // http.antMatcher("/rest/**") - // .authorizeRequests() - // .antMatchers("/rest/platform").permitAll() - // .antMatchers("/rest/authenticate").permitAll() - // .antMatchers("/rest/session").permitAll() - // .regexMatchers("/rest/pipelines\\?public").permitAll() - // .antMatchers("/rest/publications").permitAll() - // .antMatchers("/rest/reset-password").permitAll() - // .antMatchers("/rest/register").permitAll() - // .antMatchers("/rest/executions/{executionId}/summary").hasAnyRole("SERVICE") - // .antMatchers("/rest/simulate-refresh").authenticated() - // .antMatchers("/rest/statistics/**").hasAnyRole("ADVANCED", "ADMINISTRATOR") - // .antMatchers("/rest/**").authenticated() - // .anyRequest().permitAll() - // .and() - // .addFilterBefore(apikeyAuthenticationFilter(), BasicAuthenticationFilter.class) - // .exceptionHandling().authenticationEntryPoint(vipAuthenticationEntryPoint)// also done in parent but needed here when keycloak is not active. It can be done twice without harm. - // // session must be activated otherwise OIDC auth info will be lost when accessing /loginEgi - // //.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - // .and() - // .cors().and() - // .headers().frameOptions().sameOrigin().and() - // .csrf().disable(); - // } - - // @Bean - // public ApikeyAuthenticationFilter apikeyAuthenticationFilter() throws Exception { - // return new ApikeyAuthenticationFilter( - // env.getRequiredProperty(CarminProperties.APIKEY_HEADER_NAME), - // vipAuthenticationEntryPoint, authenticationManager()); - // } - @Service public static class CurrentUserProvider implements Supplier { @@ -196,7 +160,8 @@ public User get() { if (user != null) { return user; } - return getKeycloakUser(authentication); + // user = getOidcUser(authentication); + return null; } private User getApikeyUser(Authentication authentication) { @@ -207,15 +172,6 @@ private User getApikeyUser(Authentication authentication) { (SpringApiPrincipal) authentication.getPrincipal(); return springCompatibleUser.getVipUser(); } - - private User getKeycloakUser(Authentication authentication) { - if ( ! (authentication.getPrincipal() instanceof SpringKeycloakPrincipal)) { - return null; - } - SpringKeycloakPrincipal springKeycloakPrincipal = - (SpringKeycloakPrincipal) authentication.getPrincipal(); - return springKeycloakPrincipal.getVipUser(); - } } /* diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java index ce1ac3e51..bdadeaac3 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/EgiSecurityConfig.java @@ -32,7 +32,9 @@ package fr.insalyon.creatis.vip.api.security; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; @@ -54,30 +56,27 @@ * * Created by abonnet on 7/22/16. */ + +@Configuration @EnableWebSecurity -@Order(2) public class EgiSecurityConfig { @Bean - protected SecurityFilterChain configure(HttpSecurity http) throws Exception { + @Order(2) + public SecurityFilterChain egiFilterChain(HttpSecurity http) throws Exception { http - .authorizeRequests() - .anyRequest().permitAll() - .and() - .oauth2Login() - .authorizationEndpoint() - .baseUri("/oauth2/authorize-client") - .authorizationRequestRepository(authorizationRequestRepository()) - .and() - .tokenEndpoint() - .accessTokenResponseClient(accessTokenResponseClient()) - .and() - .defaultSuccessUrl("/rest/loginEgi") - .failureUrl("/loginFailure") - .and() - .cors().and() - .headers().frameOptions().sameOrigin().and() - .csrf().disable(); + .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) + .oauth2Login((oauth2)->oauth2 + .authorizationEndpoint((authorization)->authorization + .baseUri("/oauth2/authorize-client") + .authorizationRequestRepository(authorizationRequestRepository())) + .tokenEndpoint((token)->token + .accessTokenResponseClient(accessTokenResponseClient())) + .defaultSuccessUrl("/rest/loginEgi") + .failureUrl("/loginFailure")) + .cors(Customizer.withDefaults()) + .headers((headers) -> headers.frameOptions((frameOptions) -> frameOptions.sameOrigin())) + .csrf((csrf) -> csrf.disable()); return http.build(); } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java index 014f422fe..6e4af2519 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java @@ -34,7 +34,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import fr.insalyon.creatis.vip.api.exception.ApiException.ApiError; import fr.insalyon.creatis.vip.api.model.ErrorCodeAndMessage; -import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -84,8 +83,6 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo error.setErrorCode(ApiError.BAD_CREDENTIALS.getCode()); } else if (authException instanceof InsufficientAuthenticationException) { error.setErrorCode(ApiError.INSUFFICIENT_AUTH.getCode()); - } else if (authException instanceof KeycloakAuthenticationException) { - error.setErrorCode(ApiError.BAD_CREDENTIALS.getCode()); } else { error.setErrorCode(ApiError.AUTHENTICATION_ERROR.getCode()); } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/PathBasedKeycloakConfigResolver.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/PathBasedKeycloakConfigResolver.java deleted file mode 100644 index 030c10ee1..000000000 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/PathBasedKeycloakConfigResolver.java +++ /dev/null @@ -1,46 +0,0 @@ -package fr.insalyon.creatis.vip.api.security.keycloak; - -import org.keycloak.adapters.KeycloakConfigResolver; -import org.keycloak.adapters.KeycloakDeployment; -import org.keycloak.adapters.KeycloakDeploymentBuilder; -import org.keycloak.adapters.spi.HttpFacade; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * @author khalilKes keycloak path resolver to allow external keycloak.json - * - */ -@Configuration -public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Autowired - private Resource vipConfigFolder; - - private KeycloakDeployment deployment; - - @Override - public KeycloakDeployment resolve(HttpFacade.Request request) { - if (deployment != null) { - return deployment; - } - - try(InputStream is = new FileInputStream( vipConfigFolder.getFile().getAbsoluteFile() + "/keycloak.json")){ - deployment = KeycloakDeploymentBuilder.build(is); - }catch (IOException e){ - logger.error(e.getMessage()); - throw new RuntimeException("keycloak.json file exception"); - - } - return deployment; - } -} diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/SpringKeycloakPrincipal.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/SpringKeycloakPrincipal.java deleted file mode 100644 index 9ec239125..000000000 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/SpringKeycloakPrincipal.java +++ /dev/null @@ -1,19 +0,0 @@ -package fr.insalyon.creatis.vip.api.security.keycloak; - -import fr.insalyon.creatis.vip.core.client.bean.User; -import org.keycloak.KeycloakPrincipal; -import org.keycloak.KeycloakSecurityContext; - -public class SpringKeycloakPrincipal extends KeycloakPrincipal { - - private final User vipUser; - - public SpringKeycloakPrincipal(User vipUser, String name, KeycloakSecurityContext context) { - super(name, context); - this.vipUser = vipUser; - } - - public User getVipUser() { - return vipUser; - } -} diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java deleted file mode 100644 index 6f03d55d2..000000000 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/keycloak/VipKeycloakAuthenticationProvider.java +++ /dev/null @@ -1,75 +0,0 @@ -package fr.insalyon.creatis.vip.api.security.keycloak; - -import fr.insalyon.creatis.vip.core.client.bean.User; -import fr.insalyon.creatis.vip.core.server.business.BusinessException; -import fr.insalyon.creatis.vip.core.server.business.ConfigurationBusiness; -import org.keycloak.KeycloakSecurityContext; -import org.keycloak.adapters.RefreshableKeycloakSecurityContext; -import org.keycloak.adapters.spi.KeycloakAccount; -import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount; -import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; -import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.SpringSecurityMessageSource; -import org.springframework.stereotype.Component; - -@Component -public class VipKeycloakAuthenticationProvider extends KeycloakAuthenticationProvider { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConfigurationBusiness configurationBusiness; - - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - - @Autowired - public VipKeycloakAuthenticationProvider(ConfigurationBusiness configurationBusiness) { - this.configurationBusiness = configurationBusiness; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - KeycloakAuthenticationToken keycloakAuthenticationToken = - (KeycloakAuthenticationToken) super.authenticate(authentication); - - KeycloakSecurityContext session = keycloakAuthenticationToken.getAccount().getKeycloakSecurityContext(); - String email = session.getToken().getEmail(); - - User vipUser; - try{ - vipUser = configurationBusiness.getUserWithGroups(email); - } catch (BusinessException e) { - logger.error("Error when getting user from keycloak token. Doing as if there is an auth error", e); - throw new BadCredentialsException( - messages.getMessage( - "AbstractUserDetailsAuthenticationProvider.badCredentials", - "Bad credentials")); - } - if (vipUser == null) { - logger.info("Can't authenticate from keycloak because user does not exist in VIP:" + email); - throw new BadCredentialsException( - messages.getMessage( - "AbstractUserDetailsAuthenticationProvider.badCredentials", - "Bad credentials")); - } - // recreate whole keycloak user data to add the vip user in it - SpringKeycloakPrincipal springKeycloakPrincipal = new SpringKeycloakPrincipal( - vipUser, - keycloakAuthenticationToken.getAccount().getPrincipal().getName(), - keycloakAuthenticationToken.getAccount().getKeycloakSecurityContext()); - KeycloakAccount keycloakAccount = new SimpleKeycloakAccount( - springKeycloakPrincipal, - keycloakAuthenticationToken.getAccount().getRoles(), - (RefreshableKeycloakSecurityContext) keycloakAuthenticationToken.getAccount().getKeycloakSecurityContext()); - return new KeycloakAuthenticationToken( - keycloakAccount, - keycloakAuthenticationToken.isInteractive(), - keycloakAuthenticationToken.getAuthorities()); - } -} From da3c79b21aa323fa696f82686f6924eda9c638ea Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Wed, 6 Nov 2024 10:14:54 +0100 Subject: [PATCH 10/21] Move jakarta.servlet to top-level pom --- pom.xml | 18 +++++++++--------- vip-api/pom.xml | 4 ---- vip-application-importer/pom.xml | 5 ----- vip-application/pom.xml | 4 ---- vip-core/pom.xml | 4 ---- vip-datamanagement/pom.xml | 4 ---- vip-gatelab/pom.xml | 4 ---- vip-publication/pom.xml | 4 ---- vip-social/pom.xml | 5 ----- vip-visualization/pom.xml | 4 ---- 10 files changed, 9 insertions(+), 47 deletions(-) diff --git a/pom.xml b/pom.xml index 19ce20d88..af1b5b3d5 100644 --- a/pom.xml +++ b/pom.xml @@ -112,13 +112,7 @@ knowledge of the CeCILL-B license and that you accept its terms. smartgwt-lgpl 13.0p - - jakarta.servlet - jakarta.servlet-api - 6.1.0 - provided - - + - + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + provided + + org.junit.jupiter junit-jupiter diff --git a/vip-api/pom.xml b/vip-api/pom.xml index f225f2cc0..4f783b635 100644 --- a/vip-api/pom.xml +++ b/vip-api/pom.xml @@ -51,10 +51,6 @@ knowledge of the CeCILL-B license and that you accept its terms. - - jakarta.servlet - jakarta.servlet-api - jakarta.validation jakarta.validation-api diff --git a/vip-application-importer/pom.xml b/vip-application-importer/pom.xml index b8dd1455f..5688b1cb2 100644 --- a/vip-application-importer/pom.xml +++ b/vip-application-importer/pom.xml @@ -46,11 +46,6 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-ApplicationImporter - - jakarta.servlet - jakarta.servlet-api - - fr.insalyon.creatis vip-core diff --git a/vip-application/pom.xml b/vip-application/pom.xml index 77ea78bb8..99d66c8a7 100644 --- a/vip-application/pom.xml +++ b/vip-application/pom.xml @@ -46,10 +46,6 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-Application - - jakarta.servlet - jakarta.servlet-api - fr.insalyon.creatis vip-core diff --git a/vip-core/pom.xml b/vip-core/pom.xml index 320e14fcc..d2fa5e335 100644 --- a/vip-core/pom.xml +++ b/vip-core/pom.xml @@ -44,10 +44,6 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-Core - - jakarta.servlet - jakarta.servlet-api - org.gwtproject diff --git a/vip-datamanagement/pom.xml b/vip-datamanagement/pom.xml index 22d6efe09..ba6176d59 100644 --- a/vip-datamanagement/pom.xml +++ b/vip-datamanagement/pom.xml @@ -43,10 +43,6 @@ knowledge of the CeCILL-B license and that you accept its terms. jar VIP-DataManager - - jakarta.servlet - jakarta.servlet-api - fr.insalyon.creatis diff --git a/vip-gatelab/pom.xml b/vip-gatelab/pom.xml index 364b83679..119d6b0eb 100644 --- a/vip-gatelab/pom.xml +++ b/vip-gatelab/pom.xml @@ -43,10 +43,6 @@ knowledge of the CeCILL-B license and that you accept its terms. jar VIP-GateLab - - jakarta.servlet - jakarta.servlet-api - fr.insalyon.creatis vip-core diff --git a/vip-publication/pom.xml b/vip-publication/pom.xml index ce5337179..b7e8b5c7b 100644 --- a/vip-publication/pom.xml +++ b/vip-publication/pom.xml @@ -46,10 +46,6 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-Publication - - jakarta.servlet - jakarta.servlet-api - fr.insalyon.creatis vip-core diff --git a/vip-social/pom.xml b/vip-social/pom.xml index 9ccfdb4a9..a6bb17386 100644 --- a/vip-social/pom.xml +++ b/vip-social/pom.xml @@ -47,11 +47,6 @@ knowledge of the CeCILL-B license and that you accept its terms. VIP-Social - - jakarta.servlet - jakarta.servlet-api - - fr.insalyon.creatis vip-core diff --git a/vip-visualization/pom.xml b/vip-visualization/pom.xml index e027f7a9c..cafe17b51 100644 --- a/vip-visualization/pom.xml +++ b/vip-visualization/pom.xml @@ -57,10 +57,6 @@ knowledge of the CeCILL-B license and that you accept its terms. vip-datamanager ${project.version} - - jakarta.servlet - jakarta.servlet-api - From b398aae0fe387db14cbe9668e5d44679d5a82e49 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Wed, 6 Nov 2024 13:03:23 +0100 Subject: [PATCH 11/21] java21 PR adjustments - Add explicit comment on requestmatchers - Remove useless authmanager bean --- .../creatis/vip/api/security/ApiSecurityConfig.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java index 0e32bf6d5..2518f79a5 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java @@ -99,11 +99,6 @@ public ApiSecurityConfig( this.vipAuthenticationManager = new ProviderManager(providers); } - @Bean - public AuthenticationManager authenticationManager() { - return vipAuthenticationManager; - } - protected boolean isOIDCActive() { return env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); } @@ -111,6 +106,8 @@ protected boolean isOIDCActive() { @Bean @Order(1) public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { + // It is required to used AntPathRequestMatcher.antMatcher() everywhere below, + // otherwise Spring users MvcRequestMatcher as the default requestMatchers implementation. http .securityMatcher(AntPathRequestMatcher.antMatcher("/rest/**")) .authorizeHttpRequests((authorize) -> authorize @@ -141,7 +138,7 @@ public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { public ApikeyAuthenticationFilter apikeyAuthenticationFilter() throws Exception { return new ApikeyAuthenticationFilter( env.getRequiredProperty(CarminProperties.APIKEY_HEADER_NAME), - vipAuthenticationEntryPoint, authenticationManager()); + vipAuthenticationEntryPoint, vipAuthenticationManager); } @Service From 5dd4324dd90c7656b6bf6fb3655fc94adbc38ac5 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Wed, 6 Nov 2024 13:14:07 +0100 Subject: [PATCH 12/21] Update to JDK 21 in coverage and pmd workflows --- .github/workflows/coverage.yml | 4 ++-- .github/workflows/pmd.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ca520001b..554d2466e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -24,10 +24,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: "11" + java-version: "21" distribution: "temurin" - name: Build Maven diff --git a/.github/workflows/pmd.yml b/.github/workflows/pmd.yml index 73963cccc..f658abea0 100644 --- a/.github/workflows/pmd.yml +++ b/.github/workflows/pmd.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '11' + java-version: '21' - uses: pmd/pmd-github-action@v1 with: rulesets: 'ruleset.xml' From 2db453ace8f4c33abacd78f8453b0e29769842e4 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Wed, 6 Nov 2024 15:24:39 +0100 Subject: [PATCH 13/21] Upgrade hibernate validator - upgrade hibernate validator from 6.1.5.Final to 8.0.1.Final - remove direct import of jakarta.validation With this change, rest API objects validation now correctly happens at runtime, but still not in unit tests. --- vip-api/pom.xml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/vip-api/pom.xml b/vip-api/pom.xml index 4f783b635..43056a685 100644 --- a/vip-api/pom.xml +++ b/vip-api/pom.xml @@ -51,13 +51,6 @@ knowledge of the CeCILL-B license and that you accept its terms. - - jakarta.validation - jakarta.validation-api - 3.1.0 - provided - - fr.insalyon.creatis vip-core @@ -108,9 +101,9 @@ knowledge of the CeCILL-B license and that you accept its terms. - org.hibernate + org.hibernate.validator hibernate-validator - 6.1.5.Final + 8.0.1.Final org.glassfish From b4b63af000a4e93f0d977441762f315f05b63a7c Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Wed, 6 Nov 2024 15:50:29 +0100 Subject: [PATCH 14/21] Update to JDK 21 in maven workflow --- .github/workflows/maven.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index afbb04287..2a506fff1 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -25,10 +25,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '21' distribution: 'temurin' cache: maven server-id: ${{ (fromJSON(env.isProduction) && 'creatis-releases') || 'creatis-snapshots' }} From ade78b72c52e92e1cdb06661dc9d9411c0ead17d Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Fri, 8 Nov 2024 09:43:16 +0100 Subject: [PATCH 15/21] Fix file upload Was broken since the smartgwt and fileupload upgrades: - Backend fix: upgrade commons-io to 2.17.0 and pin version in top-level pom (needed for commons-fileupload2-jakarta-servlet6) - Front-end fix: Make gwt form submission synchronous with setCheckFileAccessOnSubmit(false), to workaround immediate destruction after submit (see smartgwt DynamicForm javadoc) --- pom.xml | 9 +++++++++ vip-core/pom.xml | 10 +++++----- vip-datamanagement/pom.xml | 6 ------ .../client/view/browser/FileUploadWindow.java | 1 + 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index af1b5b3d5..d5c06e0a8 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,15 @@ knowledge of the CeCILL-B license and that you accept its terms. logback-classic 1.2.3 + + + + commons-io + commons-io + 2.17.0 + diff --git a/vip-core/pom.xml b/vip-core/pom.xml index d2fa5e335..dd4f23a22 100644 --- a/vip-core/pom.xml +++ b/vip-core/pom.xml @@ -98,6 +98,11 @@ knowledge of the CeCILL-B license and that you accept its terms. slf4j-api + + commons-io + commons-io + + commons-configuration commons-configuration @@ -110,11 +115,6 @@ knowledge of the CeCILL-B license and that you accept its terms. 1.6 - - commons-io - commons-io - 2.4 - org.opensaml opensaml diff --git a/vip-datamanagement/pom.xml b/vip-datamanagement/pom.xml index ba6176d59..6240f5d70 100644 --- a/vip-datamanagement/pom.xml +++ b/vip-datamanagement/pom.xml @@ -56,12 +56,6 @@ knowledge of the CeCILL-B license and that you accept its terms. 2.0.0-M2 - - commons-io - commons-io - 2.17.0 - - com.fasterxml.jackson.core diff --git a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/client/view/browser/FileUploadWindow.java b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/client/view/browser/FileUploadWindow.java index 092832892..be0307838 100644 --- a/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/client/view/browser/FileUploadWindow.java +++ b/vip-datamanagement/src/main/java/fr/insalyon/creatis/vip/datamanager/client/view/browser/FileUploadWindow.java @@ -70,6 +70,7 @@ public FileUploadWindow(final ModalWindow modal, String baseDir, String target) form.setMethod(FormMethod.POST); form.setAction(GWT.getModuleBaseURL() + "/fileuploadservice"); form.setTarget(target); + form.setCheckFileAccessOnSubmit(false); // make JS upload synchronous as we destroy the form just after submit fileItem = new UploadItem("file"); fileItem.setTitle("File"); From f0e24103e60eea10b241c1e1d95c2da723a5c390 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Thu, 14 Nov 2024 08:32:00 +0100 Subject: [PATCH 16/21] Upgrade glassfish to 4.0.2 This was apparently needed along with the spring+hibernate upgrade, to have proper validator injection in integration tests. --- vip-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vip-api/pom.xml b/vip-api/pom.xml index 43056a685..86f470023 100644 --- a/vip-api/pom.xml +++ b/vip-api/pom.xml @@ -108,7 +108,7 @@ knowledge of the CeCILL-B license and that you accept its terms. org.glassfish jakarta.el - 3.0.3 + 4.0.2 From bdc6c86bc2be5785513104596940b1149a0a36d6 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Thu, 14 Nov 2024 08:38:12 +0100 Subject: [PATCH 17/21] Fix images dimensions in ApplicationsTileGrid --- .../core/client/view/application/ApplicationsTileGrid.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/client/view/application/ApplicationsTileGrid.java b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/client/view/application/ApplicationsTileGrid.java index 116b1b86b..22540f419 100755 --- a/vip-core/src/main/java/fr/insalyon/creatis/vip/core/client/view/application/ApplicationsTileGrid.java +++ b/vip-core/src/main/java/fr/insalyon/creatis/vip/core/client/view/application/ApplicationsTileGrid.java @@ -72,6 +72,11 @@ public ApplicationsTileGrid(String tileName) { DetailViewerField imageField = new DetailViewerField("picture"); imageField.setType("image"); + // SmartGWT 13 now sets explicit width and height attributes on tags, + // which default to all available space in the tile, causing image distortion. + // So we set the explicit width/height of our icons, which are all 48x48. + imageField.setImageHeight(48); + imageField.setImageWidth(48); DetailViewerField commonNameField = new DetailViewerField("applicationName"); commonNameField.setCellStyle("normal"); From 797049552cb62ea897e55f9b8b3334bd4b414cd9 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Tue, 19 Nov 2024 17:52:04 +0100 Subject: [PATCH 18/21] Add back OIDC Bearer token auth (aka Keycloak) - This provides support for OIDC Bearer token authentication with a Keycloak server, in a backwards-compatible way, but using Spring Security 6: - Support the same keycloak.json file as previously - Same network requests to Keycloak server - Same decoding of JWT claims for endpoints with role check - In addition to the backwards-compatible stuff: - Fix missing verification of locked account - Inner structures are ready to support multiple servers --- vip-api/pom.xml | 5 + .../vip/api/ApiPropertiesInitializer.java | 2 +- .../vip/api/security/ApiSecurityConfig.java | 82 ++++++------ .../security/VipAuthenticationEntryPoint.java | 2 +- .../apikey/ApikeyAuthenticationFilter.java | 15 +-- .../vip/api/security/oidc/OidcConfig.java | 86 ++++++++++++ .../vip/api/security/oidc/OidcResolver.java | 123 ++++++++++++++++++ .../vip/api/security/oidc/OidcToken.java | 41 ++++++ 8 files changed, 306 insertions(+), 50 deletions(-) create mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java create mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java create mode 100644 vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcToken.java diff --git a/vip-api/pom.xml b/vip-api/pom.xml index 86f470023..27ce4a663 100644 --- a/vip-api/pom.xml +++ b/vip-api/pom.xml @@ -98,6 +98,11 @@ knowledge of the CeCILL-B license and that you accept its terms. spring-security-web ${springsecurity.version} + + org.springframework.security + spring-security-oauth2-resource-server + ${springoauth.version} + diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java index e52b972fb..058fa9e31 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/ApiPropertiesInitializer.java @@ -96,7 +96,7 @@ private void verifyProperties() { if (env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE)) { - logger.info("Keycloak/OIDC activated, but this has no effect yet"); + logger.info("Keycloak/OIDC activated"); } else { logger.info("Keycloak/OIDC NOT active"); } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java index 2518f79a5..73b611664 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java @@ -38,6 +38,9 @@ import fr.insalyon.creatis.vip.api.security.apikey.ApikeyAuthenticationProvider; import fr.insalyon.creatis.vip.core.client.bean.User; +import fr.insalyon.creatis.vip.api.security.oidc.OidcConfig; +import fr.insalyon.creatis.vip.api.security.oidc.OidcResolver; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -47,9 +50,6 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.Customizer; @@ -64,17 +64,13 @@ import org.springframework.security.web.util.matcher.RegexRequestMatcher; import java.util.function.Supplier; -import java.util.ArrayList; - -import static fr.insalyon.creatis.vip.api.CarminProperties.KEYCLOAK_ACTIVATED; /** * VIP API configuration for API key and OIDC authentications. * * Authenticates /rest requests with either: - * - a static per-user API key (ApikeyAuthenticationFilter) - * - or an OIDC Bearer token (ex-Keycloak). This part is currently work-in-progress, - * with org.keycloak currently removed, and proper OIDC connector not implemented yet. + * - a static per-user API key, in apikeyAuthenticationFilter() + * - or an OIDC Bearer token, in oauth2ResourceServer() */ @Configuration @EnableWebSecurity @@ -84,30 +80,28 @@ public class ApiSecurityConfig { private final Environment env; private final VipAuthenticationEntryPoint vipAuthenticationEntryPoint; - private final AuthenticationManager vipAuthenticationManager; + private final ApikeyAuthenticationProvider apikeyAuthenticationProvider; + private final OidcConfig oidcConfig; + private final OidcResolver oidcResolver; @Autowired public ApiSecurityConfig( Environment env, ApikeyAuthenticationProvider apikeyAuthenticationProvider, - VipAuthenticationEntryPoint vipAuthenticationEntryPoint) { + VipAuthenticationEntryPoint vipAuthenticationEntryPoint, + OidcConfig oidcConfig, OidcResolver oidcResolver) { this.env = env; this.vipAuthenticationEntryPoint = vipAuthenticationEntryPoint; - // Build our AuthenticationManager instance, with one provider for each authentication method - ArrayList providers = new ArrayList<>(); - providers.add(apikeyAuthenticationProvider); - // providers.add(oidcAuthenticationProvider); - this.vipAuthenticationManager = new ProviderManager(providers); - } - - protected boolean isOIDCActive() { - return env.getProperty(KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); + this.apikeyAuthenticationProvider = apikeyAuthenticationProvider; + this.oidcConfig = oidcConfig; + this.oidcResolver = oidcResolver; } @Bean @Order(1) public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { - // It is required to used AntPathRequestMatcher.antMatcher() everywhere below, - // otherwise Spring users MvcRequestMatcher as the default requestMatchers implementation. + // Spring Security configuration for /rest API endpoints, common to both API key and OIDC authentications. + // Note that it is required to used AntPathRequestMatcher.antMatcher() everywhere below, + // otherwise Spring uses MvcRequestMatcher as the default requestMatchers implementation. http .securityMatcher(AntPathRequestMatcher.antMatcher("/rest/**")) .authorizeHttpRequests((authorize) -> authorize @@ -123,14 +117,28 @@ public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/**")).authenticated() .anyRequest().permitAll() ) - .addFilterBefore(apikeyAuthenticationFilter(), BasicAuthenticationFilter.class) - //.addFilterBefore(oidcAuthenticationFilter(), BasicAuthenticationFilter.class) .exceptionHandling((exceptionHandling) -> exceptionHandling.authenticationEntryPoint(vipAuthenticationEntryPoint)) // session must be activated otherwise OIDC auth info will be lost when accessing /loginEgi // .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .cors(Customizer.withDefaults()) .headers((headers) -> headers.frameOptions((frameOptions) -> frameOptions.sameOrigin())) .csrf((csrf) -> csrf.disable()); + // API key authentication always active + http.addFilterBefore(apikeyAuthenticationFilter(), BasicAuthenticationFilter.class); + // OIDC Bearer token authentication, if enabled + if (oidcConfig.isOIDCActive()) { + // We configure each OIDC server with issuerLocation instead of jwks_uri: on first token verification, + // this does two requests to the relevant OIDC server (obtained from the JWT "iss" field): + // - a GET .well-known/openid-configuration request to the OIDC server, to get this server jwks_uri + // - then a GET on the jwks_uri, to get the public key which is then used to verify the token + // Note that these two requests are done just once per OIDC server (not once per inbound API request). + // We also use a customized authenticationManagerResolver instead of simpler JwtDecoder bean, so that: + // - requests to the OIDC server happen at inbound-request-time instead of boot, and can be retried on failure + // - multiple servers can be supported + // - on successful authentication, Jwt principal is converted to a User principal, so DB lookup happens only once + http.oauth2ResourceServer((oauth2) -> oauth2 + .authenticationManagerResolver(oidcResolver.getAuthenticationManagerResolver())); + } return http.build(); } @@ -138,9 +146,10 @@ public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { public ApikeyAuthenticationFilter apikeyAuthenticationFilter() throws Exception { return new ApikeyAuthenticationFilter( env.getRequiredProperty(CarminProperties.APIKEY_HEADER_NAME), - vipAuthenticationEntryPoint, vipAuthenticationManager); + vipAuthenticationEntryPoint, apikeyAuthenticationProvider); } + // Provide authenticated user after a successful API key or OIDC token authentication @Service public static class CurrentUserProvider implements Supplier { @@ -148,26 +157,19 @@ public static class CurrentUserProvider implements Supplier { @Override public User get() { - Authentication authentication = - SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return null; } - User user = getApikeyUser(authentication); - if (user != null) { - return user; - } - // user = getOidcUser(authentication); - return null; - } - - private User getApikeyUser(Authentication authentication) { - if ( ! (authentication.getPrincipal() instanceof SpringApiPrincipal)) { + Object principal = authentication.getPrincipal(); + if (principal instanceof SpringApiPrincipal) { // API key authentication + return ((SpringApiPrincipal) principal).getVipUser(); + } else if (principal instanceof User) { // OIDC authentication + return (User) principal; + } else { // no resolvable user found (shouldn't happen) + logger.error("CurrentUserProvider: unknown principal class {}", principal.getClass()); return null; } - SpringApiPrincipal springCompatibleUser = - (SpringApiPrincipal) authentication.getPrincipal(); - return springCompatibleUser.getVipUser(); } } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java index 6e4af2519..79bcf16d9 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/VipAuthenticationEntryPoint.java @@ -72,7 +72,7 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - // keycloak may already have set it up + // OIDC resource server handler may already have set this header if ( ! response.containsHeader("WWW-Authenticate")) { response.addHeader("WWW-Authenticate", "API-key"); } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationFilter.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationFilter.java index cc00c683b..3a2a821b5 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationFilter.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/apikey/ApikeyAuthenticationFilter.java @@ -33,8 +33,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; @@ -59,21 +59,21 @@ public class ApikeyAuthenticationFilter extends OncePerRequestFilter { private final String apikeyHeader; private final AuthenticationEntryPoint authenticationEntryPoint; - private final AuthenticationManager authenticationManager; + private final AuthenticationProvider authenticationProvider; public ApikeyAuthenticationFilter( String apikeyHeader, AuthenticationEntryPoint authenticationEntryPoint, - AuthenticationManager authenticationManager) { + AuthenticationProvider authenticationProvider) { this.apikeyHeader = apikeyHeader; this.authenticationEntryPoint = authenticationEntryPoint; - this.authenticationManager = authenticationManager; + this.authenticationProvider = authenticationProvider; } @Override public void afterPropertiesSet() { - Assert.notNull(this.authenticationManager, - "An AuthenticationManager is required"); + Assert.notNull(this.authenticationProvider, + "An AuthenticationProvider is required"); Assert.notNull(this.authenticationEntryPoint, "An AuthenticationEntryPoint is required"); @@ -96,8 +96,7 @@ protected void doFilterInternal( logger.debug("apikey header found."); ApikeyAuthenticationToken authRequest = new ApikeyAuthenticationToken(apikey); - Authentication authResult = this.authenticationManager - .authenticate(authRequest); + Authentication authResult = this.authenticationProvider.authenticate(authRequest); logger.debug("Authentication success for : " + authResult); diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java new file mode 100644 index 000000000..a6a03de5f --- /dev/null +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java @@ -0,0 +1,86 @@ +package fr.insalyon.creatis.vip.api.security.oidc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.env.Environment; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.ArrayList; +import java.util.Collection; +import java.io.FileInputStream; +import java.net.URI; + +import fr.insalyon.creatis.vip.api.CarminProperties; + +@Configuration +public class OidcConfig { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final Environment env; + private final Collection servers; + + static private class OidcServer { + public String issuer; + public String resource; + + OidcServer(String issuer, String resource) { + this.issuer = issuer; + this.resource = resource; + } + } + + @Autowired + public OidcConfig(Environment env, Resource vipConfigFolder) throws Exception { + this.env = env; + // Build the list of OIDC servers from config file. If OIDC is disabled, just create an empty list. + ArrayList servers = new ArrayList<>(); + if (isOIDCActive()) { + final String basename = "keycloak.json"; + try { + // read and parse keycloak.json file to get OIDC server URL and clientId + String filename = vipConfigFolder.getFile().getAbsoluteFile() + "/" + basename; + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(new FileInputStream(filename)); + String baseURL = node.get("auth-server-url").asText(); + String realm = node.get("realm").asText(); + String resource = node.get("resource").asText(); + // Build OIDC server URL from auth-server-url + realm name (this is Keycloak-specific). + // We use URI.resolve() instead of just concatenation, to correctly handle optional '/' at the end of baseURL. + URI url = new URI(baseURL).resolve("realms/" + realm); + servers.add(new OidcServer(url.toASCIIString(), resource)); + } catch (Exception exception) { + // Many errors are possible here: + // IOException in FileInputStream (can't read file), JsonProcessingException in readTree (bad JSON), + // URISyntaxException in URI() (bad URL syntax), null exceptions in get().asText() (missing JSON key), + // and probably more... + // As long as isOIDCActive(), we do not try to handle them: just log and throw, causing a boot-time error. + logger.error("Failed loading {}", basename, exception); + throw exception; + } + } + this.servers = servers; + } + + public boolean isOIDCActive() { + return env.getProperty(CarminProperties.KEYCLOAK_ACTIVATED, Boolean.class, Boolean.FALSE); + } + + // list of issuers URLs + public Collection getServers() { + return servers.stream().map((server) -> server.issuer).toList(); + } + + // get resource name property for a given issuer URL + public String getIssuerResourceName(String issuer) { + for (OidcServer server: servers) { + if (server.issuer.equals(issuer)) { + return server.resource; + } + } + return null; + } +} diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java new file mode 100644 index 000000000..595197ef1 --- /dev/null +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java @@ -0,0 +1,123 @@ +package fr.insalyon.creatis.vip.api.security.oidc; + +import fr.insalyon.creatis.vip.core.client.bean.User; +import fr.insalyon.creatis.vip.core.server.business.BusinessException; +import fr.insalyon.creatis.vip.core.server.business.ConfigurationBusiness; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoders; +import org.springframework.security.oauth2.jwt.SupplierJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class OidcResolver { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final ConfigurationBusiness configurationBusiness; + private final OidcConfig oidcConfig; + + @Autowired + public OidcResolver(ConfigurationBusiness configurationBusiness, OidcConfig oidcConfig) { + this.configurationBusiness = configurationBusiness; + this.oidcConfig = oidcConfig; + } + + // Common "Bad credentials" exception for Jwt to User resolution errors + private BadCredentialsException authError() { + return new BadCredentialsException( + SpringSecurityMessageSource.getAccessor().getMessage( + "AbstractUserDetailsAuthenticationProvider.badCredentials", + "Bad credentials")); + } + + // Get DB User from authenticated JWT, using email-based resolution + private User getVipUser(Jwt jwt) throws BadCredentialsException { + String email = jwt.getClaim("email"); + if (email == null) { // no email field in token + logger.info("Can't authenticate from OIDC token: no email in token"); + throw authError(); + } + User vipUser; + try { + vipUser = configurationBusiness.getUserWithGroups(email); + } catch (BusinessException e) { // DB lookup failed + logger.error("Error when getting user from OIDC token: doing as if there is an auth error", e); + throw authError(); + } + if (vipUser == null) { // user not found + logger.info("Can't authenticate from OIDC token: user does not exist in VIP: {}", email); + throw authError(); + } + if (vipUser.isAccountLocked()) { // account locked + logger.info("Can't authenticate from OIDC token: account is locked: {}", email); + throw authError(); + } + return vipUser; + } + + // Create authorities list from jwt claims. + // Using resource_access..roles is Keycloak-specific. + private List parseAuthorities(User user, Jwt jwt) { + List roles = new ArrayList<>(); // default to no roles + try { + String resource = oidcConfig.getIssuerResourceName(jwt.getIssuer().toString()); + Map resourceAccess = jwt.getClaimAsMap("resource_access"); + Map realmAccess = (Map) resourceAccess.get(resource); + roles = (List) realmAccess.get("roles"); + // here we could also map an authority from user level, as done by Apikey auth: + // roles.add("ROLE_" + user.getLevel().name().toUpperCase()); + // but the existing Keycloak only used JWT-provided authorities + } catch (Exception e) { + logger.info("Can't get roles for user {}", user.getEmail(), e); + } + return AuthorityUtils.createAuthorityList(roles); + } + + // Custom converter class to transform Spring-provided Jwt into our own OidcToken, so that we can resolve and cache + // User as the authentication principal, and thus avoid multiple DB lookups per token within a given request. + static private class OidcJwtConverter implements Converter { + private final OidcResolver oidcResolver; + public OidcJwtConverter(OidcResolver oidcResolver) { + this.oidcResolver = oidcResolver; + } + public AbstractAuthenticationToken convert(Jwt jwt) { + // At this point, jwt has been checked for: iss, exp, nbf: i.e. trusted issuer + validity dates. + // We could also, but do not, check aud: this is optional in OIDC spec, and wasn't done by previous implementation. + // Now we have to resolve DB user, and map authorizations. + User user = oidcResolver.getVipUser(jwt); + List authorities = oidcResolver.parseAuthorities(user, jwt); + return new OidcToken(user, jwt, authorities); + } + } + + // Create a JwtIssuerAuthenticationManagerResolver instance with our custom converter. + // This is functionally equivalent to jwt.jwtAuthenticationConverter(), but for multi-tenant environment + // (see https://github.com/spring-projects/spring-security/issues/9096#issuecomment-973224956). + public JwtIssuerAuthenticationManagerResolver getAuthenticationManagerResolver() { + OidcJwtConverter converter = new OidcJwtConverter(this); + Map managers = new HashMap<>(); + for (String issuer : oidcConfig.getServers()) { + JwtDecoder decoder = new SupplierJwtDecoder(() -> JwtDecoders.fromIssuerLocation(issuer)); + JwtAuthenticationProvider provider = new JwtAuthenticationProvider(decoder); + provider.setJwtAuthenticationConverter(converter); + managers.put(issuer, provider::authenticate); + } + return new JwtIssuerAuthenticationManagerResolver(managers::get); + } +} diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcToken.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcToken.java new file mode 100644 index 000000000..321c38ca0 --- /dev/null +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcToken.java @@ -0,0 +1,41 @@ +package fr.insalyon.creatis.vip.api.security.oidc; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import fr.insalyon.creatis.vip.core.client.bean.User; + +import java.util.List; + +public class OidcToken extends AbstractAuthenticationToken { + private final User user; + private Jwt jwt; + + public OidcToken(User user, Jwt jwt, List authorities) { + super(authorities); + this.user = user; + this.jwt = jwt; + super.setAuthenticated(true); + } + + @Override + public Object getCredentials() { return jwt; } + + @Override + public Object getPrincipal() { return user; } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + if (isAuthenticated) { + throw new IllegalArgumentException( + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + } + super.setAuthenticated(false); + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + jwt = null; + } +} From 79e825c041817dcb34814dacafc5851e15983361 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Wed, 20 Nov 2024 14:12:14 +0100 Subject: [PATCH 19/21] OIDC Bearer token: update authorities parsing - Change default authorities parsing to realm_access, as in previous implementation - Add support for use-resource-role-mapping parameter in keycloak.json, to allow resource_access authorities to be configured --- .../vip/api/security/oidc/OidcConfig.java | 54 ++++++++++++------- .../vip/api/security/oidc/OidcResolver.java | 16 ++++-- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java index a6a03de5f..ad7f1ebaf 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java @@ -10,7 +10,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.JsonNode; -import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.Collection; import java.io.FileInputStream; import java.net.URI; @@ -21,15 +22,20 @@ public class OidcConfig { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Environment env; - private final Collection servers; + private final Map servers; - static private class OidcServer { - public String issuer; - public String resource; + public class OidcServer { + public final String issuer; + public final Boolean useResourceRoleMappings; + public final String resourceName; - OidcServer(String issuer, String resource) { + OidcServer(String issuer, Boolean useResourceRoleMappings, String resourceName) { this.issuer = issuer; - this.resource = resource; + if (useResourceRoleMappings && (resourceName == null || resourceName.isEmpty())) { + throw new IllegalArgumentException("useResourceRoleMappings enabled but no resourceName defined"); + } + this.useResourceRoleMappings = useResourceRoleMappings; + this.resourceName = resourceName; } } @@ -37,21 +43,36 @@ static private class OidcServer { public OidcConfig(Environment env, Resource vipConfigFolder) throws Exception { this.env = env; // Build the list of OIDC servers from config file. If OIDC is disabled, just create an empty list. - ArrayList servers = new ArrayList<>(); + HashMap servers = new HashMap<>(); if (isOIDCActive()) { final String basename = "keycloak.json"; try { - // read and parse keycloak.json file to get OIDC server URL and clientId + // read and parse keycloak.json file into one OidcServer config String filename = vipConfigFolder.getFile().getAbsoluteFile() + "/" + basename; ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(new FileInputStream(filename)); + // mandatory fields String baseURL = node.get("auth-server-url").asText(); String realm = node.get("realm").asText(); - String resource = node.get("resource").asText(); + // optional fields + Boolean useResourceRoleMappings; + if (node.hasNonNull("use-resource-role-mapping")) { + useResourceRoleMappings = node.get("use-resource-role-mapping").asBoolean(); + } else { + useResourceRoleMappings = false; + } + String resourceName; + if (node.hasNonNull("resource")) { + resourceName = node.get("resource").asText(); + } else { + resourceName = ""; + } + // Build OIDC server URL from auth-server-url + realm name (this is Keycloak-specific). // We use URI.resolve() instead of just concatenation, to correctly handle optional '/' at the end of baseURL. URI url = new URI(baseURL).resolve("realms/" + realm); - servers.add(new OidcServer(url.toASCIIString(), resource)); + String issuer = url.toASCIIString(); + servers.put(issuer, new OidcServer(issuer, useResourceRoleMappings, resourceName)); } catch (Exception exception) { // Many errors are possible here: // IOException in FileInputStream (can't read file), JsonProcessingException in readTree (bad JSON), @@ -71,16 +92,11 @@ public boolean isOIDCActive() { // list of issuers URLs public Collection getServers() { - return servers.stream().map((server) -> server.issuer).toList(); + return servers.keySet(); } // get resource name property for a given issuer URL - public String getIssuerResourceName(String issuer) { - for (OidcServer server: servers) { - if (server.issuer.equals(issuer)) { - return server.resource; - } - } - return null; + public OidcServer getServerConfig(String issuer) { + return servers.get(issuer); } } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java index 595197ef1..af9801660 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java @@ -72,14 +72,20 @@ private User getVipUser(Jwt jwt) throws BadCredentialsException { } // Create authorities list from jwt claims. - // Using resource_access..roles is Keycloak-specific. + // Parsing realm_access.roles or resource_access..roles is Keycloak-specific. private List parseAuthorities(User user, Jwt jwt) { List roles = new ArrayList<>(); // default to no roles try { - String resource = oidcConfig.getIssuerResourceName(jwt.getIssuer().toString()); - Map resourceAccess = jwt.getClaimAsMap("resource_access"); - Map realmAccess = (Map) resourceAccess.get(resource); - roles = (List) realmAccess.get("roles"); + OidcConfig.OidcServer server = oidcConfig.getServerConfig(jwt.getIssuer().toString()); + if (server.useResourceRoleMappings) { // use resource-level roles + String resource = server.resourceName; + Map resourceAccess = jwt.getClaimAsMap("resource_access"); + Map realmAccess = (Map) resourceAccess.get(resource); + roles = (List) realmAccess.get("roles"); + } else { // use realm-level roles + Map realmAccess = jwt.getClaimAsMap("realm_access"); + roles = (List) realmAccess.get("roles"); + } // here we could also map an authority from user level, as done by Apikey auth: // roles.add("ROLE_" + user.getLevel().name().toUpperCase()); // but the existing Keycloak only used JWT-provided authorities From 702d30ec489c7c6af7bd14820812755062e0d809 Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Fri, 22 Nov 2024 08:51:56 +0100 Subject: [PATCH 20/21] Minor fixes for PR #503 - Improve keycloak.json file reading - Change OidcConfig from @Configuration to @Service - Shorten AntPathRequestMatcher.antMatcher with a static import --- .../vip/api/security/ApiSecurityConfig.java | 22 +++++++++---------- .../vip/api/security/oidc/OidcConfig.java | 9 ++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java index 73b611664..9b214560e 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/ApiSecurityConfig.java @@ -60,7 +60,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.firewall.DefaultHttpFirewall; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; import org.springframework.security.web.util.matcher.RegexRequestMatcher; import java.util.function.Supplier; @@ -103,18 +103,18 @@ public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { // Note that it is required to used AntPathRequestMatcher.antMatcher() everywhere below, // otherwise Spring uses MvcRequestMatcher as the default requestMatchers implementation. http - .securityMatcher(AntPathRequestMatcher.antMatcher("/rest/**")) + .securityMatcher(antMatcher("/rest/**")) .authorizeHttpRequests((authorize) -> authorize - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/platform")).permitAll() - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/authenticate")).permitAll() - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/session")).permitAll() + .requestMatchers(antMatcher("/rest/platform")).permitAll() + .requestMatchers(antMatcher("/rest/authenticate")).permitAll() + .requestMatchers(antMatcher("/rest/session")).permitAll() .requestMatchers(new RegexRequestMatcher("/rest/pipelines\\?public", "GET")).permitAll() - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/publications")).permitAll() - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/reset-password")).permitAll() - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/register")).permitAll() - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/executions/{executionId}/summary")).hasAnyRole("SERVICE") - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/statistics/**")).hasAnyRole("ADVANCED", "ADMINISTRATOR") - .requestMatchers(AntPathRequestMatcher.antMatcher("/rest/**")).authenticated() + .requestMatchers(antMatcher("/rest/publications")).permitAll() + .requestMatchers(antMatcher("/rest/reset-password")).permitAll() + .requestMatchers(antMatcher("/rest/register")).permitAll() + .requestMatchers(antMatcher("/rest/executions/{executionId}/summary")).hasAnyRole("SERVICE") + .requestMatchers(antMatcher("/rest/statistics/**")).hasAnyRole("ADVANCED", "ADMINISTRATOR") + .requestMatchers(antMatcher("/rest/**")).authenticated() .anyRequest().permitAll() ) .exceptionHandling((exceptionHandling) -> exceptionHandling.authenticationEntryPoint(vipAuthenticationEntryPoint)) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java index ad7f1ebaf..11a951ff7 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java @@ -2,7 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.env.Environment; @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.JsonNode; +import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Collection; @@ -18,7 +19,7 @@ import fr.insalyon.creatis.vip.api.CarminProperties; -@Configuration +@Service public class OidcConfig { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Environment env; @@ -48,9 +49,9 @@ public OidcConfig(Environment env, Resource vipConfigFolder) throws Exception { final String basename = "keycloak.json"; try { // read and parse keycloak.json file into one OidcServer config - String filename = vipConfigFolder.getFile().getAbsoluteFile() + "/" + basename; + File file = vipConfigFolder.getFile().toPath().resolve(basename).toFile(); ObjectMapper mapper = new ObjectMapper(); - JsonNode node = mapper.readTree(new FileInputStream(filename)); + JsonNode node = mapper.readTree(file); // mandatory fields String baseURL = node.get("auth-server-url").asText(); String realm = node.get("realm").asText(); From d8adb42a3c211522ab9bd5cad5c21a96e90ce88c Mon Sep 17 00:00:00 2001 From: Nicolas Georges Date: Fri, 22 Nov 2024 09:48:30 +0100 Subject: [PATCH 21/21] Update exceptions handling: remove umbrella catch - In OidcConfig: let exceptions bubble up and rely on default logging, throw explicit BusinessException instead of implicit NullPointerException on missing mandatory fields in keycloak.json. - In OidcResolver.parseAuthorities: handle unknown resource name gracefully with logging, let other errors bubble up as they normally shouldn't happen with a Keycloak-generated JWT. - Also adjust some logs of abnormal cases to warning-level. --- .../vip/api/security/oidc/OidcConfig.java | 76 ++++++++++--------- .../vip/api/security/oidc/OidcResolver.java | 37 +++++---- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java index 11a951ff7..a8ad59a04 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcConfig.java @@ -1,5 +1,6 @@ package fr.insalyon.creatis.vip.api.security.oidc; +import fr.insalyon.creatis.vip.core.server.business.BusinessException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -11,10 +12,11 @@ import com.fasterxml.jackson.databind.JsonNode; import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.Collection; -import java.io.FileInputStream; import java.net.URI; import fr.insalyon.creatis.vip.api.CarminProperties; @@ -41,48 +43,48 @@ public class OidcServer { } @Autowired - public OidcConfig(Environment env, Resource vipConfigFolder) throws Exception { + public OidcConfig(Environment env, Resource vipConfigFolder) throws IOException, URISyntaxException, BusinessException { this.env = env; // Build the list of OIDC servers from config file. If OIDC is disabled, just create an empty list. HashMap servers = new HashMap<>(); if (isOIDCActive()) { + // Many errors are possible here: + // IOException in getFile() (can't read file), JsonProcessingException in readTree() (bad JSON), + // URISyntaxException in URI() (bad URL syntax), NullPointerException in get().asText() (missing JSON key), + // and probably more... + // As long as isOIDCActive(), we do not try to handle them: just let them bubble up, causing a boot-time error. final String basename = "keycloak.json"; - try { - // read and parse keycloak.json file into one OidcServer config - File file = vipConfigFolder.getFile().toPath().resolve(basename).toFile(); - ObjectMapper mapper = new ObjectMapper(); - JsonNode node = mapper.readTree(file); - // mandatory fields - String baseURL = node.get("auth-server-url").asText(); - String realm = node.get("realm").asText(); - // optional fields - Boolean useResourceRoleMappings; - if (node.hasNonNull("use-resource-role-mapping")) { - useResourceRoleMappings = node.get("use-resource-role-mapping").asBoolean(); - } else { - useResourceRoleMappings = false; - } - String resourceName; - if (node.hasNonNull("resource")) { - resourceName = node.get("resource").asText(); - } else { - resourceName = ""; - } - - // Build OIDC server URL from auth-server-url + realm name (this is Keycloak-specific). - // We use URI.resolve() instead of just concatenation, to correctly handle optional '/' at the end of baseURL. - URI url = new URI(baseURL).resolve("realms/" + realm); - String issuer = url.toASCIIString(); - servers.put(issuer, new OidcServer(issuer, useResourceRoleMappings, resourceName)); - } catch (Exception exception) { - // Many errors are possible here: - // IOException in FileInputStream (can't read file), JsonProcessingException in readTree (bad JSON), - // URISyntaxException in URI() (bad URL syntax), null exceptions in get().asText() (missing JSON key), - // and probably more... - // As long as isOIDCActive(), we do not try to handle them: just log and throw, causing a boot-time error. - logger.error("Failed loading {}", basename, exception); - throw exception; + // read and parse keycloak.json file into one OidcServer config + File file = vipConfigFolder.getFile().toPath().resolve(basename).toFile(); + ObjectMapper mapper = new ObjectMapper(); + JsonNode node = mapper.readTree(file); + // mandatory fields: just check for their presence, content will be validated by URI() below + String baseURL, realm; + if (node.hasNonNull("auth-server-url") && node.hasNonNull("realm")) { + baseURL = node.get("auth-server-url").asText(); + realm = node.get("realm").asText(); + } else { + throw new BusinessException("Failed parsing " + basename + ": missing mandatory fields"); + } + // optional fields + Boolean useResourceRoleMappings; + if (node.hasNonNull("use-resource-role-mapping")) { + useResourceRoleMappings = node.get("use-resource-role-mapping").asBoolean(); + } else { + useResourceRoleMappings = false; } + String resourceName; + if (node.hasNonNull("resource")) { + resourceName = node.get("resource").asText(); + } else { + resourceName = ""; + } + + // Build OIDC server URL from auth-server-url + realm name (this is Keycloak-specific). + // We use URI.resolve() instead of just concatenation, to correctly handle optional '/' at the end of baseURL. + URI url = new URI(baseURL).resolve("realms/" + realm); + String issuer = url.toASCIIString(); + servers.put(issuer, new OidcServer(issuer, useResourceRoleMappings, resourceName)); } this.servers = servers; } diff --git a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java index af9801660..732e69be4 100644 --- a/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java +++ b/vip-api/src/main/java/fr/insalyon/creatis/vip/api/security/oidc/OidcResolver.java @@ -50,7 +50,7 @@ private BadCredentialsException authError() { private User getVipUser(Jwt jwt) throws BadCredentialsException { String email = jwt.getClaim("email"); if (email == null) { // no email field in token - logger.info("Can't authenticate from OIDC token: no email in token"); + logger.warn("Can't authenticate from OIDC token: no email in token"); throw authError(); } User vipUser; @@ -61,7 +61,7 @@ private User getVipUser(Jwt jwt) throws BadCredentialsException { throw authError(); } if (vipUser == null) { // user not found - logger.info("Can't authenticate from OIDC token: user does not exist in VIP: {}", email); + logger.warn("Can't authenticate from OIDC token: user does not exist in VIP: {}", email); throw authError(); } if (vipUser.isAccountLocked()) { // account locked @@ -75,23 +75,28 @@ private User getVipUser(Jwt jwt) throws BadCredentialsException { // Parsing realm_access.roles or resource_access..roles is Keycloak-specific. private List parseAuthorities(User user, Jwt jwt) { List roles = new ArrayList<>(); // default to no roles - try { - OidcConfig.OidcServer server = oidcConfig.getServerConfig(jwt.getIssuer().toString()); - if (server.useResourceRoleMappings) { // use resource-level roles - String resource = server.resourceName; - Map resourceAccess = jwt.getClaimAsMap("resource_access"); - Map realmAccess = (Map) resourceAccess.get(resource); - roles = (List) realmAccess.get("roles"); - } else { // use realm-level roles - Map realmAccess = jwt.getClaimAsMap("realm_access"); + // At this point, jwt has already been verified by Spring resource server, so we assume the issuer is known (server != null). + // Also, we only handle Keycloak-generated tokens for now, so we assume realm_access and roles fields to exist. + // Thus, the only error case we handle in token parsing below is a mismatch on resourceName: + // any other issue will deliberately cause an exception and request error 500. + OidcConfig.OidcServer server = oidcConfig.getServerConfig(jwt.getIssuer().toString()); + if (server.useResourceRoleMappings) { // use resource-level roles + String resource = server.resourceName; + Map resourceAccess = jwt.getClaimAsMap("resource_access"); + Map realmAccess = (Map) resourceAccess.get(resource); + if (realmAccess != null) { roles = (List) realmAccess.get("roles"); + } else { + logger.warn("Can't get roles for user {}: resource '{}' not found in token, defaulting to no roles", + user.getEmail(), resource); } - // here we could also map an authority from user level, as done by Apikey auth: - // roles.add("ROLE_" + user.getLevel().name().toUpperCase()); - // but the existing Keycloak only used JWT-provided authorities - } catch (Exception e) { - logger.info("Can't get roles for user {}", user.getEmail(), e); + } else { // use realm-level roles + Map realmAccess = jwt.getClaimAsMap("realm_access"); + roles = (List) realmAccess.get("roles"); } + // here we could also map an authority from user level, as done by Apikey auth: + // roles.add("ROLE_" + user.getLevel().name().toUpperCase()); + // but the existing Keycloak only used JWT-provided authorities return AuthorityUtils.createAuthorityList(roles); }