Skip to content

Hotfixes to main #766

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 29 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
80d78f9
separate defaultValue and value for old input/select comps
raheeliftikhar5 Mar 5, 2024
f324f75
map ready state fix
raheeliftikhar5 Mar 5, 2024
abe4b20
update lowcoder-comps version
raheeliftikhar5 Mar 5, 2024
20f5bc8
New plugin system
ludomikula Feb 5, 2024
e78671f
Merge pull request #742 from raheeliftikhar5/handle_defaultValue
FalkWolsky Mar 5, 2024
b58a742
new: plugin endpoint security basics
ludomikula Mar 5, 2024
800c16c
Merge branch 'lowcoder-org:dev' into dev
ludomikula Mar 6, 2024
eb0e0d5
feature: Extended folder to have more meta-data i.e: title, descripti…
irfan-ikhwa Mar 6, 2024
348c98e
Merge branch 'lowcoder-org:dev' into dev
ludomikula Mar 8, 2024
f6b6bd8
fix crashing on reordering comps inside module
raheeliftikhar5 Mar 14, 2024
3809d9a
fix broken modal issue
raheeliftikhar5 Mar 14, 2024
9810150
fix bulk action updates doesn't change all comps height
raheeliftikhar5 Mar 14, 2024
4335b1e
fix iconSelector issue in js
raheeliftikhar5 Mar 14, 2024
da62927
Bugfixing in the Cerrypicked Branch 1
Mar 12, 2024
3bf3ebf
Optimize the runOpenApi plugin
snowe2010 Mar 8, 2024
784951d
Optimize the runOpenApi plugin
snowe2010 Mar 8, 2024
cbcaec6
Pull definition logic into a new function
snowe2010 Mar 8, 2024
b92024d
Small amount of code cleanup
snowe2010 Mar 8, 2024
b36a2d0
fix: complete the locale file
Mar 11, 2024
0e6b546
map ready state fix
raheeliftikhar5 Mar 5, 2024
9d28529
Google Fonts & Clearbit only on public Cloud
Mar 19, 2024
c063add
Updating the SDK, update Simplebar and Fix simplebar-placeholder problem
Mar 17, 2024
94e7c82
Updating the SDK, update Simplebar and Fix simplebar-placeholder problem
Mar 17, 2024
144ca9f
Fixing Scrollbars for Modules Editor View
Mar 18, 2024
5cadfc3
Update Scrollbars
Mar 24, 2024
7758d4a
Update Scrollbars
Mar 24, 2024
3476223
Merge pull request #767 from Lowcoder-Pro/dev
FalkWolsky Mar 24, 2024
eeef89a
Fixing AssetService - after Falk broke it.
Mar 24, 2024
d602bd4
Enabling display of Version Number for Remote Components
Mar 24, 2024
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
Prev Previous commit
Next Next commit
new: plugin endpoint security basics
  • Loading branch information
ludomikula committed Mar 5, 2024
commit b58a7429e8f1473a34034867ff07b54bc1343d1c
65 changes: 3 additions & 62 deletions server/api-service/PLUGIN.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Lowcoder plugin system (WIP)
# Lowcoder backend plugin system

This is an ongoing effort to refactor current plugin system based on pf4j library.

Expand Down Expand Up @@ -50,73 +50,14 @@ Plugin jar can be structured in any way you like. It can be a plain java project

It is composed from several parts:
- class(es) implementing **LowcoderPlugin** interface
- class(es) implementing **LowcoderEndpoint** interface, containing endpoint handler functions marked with **@EndpointExtension** annotation. These functions must obey following format:
- class(es) implementing **PluginEndpoint** interface, containing endpoint handler functions marked with **@EndpointExtension** annotation. These functions must obey following format:

```java
@EndpointExtension(uri = <endpoint uri>, method = <HTTP method>)
public Mono<ServerResponse> <handler name>(ServerRequest request)
public EndpointResponse <handler name>(EndpointRequest request)
{
... your endpoint logic implementation
}

for example:

@EndpointExtension(uri = "/hello-world", method = Method.GET)
public Mono<ServerResponse> helloWorld(ServerRequest request)
{
return ServerResponse.ok().body(Mono.just(Hello.builder().message("Hello world!").build()), Hello.class);
}
```
- TODO: class(es) impelemting **LowcoderDatasource** interface

### LowcoderPlugin implementations

Methods of interest:
- **pluginId()** - unique plugin ID - if a plugin with such ID is already loaded, subsequent plugins whith this ID will be ignored
- **description()** - short plugin description
- **load(ApplicationContext parentContext)** - is called during plugin startup - this is the place where you should completely initialize your plugin. If initialization fails, return false
- **unload()** - is called during lowcoder API server shutdown - this is the place where you should release all resources
- **endpoints()** - needs to contain all initialized **PluginEndpoints** you want to expose, for example:

```java
@Override
public List<PluginEndpoint> endpoints()
{
List<PluginEndpoint> endpoints = new ArrayList<>();

endpoints.add(new HelloWorldEndpoint());

return endpoints;
}
```
- **pluginInfo()** - should return a record object with additional information about your plugin. It is serialized to JSON as part of the **/plugins** listing (see **"info"** object in this example):

```json
[
{
"id": "example-plugin",
"description": "Example plugin for lowcoder platform",
"info": {}
},
{
"id": "enterprise",
"description": "Lowcoder enterprise plugin",
"info": {
"enabledFeatures": [
"endpointApiUsage"
]
}
}
]
```

## TODOs

1. Implement endpoint security - currently all plugin endpoints are public (probably by adding **security** attribute to **@EndpointExtension** and enforcing it)


## QUESTIONS / CONSIDERATIONS

1. currently the plugin endpoints are prefixed with **/plugin/{pluginId}/** - this is hardcoded, do we want to make it configurable?


19 changes: 11 additions & 8 deletions server/api-service/lowcoder-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@

<name>lowcoder-sdk</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -173,7 +166,17 @@
<artifactId>validation-api</artifactId>
</dependency>
</dependencies>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>17</java.version>

<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.lowcoder.domain.application.model.ApplicationStatus;
import org.lowcoder.domain.application.model.ApplicationType;
import org.lowcoder.domain.permission.model.ResourceRole;
import org.lowcoder.infra.event.EventType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down Expand Up @@ -106,15 +105,15 @@ public Mono<ResponseView<ApplicationView>> getPublishedApplication(@PathVariable
public Mono<ResponseView<ApplicationView>> getPublishedMarketPlaceApplication(@PathVariable String applicationId) {
return applicationApiService.getPublishedApplication(applicationId, ApplicationRequestType.PUBLIC_TO_MARKETPLACE)
.delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId))
.delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, EventType.VIEW))
.delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, APPLICATION_VIEW))
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<ApplicationView>> getAgencyProfileApplication(@PathVariable String applicationId) {
return applicationApiService.getPublishedApplication(applicationId, ApplicationRequestType.AGENCY_PROFILE)
.delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId))
.delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, EventType.VIEW))
.delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, APPLICATION_VIEW))
.map(ResponseView::success);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.lowcoder.api.framework.plugin;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
Expand All @@ -20,6 +19,11 @@ public class PluginClassLoader extends URLClassLoader
private static final ClassLoader baseClassLoader = ClassLoader.getPlatformClassLoader();
private final ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader();

private static final String[] excludedPaths = new String[] {
"org.lowcoder.plugin.api.",
"org/lowcoder/plugin/api/"
};

public PluginClassLoader(String name, Path pluginPath)
{
super(name, pathToURLs(pluginPath), baseClassLoader);
Expand All @@ -34,7 +38,7 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
return clazz;
}

if (name.startsWith("org.lowcoder.plugin.api."))
if (StringUtils.startsWithAny(name, excludedPaths))
{
try
{
Expand Down Expand Up @@ -67,7 +71,7 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
@Override
public URL getResource(String name) {
Objects.requireNonNull(name);
if (StringUtils.startsWithAny(name, "org/lowcoder/plugin/api/", "org.lowcoder.plugin.api."))
if (StringUtils.startsWithAny(name, excludedPaths))
{
return appClassLoader.getResource(name);
}
Expand All @@ -79,7 +83,7 @@ public URL getResource(String name) {
public Enumeration<URL> getResources(String name) throws IOException
{
Objects.requireNonNull(name);
if (StringUtils.startsWithAny(name, "org/lowcoder/plugin/api/", "org.lowcoder.plugin.api."))
if (StringUtils.startsWithAny(name, excludedPaths))
{
return appClassLoader.getResources(name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.lowcoder.api.framework.plugin.data.PluginServerRequest;
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.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactoryBean;
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.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
Expand Down Expand Up @@ -80,48 +84,47 @@ public List<RouterFunction<ServerResponse>> registeredEndpoints()

private void registerEndpointHandler(String urlPrefix, PluginEndpoint endpoint, Method handler)
{
if (handler.isAnnotationPresent(EndpointExtension.class))
if (!handler.isAnnotationPresent(EndpointExtension.class) || !checkHandlerMethod(handler))
{
if (checkHandlerMethod(handler))
if (handler.isAnnotationPresent(EndpointExtension.class))
{

EndpointExtension endpointMeta = handler.getAnnotation(EndpointExtension.class);
String endpointName = endpoint.getClass().getSimpleName() + "_" + handler.getName();

RouterFunction<ServerResponse> routerFunction = route(createRequestPredicate(urlPrefix, endpointMeta), req ->
{
Mono<ServerResponse> result = null;
try
{
EndpointResponse response = (EndpointResponse)handler.invoke(endpoint, PluginServerRequest.fromServerRequest(req));
result = createServerResponse(response);
}
catch (IllegalAccessException | InvocationTargetException cause)
{
throw new BaseException("Error running handler for [ " + endpointMeta.method() + ": " + endpointMeta.uri() + "] !");
}
return result;
});
routes.add(routerFunction);
registerRouterFunctionMapping(endpointName, routerFunction);

log.info("Registered endpoint: {} -> {}: {}", endpoint.getClass().getSimpleName(), endpointMeta.method(), urlPrefix + endpointMeta.uri());
}
else
{
log.error("Cannot register plugin endpoint: {} -> {}! Handler method must be defined as: public Mono<ServerResponse> {}(ServerRequest request)", endpoint.getClass().getSimpleName(), handler.getName(), handler.getName());
log.debug("Not registering plugin endpoint method: {} -> {}! Handler method must be defined as: public EndpointResponse methodName(EndpointRequest request)", endpoint.getClass().getSimpleName(), handler.getName(), handler.getName());
}
return;
}

EndpointExtension endpointMeta = handler.getAnnotation(EndpointExtension.class);
String endpointName = endpoint.getClass().getSimpleName() + "_" + handler.getName();
RouterFunction<ServerResponse> routerFunction = route(createRequestPredicate(urlPrefix, endpointMeta), req -> runPluginEndpointMethod(endpoint, endpointMeta, handler, req));
routes.add(routerFunction);
registerRouterFunctionMapping(endpointName, routerFunction);

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);

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;
}


private void registerRouterFunctionMapping(String endpointName, RouterFunction<ServerResponse> routerFunction)
{
String beanName = "pluginEndpoint_" + endpointName + "_" + System.currentTimeMillis();

((GenericApplicationContext)applicationContext).registerBean(beanName, RouterFunction.class, () -> {
return routerFunction;
});

((GenericApplicationContext)applicationContext).registerBean(beanName, RouterFunction.class, () -> routerFunction );
log.debug("Registering RouterFunction bean definition: {}", beanName);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.lowcoder.api.framework.plugin.security;

import java.util.function.Supplier;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class EndpointAuthorizationManager implements AuthorizationManager<MethodInvocation>
{

@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation)
{
log.info("Checking plugin endpoint invocation security for {}", invocation.getMethod().getName());

return new AuthorizationDecision(true);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.lowcoder.plugin.api.EndpointExtension;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
Expand All @@ -21,7 +20,7 @@
import reactor.core.publisher.Mono;

@Slf4j
@Component
//@Component
public class PluginAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation>
{
private final MethodSecurityExpressionHandler expressionHandler;
Expand All @@ -34,10 +33,9 @@ public PluginAuthorizationManager()
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, MethodInvocation invocation)
{
log.info(" invocation :: {}", invocation.getMethod());
log.info("Checking plugin reactive endpoint invocation security for {}", invocation.getMethod().getName());

Method method = invocation.getMethod();
EndpointExtension endpointExtension = AnnotationUtils.findAnnotation(method, EndpointExtension.class);
EndpointExtension endpointExtension = (EndpointExtension)invocation.getArguments()[1];
if (endpointExtension == null || StringUtils.isBlank(endpointExtension.authorize()))
{
return Mono.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.lowcoder.api.framework.plugin.security;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SecuredEndpoint {

}
7 changes: 1 addition & 6 deletions server/api-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,12 @@


<properties>
<revision>2.3.0-SNAPSHOT</revision>
<revision>2.4.0</revision>
<java.version>17</java.version>
<javadoc.disabled>true</javadoc.disabled>
<deploy.disabled>true</deploy.disabled>
<source.disabled>true</source.disabled>
<project.groupId>org.lowcoder</project.groupId>
<project.version>1.0-SNAPSHOT</project.version>
<skipDockerBuild>true</skipDockerBuild>
<log4j2.version>2.17.0</log4j2.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<repositories>
Expand Down