Skip to content

Commit

Permalink
Add authorization check to Extension Endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
dragonpoo authored and ludomikula committed Oct 22, 2024
1 parent dd36801 commit 49a4a61
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,30 +9,42 @@
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;
import org.springframework.context.support.GenericApplicationContext;
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;
Expand All @@ -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<PluginEndpoint> endpoints)
Expand Down Expand Up @@ -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<ServerResponse> runPluginEndpointMethod(PluginEndpoint endpoint, EndpointExtension endpointMeta, Method handler, ServerRequest request)
{
Mono<ServerResponse> 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<Authentication> monoAuthentication = ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication).cache();
Mono<AuthorizationDecision> 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.<EndpointResponse>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<ServerResponse> routerFunction)
{
String beanName = "pluginEndpoint_" + endpointName + "_" + System.currentTimeMillis();
Expand Down

0 comments on commit 49a4a61

Please sign in to comment.