From d6dfad9ca729e158d2faed42d7a76083996c4a98 Mon Sep 17 00:00:00 2001 From: Sebastian Wollner <wollner@edu-sharing.net> Date: Mon, 25 Nov 2024 12:00:51 +0100 Subject: [PATCH 1/3] feat: ability to enforce Configuration Context #ITSJOINLTY-1715 --- .../extension/custom-cache-context.xml | 10 +- .../restservices/ApiAuthenticationFilter.java | 60 +++----- .../restservices/admin/v1/AdminApi.java | 50 +++++- .../restservices/config/v1/ConfigApi.java | 2 +- .../service/admin/AdminService.java | 18 +-- .../service/config/ConfigService.java | 4 +- .../service/config/ConfigServiceFactory.java | 48 +++++- .../service/config/ConfigServiceImpl.java | 73 +++++++-- .../rest/api/src/main/resources/openapi.json | 144 ++++++++++++++++++ 9 files changed, 333 insertions(+), 76 deletions(-) diff --git a/Backend/alfresco/module/src/main/amp/config/alfresco/extension/custom-cache-context.xml b/Backend/alfresco/module/src/main/amp/config/alfresco/extension/custom-cache-context.xml index b4e235ab9..3d77059c7 100644 --- a/Backend/alfresco/module/src/main/amp/config/alfresco/extension/custom-cache-context.xml +++ b/Backend/alfresco/module/src/main/amp/config/alfresco/extension/custom-cache-context.xml @@ -54,9 +54,13 @@ <constructor-arg value="cache.eduSharingConfigCache"/> </bean> - <bean name="eduSharingContextCache" factory-bean="cacheFactory" factory-method="createCache"> - <constructor-arg value="cache.eduSharingContextCache"/> - </bean> + <bean name="eduSharingContextCacheByDomain" factory-bean="cacheFactory" factory-method="createCache"> + <constructor-arg value="cache.eduSharingContextCacheByDomain"/> + </bean> + + <bean name="eduSharingContextCacheById" factory-bean="cacheFactory" factory-method="createCache"> + <constructor-arg value="cache.eduSharingContextCacheByDomain"/> + </bean> <bean name="eduSharingVersionCache" factory-bean="cacheFactory" factory-method="createCache"> <constructor-arg value="cache.eduSharingVersionCache"/> diff --git a/Backend/services/core/src/main/java/org/edu_sharing/restservices/ApiAuthenticationFilter.java b/Backend/services/core/src/main/java/org/edu_sharing/restservices/ApiAuthenticationFilter.java index e63257d06..e63c27655 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/restservices/ApiAuthenticationFilter.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/restservices/ApiAuthenticationFilter.java @@ -1,29 +1,17 @@ package org.edu_sharing.restservices; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.*; - import com.typesafe.config.Config; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; +import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; - -import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.apache.log4j.Logger; import org.edu_sharing.alfresco.authentication.subsystems.SubsystemChainingAuthenticationService; import org.edu_sharing.alfresco.lightbend.LightbendConfigLoader; -import org.edu_sharing.alfrescocontext.gate.AlfAppContextGate; import org.edu_sharing.repository.client.tools.CCConstants; import org.edu_sharing.repository.server.AuthenticationToolAPI; import org.edu_sharing.repository.server.authentication.AuthenticationFilter; import org.edu_sharing.repository.server.authentication.ContextManagementFilter; -import org.edu_sharing.service.authentication.EduAuthentication; import org.edu_sharing.service.authentication.oauth2.TokenService; import org.edu_sharing.service.authentication.oauth2.TokenService.Token; import org.edu_sharing.service.authority.AuthorityServiceFactory; @@ -33,16 +21,19 @@ import org.edu_sharing.spring.security.basic.CSRFConfig; import org.springframework.context.ApplicationContext; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + public class ApiAuthenticationFilter implements jakarta.servlet.Filter { Logger logger = Logger.getLogger(ApiAuthenticationFilter.class); private TokenService tokenService; - @Override - public void destroy() { - } - @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { @@ -106,20 +97,18 @@ public void doFilter(ServletRequest req, ServletResponse resp, } } else if (authHdr.length() > 10 && authHdr.substring(0, 10).equalsIgnoreCase(CCConstants.AUTH_HEADER_EDU_TICKET)) { String ticket = authHdr.substring(10).trim(); - if (ticket != null) { - if (authTool.validateTicket(ticket)) { - // Force a renew of all toolpermissions since they might have now changed! - ToolPermissionServiceFactory.getInstance().getAllAvailableToolPermissions(true); - //if its APIClient username is ignored and is figured out with authentication service - authTool.storeAuthInfoInSession(authTool.getCurrentUser(), ticket, CCConstants.AUTH_TYPE_TICKET, httpReq.getSession()); - validatedAuth = authTool.validateAuthentication(session); - } + if (authTool.validateTicket(ticket)) { + // Force a renew of all toolpermissions since they might have now changed! + ToolPermissionServiceFactory.getInstance().getAllAvailableToolPermissions(true); + //if its APIClient username is ignored and is figured out with authentication service + authTool.storeAuthInfoInSession(authTool.getCurrentUser(), ticket, CCConstants.AUTH_TYPE_TICKET, httpReq.getSession()); + validatedAuth = authTool.validateAuthentication(session); } } } Config accessConfig = LightbendConfigLoader.get().getConfig("security.access"); - List<String> AUTHLESS_ENDPOINTS = Arrays.asList(new String[]{"/authentication", "/_about", "/config", "/register", "/sharing", + List<String> AUTHLESS_ENDPOINTS = Arrays.asList("/authentication", "/_about", "/config", "/register", "/sharing", "/lti/v13/oidc/login_initiations", "/lti/v13/lti13", "/lti/v13/registration/dynamic", @@ -127,13 +116,13 @@ public void doFilter(ServletRequest req, ServletResponse resp, "/lti/v13/details", "/ltiplatform/v13/openid-configuration", "/ltiplatform/v13/openid-registration", - "/ltiplatform/v13/content"}); - List<String> ADMIN_ENDPOINTS = Arrays.asList(new String[]{"/admin", "/bulk", "/lti/v13/registration/static", "/lti/v13/registration/url"}); + "/ltiplatform/v13/content"); + List<String> ADMIN_ENDPOINTS = Arrays.asList("/admin", "/bulk", "/lti/v13/registration/static", "/lti/v13/registration/url"); List<String> DISABLED_ENDPOINTS = new ArrayList<>(); try { - if (!ConfigServiceFactory.getCurrentConfig(req).getValue("register.local", true)) { - if (ConfigServiceFactory.getCurrentConfig(req).getValue("register.recoverPassword", false)) { + if (!ConfigServiceFactory.getCurrentConfig(httpReq).getValue("register.local", true)) { + if (ConfigServiceFactory.getCurrentConfig(httpReq).getValue("register.recoverPassword", false)) { DISABLED_ENDPOINTS.add("/register/v1/register"); DISABLED_ENDPOINTS.add("/register/v1/activate"); } else { @@ -141,7 +130,7 @@ public void doFilter(ServletRequest req, ServletResponse resp, DISABLED_ENDPOINTS.add("/register"); } } - } catch (Exception e) { + } catch (Exception ignored) { } boolean noAuthenticationNeeded = false; @@ -203,13 +192,8 @@ public void doFilter(ServletRequest req, ServletResponse resp, return; } - /** - * allow authless calls with AUTH_SINGLE_USE_NODEID by appauth - */ - boolean trustedAuth = false; - if (ContextManagementFilter.accessTool != null && ContextManagementFilter.accessTool.get() != null) { - trustedAuth = true; - } + // allow authless calls with AUTH_SINGLE_USE_NODEID by appauth + boolean trustedAuth = ContextManagementFilter.accessTool != null && ContextManagementFilter.accessTool.get() != null; // ignore the auth for the login if (validatedAuth == null && (!noAuthenticationNeeded && !trustedAuth)) { diff --git a/Backend/services/core/src/main/java/org/edu_sharing/restservices/admin/v1/AdminApi.java b/Backend/services/core/src/main/java/org/edu_sharing/restservices/admin/v1/AdminApi.java index 0420b0f15..01a5961fb 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/restservices/admin/v1/AdminApi.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/restservices/admin/v1/AdminApi.java @@ -11,6 +11,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.Action; @@ -44,6 +48,7 @@ import org.edu_sharing.service.admin.model.RepositoryConfig; import org.edu_sharing.service.admin.model.ServerUpdateInfo; import org.edu_sharing.service.admin.model.ToolPermission; +import org.edu_sharing.service.config.ConfigServiceFactory; import org.edu_sharing.service.lifecycle.PersonDeleteOptions; import org.edu_sharing.service.lifecycle.PersonLifecycleService; import org.edu_sharing.service.lifecycle.PersonReport; @@ -57,10 +62,6 @@ import org.edu_sharing.service.version.RepositoryVersionInfo; import org.glassfish.jersey.media.multipart.FormDataParam; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.Response; import java.io.FileWriter; import java.io.InputStream; import java.io.Serializable; @@ -1523,6 +1524,47 @@ public Response setConfig(@Context HttpServletRequest req,RepositoryConfig confi return ErrorResponse.createResponse(t); } } + + @PUT + @Path("/repositoryConfig/enforceContext/{contextId}") + @Operation(summary = "set/update the repository config object") + @ApiResponses(value = { + @ApiResponse(responseCode="200", description=RestConstants.HTTP_200, content = @Content(schema = @Schema(implementation = Void.class))), + @ApiResponse(responseCode="400", description=RestConstants.HTTP_400, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="401", description=RestConstants.HTTP_401, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="403", description=RestConstants.HTTP_403, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="404", description=RestConstants.HTTP_404, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="500", description=RestConstants.HTTP_500, content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) + public Response enforceContext(@Context HttpServletRequest req, @PathParam("contextId") String contextId) { + try { + ConfigServiceFactory.enforceContext(contextId); + return Response.ok().build(); + } catch (Throwable t) { + return ErrorResponse.createResponse(t); + } + } + + @DELETE + @Path("/repositoryConfig/enforceContext") + @Operation(summary = "set/update the repository config object") + @ApiResponses(value = { + @ApiResponse(responseCode="200", description=RestConstants.HTTP_200, content = @Content(schema = @Schema(implementation = Void.class))), + @ApiResponse(responseCode="400", description=RestConstants.HTTP_400, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="401", description=RestConstants.HTTP_401, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="403", description=RestConstants.HTTP_403, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="404", description=RestConstants.HTTP_404, content = @Content(schema = @Schema(implementation = ErrorResponse.class))), + @ApiResponse(responseCode="500", description=RestConstants.HTTP_500, content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) + public Response clearEnforcedContext(@Context HttpServletRequest req) { + try { + ConfigServiceFactory.clearEnforcedContext(); + return Response.ok().build(); + } catch (Throwable t) { + return ErrorResponse.createResponse(t); + } + } + + + @GET @Path("/configFile") @Operation(summary = "get a base system config file (e.g. edu-sharing.conf)") diff --git a/Backend/services/core/src/main/java/org/edu_sharing/restservices/config/v1/ConfigApi.java b/Backend/services/core/src/main/java/org/edu_sharing/restservices/config/v1/ConfigApi.java index 49704fbb5..7498aea31 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/restservices/config/v1/ConfigApi.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/restservices/config/v1/ConfigApi.java @@ -54,7 +54,7 @@ public Response getConfig() { ConfigService configService = ConfigServiceFactory.getConfigService(); config.setGlobal(configService.getConfig().values); try { - Context context = configService.getContext(ConfigServiceFactory.getCurrentDomain()); + Context context = configService.getContextByDomain(ConfigServiceFactory.getCurrentDomain()); if (context != null) { config.setContextId(context.id); config.setCurrent(configService.getConfigByContext(context).values); diff --git a/Backend/services/core/src/main/java/org/edu_sharing/service/admin/AdminService.java b/Backend/services/core/src/main/java/org/edu_sharing/service/admin/AdminService.java index 18d6f95df..340a44b48 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/service/admin/AdminService.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/service/admin/AdminService.java @@ -1,28 +1,28 @@ package org.edu_sharing.service.admin; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Properties; - import org.alfresco.service.cmr.repository.NodeRef; import org.edu_sharing.repository.client.rpc.cache.CacheCluster; import org.edu_sharing.repository.client.rpc.cache.CacheInfo; import org.edu_sharing.repository.server.jobs.quartz.ImmediateJobListener; import org.edu_sharing.repository.server.jobs.quartz.JobDescription; +import org.edu_sharing.repository.server.jobs.quartz.JobInfo; import org.edu_sharing.repository.server.tools.ApplicationInfo; import org.edu_sharing.repository.server.tools.PropertiesHelper; import org.edu_sharing.restservices.admin.v1.model.PluginStatus; import org.edu_sharing.service.admin.model.GlobalGroup; -import org.edu_sharing.repository.server.jobs.quartz.JobInfo; import org.edu_sharing.service.admin.model.RepositoryConfig; import org.edu_sharing.service.admin.model.ServerUpdateInfo; import org.edu_sharing.service.admin.model.ToolPermission; import org.edu_sharing.service.version.RepositoryVersionInfo; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; + public interface AdminService { List<JobInfo> getJobs() throws Throwable; diff --git a/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigService.java b/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigService.java index 1b9e4d116..4a0264d96 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigService.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigService.java @@ -17,9 +17,11 @@ public interface ConfigService { * @return * @throws Exception */ - Context getContext(String domain) throws Exception; + Context getContextByDomain(String domain) throws Exception; List<Context> getAvailableContext() throws Exception; + Context getContextById(String id) throws Exception; + Context createOrUpdateContext(Context context); Config getConfigByDomain(String domain) throws Exception; diff --git a/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceFactory.java b/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceFactory.java index 618d7fc7f..2cf0924f7 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceFactory.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceFactory.java @@ -1,21 +1,23 @@ package org.edu_sharing.service.config; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; -import org.edu_sharing.repository.server.AuthenticationToolAPI; import org.edu_sharing.alfresco.repository.server.authentication.Context; import org.edu_sharing.alfresco.service.config.model.Config; import org.edu_sharing.alfresco.service.config.model.KeyValuePair; import org.edu_sharing.alfresco.service.config.model.Language; +import org.edu_sharing.repository.server.AuthenticationToolAPI; import org.edu_sharing.repository.server.RequestHelper; - -import jakarta.servlet.ServletRequest; -import jakarta.servlet.http.HttpServletRequest; import org.edu_sharing.spring.ApplicationContextFactory; +import java.util.Arrays; import java.util.List; +import java.util.Optional; public class ConfigServiceFactory { - private static final String[] DEFAULT_LANGUAGES = new String[]{"de", "en"}; + private static final String[] DEFAULT_LANGUAGES = new String[]{"de", "en"}; + public static final String ENFORCED_CONTEXT = "ENFORCED_CONTEXT"; static Logger logger = Logger.getLogger(ConfigServiceFactory.class); public static ConfigService getConfigService(){ @@ -33,7 +35,7 @@ public static String getCurrentContextId(){ } public static String getCurrentContextId(HttpServletRequest req){ try { - org.edu_sharing.alfresco.service.config.model.Context context = getConfigService().getContext(getCurrentDomain(req)); + org.edu_sharing.alfresco.service.config.model.Context context = getConfigService().getContextByDomain(getCurrentDomain(req)); if(context == null) { return null; } @@ -43,7 +45,7 @@ public static String getCurrentContextId(HttpServletRequest req){ return null; } } - public static Config getCurrentConfig(ServletRequest req) throws Exception { + public static Config getCurrentConfig(HttpServletRequest req) throws Exception { try { return getConfigService().getConfigByDomain(req==null ? getCurrentDomain() : getCurrentDomain(req)); }catch(Throwable t) { @@ -54,12 +56,42 @@ public static Config getCurrentConfig(ServletRequest req) throws Exception { public static String getCurrentDomain() { return getCurrentDomain(Context.getCurrentInstance().getRequest()); } - public static String getCurrentDomain(ServletRequest req) { + + public static String getCurrentDomain(HttpServletRequest req) { + Object enforcedContext = req.getSession().getAttribute(ENFORCED_CONTEXT); + if(StringUtils.isNotBlank((String)enforcedContext)){ + return enforcedContext.toString(); + } + String domain = new RequestHelper(req).getServerName(); logger.debug("current domain:" + domain); return domain; } + public static void enforceContext(String contextId){ + try { + org.edu_sharing.alfresco.service.config.model.Context context = getConfigService().getContextById(contextId); + if(context == null){ + throw new IllegalArgumentException(String.format("Context with contextId %s does not exists",contextId)); + } + + if(context.domain == null || context.domain.length == 0){ + throw new IllegalArgumentException(String.format("Context %s doesn't has domains",contextId)); + } + + Optional<String> domain = Arrays.stream(context.domain).findFirst(); + Context.getCurrentInstance().getRequest().getSession().setAttribute(ENFORCED_CONTEXT, domain.get()); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void clearEnforcedContext(){ + Context.getCurrentInstance().getRequest().getSession().removeAttribute(ENFORCED_CONTEXT); + } + public static List<KeyValuePair> getLanguageData(List<Language> languages,String language) { if(languages!=null && languages.size()>0) { for(org.edu_sharing.alfresco.service.config.model.Language entry : languages) { diff --git a/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceImpl.java b/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceImpl.java index 4fdec3579..86e5087d1 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceImpl.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/service/config/ConfigServiceImpl.java @@ -45,7 +45,8 @@ public class ConfigServiceImpl implements ConfigService, ApplicationListener<Ref private static String CACHE_KEY = "CLIENT_CONFIG"; // we use a non-serializable Config as value because this is a local cache and not distributed private static SimpleCache<String, Config> configCache = AlfAppContextGate.getApplicationContext().getBean("eduSharingConfigCache", SimpleCache.class); - private static SimpleCache<String, Context> contextCache = AlfAppContextGate.getApplicationContext().getBean("eduSharingContextCache", SimpleCache.class); + private static SimpleCache<String, Context> contextCacheByDomain = AlfAppContextGate.getApplicationContext().getBean("eduSharingContextCacheByDomain", SimpleCache.class); + private static SimpleCache<String, Context> contextCacheById = AlfAppContextGate.getApplicationContext().getBean("eduSharingContextCacheById", SimpleCache.class); private static final Unmarshaller jaxbUnmarshaller; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -106,16 +107,26 @@ public void deleteContext(String id) throws Exception { } @Override - public Context getContext(String domain) throws Exception { - if(StringUtils.isBlank(domain)) { + public Context getContextByDomain(String domain) throws Exception { + if (StringUtils.isBlank(domain)) { return null; } buildContextCache(); - return contextCache.get(domain); + return contextCacheByDomain.get(domain); } @Override - public List<Context> getAvailableContext() throws Exception { + public Context getContextById(String id) throws Exception { + if (StringUtils.isBlank(id)) { + return null; + } + + buildContextCache(); + return contextCacheById.get(id); + } + + @Override + public List<Context> getAvailableContext() { return AuthenticationUtil.runAsSystem(() -> { String eduSharingSystemFolderContext = userEnvironmentTool.getEdu_SharingContextFolder(); Map<String, Map<String, Object>> dynamicContextObjects = nodeService.getChildrenPropsByType(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, eduSharingSystemFolderContext, CCConstants.CCM_TYPE_CONTEXT); @@ -130,9 +141,7 @@ public List<Context> getAvailableContext() throws Exception { } private void buildContextCache() throws Exception { - if (contextCache.getKeys().isEmpty()) { - // put an element so that next time, the cache is never empty! - contextCache.put("", null); + if (contextCacheByDomain.getKeys().isEmpty()) { Config config = getConfig(); if (config.contexts != null && config.contexts.context != null) { for (Context context : config.contexts.context) { @@ -141,7 +150,7 @@ private void buildContextCache() throws Exception { } for (String dom : context.domain) { - contextCache.put(dom, context); + contextCacheByDomain.put(dom, context); } } } @@ -155,13 +164,53 @@ private void buildContextCache() throws Exception { .map(x -> x.get(CCConstants.CCM_PROP_CONTEXT_CONFIG).toString()) .map(CheckedFunction.wrap(x -> objectMapper.readValue(x, Context.class), null)) .filter(Objects::nonNull) - .forEach(x -> Arrays.stream(x.domain).forEach(y -> contextCache.put(y, x))); + .forEach(x -> Arrays.stream(x.domain).filter(Objects::nonNull).forEach(y -> contextCacheByDomain.put(y, x))); + + return null; + }); + + + + if(contextCacheByDomain.getKeys().isEmpty()){ + // put an element so that next time, the cache is never empty! + contextCacheByDomain.put("", null); + } + } + + if(contextCacheById.getKeys().isEmpty()){ + Config config = getConfig(); + if (config.contexts != null && config.contexts.context != null) { + for (Context context : config.contexts.context) { + if (StringUtils.isNotBlank(context.id)) { + contextCacheById.put(context.id, context); + } + } + } + AuthenticationUtil.runAsSystem(() -> { + String eduSharingSystemFolderContext = userEnvironmentTool.getEdu_SharingContextFolder(); + Map<String, Map<String, Object>> dynamicContextObjects = nodeService.getChildrenPropsByType(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, eduSharingSystemFolderContext, CCConstants.CCM_TYPE_CONTEXT); + dynamicContextObjects + .values() + .stream() + .map(x -> x.get(CCConstants.CCM_PROP_CONTEXT_CONFIG).toString()) + .map(CheckedFunction.wrap(x -> objectMapper.readValue(x, Context.class), null)) + .filter(Objects::nonNull) + .filter(x -> StringUtils.isNotBlank(x.id)) + .forEach(x -> contextCacheById.put(x.id, x)); return null; }); + + if(contextCacheById.getKeys().isEmpty()){ + // put an element so that next time, the cache is never empty! + contextCacheById.put("", null); + } } } + + + @Override public Context createOrUpdateContext(Context context) { return AuthenticationUtil.runAsSystem(() -> { @@ -183,7 +232,7 @@ public Context createOrUpdateContext(Context context) { @Override public Config getConfigByDomain(String domain) throws Exception { - Context context = getContext(domain); + Context context = getContextByDomain(domain); if (context == null) { throw new IllegalArgumentException("Context with domain " + domain + " does not exists"); } @@ -308,7 +357,7 @@ private void overrideValues(Values values, Values override) throws private void refresh() { configCache.clear(); - contextCache.clear(); + contextCacheByDomain.clear(); try { getConfig(); } catch (Exception e) { diff --git a/Backend/services/rest/api/src/main/resources/openapi.json b/Backend/services/rest/api/src/main/resources/openapi.json index 724e4ebc5..079285d0a 100644 --- a/Backend/services/rest/api/src/main/resources/openapi.json +++ b/Backend/services/rest/api/src/main/resources/openapi.json @@ -11165,6 +11165,150 @@ ] } }, + "/admin/v1/repositoryConfig/enforceContext": { + "delete": { + "operationId": "clearEnforcedContext", + "responses": { + "200": { + "content": { + "application/json": {} + }, + "description": "OK." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Preconditions are not present." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authorization failed." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Session user has insufficient rights to perform this operation." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Ressources are not found." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Fatal error occured." + } + }, + "summary": "set/update the repository config object", + "tags": [ + "ADMIN v1" + ] + } + }, + "/admin/v1/repositoryConfig/enforceContext/{contextId}": { + "put": { + "operationId": "enforceContext", + "parameters": [ + { + "in": "path", + "name": "contextId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": {} + }, + "description": "OK." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Preconditions are not present." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authorization failed." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Session user has insufficient rights to perform this operation." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Ressources are not found." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Fatal error occured." + } + }, + "summary": "set/update the repository config object", + "tags": [ + "ADMIN v1" + ] + } + }, "/admin/v1/serverUpdate/list": { "get": { "description": "list available update tasks", From dad0e6859075a21400a808364dc00e47a27c9bd4 Mon Sep 17 00:00:00 2001 From: Torsten Simon <simon@edu-sharing.net> Date: Wed, 16 Oct 2024 10:46:42 +0200 Subject: [PATCH 2/3] fix:default license version 4.0 if values are unset --- .../edu_sharing/service/license/LicenseService.java | 6 +++--- .../license-details/license-details.component.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Backend/services/core/src/main/java/org/edu_sharing/service/license/LicenseService.java b/Backend/services/core/src/main/java/org/edu_sharing/service/license/LicenseService.java index 59bfc681a..2812f5465 100644 --- a/Backend/services/core/src/main/java/org/edu_sharing/service/license/LicenseService.java +++ b/Backend/services/core/src/main/java/org/edu_sharing/service/license/LicenseService.java @@ -4,7 +4,7 @@ import org.edu_sharing.repository.tools.URLHelper; public class LicenseService { - + private static String DEFAULT_LICENSE_VERSION = "4.0"; public String getIconUrl(String license,boolean dynamic){ if(license==null || license.isEmpty()) license="none"; @@ -58,7 +58,7 @@ public String getLicenseUrl(String license, String locale, String version){ } if(result != null){ - version = (version == null) ? "3.0" : version; + version = (version == null) ? DEFAULT_LICENSE_VERSION : version; if(result.contains("${version}")){ result = result.replace("${version}", version); } @@ -71,5 +71,5 @@ public String getLicenseUrl(String license, String locale, String version){ return result; } - + } diff --git a/Frontend/src/app/features/mds/mds-editor/widgets/mds-editor-widget-license/license-details/license-details.component.ts b/Frontend/src/app/features/mds/mds-editor/widgets/mds-editor-widget-license/license-details/license-details.component.ts index 9db094a36..ec679c049 100644 --- a/Frontend/src/app/features/mds/mds-editor/widgets/mds-editor-widget-license/license-details/license-details.component.ts +++ b/Frontend/src/app/features/mds/mds-editor/widgets/mds-editor-widget-license/license-details/license-details.component.ts @@ -57,11 +57,11 @@ export class LicenseDetailsComponent implements OnChanges { if (license.indexOf('ND') !== -1) this.ccShare = 'ND'; if (license.indexOf('NC') !== -1) this.ccCommercial = 'NC'; - this.ccVersion = this.getValueForAll( - RestConstants.CCM_PROP_LICENSE_CC_VERSION, - this.ccVersion, - ); - this.ccCountry = this.getValueForAll(RestConstants.CCM_PROP_LICENSE_CC_LOCALE); + // also see @LicenseService + this.ccVersion = + this.getValueForAll(RestConstants.CCM_PROP_LICENSE_CC_VERSION, this.ccVersion) || + '4.0'; + this.ccCountry = this.getValueForAll(RestConstants.CCM_PROP_LICENSE_CC_LOCALE) || 'DE'; } if (license === 'CC_0') { this.type = 'CC_0'; From 58f3b1a1ef638d8c86471911bc623bea7e7334c4 Mon Sep 17 00:00:00 2001 From: Frank Thomschke <thomschke@edu-sharing.net> Date: Tue, 26 Nov 2024 14:41:59 +0100 Subject: [PATCH 3/3] Bump Redis to 7.4.1 due to hostname support --- deploy/docker/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/docker/pom.xml b/deploy/docker/pom.xml index 52980929b..25735f76a 100644 --- a/deploy/docker/pom.xml +++ b/deploy/docker/pom.xml @@ -87,7 +87,7 @@ </docker.edu_sharing.community.common.redis.name> <docker.edu_sharing.community.common.redis.tag> - 6.2.14 + 7.4.1 </docker.edu_sharing.community.common.redis.tag> <docker.edu_sharing.community.common.varnish.name>