From 49a4a61c90de7d53746e23511de1ca9902c32ac6 Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 22 Oct 2024 13:00:09 -0400 Subject: [PATCH] Add authorization check to Extension Endpoint --- .../configuration/PluginConfiguration.java | 25 +----- .../endpoint/PluginEndpointHandlerImpl.java | 89 +++++++++++++++---- 2 files changed, 75 insertions(+), 39 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java index c7feaae0f..92dd32b73 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java @@ -1,28 +1,18 @@ package org.lowcoder.api.framework.configuration; -import java.util.ArrayList; - import org.lowcoder.api.framework.plugin.LowcoderPluginManager; import org.lowcoder.api.framework.plugin.endpoint.PluginEndpointHandler; -// Falk: eventually not needed -import org.lowcoder.api.framework.plugin.security.PluginAuthorizationManager; -import org.lowcoder.plugin.api.EndpointExtension; -import org.springframework.aop.Advisor; -import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; -import org.springframework.context.annotation.Role; -import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder; -import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor; import org.springframework.web.reactive.function.server.RequestPredicates; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerResponse; - import reactor.core.publisher.Mono; +import java.util.ArrayList; + @Configuration public class PluginConfiguration { @@ -43,15 +33,4 @@ RouterFunction pluginEndpoints(LowcoderPluginManager pluginManager, PluginEnd return (endpoints == null) ? pluginsList : pluginsList.andOther(endpoints); } - - // Falk: eventually not needed - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - Advisor protectPluginEndpoints(PluginAuthorizationManager pluginAauthManager) - { - AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(EndpointExtension.class, true); - AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(pointcut, pluginAauthManager); - interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() -1); - return interceptor; - } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/endpoint/PluginEndpointHandlerImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/endpoint/PluginEndpointHandlerImpl.java index 214252827..1e1b3c8e3 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/endpoint/PluginEndpointHandlerImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/endpoint/PluginEndpointHandlerImpl.java @@ -1,5 +1,6 @@ package org.lowcoder.api.framework.plugin.endpoint; +import static org.lowcoder.sdk.exception.BizError.NOT_AUTHORIZED; import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.OPTIONS; @@ -8,22 +9,30 @@ import static org.springframework.web.reactive.function.server.RequestPredicates.PUT; import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lowcoder.api.framework.plugin.data.PluginServerRequest; +import org.lowcoder.api.framework.plugin.security.PluginAuthorizationManager; import org.lowcoder.api.framework.plugin.security.SecuredEndpoint; import org.lowcoder.plugin.api.EndpointExtension; import org.lowcoder.plugin.api.PluginEndpoint; import org.lowcoder.plugin.api.data.EndpointRequest; import org.lowcoder.plugin.api.data.EndpointResponse; import org.lowcoder.sdk.exception.BaseException; +import org.lowcoder.sdk.exception.BizException; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.aop.framework.ReflectiveMethodInvocation; import org.springframework.aop.target.SimpleBeanTargetSource; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; @@ -31,7 +40,11 @@ import org.springframework.core.ResolvableType; import org.springframework.http.ResponseCookie; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.RequestPredicate; import org.springframework.web.reactive.function.server.RouterFunction; @@ -52,6 +65,7 @@ public class PluginEndpointHandlerImpl implements PluginEndpointHandler private final ApplicationContext applicationContext; private final DefaultListableBeanFactory beanFactory; + private final PluginAuthorizationManager pluginAuthorizationManager; @Override public void registerEndpoints(String pluginUrlPrefix, List endpoints) @@ -101,26 +115,69 @@ private void registerEndpointHandler(String urlPrefix, PluginEndpoint endpoint, log.info("Registered endpoint: {} -> {}: {}", endpoint.getClass().getSimpleName(), endpointMeta.method(), urlPrefix + endpointMeta.uri()); } - - @SecuredEndpoint + public Mono runPluginEndpointMethod(PluginEndpoint endpoint, EndpointExtension endpointMeta, Method handler, ServerRequest request) { - Mono result = null; - try - { - log.info("Running plugin endpoint method {}\nRequest: {}", handler.getName(), request); + log.info("Running plugin endpoint method {}\nRequest: {}", handler.getName(), request); - EndpointResponse response = (EndpointResponse)handler.invoke(endpoint, PluginServerRequest.fromServerRequest(request)); - result = createServerResponse(response); - } - catch (IllegalAccessException | InvocationTargetException cause) - { - throw new BaseException("Error running handler for [ " + endpointMeta.method() + ": " + endpointMeta.uri() + "] !"); - } - return result; + Mono monoAuthentication = ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication).cache(); + Mono decisionMono = monoAuthentication.flatMap(authentication -> { + MethodInvocation methodInvocation = null; + try { + methodInvocation = getMethodInvocation(endpointMeta, authentication); + } catch (NoSuchMethodException e) { + return Mono.error(new RuntimeException(e)); + } + return pluginAuthorizationManager.check(monoAuthentication, methodInvocation); + }); + + return decisionMono.handle((authorizationDecision, sink) -> { + if(!authorizationDecision.isGranted()) sink.error(new BizException(NOT_AUTHORIZED, "NOT_AUTHORIZED")); + try { + sink.next((EndpointResponse) handler.invoke(endpoint, PluginServerRequest.fromServerRequest(request))); + } catch (IllegalAccessException | InvocationTargetException e) { + sink.error(new RuntimeException(e)); + } + }).flatMap(this::createServerResponse); } - - + + private static @NotNull MethodInvocation getMethodInvocation(EndpointExtension endpointMeta, Authentication authentication) throws NoSuchMethodException { + Method method = Authentication.class.getMethod("isAuthenticated"); + Object[] arguments = new Object[]{"someString", endpointMeta}; + return new MethodInvocation() { + @NotNull + @Override + public Method getMethod() { + return method; + } + + @NotNull + @Override + public Object[] getArguments() { + return arguments; + } + + @Nullable + @Override + public Object proceed() throws Throwable { + return null; + } + + @Nullable + @Override + public Object getThis() { + return authentication; + } + + @NotNull + @Override + public AccessibleObject getStaticPart() { + return null; + } + }; + } + + private void registerRouterFunctionMapping(String endpointName, RouterFunction routerFunction) { String beanName = "pluginEndpoint_" + endpointName + "_" + System.currentTimeMillis();