Skip to content

Add authorization check to Extension Endpoint #1250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading