From dc74de3b4435f0fb66f808330fa0ead9ff48e740 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Thu, 21 May 2020 13:41:01 +0200 Subject: [PATCH 01/13] added ClusterRestController, migrated from ClusterRestService --- .../rest/impl/ClusterRestController.java | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java new file mode 100644 index 00000000000..431d1b49447 --- /dev/null +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.rest.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Paths; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import com.evolveum.midpoint.CacheInvalidationContext; +import com.evolveum.midpoint.TerminateSessionEvent; +import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; +import com.evolveum.midpoint.model.api.ModelPublicConstants; +import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; +import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; +import com.evolveum.midpoint.repo.api.CacheDispatcher; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskConstants; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.TerminateSessionEventType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementListType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SchedulerInformationType; + +/** + * REST service used for inter-cluster communication. + *

+ * These methods are NOT to be called generally by clients. + * They are to be called internally by midPoint running on other cluster nodes. + *

+ * So the usual form of authentication will be CLUSTER (a.k.a. node authentication). + * However, for diagnostic purposes we might allow also administrator access sometimes in the future. + */ +@RestController +@RequestMapping("/cluster") // TODO: /ws/cluster +public class ClusterRestController extends AbstractRestController { + + public static final String CLASS_DOT = ClusterRestController.class.getName() + "."; + + private static final String OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT = CLASS_DOT + "executeClusterCacheInvalidationEvent"; + private static final String OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT = CLASS_DOT + "executeClusterTerminateSessionEvent"; + private static final String OPERATION_GET_LOCAL_SCHEDULER_INFORMATION = CLASS_DOT + "getLocalSchedulerInformation"; + private static final String OPERATION_STOP_LOCAL_SCHEDULER = CLASS_DOT + "stopLocalScheduler"; + private static final String OPERATION_START_LOCAL_SCHEDULER = CLASS_DOT + "startLocalScheduler"; + private static final String OPERATION_STOP_LOCAL_TASK = CLASS_DOT + "stopLocalTask"; + + private static final String OPERATION_GET_REPORT_FILE = CLASS_DOT + "getReportFile"; + private static final String OPERATION_DELETE_REPORT_FILE = CLASS_DOT + "deleteReportFile"; + + private static final String EXPORT_DIR = "export/"; + + public static final String EVENT_INVALIDATION = "/event/invalidation/"; + public static final String EVENT_TERMINATE_SESSION = "/event/terminateSession/"; + public static final String EVENT_LIST_USER_SESSION = "/event/listUserSession"; + + @Autowired private MidpointConfiguration midpointConfiguration; + @Autowired private GuiProfiledPrincipalManager focusProfileService; + + @Autowired private CacheDispatcher cacheDispatcher; + + public ClusterRestController() { + // nothing to do + } + + @PostMapping(EVENT_INVALIDATION + "{type}") + public ResponseEntity executeClusterCacheInvalidationEvent( + @PathVariable("type") String type) { + return executeClusterCacheInvalidationEvent(type, null); + } + + @PostMapping(EVENT_INVALIDATION + "{type}/{oid}") + public ResponseEntity executeClusterCacheInvalidationEvent( + @PathVariable("type") String type, + @PathVariable("oid") String oid) { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT); + + ResponseEntity response; + try { + checkNodeAuthentication(); + + Class clazz = type != null ? ObjectTypes.getClassFromRestType(type) : null; + + // clusterwide is false: we got this from another node so we don't need to redistribute it + cacheDispatcher.dispatchInvalidation(clazz, oid, false, new CacheInvalidationContext(true, null)); + + result.recordSuccess(); + response = createResponse(HttpStatus.OK, result); + } catch (Throwable t) { + response = handleException(result, t); + } + finishRequest(); + return response; + } + + @PostMapping(EVENT_TERMINATE_SESSION) + public ResponseEntity executeClusterTerminateSessionEvent( + @RequestBody TerminateSessionEventType event) { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT); + + ResponseEntity response; + try { + checkNodeAuthentication(); + + focusProfileService.terminateLocalSessions(TerminateSessionEvent.fromEventType(event)); + + result.recordSuccess(); + response = createResponse(HttpStatus.OK, result); + } catch (Throwable t) { + response = handleException(result, t); + } + finishRequest(); + return response; + } + + @GetMapping(EVENT_LIST_USER_SESSION) + public ResponseEntity listUserSession() { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); + + ResponseEntity response; + try { + checkNodeAuthentication(); + List principals = focusProfileService.getLocalLoggedInPrincipals(); + + UserSessionManagementListType list = new UserSessionManagementListType(); + list.getSession().addAll(principals); + + response = createResponse(HttpStatus.OK, list, result); + } catch (Throwable t) { + response = handleException(result, t); + } + result.computeStatus(); + finishRequest(); + return response; + } + + @GetMapping(TaskConstants.GET_LOCAL_SCHEDULER_INFORMATION_REST_PATH) + public ResponseEntity getLocalSchedulerInformation() { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); + + ResponseEntity response; + try { + checkNodeAuthentication(); + SchedulerInformationType schedulerInformation = taskManager.getLocalSchedulerInformation(result); + response = createResponse(HttpStatus.OK, schedulerInformation, result); + } catch (Throwable t) { + response = handleException(result, t); + } + result.computeStatus(); + finishRequest(); + return response; + } + + @PostMapping(TaskConstants.STOP_LOCAL_SCHEDULER_REST_PATH) + public ResponseEntity stopLocalScheduler() { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_STOP_LOCAL_SCHEDULER); + + ResponseEntity response; + try { + checkNodeAuthentication(); + taskManager.stopLocalScheduler(result); + response = createResponse(HttpStatus.OK, result); + } catch (Throwable t) { + response = handleException(result, t); + } + result.computeStatus(); + finishRequest(); + return response; + } + + @PostMapping(TaskConstants.START_LOCAL_SCHEDULER_REST_PATH) + public ResponseEntity startLocalScheduler() { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_START_LOCAL_SCHEDULER); + + ResponseEntity response; + try { + checkNodeAuthentication(); + taskManager.startLocalScheduler(result); + response = createResponse(HttpStatus.OK, result); + } catch (Throwable t) { + response = handleException(result, t); + } + result.computeStatus(); + finishRequest(); + return response; + } + + @PostMapping(TaskConstants.STOP_LOCAL_TASK_REST_PATH_PREFIX + "{oid}" + TaskConstants.STOP_LOCAL_TASK_REST_PATH_SUFFIX) + public ResponseEntity stopLocalTask(@PathVariable("oid") String oid) { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_STOP_LOCAL_TASK); + + ResponseEntity response; + try { + checkNodeAuthentication(); + taskManager.stopLocalTask(oid, result); + response = createResponse(HttpStatus.OK, result); + } catch (Throwable t) { + response = handleException(result, t); + } + result.computeStatus(); + finishRequest(); + return response; + } + + @GetMapping( + value = ModelPublicConstants.CLUSTER_REPORT_FILE_PATH, + produces = "application/octet-stream") + public ResponseEntity getReportFile( + @RequestParam(ModelPublicConstants.CLUSTER_REPORT_FILE_FILENAME_PARAMETER) String fileName) { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_GET_REPORT_FILE); + + ResponseEntity response; + try { + checkNodeAuthentication(); + FileResolution resolution = resolveFile(fileName); + if (resolution.status == null) { + // we are only interested in the content, not in its type nor length + // TODO how to test this? + response = ResponseEntity.ok(new FileInputStream(resolution.file)); + } else { + response = ResponseEntity.status(resolution.status).build(); + } + result.computeStatus(); + } catch (Throwable t) { + response = handleException(null, t); // we don't return the operation result + } + finishRequest(); + return response; + } + + @DeleteMapping(ModelPublicConstants.CLUSTER_REPORT_FILE_PATH) + public ResponseEntity deleteReportFile( + @RequestParam(ModelPublicConstants.CLUSTER_REPORT_FILE_FILENAME_PARAMETER) String fileName) { + Task task = initRequest(); + OperationResult result = createSubresult(task, OPERATION_DELETE_REPORT_FILE); + + ResponseEntity response; + try { + checkNodeAuthentication(); + FileResolution resolution = resolveFile(fileName); + if (resolution.status == null) { + if (!resolution.file.delete()) { + logger.warn("Couldn't delete report output file {}", resolution.file); + } + response = ResponseEntity.ok().build(); + } else { + response = ResponseEntity.status(resolution.status).build(); + } + result.computeStatus(); + } catch (Throwable t) { + response = handleException(null, t); // we don't return the operation result + } + finishRequest(); + return response; + } + + static class FileResolution { + File file; + HttpStatus status; + } + + private FileResolution resolveFile(String fileName) { + FileResolution rv = new FileResolution(); + rv.file = Paths.get(midpointConfiguration.getMidpointHome(), EXPORT_DIR, fileName).toFile(); + + if (forbiddenFileName(fileName)) { + logger.warn("File name '{}' is forbidden", fileName); + rv.status = HttpStatus.FORBIDDEN; + } else if (!rv.file.exists()) { + logger.warn("Report output file '{}' does not exist", rv.file); + rv.status = HttpStatus.NOT_FOUND; + } else if (rv.file.isDirectory()) { + logger.warn("Report output file '{}' is a directory", rv.file); + rv.status = HttpStatus.FORBIDDEN; + } + return rv; + } + + private boolean forbiddenFileName(String fileName) { + return fileName.contains("/../"); + } + + private void checkNodeAuthentication() throws SecurityViolationException { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (!(authentication instanceof NodeAuthenticationToken)) { + throw new SecurityViolationException("Node authentication is expected but not present"); + } + // TODO consider allowing administrator access here as well + } +} From 1687f4831ef719d8bc90e509ad22730faaf45e73 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Thu, 21 May 2020 13:41:39 +0200 Subject: [PATCH 02/13] AbstractRestController: removed import of ModelRestService constant --- .../evolveum/midpoint/rest/impl/AbstractRestController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java index 219d40a8a15..fdcc7fea614 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java @@ -14,7 +14,6 @@ import com.evolveum.midpoint.audit.api.AuditEventStage; import com.evolveum.midpoint.audit.api.AuditEventType; import com.evolveum.midpoint.audit.api.AuditService; -import com.evolveum.midpoint.model.impl.ModelRestService; import com.evolveum.midpoint.model.impl.security.SecurityHelper; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.constants.SchemaConstants; @@ -36,6 +35,7 @@ class AbstractRestController { protected final Trace logger = TraceManager.getTrace(getClass()); + private final String opNamePrefix = getClass().getName() + "."; @Autowired protected AuditService auditService; @@ -44,7 +44,7 @@ class AbstractRestController { protected Task initRequest() { // No need to audit login. it was already audited during authentication - Task task = taskManager.createTaskInstance(ModelRestService.OPERATION_REST_SERVICE); + Task task = taskManager.createTaskInstance(opNamePrefix + "restService"); task.setChannel(SchemaConstants.CHANNEL_REST_URI); return task; } From 45c1b3c039e1b9970a241f46df8dfb643191f37d Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Fri, 22 May 2020 13:07:15 +0200 Subject: [PATCH 03/13] AbstractSpringBootApplication.java: removing cxfServlet + cleanup --- gui/admin-gui/pom.xml | 4 --- .../boot/AbstractSpringBootApplication.java | 29 ++----------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/gui/admin-gui/pom.xml b/gui/admin-gui/pom.xml index 364faef3c7a..e3ccd05a572 100644 --- a/gui/admin-gui/pom.xml +++ b/gui/admin-gui/pom.xml @@ -81,10 +81,6 @@ spring-boot-starter-security runtime - - org.apache.cxf - cxf-rt-transports-http - org.apache.cxf cxf-core diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/boot/AbstractSpringBootApplication.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/boot/AbstractSpringBootApplication.java index 66eedb9783f..00c5c2a6451 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/boot/AbstractSpringBootApplication.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/boot/AbstractSpringBootApplication.java @@ -8,7 +8,6 @@ import javax.servlet.DispatcherType; -import org.apache.cxf.transport.servlet.CXFServlet; import org.apache.wicket.Application; import org.apache.wicket.protocol.http.WicketFilter; import org.springframework.beans.factory.annotation.Autowired; @@ -21,11 +20,7 @@ import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.management.HeapDumpWebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.JvmMetricsAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.*; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration; @@ -41,25 +36,20 @@ import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; -import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.web.context.request.RequestContextListener; import org.springframework.web.filter.DelegatingFilterProxy; +import ro.isdc.wro.http.WroFilter; import com.evolveum.midpoint.init.StartupConfiguration; import com.evolveum.midpoint.model.api.authentication.NodeAuthenticationEvaluator; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.util.MidPointProfilingServletFilter; -import ro.isdc.wro.http.WroFilter; - /** * @author katka - * */ @ImportAutoConfiguration(classes = { EmbeddedTomcatAutoConfiguration.class, @@ -91,12 +81,9 @@ }) public abstract class AbstractSpringBootApplication extends SpringBootServletInitializer { - private static final Trace LOGGER = TraceManager.getTrace(MidPointSpringApplication.class); - @Autowired StartupConfiguration startupConfiguration; @Autowired NodeAuthenticationEvaluator nodeAuthenticator; - @Bean public ServletListenerRegistrationBean requestContextListener() { return new ServletListenerRegistrationBean<>(new RequestContextListener()); @@ -106,7 +93,6 @@ public ServletListenerRegistrationBean requestContextLis public FilterRegistrationBean midPointProfilingServletFilter() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(new MidPointProfilingServletFilter()); - // registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); registration.addUrlPatterns("/*"); return registration; } @@ -144,17 +130,6 @@ public FilterRegistrationBean webResourceOptimizer(WroFilter wroFilte return registration; } - @Bean - public ServletRegistrationBean cxfServlet() { - ServletRegistrationBean registration = new ServletRegistrationBean<>(); - registration.setServlet(new CXFServlet()); - registration.addInitParameter("service-list-path", "midpointservices"); - registration.setLoadOnStartup(1); - registration.addUrlMappings("/model/*", "/ws/*"); - - return registration; - } - @Bean public ErrorPageRegistrar errorPageRegistrar() { return new MidPointErrorPageRegistrar(); From 3d3efc17ecfc62eb1d4d75752514d9430909299e Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Fri, 22 May 2020 13:09:32 +0200 Subject: [PATCH 04/13] admin-gui: mapping for rest on /ws, but also /rest or /api --- .../web/security/BasicWebSecurityConfig.java | 75 +++--------------- .../midpoint/web/security/PageUrlMapping.java | 79 ++++++++----------- .../web/security/util/SecurityUtils.java | 12 +-- 3 files changed, 55 insertions(+), 111 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/BasicWebSecurityConfig.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/BasicWebSecurityConfig.java index b7f2e2900fa..f0936ad47e5 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/BasicWebSecurityConfig.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/BasicWebSecurityConfig.java @@ -6,33 +6,19 @@ */ package com.evolveum.midpoint.web.security; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.SystemConfigurationTypeUtil; -import com.evolveum.midpoint.security.api.SecurityContextManager; -import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.security.factory.channel.AuthChannelRegistryImpl; -import com.evolveum.midpoint.web.security.factory.module.AuthModuleRegistryImpl; -import com.evolveum.midpoint.web.security.filter.MidpointAnonymousAuthenticationFilter; -import com.evolveum.midpoint.web.security.filter.MidpointRequestAttributeAuthenticationFilter; -import com.evolveum.midpoint.web.security.filter.configurers.AuthFilterConfigurer; -import org.jasig.cas.client.session.SingleSignOutFilter; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; -import org.springframework.security.cas.web.CasAuthenticationFilter; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; @@ -42,18 +28,16 @@ import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; -import org.springframework.security.web.authentication.preauth.RequestAttributeAuthenticationFilter; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.security.web.session.HttpSessionEventPublisher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.AntPathMatcher; -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.web.security.factory.channel.AuthChannelRegistryImpl; +import com.evolveum.midpoint.web.security.factory.module.AuthModuleRegistryImpl; +import com.evolveum.midpoint.web.security.filter.MidpointAnonymousAuthenticationFilter; +import com.evolveum.midpoint.web.security.filter.configurers.AuthFilterConfigurer; /** * @author skublik @@ -63,20 +47,12 @@ @EnableWebSecurity public class BasicWebSecurityConfig extends WebSecurityConfigurerAdapter { - private static final Trace LOGGER = TraceManager.getTrace(BasicWebSecurityConfig.class); - @Autowired private AuthModuleRegistryImpl authRegistry; @Autowired AuthChannelRegistryImpl authChannelRegistry; - @Autowired - private AuthenticationManager authenticationManager; - - @Autowired - private SystemObjectCache systemObjectCache; - @Autowired private SessionRegistry sessionRegistry; @@ -94,8 +70,8 @@ public void setObjectPostProcessor(ObjectPostProcessor objectPostProcess @Bean public MidPointGuiAuthorizationEvaluator accessDecisionManager(SecurityEnforcer securityEnforcer, - SecurityContextManager securityContextManager, - TaskManager taskManager) { + SecurityContextManager securityContextManager, + TaskManager taskManager) { return new MidPointGuiAuthorizationEvaluator(securityEnforcer, securityContextManager, taskManager); } @@ -132,30 +108,6 @@ public void configure(WebSecurity web) throws Exception { // Web (SOAP) services web.ignoring().antMatchers("/model/**"); - // REST service - web.ignoring().requestMatchers(new RequestMatcher() { - @Override - public boolean matches(HttpServletRequest httpServletRequest) { - AntPathMatcher mather = new AntPathMatcher(); - boolean isExperimentalEnabled = false; - try { - isExperimentalEnabled = SystemConfigurationTypeUtil.isExperimentalCodeEnabled( - systemObjectCache.getSystemConfiguration(new OperationResult("Load System Config")).asObjectable()); - } catch (SchemaException e) { - LOGGER.error("Couldn't load system configuration", e); - } - if (isExperimentalEnabled - && mather.match("/ws/rest/**", httpServletRequest.getRequestURI().substring(httpServletRequest.getContextPath().length()))) { - return false; - } - if (mather.match("/ws/**", httpServletRequest.getRequestURI().substring(httpServletRequest.getContextPath().length()))) { - return true; - } - return false; - } - }); - web.ignoring().antMatchers("/rest/**"); - // Special intra-cluster service to download and delete report outputs web.ignoring().antMatchers("/report"); @@ -175,7 +127,6 @@ public boolean matches(HttpServletRequest httpServletRequest) { @Override protected void configure(HttpSecurity http) throws Exception { - AnonymousAuthenticationFilter anonymousFilter = new MidpointAnonymousAuthenticationFilter(authRegistry, authChannelRegistry, UUID.randomUUID().toString(), "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/PageUrlMapping.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/PageUrlMapping.java index 81141c75875..5e8f8681756 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/PageUrlMapping.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/PageUrlMapping.java @@ -7,17 +7,17 @@ package com.evolveum.midpoint.web.security; +import static com.evolveum.midpoint.security.api.AuthorizationConstants.*; + import com.evolveum.midpoint.util.DisplayableValue; import com.evolveum.midpoint.web.application.AuthorizationActionValue; -import static com.evolveum.midpoint.security.api.AuthorizationConstants.*; - /** * @author lazyman */ public enum PageUrlMapping { - ADMIN_USER_DETAILS("/admin/user/**", new DisplayableValue[]{ + ADMIN_USER_DETAILS("/admin/user/**", new AuthorizationActionValue(AUTZ_UI_USER_DETAILS_URL, "PageAdminUsers.authUri.userDetails.label", "PageAdminUsers.authUri.userDetails.description"), new AuthorizationActionValue(AUTZ_UI_USERS_ALL_URL, @@ -25,9 +25,8 @@ public enum PageUrlMapping { new AuthorizationActionValue(AUTZ_GUI_ALL_URL, "PageAdminUsers.authUri.usersAll.label", "PageAdminUsers.authUri.guiAll.description"), new AuthorizationActionValue(AUTZ_GUI_ALL_DEPRECATED_URL, - "PageAdminUsers.authUri.usersAll.label", "PageAdminUsers.authUri.guiAll.description") - }), - TASK_DETAILS("/admin/task/**", new DisplayableValue[]{ + "PageAdminUsers.authUri.usersAll.label", "PageAdminUsers.authUri.guiAll.description")), + TASK_DETAILS("/admin/task/**", new AuthorizationActionValue(AUTZ_UI_TASK_DETAIL_URL, "PageAdminTasks.authUri.taskDetails.label", "PageAdminTasks.authUri.taskDetails.description"), new AuthorizationActionValue(AUTZ_UI_TASKS_ALL_URL, @@ -35,9 +34,8 @@ public enum PageUrlMapping { new AuthorizationActionValue(AUTZ_GUI_ALL_URL, "PageAdminTasks.authUri.tasksAll.label", "PageAdminTasks.authUri.guiAll.description"), new AuthorizationActionValue(AUTZ_GUI_ALL_DEPRECATED_URL, - "PageAdminTasks.authUri.tasksAll.label", "PageAdminTasks.authUri.guiAll.description") - }), - ROLE_DETAILS("/admin/role/**", new DisplayableValue[]{ + "PageAdminTasks.authUri.tasksAll.label", "PageAdminTasks.authUri.guiAll.description")), + ROLE_DETAILS("/admin/role/**", new AuthorizationActionValue(AUTZ_UI_ROLE_DETAILS_URL, "PageAdminRoles.authUri.roleDetails.label", "PageAdminRoles.authUri.roleDetails.description"), new AuthorizationActionValue(AUTZ_UI_ROLES_ALL_URL, @@ -45,9 +43,8 @@ public enum PageUrlMapping { new AuthorizationActionValue(AUTZ_GUI_ALL_URL, "PageAdminRoles.authUri.rolesAll.label", "PageAdminRoles.authUri.guiAll.description"), new AuthorizationActionValue(AUTZ_GUI_ALL_DEPRECATED_URL, - "PageAdminRoles.authUri.rolesAll.label", "PageAdminRoles.authUri.guiAll.description") - }), - RESOURCE_DETAILS("/admin/resource/**", new DisplayableValue[]{ + "PageAdminRoles.authUri.rolesAll.label", "PageAdminRoles.authUri.guiAll.description")), + RESOURCE_DETAILS("/admin/resource/**", new AuthorizationActionValue(AUTZ_UI_RESOURCE_DETAILS_URL, "PageAdminResources.authUri.resourceDetails.label", "PageAdminResources.authUri.resourceDetails.description"), new AuthorizationActionValue(AUTZ_UI_RESOURCES_ALL_URL, @@ -55,9 +52,8 @@ public enum PageUrlMapping { new AuthorizationActionValue(AUTZ_GUI_ALL_URL, "PageAdminRoles.authUri.rolesAll.label", "PageAdminRoles.authUri.guiAll.description"), new AuthorizationActionValue(AUTZ_GUI_ALL_DEPRECATED_URL, - "PageAdminRoles.authUri.rolesAll.label", "PageAdminRoles.authUri.guiAll.description") - }), - CASE_DETAILS("/admin/workItem/**", new DisplayableValue[]{ + "PageAdminRoles.authUri.rolesAll.label", "PageAdminRoles.authUri.guiAll.description")), + CASE_DETAILS("/admin/workItem/**", new AuthorizationActionValue(AUTZ_UI_WORK_ITEM_URL, "PageCaseWorkItem.authUri.workItemDetails.label", "PageCaseWorkItem.authUri.workItemDetails.description"), new AuthorizationActionValue(AUTZ_UI_WORK_ITEMS_ALL_URL, @@ -65,46 +61,41 @@ public enum PageUrlMapping { new AuthorizationActionValue(AUTZ_GUI_ALL_URL, "PageCaseWorkItems.authUri.guiAll.label", "PageAdminRoles.authUri.guiAll.description"), new AuthorizationActionValue(AUTZ_GUI_ALL_DEPRECATED_URL, - "PageCaseWorkItems.authUri.guiAll.label", "PageAdminRoles.authUri.guiAll.description") - }), - ACTUATOR("/actuator/**", new DisplayableValue[]{ + "PageCaseWorkItems.authUri.guiAll.label", "PageAdminRoles.authUri.guiAll.description")), + ACTUATOR("/actuator/**", new AuthorizationActionValue(AUTZ_ACTUATOR_ALL_URL, - "ActuatorEndpoint.authActuator.all.label", "ActuatorEndpoint.authActuator.all.description") - }), - ACTUATOR_THREAD_DUMP("/actuator/threaddump", new DisplayableValue[]{ + "ActuatorEndpoint.authActuator.all.label", "ActuatorEndpoint.authActuator.all.description")), + ACTUATOR_THREAD_DUMP("/actuator/threaddump", new AuthorizationActionValue(AUTZ_ACTUATOR_THREAD_DUMP_URL, - "ActuatorEndpoint.authActuator.threadDump.label", "ActuatorEndpoint.authActuator.threadDump.description") - }), - ACTUATOR_HEAP_DUMP("/actuator/heapdump", new DisplayableValue[]{ + "ActuatorEndpoint.authActuator.threadDump.label", "ActuatorEndpoint.authActuator.threadDump.description")), + ACTUATOR_HEAP_DUMP("/actuator/heapdump", new AuthorizationActionValue(AUTZ_ACTUATOR_HEAP_DUMP_URL, - "ActuatorEndpoint.authActuator.heapDump.label", "ActuatorEndpoint.authActuator.heapDump.description") - }), - ACTUATOR_ENV("/actuator/env/**", new DisplayableValue[]{ + "ActuatorEndpoint.authActuator.heapDump.label", "ActuatorEndpoint.authActuator.heapDump.description")), + ACTUATOR_ENV("/actuator/env/**", new AuthorizationActionValue(AUTZ_ACTUATOR_ENV_URL, - "ActuatorEndpoint.authActuator.env.label", "ActuatorEndpoint.authActuator.env.description") - }), - ACTUATOR_INFO("/actuator/info", new DisplayableValue[]{ + "ActuatorEndpoint.authActuator.env.label", "ActuatorEndpoint.authActuator.env.description")), + ACTUATOR_INFO("/actuator/info", new AuthorizationActionValue(AUTZ_ACTUATOR_INFO_URL, - "ActuatorEndpoint.authActuator.info.label", "ActuatorEndpoint.authActuator.info.description") - }), - ACTUATOR_METRICS("/actuator/metrics/**", new DisplayableValue[]{ + "ActuatorEndpoint.authActuator.info.label", "ActuatorEndpoint.authActuator.info.description")), + ACTUATOR_METRICS("/actuator/metrics/**", new AuthorizationActionValue(AUTZ_ACTUATOR_METRICS_URL, - "ActuatorEndpoint.authActuator.metrics.label", "ActuatorEndpoint.authActuator.metrics.description") - }), - REST("/ws/rest/**", new DisplayableValue[]{ + "ActuatorEndpoint.authActuator.metrics.label", "ActuatorEndpoint.authActuator.metrics.description")), + REST("/ws/**", + new AuthorizationActionValue(AUTZ_REST_ALL_URL, + "RestEndpoint.authRest.all.label", "RestEndpoint.authRest.all.description")), + REST2("/rest/**", new AuthorizationActionValue(AUTZ_REST_ALL_URL, - "RestEndpoint.authRest.all.label", "RestEndpoint.authRest.all.description") - }), - REST2("/rest2/**", new DisplayableValue[]{ + "RestEndpoint.authRest.all.label", "RestEndpoint.authRest.all.description")), + REST3("/api/**", new AuthorizationActionValue(AUTZ_REST_ALL_URL, - "RestEndpoint.authRest.all.label", "RestEndpoint.authRest.all.description") - }); + "RestEndpoint.authRest.all.label", "RestEndpoint.authRest.all.description")); - private String url; + private final String url; - private DisplayableValue[] action; + // final, but array is still mutable + private final DisplayableValue[] action; - private PageUrlMapping(String url, DisplayableValue[] action) { + PageUrlMapping(String url, DisplayableValue... action) { this.url = url; this.action = action; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/util/SecurityUtils.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/util/SecurityUtils.java index 30a9105556a..98cf28ecfd7 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/util/SecurityUtils.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/util/SecurityUtils.java @@ -87,8 +87,9 @@ public static GuiProfiledPrincipal getPrincipalUser() { static { Map map = new HashMap<>(); - map.put("ws/rest", SchemaConstants.CHANNEL_REST_URI); - map.put("rest2", SchemaConstants.CHANNEL_REST_URI); + map.put("ws", SchemaConstants.CHANNEL_REST_URI); + map.put("rest", SchemaConstants.CHANNEL_REST_URI); + map.put("api", SchemaConstants.CHANNEL_REST_URI); map.put("actuator", SchemaConstants.CHANNEL_ACTUATOR_URI); map.put("resetPassword", SchemaConstants.CHANNEL_GUI_RESET_PASSWORD_URI); map.put("registration", SchemaConstants.CHANNEL_GUI_SELF_REGISTRATION_URI); @@ -198,7 +199,7 @@ public static AuthenticationSequenceType getSequenceByPath(HttpServletRequest ht if (partsOfLocalPath.length >= 2 && partsOfLocalPath[0].equals(ModuleWebSecurityConfigurationImpl.DEFAULT_PREFIX_OF_MODULE)) { AuthenticationSequenceType sequence = searchSequence(partsOfLocalPath[1], false, authenticationPolicy); if (sequence == null) { - LOGGER.debug("Couldn't find sequence by preffix {}, so try default channel", partsOfLocalPath[1]); + LOGGER.debug("Couldn't find sequence by prefix {}, so try default channel", partsOfLocalPath[1]); sequence = searchSequence(SecurityPolicyUtil.DEFAULT_CHANNEL, true, authenticationPolicy); } return sequence; @@ -240,7 +241,8 @@ public static String findChannelByRequest(HttpServletRequest httpRequest) { private static AuthenticationSequenceType getSpecificSequence(HttpServletRequest httpRequest) { String localePath = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length()); String channel = searchChannelByPath(localePath); - if (LOCAL_PATH_AND_CHANNEL.get("ws/rest").equals(channel)) { + // TODO: what exactly does this do, can't it be channel.equals(SchemaConstants.CHANNEL_REST_URI)? + if (LOCAL_PATH_AND_CHANNEL.get("ws").equals(channel)) { String header = httpRequest.getHeader("Authorization"); if (header != null) { String type = header.split(" ")[0]; @@ -333,7 +335,7 @@ private static List getSpecificModuleFilter(String urlSuffix, HttpSe AuthenticationModulesType authenticationModulesType, CredentialsPolicyType credentialPolicy) { String localePath = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length()); String channel = searchChannelByPath(localePath); - if (LOCAL_PATH_AND_CHANNEL.get("ws/rest").equals(channel)) { + if (LOCAL_PATH_AND_CHANNEL.get("ws").equals(channel)) { String header = httpRequest.getHeader("Authorization"); if (header != null) { String type = header.split(" ")[0]; From 03cd3a7f155393b07f5f52848d7302e973395414 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Fri, 22 May 2020 13:12:03 +0200 Subject: [PATCH 05/13] added ClusterServiceConsts.java with shared consts for service+client --- .../model/impl/ClusterCacheListener.java | 22 ++++++++-------- .../model/impl/ClusterServiceConsts.java | 11 ++++++++ .../ClusterwideUserSessionManagerImpl.java | 25 ++++++++++--------- 3 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterServiceConsts.java diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java index 787f7af9099..22ddd153323 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterCacheListener.java @@ -6,28 +6,26 @@ */ package com.evolveum.midpoint.model.impl; +import javax.annotation.PostConstruct; +import javax.ws.rs.core.Response; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + import com.evolveum.midpoint.CacheInvalidationContext; -import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; import com.evolveum.midpoint.repo.api.CacheDispatcher; -import com.evolveum.midpoint.repo.api.CacheInvalidationDetails; import com.evolveum.midpoint.repo.api.CacheListener; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.ClusterExecutionHelper; -import com.evolveum.midpoint.task.api.ClusterExecutionOptions; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.ws.rs.core.Response; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; @Component public class ClusterCacheListener implements CacheListener { @@ -59,7 +57,7 @@ public void invalidate(Class type, String oid, boolean // Regular cache invalidation can be skipped for nodes not checking in. Cache entries will expire on such nodes // eventually. (We can revisit this design decision if needed.) clusterExecutionHelper.execute((client, node, result1) -> { - client.path(ClusterRestService.EVENT_INVALIDATION + + client.path(ClusterServiceConsts.EVENT_INVALIDATION + ObjectTypes.getRestTypeFromClass(type) + (oid != null ? "/" + oid : "")); Response response = client.post(null); Response.StatusType statusInfo = response.getStatusInfo(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterServiceConsts.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterServiceConsts.java new file mode 100644 index 00000000000..fcf8e24c812 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterServiceConsts.java @@ -0,0 +1,11 @@ +package com.evolveum.midpoint.model.impl; + +/** + * URL pattern constants shared by cluster client and REST service. + */ +public class ClusterServiceConsts { + + public static final String EVENT_INVALIDATION = "/event/invalidation/"; + public static final String EVENT_TERMINATE_SESSION = "/event/terminateSession/"; + public static final String EVENT_LIST_USER_SESSION = "/event/listUserSession"; +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java index cef48ef0284..e30b3690eff 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/ClusterwideUserSessionManagerImpl.java @@ -7,10 +7,20 @@ package com.evolveum.midpoint.model.impl.security; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.ws.rs.core.Response; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + import com.evolveum.midpoint.TerminateSessionEvent; import com.evolveum.midpoint.model.api.authentication.ClusterwideUserSessionManager; import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; -import com.evolveum.midpoint.model.impl.ClusterRestService; +import com.evolveum.midpoint.model.impl.ClusterServiceConsts; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.ClusterExecutionHelper; import com.evolveum.midpoint.task.api.ClusterExecutionOptions; @@ -20,15 +30,6 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementListType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.ws.rs.core.Response; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; /** * Takes care for clusterwide user session management. @@ -49,7 +50,7 @@ public void terminateSessions(TerminateSessionEvent terminateSessionEvent, Task // We try to invoke this call also on nodes that are in transition. It is quite important // that terminate session is executed on as wide scale as realistically possible. clusterExecutionHelper.execute((client, node, result1) -> { - client.path(ClusterRestService.EVENT_TERMINATE_SESSION); + client.path(ClusterServiceConsts.EVENT_TERMINATE_SESSION); Response response = client.post(terminateSessionEvent.toEventType()); LOGGER.info("Remote-node user session termination finished on {} with status {}, {}", node.getNodeIdentifier(), response.getStatusInfo().getStatusCode(), response.getStatusInfo().getReasonPhrase()); @@ -69,7 +70,7 @@ public List getLoggedInPrincipals(Task task, Operatio // We try to invoke this call also on nodes that are in transition. We want to get // information as complete as realistically possible. clusterExecutionHelper.execute((client, node, result1) -> { - client.path(ClusterRestService.EVENT_LIST_USER_SESSION); + client.path(ClusterServiceConsts.EVENT_LIST_USER_SESSION); Response response = client.get(); LOGGER.info("Remote-node retrieval of user sessions finished on {} with status {}, {}", node.getNodeIdentifier(), response.getStatusInfo().getStatusCode(), response.getStatusInfo().getReasonPhrase()); From b70c95581cbf245da105943dec0628b7a3a49a33 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Fri, 22 May 2020 13:15:05 +0200 Subject: [PATCH 06/13] ctx-model.xml: removed /ws mappings and all traces of jaxrs --- .../src/main/resources/ctx-model.xml | 54 +------------------ 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/model/model-impl/src/main/resources/ctx-model.xml b/model/model-impl/src/main/resources/ctx-model.xml index c367c974e66..8e517b8d8c7 100644 --- a/model/model-impl/src/main/resources/ctx-model.xml +++ b/model/model-impl/src/main/resources/ctx-model.xml @@ -10,15 +10,11 @@ + http://www.springframework.org/schema/context/spring-context-3.0.xsd" + default-lazy-init="false"> @@ -227,7 +223,6 @@ com.evolveum.midpoint.model.impl.sync.action.InactivateFocusAction - com.evolveum.midpoint.model.impl.sync.action.LinkAction @@ -256,16 +251,6 @@ com.evolveum.midpoint.model.impl.sync.action.InactivateFocusAction - - - @@ -276,39 +261,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From f733432a0e743633f975b31739938df13f5a2134 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Fri, 22 May 2020 13:15:36 +0200 Subject: [PATCH 07/13] testing/rest system-configuration.xml: experimental code turned off --- testing/rest/src/test/resources/repo/system-configuration.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testing/rest/src/test/resources/repo/system-configuration.xml b/testing/rest/src/test/resources/repo/system-configuration.xml index f2836f17b36..7e8c8805f29 100644 --- a/testing/rest/src/test/resources/repo/system-configuration.xml +++ b/testing/rest/src/test/resources/repo/system-configuration.xml @@ -47,9 +47,5 @@ IDM_LOG INFO - - - true - From 58260c73d28d5767555552b873eb92fad13a8c44 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Fri, 22 May 2020 13:17:35 +0200 Subject: [PATCH 08/13] rest-impl: multi mapping of REST to /ws, /rest and /api + 404 fallback --- .../rest/impl/AbstractRestController.java | 27 +++++++++++ .../rest/impl/ClusterRestController.java | 15 +++---- .../impl/ExtensionSchemaRestController.java | 2 +- .../rest/impl/ModelRestController.java | 5 +-- .../midpoint/rest/impl/RestApiIndex.java | 45 ++++++++++++------- 5 files changed, 66 insertions(+), 28 deletions(-) diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java index fdcc7fea614..db3fa74bac9 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/AbstractRestController.java @@ -3,12 +3,17 @@ import static org.springframework.http.ResponseEntity.status; import java.net.URI; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; @@ -179,4 +184,26 @@ private void auditEvent() { auditService.audit(record, task); } + + private final String[] requestMappingPaths = + getClass().getAnnotation(RequestMapping.class).value(); + + /** + * Returns base path (without servlet context) reflecting currently used request. + * This solves the problem of base path being one of multiple possible mappings. + */ + protected String controllerBasePath() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes instanceof ServletRequestAttributes) { + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + String servletPath = request.getServletPath(); + for (String requestMappingPath : requestMappingPaths) { + if (servletPath.startsWith(requestMappingPath)) { + return requestMappingPath; + } + } + } + + throw new NullPointerException("Base controller URL could not be determined."); + } } diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java index 431d1b49447..ff5a93bcd70 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ClusterRestController.java @@ -23,6 +23,7 @@ import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; import com.evolveum.midpoint.model.api.ModelPublicConstants; import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; +import com.evolveum.midpoint.model.impl.ClusterServiceConsts; import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; import com.evolveum.midpoint.repo.api.CacheDispatcher; import com.evolveum.midpoint.schema.constants.ObjectTypes; @@ -46,7 +47,7 @@ * However, for diagnostic purposes we might allow also administrator access sometimes in the future. */ @RestController -@RequestMapping("/cluster") // TODO: /ws/cluster +@RequestMapping({"/ws/cluster", "/rest/cluster", "/api/cluster"}) public class ClusterRestController extends AbstractRestController { public static final String CLASS_DOT = ClusterRestController.class.getName() + "."; @@ -63,10 +64,6 @@ public class ClusterRestController extends AbstractRestController { private static final String EXPORT_DIR = "export/"; - public static final String EVENT_INVALIDATION = "/event/invalidation/"; - public static final String EVENT_TERMINATE_SESSION = "/event/terminateSession/"; - public static final String EVENT_LIST_USER_SESSION = "/event/listUserSession"; - @Autowired private MidpointConfiguration midpointConfiguration; @Autowired private GuiProfiledPrincipalManager focusProfileService; @@ -76,13 +73,13 @@ public ClusterRestController() { // nothing to do } - @PostMapping(EVENT_INVALIDATION + "{type}") + @PostMapping(ClusterServiceConsts.EVENT_INVALIDATION + "{type}") public ResponseEntity executeClusterCacheInvalidationEvent( @PathVariable("type") String type) { return executeClusterCacheInvalidationEvent(type, null); } - @PostMapping(EVENT_INVALIDATION + "{type}/{oid}") + @PostMapping(ClusterServiceConsts.EVENT_INVALIDATION + "{type}/{oid}") public ResponseEntity executeClusterCacheInvalidationEvent( @PathVariable("type") String type, @PathVariable("oid") String oid) { @@ -107,7 +104,7 @@ public ResponseEntity executeClusterCacheInvalidationEvent( return response; } - @PostMapping(EVENT_TERMINATE_SESSION) + @PostMapping(ClusterServiceConsts.EVENT_TERMINATE_SESSION) public ResponseEntity executeClusterTerminateSessionEvent( @RequestBody TerminateSessionEventType event) { Task task = initRequest(); @@ -128,7 +125,7 @@ public ResponseEntity executeClusterTerminateSessionEvent( return response; } - @GetMapping(EVENT_LIST_USER_SESSION) + @GetMapping(ClusterServiceConsts.EVENT_LIST_USER_SESSION) public ResponseEntity listUserSession() { Task task = initRequest(); OperationResult result = createSubresult(task, OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ExtensionSchemaRestController.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ExtensionSchemaRestController.java index d1a534e9bae..b03caeadebb 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ExtensionSchemaRestController.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ExtensionSchemaRestController.java @@ -35,7 +35,7 @@ import com.evolveum.midpoint.util.logging.LoggingUtils; @RestController -@RequestMapping(value = "/rest2/schema") +@RequestMapping({ "/ws/schema", "/rest/schema", "/api/schema" }) public class ExtensionSchemaRestController extends AbstractRestController { @Autowired private PrismContext prismContext; diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ModelRestController.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ModelRestController.java index 89fd9afc104..625063c1af3 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ModelRestController.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/ModelRestController.java @@ -45,10 +45,9 @@ import com.evolveum.prism.xml.ns._public.query_3.QueryType; @RestController -@RequestMapping(value = ModelRestController.BASE_PATH) +@RequestMapping({ "/ws/rest", "/rest/model", "/api/model" }) public class ModelRestController extends AbstractRestController { - public static final String BASE_PATH = "/rest2"; public static final String GET_OBJECT_PATH = "/{type}/{id}"; private static final String CURRENT = "current"; @@ -345,7 +344,7 @@ public ResponseEntity addObject( @NotNull public URI uriGetObject(@PathVariable("type") String type, String oid) { return ServletUriComponentsBuilder.fromCurrentContextPath() - .path(BASE_PATH + GET_OBJECT_PATH) + .path(controllerBasePath() + GET_OBJECT_PATH) .build(type, oid); } diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestApiIndex.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestApiIndex.java index edd3b2eab4e..2645530b442 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestApiIndex.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestApiIndex.java @@ -3,8 +3,11 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.bind.annotation.GetMapping; @@ -12,44 +15,48 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.method.HandlerMethod; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.condition.MediaTypeExpression; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import org.springframework.web.util.UriComponentsBuilder; /** * Support for simple index page with REST API endpoints (HTML and JSON). */ @RestController -@RequestMapping("/rest2") -public class RestApiIndex { +@RequestMapping({ "/ws", "/rest", "/api" }) +public class RestApiIndex extends AbstractRestController { + private final String contextPath; private final List uiRestInfo; - public RestApiIndex(RequestMappingHandlerMapping handlerMapping) { + public RestApiIndex(RequestMappingHandlerMapping handlerMapping, ServletContext servletContext) { + contextPath = servletContext.getContextPath(); uiRestInfo = operationInfoStream(handlerMapping) .filter(info -> info.handler.getBeanType().getName() .startsWith("com.evolveum.midpoint.rest.")) .collect(Collectors.toList()); } - private Stream operationInfoStream(RequestMappingHandlerMapping handlerMapping) { + private Stream operationInfoStream( + RequestMappingHandlerMapping handlerMapping) { return handlerMapping.getHandlerMethods().entrySet().stream() .map(entry -> new OperationInfo(entry.getKey(), entry.getValue())); } @GetMapping() - public List index() { + public List index(HttpServletRequest request) { + String uri = request.getRequestURI(); return uiRestInfo.stream() - .flatMap(OperationInfo::operationJsonStream) + .flatMap(operationInfo -> operationInfo.operationJsonStream(contextPath, uri)) .sorted(Comparator.comparing(json -> json.urlPattern)) .collect(Collectors.toList()); } @GetMapping(produces = "text/html") - public String indexHtml() { + public String indexHtml(HttpServletRequest request) { StringBuilder html = new StringBuilder("" + "REST-ish API" + "" + "" + "

REST operations

This is NOT Swagger! Click at your own risk!