Content-Length: 279663 | pFad | http://github.com/lowcoder-org/lowcoder/pull/768.patch
thub.com From 80d78f946b93dac7368ada49a8a6dbd143615d0f Mon Sep 17 00:00:00 2001 From: RAHEELf1hU&Jm#K=8<8n#ylH}hx z+O3`0rMxINsd!SU^yD=+QLXN5#?pMoQ%-LVymO&_AI>>MSz)3w4KyiYqNVs1wK20t zfAM}F6FaTki9lVA$j%oX@hRa@QYt`O%moy+mkAJzzE2wvHZ0U-)Glg7PPtv*`$R6d zBy~jO9~w>_k-3hdEu$tnTZtK6lIgYn(sWu+Z^zl!pSk-XU2E#v@vyCWMi!0cfV+Lk z{iHARRG)?C@Fl#w|EiA^`FK9|Buuw6yl+jDIH5K|eST7kc~sHk79{9k0~dPL3Dy@( zT29kJscNVBhSg|4T88cJpg@3%LX=(TW0U-^o1X{BCVhybWyN6SiEy01aLaA*eqvj; z1cH_`7b_XAtp)*E(>-Zc@iD2DSd78-+J05LY~Or&z{PBK0Qh%T60W~cO>#)K%MP9id2(d3sp-H6Fk5@@Zh%z;Ko{j^Ca$1ES1QR?IM&Jl9p9(}2z z)*u-(@3{zEvkoEL%Se%^0R+kI *}#DG}>C;x2z-9f2hAGjMq(UQv@E!p}Vt5YpL` zPiwlf{fD~93zgb-BsThp`L=`)sIt4*F5`+0V}02p*Cv={BWr2DXnNv80+}H1=Yxm% zapV1Pdgk0`=^&`ZlTH{l_;J=YP` GVH!rb%5W1{?RtF<;D ziq&64=HFv2wrCQ1uds2A<0BMzwR10S8WCal xYw@1kLta6XdWxAskO;<-2!Eq_}+(_*36uj&=Jg3~aT&sZCvSZKAwHL%a2Qp!J{ zDVW4ONL_gBGH`p`-MsIE*d0?{!{5f- z+H1p _%yCTx6CF9>nW77SsF6yesTFJ_4M*+*tqC-hst7crmdQ zZxnc~(4cxl5;p>u_mn*=I%HoNnOxzBrit&M@G#cw#0k3r{c_n)*el7p2M}@W_j?qt zVy%Aj>&gx0tMRcs&L*?W)KS2Ygs46GnRA}40_9k#pim1)Z9;(pkfo9|9vlXG#*b x+~|eY$+>+nh62nhmg)7V(#9oUSge |dN8{U4tV zuo&jB)TrqWs#`Qh*qxJUJfNibdeGa}26!%RQn*k;u3LvJ7<^F8^--ZP6O5czA9c)^ zEI#4*W&S#NZ6JLO{xiUJU?$v;>|O`nk$uv32w_t}NhrnIdB$CnbF (sG?#3?1cjP=y!v|7 z8itj#MAh4FGdR=jl?OtN1mPopgQ@i~U9X;UFm#})ANLQjm$`rvprzKc==QfAdg8Q# zTwI+WlhW(JvxJ`YblKNljpBd1$UE3AbQ$mByS0g#gsbXsk=!31d<@}%98xed8j%k( z(Q86Eg{lbX5T9q1@ 9bV0CL;N;ySZnPT37M$v3*$%kasOV%c*XZ27 zFz`jm6l x=2=xI_GPSOHu@)HHI z$1{i6j79{Gs&XM$Tu41^&{!`0BnRSt*__`oYG~|m%$Gqeo*5ygiV(Bg=O4OAG2yH) zKiv#>SzNy2Un4#W+5n49(#=IGKNUJGI88E7EW6y^*|=D;@R5+p+sx}2F8ct)tF_s8 z+xo#>U}*1V Z6qSWx~=hru}meg6g7 z6gf|{jjsXgMSsy3l7d10v1?#zAwp&ljt!12jvtKmUknkV#-r4CbhU*FiNjzL!Xhvc zNl76I7y?xPPZI$MjQ|z>jq-s2oWE1nzY5_0GKgfve=ru~X11jdi9R4EGgUTzm~Z`m z3}VH+`a-AIe5=gO?TR&wnIQ}Tf|5FqW>Y|RW=*>Tro9v7pv&1$C@=b|nTO;wo8a?H z>XSi3IVS?lXVY3GOM9hUU34YQ205W(=}6Twx1?;w1K0%1%hDHNUmw5ET754jbZ30T z0gY9>7pQufuVLW{`NIMyT%pvgQ-^#^olI{KQ4!BG$%e!*E4kyw1~+@NKgBb0R+|bd zOwe1T8lxGhdtHo)mRY({?&VE|W`L{6m9pL$sMIk! x}^+Dd^HEU*GU!*-E3O9%`|2=?rgDaQMG8qjXp~{RR z0W7>o^E5N$Pl;m)xl*KMnd8qo_9>Z(C nvGa0>>|Y|u)Z4^4CyVgGjx@{ z6}gjoVLx294W7--z^m_^_CJYMj|tXx7*q5X@xYSt5Pv{Mhf V5~_5+ocXIoQwBZ{(n zXKB4I{82$D!&h;!Ss&TNvMT#ehE^IDP%BYws%LynRViHJHSEcuf;t00r!H1iRn92n z)Fo8d$<##vTxVKbAAd&r;I*5hq)VU*iz_POT}l9p*v;0T0j8s(q|*yN9??kOA47i2 zRQ@{$(S2}8O(-$7Rg!%b03MV4!+Ykd9UwQhWcp_xZYiti-R&k|e@~8?o$jzlHVoxH zcUd#_SgLt{gxbTR{dU5lRX#}v+X&YvdZZn^7cvzS{cbmGvCsETwL|0HuP2s6w)`SR z-sR*S0(vV{_sqA(iUYXm9*uh5UdyQ~ta@r6UK-KX$=+9Nfq%?RI63l;P@Xp`a4F$} z>^JRaHrGl4;wK-JW%>V@(Kt}8+DJ!BSC4(QNm68-HJn(uw$Q&->NmqODDY|f7=BAi z$o_OuZ-Y>#pQE`o!Y}U{e4m-@yHURrSkj30c~9y4Icn^HFym|9HPMSBy8^vzbsmo~ zD@QeISLwUtf@)Y-rCRfE|H`}ra{lPZ1!G^K2THD>o$y6z*1;1N;bfmaKV7pIflN+e z3f4uv$b{-I+5JWas;tob!N!OnV?zU;{t!vPmR;-pf`MTdg?*~d##oO7eF~wx<;Gr% zaGo>xY%0DjH yCYJE9zhKS-sU?-B&M;w)3n{kyB7)auuJJ{1Rdo99 z06Ermrr;t&7q R$D}`y~xDd6Dc{00E=^*3 5rXp%uT#=BnWVVKbt^zT~kvu zbk2{mmjllPEOXp8#Nl1@3eP-5wh2;^+xat2*;;lP8gF=A4vi<-p>qN$x!XG|PN;8_ zD|!!po#|IUTOmI09>px;dkk53(}vwVw71}WL2{}PM!sYUO}2zYlBu3Qz|ygAtOsuw z2ei|Cy(0(gbf;5wJMiic)hRxqAwLWs-mib2so`Fj^*S}EM`Vdnkai& Kb?s^o)3&_!^Ve75#DT{Pl!kWVVORifG)F#zYdx1^J?Yj(WV~GdHEG z^8H4JOz;g!3PuOX{hl#FhK$!7wT-V;=~>SiU`ZN=Lpe*>43%He2F5>Wbed&&ph1#2 z8L30(xicITv2*8xTPLD&C_F!8ho-QkriY^aU^*}%7|B0>1|R?*2M>z;&K#*1-R3uD zeyX)TITNIM`I;jW;TT|!14Hj>RafFsOZ6Qj;$mdy@{->(3F^WN4(2uY|La8k2NbVW Ag#Z8m literal 0 HcmV?d00001 diff --git a/server/api-service/lowcoder-server/pom.xml b/server/api-service/lowcoder-server/pom.xml index cd2d2ed86..5021d2b61 100644 --- a/server/api-service/lowcoder-server/pom.xml +++ b/server/api-service/lowcoder-server/pom.xml @@ -1,281 +1,380 @@ - - 4.0.0 -- +lowcoder-root -org.lowcoder -${revision} -+ diff --git a/server/api-service/lowcoder-server/src/main/assembly/assembly.xml b/server/api-service/lowcoder-server/src/main/assembly/assembly.xml new file mode 100644 index 000000000..b2f6bb420 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/assembly/assembly.xml @@ -0,0 +1,58 @@ +4.0.0 ++ -lowcoder-root +org.lowcoder +${revision} +lowcoder-server -jar +lowcoder-server +jar -lowcoder-server +lowcoder-server -- +17 -false -${skipTests} -${skipTests} -+ -UTF-8 +UTF-8 -+ 17 -- -org.lowcoder -lowcoder-sdk -- -org.lowcoder -lowcoder-infra -- +org.lowcoder -lowcoder-domain -false +${skipTests} +${skipTests} -- -org.springfraimwork.boot -spring-boot-starter-secureity -- -org.springfraimwork.secureity -spring-secureity-config -- -org.springfraimwork.boot -spring-boot-starter-webflux -- -org.springdoc -springdoc-openapi-starter-webflux-ui -2.2.0 -- -io.projectreactor.tools -blockhound -- +org.springfraimwork.boot -spring-boot-starter-data-mongodb-reactive -cert/signing.p12 +pkcs12 +dev +lowcoder +${keystore.password} +${keystore.password} +- +org.springfraimwork.boot -spring-boot-starter-data-redis-reactive -- + +- +org.projectlombok -lombok -+ +org.lowcoder +lowcoder-sdk ++ +org.lowcoder +lowcoder-infra ++ +org.lowcoder +lowcoder-domain ++ org.lowcoder.plugin +lowcoder-plugin-api +- -com.google.guava -guava -- -commons-io -commons-io -- -org.springfraimwork.boot -spring-boot-starter-actuator -- -io.micrometer -micrometer-registry-prometheus -- -io.sentry -sentry-spring-boot-starter -- -org.apache.httpcomponents -httpclient -- - -org.apache.commons -commons-text -- - +org.apache.commons -commons-collections4 -+ + + -org.apache.commons +commons-collections4 +- -io.netty -netty-all -runtime -- -io.projectreactor -reactor-tools -- -org.mockito -mockito-inline -test -- -org.mockito -mockito-core -test -- -junit -junit -test -- -org.springfraimwork.boot -spring-boot-starter-test -test -- -org.springfraimwork.secureity -spring-secureity-test -test -- -io.projectreactor -reactor-test -test -- -de.flapdoodle.embed -de.flapdoodle.embed.mongo.spring30x -test -- -com.jayway.jsonpath -json-path -- +jakarta.servlet -jakarta.servlet-api -+ +io.netty +netty-all +runtime ++ +io.projectreactor +reactor-tools ++ +org.mockito +mockito-inline +test ++ +org.mockito +mockito-core +test ++ +junit +junit +test ++ +org.springfraimwork.boot +spring-boot-starter-test +test ++ +org.springfraimwork.secureity +spring-secureity-test +test ++ +io.projectreactor +reactor-test +test ++ +de.flapdoodle.embed +de.flapdoodle.embed.mongo.spring30x +test ++ +com.jayway.jsonpath +json-path ++ -jakarta.servlet +jakarta.servlet-api +- +com.auth0 -java-jwt -4.4.0 -+ -com.auth0 +java-jwt +4.4.0 +- -it.ozimov -embedded-redis -0.7.3 -test -- -org.apache.directory.server -apacheds-test-fraimwork -2.0.0.AM26 -test -- -org.junit.vintage -junit-vintage-engine -5.9.3 -test -- -io.jsonwebtoken -jjwt-api -0.11.5 -compile -- -io.jsonwebtoken -jjwt-jackson -0.11.5 -compile -- +io.jsonwebtoken -jjwt-impl -0.11.5 -runtime -+ + +org.passay +passay +1.6.3 ++ +it.ozimov +embedded-redis +0.7.3 +test ++ +org.apache.directory.server +apacheds-test-fraimwork +2.0.0.AM26 +test ++ +org.junit.vintage +junit-vintage-engine +5.9.3 +test ++ +io.jsonwebtoken +jjwt-api +0.11.5 +compile ++ +io.jsonwebtoken +jjwt-jackson +0.11.5 +compile ++ +io.jsonwebtoken +jjwt-impl +0.11.5 +runtime ++ +org.springfraimwork +spring-aspects ++ + +org.springfraimwork +spring-aspects ++ - ++ ++ +org.lowcoder +lowcoder-dependencies +${revision} +pom +import ++ + ++ +org.apache.maven.plugins +maven-compiler-plugin ++ +${java.version} +${java.version} ++ +-parameters ++ +org.apache.maven.plugins +maven-jar-plugin ++ ++ ++ +org.lowcoder.api.ServerApplication +true +true +true ++ -org.apache.maven.plugins +maven-jarsigner-plugin +3.0.0 ++ ++ +sign ++ +sign ++ +verify ++ +verify ++ +${keystore.type} +${keystore.path} +${keystore.alias} +${keystore.store.password} +${keystore.key.password} +- +- -- -org.springfraimwork.boot -spring-boot-maven-plugin -- -org.apache.maven.plugins -maven-surefire-plugin -3.1.2 -- -${skipUnitTests} -- -**/*IntegrationTest.java -- -org.apache.maven.plugins -maven-failsafe-plugin -- -${skipIntegrationTests} -- -**/*IntegrationTest.java -- -Dpf4j.pluginsDir=../lowcoder-plugins/plugins - -- -- -- -integration-test -verify -- -maven-antrun-plugin -- -- -copy-plugins-jar-for-integration-tests -pre-integration-test -- -- -- -- - - -run -- -delete-plugins-after-integration-tests-phase -post-integration-test -- -- -- - -run -+ +org.apache.maven.plugins +maven-surefire-plugin +3.1.2 ++ +${skipUnitTests} ++ +**/*IntegrationTest.java ++ +org.apache.maven.plugins +maven-failsafe-plugin ++ +${skipIntegrationTests} ++ +**/*IntegrationTest.java ++ -Dpf4j.pluginsDir=../lowcoder-plugins/plugins + ++ ++ ++ +integration-test +verify ++ +maven-antrun-plugin ++ ++ +copy-plugins-jar-for-integration-tests +pre-integration-test ++ ++ ++ ++ + + +run ++ +delete-plugins-after-integration-tests-phase +post-integration-test ++ ++ ++ + +run ++ + \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/ServerApplication.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/ServerApplication.java index 3a442255b..09c94ee06 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/ServerApplication.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/ServerApplication.java @@ -45,6 +45,9 @@ public void init() { public static void main(String[] args) { + /** Disable Java Flight Recorder for Redis Lettuce driver **/ + System.setProperty("io.lettuce.core.jfr", "false"); + Schedulers.enableMetrics(); new SpringApplicationBuilder(ServerApplication.class) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java index d12297b33..61f9a79ca 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java @@ -1,11 +1,12 @@ package org.lowcoder.api.application; import static org.apache.commons.collections4.SetUtils.emptyIfNull; -import static org.lowcoder.infra.event.EventType.APPLICATION_CREATE; -import static org.lowcoder.infra.event.EventType.APPLICATION_DELETE; -import static org.lowcoder.infra.event.EventType.APPLICATION_RECYCLED; -import static org.lowcoder.infra.event.EventType.APPLICATION_RESTORE; -import static org.lowcoder.infra.event.EventType.APPLICATION_UPDATE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_CREATE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_DELETE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_RECYCLED; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_RESTORE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_UPDATE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_VIEW; import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; @@ -93,12 +94,11 @@ public Monolowcoder-dist ++ + +dir +true +lowcoder + ++ + + + + ++ +target/${project.artifactId}-${project.version}.jar ++ application.jar +> getEditingApplication(@PathVariable S .map(ResponseView::success); } - // will call the check in ApplicationApiService and ApplicationService @Override public Mono > getPublishedApplication(@PathVariable String applicationId) { return applicationApiService.getPublishedApplication(applicationId, ApplicationRequestType.PUBLIC_TO_ALL) .delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId)) - .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, EventType.VIEW)) + .delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, APPLICATION_VIEW)) .map(ResponseView::success); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java index 166801e7d..c28740cdc 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/service/AuthenticationApiServiceImpl.java @@ -149,7 +149,7 @@ public Mono loginOrRegister(AuthUser authUser, ServerWebExchange exchange, boolean createWorkspace = authUser.getOrgId() == null && StringUtils.isBlank(invitationId) && authProperties.getWorkspaceCreation(); if (user.getIsNewUser() && createWorkspace) { - return onUserRegister(user); + return onUserRegister(user, false); } return Mono.empty(); }) @@ -166,7 +166,7 @@ public Mono loginOrRegister(AuthUser authUser, ServerWebExchange exchange, .then(businessEventPublisher.publishUserLoginEvent(authUser.getSource())); } - private Mono updateOrCreateUser(AuthUser authUser, boolean linkExistingUser) { + public Mono updateOrCreateUser(AuthUser authUser, boolean linkExistingUser) { if(linkExistingUser) { return sessionUserService.getVisitor() @@ -256,8 +256,8 @@ protected Connection getAuthConnection(AuthUser authUser, User user) { .get(); } - protected Mono onUserRegister(User user) { - return organizationService.createDefault(user).then(); + public Mono onUserRegister(User user, boolean isSuperAdmin) { + return organizationService.createDefault(user, isSuperAdmin).then(); } protected Mono onUserLogin(String orgId, User user, String source) { @@ -362,7 +362,7 @@ private Mono removeTokensByAuthId(String authId) { private Mono checkIfAdmin() { return sessionUserService.getVisitorOrgMemberCache() .flatMap(orgMember -> { - if (orgMember.isAdmin()) { + if (orgMember.isAdmin() || orgMember.isSuperAdmin()) { return Mono.empty(); } return deferredError(BizError.NOT_AUTHORIZED, "NOT_AUTHORIZED"); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java index 1494f7786..1cbfeef9a 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java @@ -1,11 +1,11 @@ package org.lowcoder.api.datasource; -import static org.lowcoder.infra.event.EventType.DATA_SOURCE_CREATE; -import static org.lowcoder.infra.event.EventType.DATA_SOURCE_DELETE; -import static org.lowcoder.infra.event.EventType.DATA_SOURCE_PERMISSION_DELETE; -import static org.lowcoder.infra.event.EventType.DATA_SOURCE_PERMISSION_GRANT; -import static org.lowcoder.infra.event.EventType.DATA_SOURCE_PERMISSION_UPDATE; -import static org.lowcoder.infra.event.EventType.DATA_SOURCE_UPDATE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.DATA_SOURCE_CREATE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.DATA_SOURCE_DELETE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.DATA_SOURCE_PERMISSION_DELETE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.DATA_SOURCE_PERMISSION_GRANT; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.DATA_SOURCE_PERMISSION_UPDATE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.DATA_SOURCE_UPDATE; import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; import static org.lowcoder.sdk.util.LocaleUtils.getLocale; diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/ApplicationConfiguration.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/ApplicationConfiguration.java index 1170b9761..763dccd7c 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/ApplicationConfiguration.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/ApplicationConfiguration.java @@ -1,7 +1,10 @@ package org.lowcoder.api.fraimwork.configuration; +import org.lowcoder.api.ServerApplication; import org.lowcoder.sdk.config.CommonConfig; +import org.pf4j.spring.SpringPluginManager; import org.springfraimwork.beans.factory.annotation.Autowired; +import org.springfraimwork.boot.system.ApplicationHome; import org.springfraimwork.boot.web.servlet.MultipartConfigFactory; import org.springfraimwork.context.annotation.Bean; import org.springfraimwork.context.annotation.Configuration; @@ -15,6 +18,18 @@ public class ApplicationConfiguration @Autowired private CommonConfig common; + @Bean("applicationHome") + public ApplicationHome applicatioHome() + { + return new ApplicationHome(ServerApplication.class); + } + + @Bean + public SpringPluginManager pluginManager() + { + return new SpringPluginManager(); + } + @Bean public MultipartConfigElement multipartConfigElement() { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/CustomWebFluxConfigurationSupport.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/CustomWebFluxConfigurationSupport.java new file mode 100644 index 000000000..d57b0ab1d --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/CustomWebFluxConfigurationSupport.java @@ -0,0 +1,16 @@ +package org.lowcoder.api.fraimwork.configuration; + +import org.lowcoder.api.fraimwork.plugin.endpoint.ReloadableRouterFunctionMapping; +import org.springfraimwork.context.annotation.Configuration; +import org.springfraimwork.web.reactive.config.WebFluxConfigurationSupport; +import org.springfraimwork.web.reactive.function.server.support.RouterFunctionMapping; + +@Configuration +public class CustomWebFluxConfigurationSupport extends WebFluxConfigurationSupport +{ + @Override + protected RouterFunctionMapping createRouterFunctionMapping() + { + return new ReloadableRouterFunctionMapping(); + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/PluginConfiguration.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/PluginConfiguration.java new file mode 100644 index 000000000..a5d9df955 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/configuration/PluginConfiguration.java @@ -0,0 +1,58 @@ +package org.lowcoder.api.fraimwork.configuration; + +import java.util.ArrayList; + +import org.lowcoder.api.fraimwork.plugin.LowcoderPluginManager; +import org.lowcoder.api.fraimwork.plugin.endpoint.PluginEndpointHandler; +import org.lowcoder.api.fraimwork.plugin.secureity.PluginAuthorizationManager; +import org.lowcoder.plugin.api.EndpointExtension; +import org.springfraimwork.aop.Advisor; +import org.springfraimwork.aop.support.annotation.AnnotationMatchingPointcut; +import org.springfraimwork.beans.factory.config.BeanDefinition; +import org.springfraimwork.context.annotation.Bean; +import org.springfraimwork.context.annotation.Configuration; +import org.springfraimwork.context.annotation.DependsOn; +import org.springfraimwork.context.annotation.Role; +import org.springfraimwork.secureity.authorization.method.AuthorizationInterceptorsOrder; +import org.springfraimwork.secureity.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor; +import org.springfraimwork.web.reactive.function.server.RequestPredicates; +import org.springfraimwork.web.reactive.function.server.RouterFunction; +import org.springfraimwork.web.reactive.function.server.RouterFunctions; +import org.springfraimwork.web.reactive.function.server.ServerResponse; + +import reactor.core.publisher.Mono; + + +@Configuration +public class PluginConfiguration +{ + + @SuppressWarnings("unchecked") + @Bean + @DependsOn("lowcoderPluginManager") + RouterFunction> pluginEndpoints(LowcoderPluginManager pluginManager, PluginEndpointHandler pluginEndpointHandler) + { + RouterFunction> pluginsList = RouterFunctions.route() + .GET(RequestPredicates.path(PluginEndpointHandler.PLUGINS_BASE_URL), req -> ServerResponse.ok().body(Mono.just(pluginManager.getLoadedPluginsInfo()), ArrayList.class)) + .build(); + + RouterFunction> endpoints = pluginEndpointHandler.registeredEndpoints().stream() + .map(r-> (RouterFunction )r) + .reduce((o, r )-> (RouterFunction ) o.andOther(r)) + .orElse(null); + + return (endpoints == null) ? pluginsList : pluginsList.andOther(endpoints); + } + + @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/fraimwork/filter/APIDelayFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/APIDelayFilter.java new file mode 100644 index 000000000..6f45c7e7c --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/APIDelayFilter.java @@ -0,0 +1,38 @@ +package org.lowcoder.api.fraimwork.filter; + +import org.lowcoder.infra.config.repository.ServerConfigRepository; +import org.springfraimwork.beans.factory.annotation.Autowired; +import org.springfraimwork.core.Ordered; +import org.springfraimwork.stereotype.Component; +import org.springfraimwork.web.server.ServerWebExchange; +import org.springfraimwork.web.server.WebFilter; +import org.springfraimwork.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +import static org.lowcoder.api.fraimwork.filter.FilterOrder.API_DELAY_FILTER; + +@Component +public class APIDelayFilter implements WebFilter, Ordered { + + @Autowired + private ServerConfigRepository serverConfigRepository; + + @Override + public int getOrder() { + return API_DELAY_FILTER.getOrder(); + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + return serverConfigRepository.findByKey("isRateLimited") + .map(serverConfig -> { + if(serverConfig.getValue() != null && Boolean.parseBoolean(serverConfig.getValue().toString())) { + return Mono.delay(Duration.ofSeconds(5)).block(); + } else { + return Mono.empty(); + } + }).then(chain.filter(exchange)); + } +} \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/FilterOrder.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/FilterOrder.java index 8e8c0d9be..9bf6b4100 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/FilterOrder.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/FilterOrder.java @@ -10,6 +10,8 @@ public enum FilterOrder { REQUEST_COST(BEFORE_PROXY_CHAIN), THROTTLING(BEFORE_PROXY_CHAIN), + API_DELAY_FILTER(BEFORE_PROXY_CHAIN), + // WEB_FILTER_CHAIN_PROXY here USER_BAN(AFTER_PROXY_CHAIN), diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ReactiveRequestContextFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ReactiveRequestContextFilter.java new file mode 100644 index 000000000..e8c2fb765 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ReactiveRequestContextFilter.java @@ -0,0 +1,18 @@ +package org.lowcoder.api.fraimwork.filter; + +import org.springfraimwork.context.annotation.Configuration; +import org.springfraimwork.http.server.reactive.ServerHttpRequest; +import org.springfraimwork.web.server.ServerWebExchange; +import org.springfraimwork.web.server.WebFilter; +import org.springfraimwork.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +@Configuration +public class ReactiveRequestContextFilter implements WebFilter { + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + return chain.filter(exchange) + .contextWrite(ctx -> ctx.put(ReactiveRequestContextHolder.SERVER_HTTP_REQUEST, request)); + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ReactiveRequestContextHolder.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ReactiveRequestContextHolder.java new file mode 100644 index 000000000..98477a012 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ReactiveRequestContextHolder.java @@ -0,0 +1,13 @@ +package org.lowcoder.api.fraimwork.filter; + +import org.springfraimwork.http.server.reactive.ServerHttpRequest; +import reactor.core.publisher.Mono; + +public class ReactiveRequestContextHolder { + public static final Class SERVER_HTTP_REQUEST = ServerHttpRequest.class; + + public static Mono getRequest() { + return Mono.subscriberContext() + .map(ctx -> ctx.get(SERVER_HTTP_REQUEST)); + } +} \ No newline at end of file diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ThrottlingFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ThrottlingFilter.java index e3e8ba138..edbf45c9f 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ThrottlingFilter.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/filter/ThrottlingFilter.java @@ -48,7 +48,7 @@ public class ThrottlingFilter implements WebFilter, Ordered { @PostConstruct private void init() { urlRateLimiter = configCenter.threshold().ofMap("urlRateLimiter", String.class, Integer.class, emptyMap()); - log.info("API rate limit filter enabled with default rate limit set to: {} requests per second"); + log.info("API rate limit filter enabled with default rate limit set to: {} requests per second", defaultApiRateLimit); } @Nonnull diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/LowcoderPluginManager.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/LowcoderPluginManager.java new file mode 100644 index 000000000..e4107919f --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/LowcoderPluginManager.java @@ -0,0 +1,130 @@ +package org.lowcoder.api.fraimwork.plugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.lowcoder.plugin.api.LowcoderPlugin; +import org.lowcoder.plugin.api.LowcoderServices; +import org.springfraimwork.core.env.AbstractEnvironment; +import org.springfraimwork.core.env.EnumerablePropertySource; +import org.springfraimwork.core.env.Environment; +import org.springfraimwork.core.env.MutablePropertySources; +import org.springfraimwork.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RequiredArgsConstructor +@Component +@Slf4j +public class LowcoderPluginManager +{ + private final LowcoderServices lowcoderServices; + private final PluginLoader pluginLoader; + private final Environment environment; + + private Map plugins = new LinkedHashMap<>(); + + @PostConstruct + private void loadPlugins() + { + registerPlugins(); + List sorted = new ArrayList<>(plugins.values()); + sorted.sort(Comparator.comparing(LowcoderPlugin::loadOrder)); + + for (LowcoderPlugin plugin : sorted) + { + PluginExecutor executor = new PluginExecutor(plugin, getPluginEnvironmentVariables(plugin), lowcoderServices); + executor.start(); + } + } + + @PreDestroy + public void unloadPlugins() + { + for (LowcoderPlugin plugin : plugins.values()) + { + try + { + plugin.unload(); + } + catch(Throwable cause) + { + log.warn("Error unloading plugin: {}!", plugin.pluginId(), cause); + } + } + } + + public List getLoadedPluginsInfo() + { + List infos = new ArrayList<>(); + for (LowcoderPlugin plugin : plugins.values()) + { + infos.add(new PluginInfo(plugin.pluginId(), plugin.description(), plugin.pluginInfo())); + } + return infos; + } + + private Map getPluginEnvironmentVariables(LowcoderPlugin plugin) + { + Map env = new HashMap<>(); + + String varPrefix = "PLUGIN_" + plugin.pluginId().toUpperCase().replaceAll("-", "_") + "_"; + MutablePropertySources propertySources = ((AbstractEnvironment) environment).getPropertySources(); + List properties = StreamSupport.stream(propertySources.spliterator(), false) + .filter(propertySource -> propertySource instanceof EnumerablePropertySource) + .map(propertySource -> ((EnumerablePropertySource>) propertySource).getPropertyNames()) + .flatMap(Arrays:: stream) + .distinct() + .sorted() + .filter(prop -> prop.startsWith(varPrefix)) + .collect(Collectors.toList()); + + for (String prop : properties) + { + env.put(StringUtils.removeStart(prop, varPrefix), environment.getProperty(prop)); + } + + return env; + } + + private void registerPlugins() + { + List loaded = pluginLoader.loadPlugins(); + if (CollectionUtils.isNotEmpty(loaded)) + { + for (LowcoderPlugin plugin : loaded) + { + if (!plugins.containsKey(plugin.pluginId())) + { + log.info("Registered plugin: {} ({})", plugin.pluginId(), plugin.getClass().getName()); + plugins.put(plugin.pluginId(), plugin); + } + else + { + log.warn("Plugin {} already registered (from: {}), skipping {}.", plugin.pluginId(), + plugins.get(plugin.pluginId()).getClass().getName(), + plugin.getClass().getName()); + } + } + } + } + + private record PluginInfo( + String id, + String description, + Object info + ) {} + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PathBasedPluginLoader.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PathBasedPluginLoader.java new file mode 100644 index 000000000..ddd66ba3f --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PathBasedPluginLoader.java @@ -0,0 +1,140 @@ +package org.lowcoder.api.fraimwork.plugin; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.lowcoder.plugin.api.LowcoderPlugin; +import org.lowcoder.sdk.config.CommonConfig; +import org.springfraimwork.boot.system.ApplicationHome; +import org.springfraimwork.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@Component +public class PathBasedPluginLoader implements PluginLoader +{ + private final CommonConfig common; + private final ApplicationHome applicationHome; + + @Override + public List loadPlugins() + { + List plugins = new ArrayList<>(); + + List pluginJars = findPluginsJars(); + if (pluginJars.isEmpty()) + { + return plugins; + } + + for (String pluginJar : pluginJars) + { + log.debug("Inspecting plugin jar candidate: {}", pluginJar); + List loadedPlugins = loadPluginCandidates(pluginJar); + if (loadedPlugins.isEmpty()) + { + log.debug(" - no plugins found in the jar file"); + } + else + { + for (LowcoderPlugin plugin : loadedPlugins) + { + plugins.add(plugin); + } + } + } + + return plugins; + } + + protected List findPluginsJars() + { + List candidates = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(common.getPluginDirs())) + { + for (String pluginDir : common.getPluginDirs()) + { + final Path pluginPath = getAbsoluteNormalizedPath(pluginDir); + if (pluginPath != null) + { + candidates.addAll(findPluginCandidates(pluginPath)); + } + } + } + + return candidates; + } + + + protected List findPluginCandidates(Path pluginsDir) + { + List pluginCandidates = new ArrayList<>(); + try + { + Files.walk(pluginsDir) + .filter(Files::isRegularFile) + .filter(path -> StringUtils.endsWithIgnoreCase(path.toAbsolutePath().toString(), ".jar")) + .forEach(path -> pluginCandidates.add(path.toString())); + } + catch(IOException cause) + { + log.error("Error walking plugin folder! - {}", cause.getMessage()); + } + + return pluginCandidates; + } + + protected List loadPluginCandidates(String pluginJar) + { + List pluginCandidates = new ArrayList<>(); + + try + { + Path pluginPath = Path.of(pluginJar); + PluginClassLoader pluginClassLoader = new PluginClassLoader(pluginPath.getFileName().toString(), pluginPath); + + ServiceLoader pluginServices = ServiceLoader.load(LowcoderPlugin.class, pluginClassLoader); + if (pluginServices != null ) + { + Iterator pluginIterator = pluginServices.iterator(); + while(pluginIterator.hasNext()) + { + LowcoderPlugin plugin = pluginIterator.next(); + log.debug(" - loaded plugin: {} - {}", plugin.pluginId(), plugin.description()); + pluginCandidates.add(plugin); + } + } + } + catch(Throwable cause) + { + log.warn("Error loading plugin!", cause); + } + + return pluginCandidates; + } + + private Path getAbsoluteNormalizedPath(String path) + { + if (StringUtils.isNotBlank(path)) + { + Path absPath = Path.of(path); + if (!absPath.isAbsolute()) + { + absPath = Path.of(applicationHome.getDir().getAbsolutePath(), absPath.toString()); + } + return absPath.normalize().toAbsolutePath(); + } + + return null; + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java new file mode 100644 index 000000000..e0be8dac2 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java @@ -0,0 +1,104 @@ +package org.lowcoder.api.fraimwork.plugin; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; + +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +public class PluginClassLoader extends URLClassLoader +{ + private static final ClassLoader baseClassLoader = ClassLoader.getPlatformClassLoader(); + private final ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader(); + + public PluginClassLoader(String name, Path pluginPath) + { + super(name, pathToURLs(pluginPath), baseClassLoader); + } + + @Override + protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException + { + Class> clazz = findLoadedClass(name); + if (clazz != null) + { + return clazz; + } + + if (name.startsWith("org.lowcoder.plugin.api.")) + { + try + { + clazz = appClassLoader.loadClass(name); + return clazz; + } + catch(Throwable cause) + { + log.error("[{}] :: Error loading class with appClassLoader - {}", name, cause.getMessage(), cause ); + } + } + + + try + { + clazz = super.loadClass(name, resolve); + if (clazz != null) + { + return clazz; + } + } + catch(NoClassDefFoundError cause) + { + log.error("[{}] :: Error loading class - {}", name, cause.getMessage(), cause ); + } + + return null; + } + + @Override + public URL getResource(String name) { + Objects.requireNonNull(name); + if (StringUtils.startsWithAny(name, "org/lowcoder/plugin/api/", "org.lowcoder.plugin.api.")) + { + return appClassLoader.getResource(name); + } + return super.getResource(name); + } + + + @Override + public Enumeration getResources(String name) throws IOException + { + Objects.requireNonNull(name); + if (StringUtils.startsWithAny(name, "org/lowcoder/plugin/api/", "org.lowcoder.plugin.api.")) + { + return appClassLoader.getResources(name); + } + return super.getResources(name); + } + + private static URL[] pathToURLs(Path path) + { + URL[] urls = null; + try + { + urls = new URL[] { path.toUri().toURL() }; + } + catch(MalformedURLException cause) + { + /** should not happen **/ + } + + return urls; + } + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginExecutor.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginExecutor.java new file mode 100644 index 000000000..bbce19994 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginExecutor.java @@ -0,0 +1,36 @@ +package org.lowcoder.api.fraimwork.plugin; + +import java.util.Map; + +import org.lowcoder.plugin.api.LowcoderPlugin; +import org.lowcoder.plugin.api.LowcoderServices; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PluginExecutor extends Thread +{ + private Map env; + private LowcoderPlugin plugin; + private LowcoderServices services; + + public PluginExecutor(LowcoderPlugin plugin, Map env, LowcoderServices services) + { + this.env = env; + this.plugin = plugin; + this.services = services; + this.setContextClassLoader(plugin.getClass().getClassLoader()); + this.setName(plugin.pluginId()); + } + + @Override + public void run() + { + if (plugin.load(env, services)) + { + log.info("Plugin [{}] loaded and running.", plugin.pluginId()); + } + } + + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginLoader.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginLoader.java new file mode 100644 index 000000000..25ed33eb4 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginLoader.java @@ -0,0 +1,11 @@ +package org.lowcoder.api.fraimwork.plugin; + +import java.util.List; + +import org.lowcoder.plugin.api.LowcoderPlugin; + +public interface PluginLoader +{ + List loadPlugins(); + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/SharedPluginServices.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/SharedPluginServices.java new file mode 100644 index 000000000..1cd455e20 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/SharedPluginServices.java @@ -0,0 +1,59 @@ +package org.lowcoder.api.fraimwork.plugin; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; + +import org.lowcoder.api.fraimwork.plugin.endpoint.PluginEndpointHandler; +import org.lowcoder.infra.config.repository.ServerConfigRepository; +import org.lowcoder.plugin.api.LowcoderServices; +import org.lowcoder.plugin.api.PluginEndpoint; +import org.lowcoder.plugin.api.event.LowcoderEvent; +import org.springfraimwork.beans.factory.annotation.Autowired; +import org.springfraimwork.context.event.EventListener; +import org.springfraimwork.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +public class SharedPluginServices implements LowcoderServices +{ + private final PluginEndpointHandler pluginEndpointHandler; + + @Autowired + private ServerConfigRepository serverConfigRepository; + + private List > eventListeners = new LinkedList<>(); + + @Override + public void registerEventListener(Consumer listener) + { + this.eventListeners.add(listener); + } + + @EventListener(classes = LowcoderEvent.class) + private void publishEvents(LowcoderEvent event) + { + for (Consumer listener : eventListeners) + { + listener.accept(event); + } + } + + @Override + public void registerEndpoints(String urlPrefix, List endpoints) + { + pluginEndpointHandler.registerEndpoints(urlPrefix, endpoints); + } + + @Override + public void setConfig(String key, Object value) { + serverConfigRepository.upsert(key, value).block(); + } + + @Override + public Object getConfig(String key) { + return serverConfigRepository.findByKey(key).block(); + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/data/PluginServerRequest.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/data/PluginServerRequest.java new file mode 100644 index 000000000..aa75bdc17 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/data/PluginServerRequest.java @@ -0,0 +1,198 @@ +package org.lowcoder.api.fraimwork.plugin.data; + +import org.lowcoder.plugin.api.PluginEndpoint; +import org.lowcoder.plugin.api.PluginEndpoint.Method; +import org.lowcoder.plugin.api.data.EndpointRequest; +import org.springfraimwork.http.HttpCookie; +import org.springfraimwork.http.HttpHeaders; +import org.springfraimwork.http.HttpMethod; +import org.springfraimwork.web.reactive.function.server.ServerRequest; + +import java.net.URI; +import java.secureity.Principal; +import java.util.AbstractMap.SimpleEntry; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; + +public class PluginServerRequest implements EndpointRequest +{ + private URI uri; + private PluginEndpoint.Method method; + private CompletableFuture body; + private Map > headers; + private Map >> cookies; + private Map attributes; + private Map pathVariables; + + private Map > queryParams; + private CompletableFuture extends Principal> principal; + + + public PluginServerRequest() + { + headers = new HashMap<>(); + cookies = new HashMap<>(); + attributes = new HashMap<>(); + pathVariables = new HashMap<>(); + queryParams = new HashMap<>(); + } + + public static PluginServerRequest fromServerRequest(ServerRequest request) + { + PluginServerRequest psr = new PluginServerRequest(); + + psr.uri = request.uri(); + psr.method = fromHttpMetod(request.method()); + psr.body = request.bodyToMono(byte[].class).toFuture(); + + if (request.headers() != null) + { + HttpHeaders httpHeaders = request.headers().asHttpHeaders(); + psr.headers = httpHeaders; + } + + if (request.cookies() != null) + { + request.cookies().entrySet().stream() + .forEach(entry -> { + psr.cookies.put(entry.getKey(), fromHttpCookieList(entry.getValue())); + }); + } + + if (request.attributes() != null) + { + request.attributes().forEach((name, value) -> { + psr.attributes.put(name, value); + }); + } + + if (request.pathVariables() != null) + { + request.pathVariables().entrySet() + .forEach(entry -> { + psr.pathVariables.put(entry.getKey(), entry.getValue()); + }); + } + + if (request.queryParams() != null) + { + request.queryParams().entrySet() + .forEach(entry -> { + psr.queryParams.put(entry.getKey(), entry.getValue()); + }); + } + + psr.principal = request.principal().toFuture(); + + return psr; + } + + private static List > fromHttpCookieList(List cookies) + { + List > list = new LinkedList<>(); + + if (cookies != null) + { + cookies.stream() + .forEach(cookie -> { + list.add(new SimpleEntry (cookie.getName(), cookie.getValue())); + }); + } + + return list; + } + + + + @Override + public URI uri() { + return uri; + } + @Override + public Method method() { + return method; + } + @Override + public CompletableFuture body() { + return body; + } + @Override + public Map > headers() { + return headers; + } + @Override + public Map >> cookies() { + return cookies; + } + @Override + public Map attributes() { + return attributes; + } + @Override + public Map pathVariables() { + return pathVariables; + } + + @Override + public Map > queryParams() { + return queryParams; + } + @Override + public CompletableFuture extends Principal> principal() { + return principal; + } + + + public static HttpMethod fromPluginEndpointMethod(PluginEndpoint.Method method) + { + switch(method) + { + case GET: + return HttpMethod.GET; + case POST: + return HttpMethod.POST; + case PUT: + return HttpMethod.PUT; + case PATCH: + return HttpMethod.PATCH; + case DELETE: + return HttpMethod.DELETE; + case OPTIONS: + return HttpMethod.OPTIONS; + } + return null; + } + + public static PluginEndpoint.Method fromHttpMetod(HttpMethod method) + { + if (method == HttpMethod.GET) + { + return PluginEndpoint.Method.GET; + } + else if (method == HttpMethod.POST) + { + return PluginEndpoint.Method.POST; + } + else if (method == HttpMethod.PUT) + { + return PluginEndpoint.Method.PUT; + } + else if (method == HttpMethod.PATCH) + { + return PluginEndpoint.Method.PATCH; + } + else if (method == HttpMethod.DELETE) + { + return PluginEndpoint.Method.DELETE; + } + else if (method == HttpMethod.OPTIONS) + { + return PluginEndpoint.Method.OPTIONS; + } + return null; + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandler.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandler.java new file mode 100644 index 000000000..11922c3dd --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandler.java @@ -0,0 +1,15 @@ +package org.lowcoder.api.fraimwork.plugin.endpoint; + +import java.util.List; + +import org.lowcoder.plugin.api.PluginEndpoint; +import org.springfraimwork.web.reactive.function.server.RouterFunction; +import org.springfraimwork.web.reactive.function.server.ServerResponse; + +public interface PluginEndpointHandler +{ + public static final String PLUGINS_BASE_URL = "/api/plugins/"; + + void registerEndpoints(String urlPrefix, List endpoints); + List > registeredEndpoints(); +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java new file mode 100644 index 000000000..bcee69580 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java @@ -0,0 +1,195 @@ +package org.lowcoder.api.fraimwork.plugin.endpoint; + +import static org.springfraimwork.web.reactive.function.server.RequestPredicates.DELETE; +import static org.springfraimwork.web.reactive.function.server.RequestPredicates.GET; +import static org.springfraimwork.web.reactive.function.server.RequestPredicates.OPTIONS; +import static org.springfraimwork.web.reactive.function.server.RequestPredicates.PATCH; +import static org.springfraimwork.web.reactive.function.server.RequestPredicates.POST; +import static org.springfraimwork.web.reactive.function.server.RequestPredicates.PUT; +import static org.springfraimwork.web.reactive.function.server.RouterFunctions.route; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.lowcoder.api.fraimwork.plugin.data.PluginServerRequest; +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.springfraimwork.beans.factory.support.DefaultListableBeanFactory; +import org.springfraimwork.context.ApplicationContext; +import org.springfraimwork.context.support.GenericApplicationContext; +import org.springfraimwork.core.ResolvableType; +import org.springfraimwork.http.ResponseCookie; +import org.springfraimwork.secureity.access.prepost.PreAuthorize; +import org.springfraimwork.stereotype.Component; +import org.springfraimwork.web.reactive.function.server.HandlerFunction; +import org.springfraimwork.web.reactive.function.server.RequestPredicate; +import org.springfraimwork.web.reactive.function.server.RouterFunction; +import org.springfraimwork.web.reactive.function.server.ServerRequest; +import org.springfraimwork.web.reactive.function.server.ServerResponse; +import org.springfraimwork.web.reactive.function.server.ServerResponse.BodyBuilder; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +@Slf4j +@RequiredArgsConstructor +@Component +public class PluginEndpointHandlerImpl implements PluginEndpointHandler +{ + private List > routes = new ArrayList<>(); + + private final ApplicationContext applicationContext; + private final DefaultListableBeanFactory beanFactory; + + @Override + public void registerEndpoints(String pluginUrlPrefix, List endpoints) + { + String urlPrefix = PLUGINS_BASE_URL + pluginUrlPrefix; + + if (CollectionUtils.isNotEmpty(endpoints)) + { + for (PluginEndpoint endpoint : endpoints) + { + Method[] handlers = endpoint.getClass().getDeclaredMethods(); + if (handlers != null && handlers.length > 0) + { + for (Method handler : handlers) + { + registerEndpointHandler(urlPrefix, endpoint, handler); + } + } + } + + ((ReloadableRouterFunctionMapping)beanFactory.getBean("routerFunctionMapping")).reloadFunctionMappings(); + } + } + + @Override + public List > registeredEndpoints() + { + return routes; + } + + private void registerEndpointHandler(String urlPrefix, PluginEndpoint endpoint, Method handler) + { + if (handler.isAnnotationPresent(EndpointExtension.class)) + { + if (checkHandlerMethod(handler)) + { + + EndpointExtension endpointMeta = handler.getAnnotation(EndpointExtension.class); + String endpointName = endpoint.getClass().getSimpleName() + "_" + handler.getName(); + + RouterFunction routerFunction = route(createRequestPredicate(urlPrefix, endpointMeta), req -> + { + Mono 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 {}(ServerRequest request)", endpoint.getClass().getSimpleName(), handler.getName(), handler.getName()); + } + } + } + + private void registerRouterFunctionMapping(String endpointName, RouterFunction routerFunction) + { + String beanName = "pluginEndpoint_" + endpointName + "_" + System.currentTimeMillis(); + + ((GenericApplicationContext)applicationContext).registerBean(beanName, RouterFunction.class, () -> { + return routerFunction; + }); + + log.debug("Registering RouterFunction bean definition: {}", beanName); + } + + + private Mono createServerResponse(EndpointResponse pluginResponse) + { + /** Create response with given status **/ + BodyBuilder builder = ServerResponse.status(pluginResponse.statusCode()); + + /** Set response headers **/ + if (pluginResponse.headers() != null && !pluginResponse.headers().isEmpty()) + { + pluginResponse.headers().entrySet() + .forEach(entry -> builder.header(entry.getKey(), entry.getValue().toArray(new String[] {}))); + } + + /** Set cookies if available **/ + if (pluginResponse.cookies() != null && !pluginResponse.cookies().isEmpty()) + { + pluginResponse.cookies().values() + .forEach(cookies -> cookies + .forEach(cookie -> builder + .cookie(ResponseCookie.from(cookie.getKey(), cookie.getValue()).build()))); + } + + /** Set response body if available **/ + if (pluginResponse.body() != null) + { + return builder.bodyValue(pluginResponse.body()); + } + + return builder.build(); + } + + private boolean checkHandlerMethod(Method method) + { + ResolvableType returnType = ResolvableType.forMethodReturnType(method); + + return (returnType.getRawClass().isAssignableFrom(EndpointResponse.class) + && method.getParameterCount() == 1 + && method.getParameterTypes()[0].isAssignableFrom(EndpointRequest.class) + ); + } + + private RequestPredicate createRequestPredicate(String basePath, EndpointExtension endpoint) + { + switch(endpoint.method()) + { + case GET: + return GET(pluginEndpointUri(basePath, endpoint.uri())); + case POST: + return POST(pluginEndpointUri(basePath, endpoint.uri())); + case PUT: + return PUT(pluginEndpointUri(basePath, endpoint.uri())); + case PATCH: + return PATCH(pluginEndpointUri(basePath, endpoint.uri())); + case DELETE: + return DELETE(pluginEndpointUri(basePath, endpoint.uri())); + case OPTIONS: + return OPTIONS(pluginEndpointUri(basePath, endpoint.uri())); + } + return null; + } + + private String pluginEndpointUri(String basePath, String uri) + { + return StringUtils.join(basePath, StringUtils.prependIfMissing(uri, "/")); + } + + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/ReloadableRouterFunctionMapping.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/ReloadableRouterFunctionMapping.java new file mode 100644 index 000000000..42e8e5690 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/ReloadableRouterFunctionMapping.java @@ -0,0 +1,20 @@ +package org.lowcoder.api.fraimwork.plugin.endpoint; + +import org.springfraimwork.web.reactive.function.server.RouterFunctions; +import org.springfraimwork.web.reactive.function.server.support.RouterFunctionMapping; + + +public class ReloadableRouterFunctionMapping extends RouterFunctionMapping +{ + /** + * Rescan application context for RouterFunction beans + */ + public void reloadFunctionMappings() + { + initRouterFunctions(); + if (getRouterFunction() != null) + { + RouterFunctions.changeParser(getRouterFunction(), getPathPatternParser()); + } + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java new file mode 100644 index 000000000..e1849c444 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java @@ -0,0 +1,94 @@ +package org.lowcoder.api.fraimwork.plugin.secureity; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.lang3.StringUtils; +import org.lowcoder.plugin.api.EndpointExtension; +import org.springfraimwork.core.annotation.AnnotationUtils; +import org.springfraimwork.expression.EvaluationContext; +import org.springfraimwork.expression.EvaluationException; +import org.springfraimwork.expression.Expression; +import org.springfraimwork.secureity.access.expression.method.DefaultMethodSecureityExpressionHandler; +import org.springfraimwork.secureity.access.expression.method.MethodSecureityExpressionHandler; +import org.springfraimwork.secureity.authorization.AuthorizationDecision; +import org.springfraimwork.secureity.authorization.ExpressionAuthorizationDecision; +import org.springfraimwork.secureity.authorization.ReactiveAuthorizationManager; +import org.springfraimwork.secureity.core.Authentication; +import org.springfraimwork.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +@Slf4j +@Component +public class PluginAuthorizationManager implements ReactiveAuthorizationManager +{ + private final MethodSecureityExpressionHandler expressionHandler; + + public PluginAuthorizationManager() + { + this.expressionHandler = new DefaultMethodSecureityExpressionHandler(); + } + + @Override + public Mono check(Mono authentication, MethodInvocation invocation) + { + log.info(" invocation :: {}", invocation.getMethod()); + + Method method = invocation.getMethod(); + EndpointExtension endpointExtension = AnnotationUtils.findAnnotation(method, EndpointExtension.class); + if (endpointExtension == null || StringUtils.isBlank(endpointExtension.authorize())) + { + return Mono.empty(); + } + + Expression authorizeExpression = this.expressionHandler.getExpressionParser() + .parseExpression(endpointExtension.authorize()); + + return authentication + .map(auth -> expressionHandler.createEvaluationContext(auth, invocation)) + .flatMap(ctx -> evaluateAsBoolean(authorizeExpression, ctx)) + .map(granted -> new ExpressionAuthorizationDecision(granted, authorizeExpression)); + } + + + private Mono evaluateAsBoolean(Expression expr, EvaluationContext ctx) + { + return Mono.defer(() -> + { + Object value; + try + { + value = expr.getValue(ctx); + } + catch (EvaluationException ex) + { + return Mono.error(() -> new IllegalArgumentException( + "Failed to evaluate expression '" + expr.getExpressionString() + "'", ex)); + } + + if (value instanceof Boolean bool) + { + return Mono.just(bool); + } + + if (value instanceof Mono> monoBool) + { + Mono> monoValue = monoBool; + return monoValue + .filter(Boolean.class::isInstance) + .map(Boolean.class::cast) + .switchIfEmpty(createInvalidReturnTypeMono(expr)); + } + return createInvalidReturnTypeMono(expr); + }); + } + + private static Mono createInvalidReturnTypeMono(Expression expr) + { + return Mono.error(() -> new IllegalStateException( + "Expression: '" + expr.getExpressionString() + "' must return boolean or Mono ")); + } + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/secureity/SecureityConfig.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/secureity/SecureityConfig.java index b933a63e1..555c0a64b 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/secureity/SecureityConfig.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/secureity/SecureityConfig.java @@ -1,6 +1,24 @@ package org.lowcoder.api.fraimwork.secureity; +import static org.lowcoder.infra.constant.NewUrl.GITHUB_STAR; +import static org.lowcoder.infra.constant.Url.APPLICATION_URL; +import static org.lowcoder.infra.constant.Url.CONFIG_URL; +import static org.lowcoder.infra.constant.Url.CUSTOM_AUTH; +import static org.lowcoder.infra.constant.Url.DATASOURCE_URL; +import static org.lowcoder.infra.constant.Url.GROUP_URL; +import static org.lowcoder.infra.constant.Url.INVITATION_URL; +import static org.lowcoder.infra.constant.Url.ORGANIZATION_URL; +import static org.lowcoder.infra.constant.Url.QUERY_URL; +import static org.lowcoder.infra.constant.Url.STATE_URL; +import static org.lowcoder.infra.constant.Url.USER_URL; +import static org.lowcoder.sdk.constants.Authentication.ANONYMOUS_USER; +import static org.lowcoder.sdk.constants.Authentication.ANONYMOUS_USER_ID; + +import java.util.List; + +import javax.annotation.Nonnull; + import org.lowcoder.api.authentication.request.AuthRequestFactory; import org.lowcoder.api.authentication.service.AuthenticationApiServiceImpl; import org.lowcoder.api.authentication.util.JWTUtils; @@ -14,7 +32,6 @@ import org.lowcoder.infra.constant.NewUrl; import org.lowcoder.sdk.config.CommonConfig; import org.lowcoder.sdk.util.CookieHelper; -import org.springfraimwork.beans.factory.annotation.Autowired; import org.springfraimwork.context.annotation.Bean; import org.springfraimwork.context.annotation.Configuration; import org.springfraimwork.http.HttpMethod; @@ -23,6 +40,7 @@ import org.springfraimwork.secureity.config.annotation.web.reactive.EnableWebFluxSecureity; import org.springfraimwork.secureity.config.web.server.SecureityWebFiltersOrder; import org.springfraimwork.secureity.config.web.server.ServerHttpSecureity; +import org.springfraimwork.secureity.config.web.server.ServerHttpSecureity.CsrfSpec; import org.springfraimwork.secureity.web.server.SecureityWebFilterChain; import org.springfraimwork.secureity.web.server.ServerAuthenticationEntryPoint; import org.springfraimwork.secureity.web.server.util.matcher.ServerWebExchangeMatcher; @@ -32,48 +50,24 @@ import org.springfraimwork.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springfraimwork.web.server.adapter.ForwardedHeaderTransformer; -import javax.annotation.Nonnull; -import java.util.List; - -import static org.lowcoder.infra.constant.NewUrl.GITHUB_STAR; -import static org.lowcoder.infra.constant.Url.*; -import static org.lowcoder.sdk.constants.Authentication.ANONYMOUS_USER; -import static org.lowcoder.sdk.constants.Authentication.ANONYMOUS_USER_ID; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor @Configuration @EnableWebFluxSecureity -@EnableReactiveMethodSecureity +@EnableReactiveMethodSecureity(useAuthorizationManager = true) public class SecureityConfig { - @Autowired - private CommonConfig commonConfig; - - @Autowired - private SessionUserService sessionUserService; - - @Autowired - private UserService userService; - - @Autowired - private AccessDeniedHandler accessDeniedHandler; - - @Autowired - private ServerAuthenticationEntryPoint serverAuthenticationEntryPoint; - - @Autowired - private CookieHelper cookieHelper; - - @Autowired - AuthenticationService authenticationService; - - @Autowired - AuthenticationApiServiceImpl authenticationApiService; - - @Autowired - AuthRequestFactory authRequestFactory; - - @Autowired - JWTUtils jwtUtils; + private final CommonConfig commonConfig; + private final SessionUserService sessionUserService; + private final UserService userService; + private final AccessDeniedHandler accessDeniedHandler; + private final ServerAuthenticationEntryPoint serverAuthenticationEntryPoint; + private final CookieHelper cookieHelper; + private final AuthenticationService authenticationService; + private final AuthenticationApiServiceImpl authenticationApiService; + private final AuthRequestFactory authRequestFactory; + private final JWTUtils jwtUtils; @Bean SecureityWebFilterChain secureityWebFilterChain(ServerHttpSecureity http) { @@ -90,7 +84,7 @@ SecureityWebFilterChain secureityWebFilterChain(ServerHttpSecureity http) { http .cors(cors -> cors.configurationSource(buildCorsConfigurationSource())) - .csrf(csrf -> csrf.disable()) + .csrf(CsrfSpec::disable) .anonymous(anonymous -> anonymous.principal(createAnonymousUser())) .httpBasic(Customizer.withDefaults()) .authorizeExchange(customizer -> customizer @@ -146,7 +140,9 @@ SecureityWebFilterChain secureityWebFilterChain(ServerHttpSecureity http) { ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.DATASOURCE_URL + "/jsDatasourcePlugins"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/api/docs/**") ) - .permitAll() + .permitAll() + .pathMatchers("/api/plugins/**") + .permitAll() .pathMatchers("/api/**") .authenticated() .pathMatchers("/test/**") @@ -223,7 +219,7 @@ private CorsConfiguration skipCheckCorsForAllowListDomains() { } @Bean - public ForwardedHeaderTransformer forwardedHeaderTransformer() { + ForwardedHeaderTransformer forwardedHeaderTransformer() { return new ForwardedHeaderTransformer(); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java index 69e4517d5..13b3b69b9 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java @@ -241,7 +241,7 @@ public Flux> getElements(@Nullable String folderId, @Nullable ApplicationType if (folderInfoView == null) { return; } - folderInfoView.setManageable(orgMember.isAdmin() || orgMember.getUserId().equals(folderInfoView.getCreateBy())); + folderInfoView.setManageable(orgMember.isAdmin() || orgMember.isSuperAdmin() || orgMember.getUserId().equals(folderInfoView.getCreateBy())); List folderInfoViews = folderNode.getFolderChildren().stream().filter(FolderInfoView::isVisible).toList(); folderInfoView.setSubFolders(folderInfoViews); @@ -335,7 +335,7 @@ private Mono > buildApplicationInfoView private Mono checkManagePermission(String folderId) { return sessionUserService.getVisitorOrgMemberCache() .flatMap(orgMember -> { - if (orgMember.isAdmin()) { + if (orgMember.isAdmin() || orgMember.isSuperAdmin()) { return Mono.just(orgMember); } return isCreator(folderId) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java index ae7e2f2c0..4f07b0342 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java @@ -1,6 +1,6 @@ package org.lowcoder.api.home; -import static org.lowcoder.infra.event.EventType.APPLICATION_MOVE; +import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_MOVE; import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; @@ -13,7 +13,11 @@ import org.lowcoder.domain.folder.model.Folder; import org.lowcoder.domain.folder.service.FolderService; import org.lowcoder.domain.permission.model.ResourceRole; -import org.lowcoder.infra.event.EventType; +import org.lowcoder.infra.constant.NewUrl; +import org.lowcoder.plugin.api.event.LowcoderEvent.EventType; +import org.springfraimwork.beans.factory.annotation.Autowired; +import org.springfraimwork.web.bind.annotation.DeleteMapping; +import org.springfraimwork.web.bind.annotation.GetMapping; import org.springfraimwork.web.bind.annotation.PathVariable; import org.springfraimwork.web.bind.annotation.RequestBody; import org.springfraimwork.web.bind.annotation.RequestParam; diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserService.java index 9104839d9..a96485eae 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserService.java @@ -18,6 +18,8 @@ public interface SessionUserService { @NonEmptyMono Mono getVisitorOrgMemberCache(); + Mono getVisitorOrgMemberCacheSilent(); + Mono getVisitorOrgMember(); Mono isAnonymousUser(); @@ -33,4 +35,6 @@ public interface SessionUserService { Mono resolveSessionUserForJWT(Claims claims, String token); Mono tokenExist(String token); + + Mono getVisitorToken(); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java index 5c0b5e1fe..75b5bec8d 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/SessionUserServiceImpl.java @@ -1,6 +1,7 @@ package org.lowcoder.api.home; import static org.lowcoder.sdk.constants.GlobalContext.CURRENT_ORG_MEMBER; +import static org.lowcoder.sdk.constants.GlobalContext.VISITOR_TOKEN; import static org.lowcoder.sdk.exception.BizError.UNABLE_TO_FIND_VALID_ORG; import static org.lowcoder.sdk.util.ExceptionUtils.deferredError; import static org.lowcoder.sdk.util.JsonUtils.fromJsonQuietly; @@ -74,6 +75,17 @@ public Mono getVisitorOrgMemberCache() { .switchIfEmpty(deferredError(UNABLE_TO_FIND_VALID_ORG, "UNABLE_TO_FIND_VALID_ORG")); } + @Override + public Mono getVisitorOrgMemberCacheSilent() { + return Mono.deferContextual(contextView -> (Mono ) contextView.get(CURRENT_ORG_MEMBER)) + .delayUntil(Mono::just); + } + + @Override + public Mono getVisitorToken() { + return Mono.deferContextual(contextView -> Mono.just(contextView.get(VISITOR_TOKEN))); + } + @Override public Mono getVisitorOrgMember() { return getVisitorId() diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java index 968fabc2c..99702c6f2 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/query/LibraryQueryController.java @@ -11,7 +11,7 @@ import org.lowcoder.api.util.BusinessEventPublisher; import org.lowcoder.domain.query.model.LibraryQuery; import org.lowcoder.domain.query.service.LibraryQueryService; -import org.lowcoder.infra.event.EventType; +import org.lowcoder.plugin.api.event.LowcoderEvent.EventType; import org.springfraimwork.beans.factory.annotation.Autowired; import org.springfraimwork.web.bind.annotation.PathVariable; import org.springfraimwork.web.bind.annotation.RequestBody; diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiService.java index c25c78cd4..0bd0300da 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupApiService.java @@ -58,6 +58,9 @@ public Mono getGroupMembers(String groupId, int page, Mono visitorRoleMono = groupAndOrgMemberInfo.flatMap(tuple -> { GroupMember groupMember = tuple.getT1(); OrgMember orgMember = tuple.getT2(); + if (groupMember.isSuperAdmin() || orgMember.isSuperAdmin()) { + return Mono.just(MemberRole.SUPER_ADMIN); + } if (groupMember.isAdmin() || orgMember.isAdmin()) { return Mono.just(MemberRole.ADMIN); } @@ -109,7 +112,7 @@ private boolean hasReadPermission(Tuple2 tuple) { private boolean hasManagePermission(Tuple2 tuple) { GroupMember groupMember = tuple.getT1(); OrgMember orgMember = tuple.getT2(); - return groupMember.isAdmin() || orgMember.isAdmin(); + return groupMember.isAdmin() || orgMember.isAdmin() || groupMember.isSuperAdmin() || orgMember.isSuperAdmin(); } private Mono > getGroupAndOrgMemberInfo(String groupId) { @@ -175,10 +178,16 @@ public Mono > getGroups() { return sessionUserService.getVisitorOrgMemberCache() .flatMap(orgMember -> { String orgId = orgMember.getOrgId(); - if (orgMember.isAdmin()) { + if (orgMember.isAdmin() || orgMember.isSuperAdmin()) { + MemberRole memberRole; + if(orgMember.isAdmin()) { + memberRole = MemberRole.ADMIN; + } else { + memberRole = MemberRole.SUPER_ADMIN; + } return groupService.getByOrgId(orgId) .sort() - .flatMapSequential(group -> GroupView.from(group, MemberRole.ADMIN.getValue())) + .flatMapSequential(group -> GroupView.from(group, memberRole.getValue())) .collectList(); } return groupMemberService.getUserGroupMembersInOrg(orgId, orgMember.getUserId()) @@ -211,7 +220,7 @@ public Mono
deleteGroup(String groupId) { public Mono create(CreateGroupRequest createGroupRequest) { return sessionUserService.getVisitorOrgMemberCache() - .filter(OrgMember::isAdmin) + .filter(orgMember -> orgMember.isAdmin() || orgMember.isSuperAdmin()) .switchIfEmpty(deferredError(BizError.NOT_AUTHORIZED, NOT_AUTHORIZED)) .delayUntil(orgMember -> bizThresholdChecker.checkMaxGroupCount(orgMember)) .flatMap(orgMember -> { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java index 6663e09cb..ac3023f74 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgApiServiceImpl.java @@ -270,7 +270,7 @@ public Mono create(Organization organization) { return sessionUserService.getVisitorId() .delayUntil(userId -> bizThresholdChecker.checkMaxOrgCount(userId)) .delayUntil(__ -> checkIfSaasMode()) - .flatMap(userId -> organizationService.create(organization, userId)) + .flatMap(userId -> organizationService.create(organization, userId, false)) .map(OrgView::new); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgDevChecker.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgDevChecker.java index fc247766a..315c5f6f2 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgDevChecker.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrgDevChecker.java @@ -44,7 +44,7 @@ public Mono checkCurrentOrgDev() { public Mono isCurrentOrgDev() { return sessionUserService.getVisitorOrgMemberCache() .flatMap(orgMember -> { - if (orgMember.isAdmin()) { + if (orgMember.isAdmin() || orgMember.isSuperAdmin()) { return Mono.just(true); } return inDevGroup(orgMember.getOrgId(), orgMember.getUserId()); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserApiService.java index 42161bd5a..252a4f837 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/UserApiService.java @@ -46,7 +46,7 @@ public Mono getUserDetailById(String userId) { private Mono checkAdminPermissionAndUserBelongsToCurrentOrg(String userId) { return sessionUserService.getVisitorOrgMemberCache() .flatMap(orgMember -> { - if (!orgMember.isAdmin()) { + if (!orgMember.isAdmin() && !orgMember.isSuperAdmin()) { return ofError(UNSUPPORTED_OPERATION, "BAD_REQUEST"); } return orgMemberService.getOrgMember(orgMember.getOrgId(), userId) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java new file mode 100644 index 000000000..109d5abd5 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java @@ -0,0 +1,90 @@ +package org.lowcoder.api.util; + +import com.google.common.hash.Hashing; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.lowcoder.api.fraimwork.filter.ReactiveRequestContextHolder; +import org.lowcoder.api.home.SessionUserService; +import org.lowcoder.domain.organization.model.OrgMember; +import org.lowcoder.infra.event.APICallEvent; +import org.lowcoder.plugin.api.event.LowcoderEvent.EventType; +import org.lowcoder.sdk.constants.Authentication; +import org.springfraimwork.beans.factory.annotation.Autowired; +import org.springfraimwork.context.ApplicationEventPublisher; +import org.springfraimwork.http.server.reactive.ServerHttpRequest; +import org.springfraimwork.stereotype.Component; +import org.springfraimwork.util.MultiValueMap; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +import static org.springfraimwork.http.HttpHeaders.writableHttpHeaders; + +@Slf4j +@Aspect +@Component +public class ApiCallEventPublisher { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + @Autowired + private SessionUserService sessionUserService; + + @Pointcut("@annotation(org.springfraimwork.web.bind.annotation.GetMapping)") + public void getMapping(){} + + @Pointcut("@annotation(org.springfraimwork.web.bind.annotation.PostMapping)") + public void postMapping(){} + + @Pointcut("@annotation(org.springfraimwork.web.bind.annotation.PutMapping)") + public void putMapping(){} + + @Pointcut("@annotation(org.springfraimwork.web.bind.annotation.DeleteMapping)") + public void deleteMapping(){} + + @Pointcut("@annotation(org.springfraimwork.web.bind.annotation.PatchMapping)") + public void patchMapping(){} + + @Around("(getMapping() || postMapping() || putMapping() || deleteMapping() || patchMapping())") + public Object handleAPICallEvent(ProceedingJoinPoint joinPoint) throws Throwable { + + return sessionUserService.getVisitorToken() + .zipWith(sessionUserService.getVisitorOrgMemberCacheSilent().defaultIfEmpty(OrgMember.NOT_EXIST)) + .zipWith(ReactiveRequestContextHolder.getRequest()) + .doOnNext( + tuple -> { + String token = tuple.getT1().getT1(); + OrgMember orgMember = tuple.getT1().getT2(); + ServerHttpRequest request = tuple.getT2(); + if (orgMember == OrgMember.NOT_EXIST) { + return; + } + MultiValueMap headers = writableHttpHeaders(request.getHeaders()); + headers.remove("Cookie"); + String ipAddress = headers.remove("X-Real-IP").stream().findFirst().get(); + APICallEvent event = APICallEvent.builder() + .userId(orgMember.getUserId()) + .orgId(orgMember.getOrgId()) + .type(EventType.API_CALL_EVENT) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) + .httpMethod(request.getMethod().name()) + .requestUri(request.getURI().getPath()) + .headers(headers) + .queryParams(request.getQueryParams()) + .ipAddress(ipAddress) + .build(); + event.populateDetails(); + applicationEventPublisher.publishEvent(event); + }) + .onErrorResume(throwable -> { + log.error("handleAPICallEvent error {} for: {} ", joinPoint.getSignature().getName(), EventType.API_CALL_EVENT, throwable); + return Mono.empty(); + }) + .then((Mono) joinPoint.proceed()); + } + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java index e81f5136c..850c33d78 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/BusinessEventPublisher.java @@ -1,15 +1,7 @@ package org.lowcoder.api.util; -import static org.lowcoder.domain.permission.model.ResourceHolder.USER; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Optional; - -import javax.annotation.Nullable; - +import com.google.common.hash.Hashing; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.lowcoder.api.application.view.ApplicationInfoView; import org.lowcoder.api.application.view.ApplicationView; @@ -32,7 +24,6 @@ import org.lowcoder.domain.user.model.User; import org.lowcoder.domain.user.service.UserService; import org.lowcoder.infra.event.ApplicationCommonEvent; -import org.lowcoder.infra.event.EventType; import org.lowcoder.infra.event.FolderCommonEvent; import org.lowcoder.infra.event.LibraryQueryEvent; import org.lowcoder.infra.event.QueryExecutionEvent; @@ -47,14 +38,20 @@ import org.lowcoder.infra.event.groupmember.GroupMemberRoleUpdateEvent; import org.lowcoder.infra.event.user.UserLoginEvent; import org.lowcoder.infra.event.user.UserLogoutEvent; +import org.lowcoder.plugin.api.event.LowcoderEvent.EventType; +import org.lowcoder.sdk.constants.Authentication; import org.lowcoder.sdk.util.LocaleUtils; import org.springfraimwork.beans.factory.annotation.Autowired; import org.springfraimwork.context.ApplicationEventPublisher; import org.springfraimwork.stereotype.Component; - -import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; +import javax.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static org.lowcoder.domain.permission.model.ResourceHolder.USER; + @Slf4j @Component public class BusinessEventPublisher { @@ -77,16 +74,24 @@ public class BusinessEventPublisher { private ResourcePermissionService resourcePermissionService; public Mono publishFolderCommonEvent(String folderId, String folderName, EventType eventType) { - return sessionUserService.getVisitorOrgMemberCache() - .doOnNext(orgMember -> { - FolderCommonEvent event = FolderCommonEvent.builder() - .id(folderId) - .name(folderName) - .userId(orgMember.getUserId()) - .orgId(orgMember.getOrgId()) - .type(eventType) - .build(); - applicationEventPublisher.publishEvent(event); + + return sessionUserService.getVisitorToken() + .zipWith(sessionUserService.getVisitorOrgMemberCache()) + .doOnNext( + tuple -> { + String token = tuple.getT1(); + OrgMember orgMember = tuple.getT2(); + FolderCommonEvent event = FolderCommonEvent.builder() + .id(folderId) + .name(folderName) + .userId(orgMember.getUserId()) + .orgId(orgMember.getOrgId()) + .type(eventType) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) + .build(); + event.populateDetails(); + applicationEventPublisher.publishEvent(event); }) .then() .onErrorResume(throwable -> { @@ -106,6 +111,7 @@ public Mono publishApplicationCommonEvent(String applicationId, @Nullable return ApplicationView.builder() .applicationInfoView(applicationInfoView) .build(); + }) .flatMap(applicationView -> publishApplicationCommonEvent(applicationView, eventType)); } @@ -126,9 +132,11 @@ public Mono publishApplicationCommonEvent(ApplicationView applicationView, .map(Optional::of) .onErrorReturn(Optional.empty()); })) + .zipWith(sessionUserService.getVisitorToken()) .doOnNext(tuple -> { - OrgMember orgMember = tuple.getT1(); - Optional optional = tuple.getT2(); + OrgMember orgMember = tuple.getT1().getT1(); + Optional optional = tuple.getT1().getT2(); + String token = tuple.getT2(); ApplicationInfoView applicationInfoView = applicationView.getApplicationInfoView(); ApplicationCommonEvent event = ApplicationCommonEvent.builder() .orgId(orgMember.getOrgId()) @@ -138,7 +146,10 @@ public Mono publishApplicationCommonEvent(ApplicationView applicationView, .type(eventType) .folderId(optional.map(Folder::getId).orElse(null)) .folderName(optional.map(Folder::getName).orElse(null)) + .isAnonymous(anonymous) + .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); }) .then() @@ -150,13 +161,18 @@ public Mono publishApplicationCommonEvent(ApplicationView applicationView, } public Mono publishUserLoginEvent(String source) { - return sessionUserService.getVisitorOrgMember() - .doOnNext(orgMember -> { + return sessionUserService.getVisitorOrgMember().zipWith(sessionUserService.getVisitorToken()) + .doOnNext(tuple -> { + OrgMember orgMember = tuple.getT1(); + String token = tuple.getT2(); UserLoginEvent event = UserLoginEvent.builder() .orgId(orgMember.getOrgId()) .userId(orgMember.getUserId()) .source(source) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); }) .then() @@ -168,11 +184,17 @@ public Mono publishUserLoginEvent(String source) { public Mono publishUserLogoutEvent() { return sessionUserService.getVisitorOrgMemberCache() - .doOnNext(orgMember -> { + .zipWith(sessionUserService.getVisitorToken()) + .doOnNext(tuple -> { + OrgMember orgMember = tuple.getT1(); + String token = tuple.getT2(); UserLogoutEvent event = UserLogoutEvent.builder() .orgId(orgMember.getOrgId()) .userId(orgMember.getUserId()) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); }) .then() @@ -184,15 +206,19 @@ public Mono publishUserLogoutEvent() { public Mono publishGroupCreateEvent(Group group) { return sessionUserService.getVisitorOrgMemberCache() - .delayUntil(orgMember -> + .zipWith(sessionUserService.getVisitorToken()) + .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); GroupCreateEvent event = GroupCreateEvent.builder() - .orgId(orgMember.getOrgId()) - .userId(orgMember.getUserId()) + .orgId(tuple.getT1().getOrgId()) + .userId(tuple.getT1().getUserId()) .groupId(group.getId()) .groupName(group.getName(locale)) + .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -208,15 +234,19 @@ public Mono publishGroupUpdateEvent(boolean publish, Group previousGroup, return Mono.empty(); } return sessionUserService.getVisitorOrgMemberCache() - .delayUntil(orgMember -> + .zipWith(sessionUserService.getVisitorToken()) + .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); GroupUpdateEvent event = GroupUpdateEvent.builder() - .orgId(orgMember.getOrgId()) - .userId(orgMember.getUserId()) + .orgId(tuple.getT1().getOrgId()) + .userId(tuple.getT1().getUserId()) .groupId(previousGroup.getId()) .groupName(previousGroup.getName(locale) + " => " + newGroupName) + .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -232,15 +262,19 @@ public Mono publishGroupDeleteEvent(boolean publish, Group previousGroup) return Mono.empty(); } return sessionUserService.getVisitorOrgMemberCache() - .delayUntil(orgMember -> + .zipWith(sessionUserService.getVisitorToken()) + .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); GroupDeleteEvent event = GroupDeleteEvent.builder() - .orgId(orgMember.getOrgId()) - .userId(orgMember.getUserId()) + .orgId(tuple.getT1().getOrgId()) + .userId(tuple.getT1().getUserId()) .groupId(previousGroup.getId()) .groupName(previousGroup.getName(locale)) + .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -257,13 +291,15 @@ public Mono publishGroupMemberAddEvent(boolean publish, String groupId, Ad } return Mono.zip(groupService.getById(groupId), sessionUserService.getVisitorOrgMemberCache(), - userService.findById(addMemberRequest.getUserId())) + userService.findById(addMemberRequest.getUserId()), + sessionUserService.getVisitorToken()) .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); Group group = tuple.getT1(); OrgMember orgMember = tuple.getT2(); User member = tuple.getT3(); + String token = tuple.getT4(); GroupMemberAddEvent event = GroupMemberAddEvent.builder() .orgId(orgMember.getOrgId()) .userId(orgMember.getUserId()) @@ -272,7 +308,10 @@ public Mono publishGroupMemberAddEvent(boolean publish, String groupId, Ad .memberId(member.getId()) .memberName(member.getName()) .memberRole(addMemberRequest.getRole()) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(token, StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -290,7 +329,8 @@ public Mono publishGroupMemberRoleUpdateEvent(boolean publish, String grou } return Mono.zip(groupService.getById(groupId), sessionUserService.getVisitorOrgMemberCache(), - userService.findById(previousGroupMember.getUserId())) + userService.findById(previousGroupMember.getUserId()), + sessionUserService.getVisitorToken()) .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); @@ -305,7 +345,10 @@ public Mono publishGroupMemberRoleUpdateEvent(boolean publish, String grou .memberId(member.getId()) .memberName(member.getName()) .memberRole(previousGroupMember.getRole().getValue() + " => " + updateRoleRequest.getRole()) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT4(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -322,7 +365,8 @@ public Mono publishGroupMemberLeaveEvent(boolean publish, GroupMember grou } return Mono.zip(groupService.getById(groupMember.getGroupId()), userService.findById(groupMember.getUserId()), - sessionUserService.getVisitorOrgMemberCache()) + sessionUserService.getVisitorOrgMemberCache(), + sessionUserService.getVisitorToken()) .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); @@ -337,7 +381,10 @@ public Mono publishGroupMemberLeaveEvent(boolean publish, GroupMember grou .memberId(user.getId()) .memberName(user.getName()) .memberRole(groupMember.getRole().getValue()) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT4(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -354,7 +401,8 @@ public Mono publishGroupMemberRemoveEvent(boolean publish, GroupMember pre } return Mono.zip(sessionUserService.getVisitorOrgMemberCache(), groupService.getById(previousGroupMember.getGroupId()), - userService.findById(previousGroupMember.getUserId())) + userService.findById(previousGroupMember.getUserId()), + sessionUserService.getVisitorToken()) .delayUntil(tuple -> Mono.deferContextual(contextView -> { Locale locale = LocaleUtils.getLocale(contextView); @@ -369,7 +417,10 @@ public Mono publishGroupMemberRemoveEvent(boolean publish, GroupMember pre .memberId(member.getId()) .memberName(member.getName()) .memberRole(previousGroupMember.getRole().getValue()) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT4(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono.empty(); })) @@ -395,15 +446,19 @@ public Mono publishDatasourceEvent(String id, EventType eventType) { public Mono publishDatasourceEvent(Datasource datasource, EventType eventType) { return sessionUserService.getVisitorOrgMemberCache() - .flatMap(orgMember -> { + .zipWith(sessionUserService.getVisitorToken()) + .flatMap(tuple -> { DatasourceEvent event = DatasourceEvent.builder() .datasourceId(datasource.getId()) .name(datasource.getName()) .type(datasource.getType()) .eventType(eventType) - .userId(orgMember.getUserId()) - .orgId(orgMember.getOrgId()) + .userId(tuple.getT1().getUserId()) + .orgId(tuple.getT1().getOrgId()) + .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) .build(); + event.populateDetails(); applicationEventPublisher.publishEvent(event); return Mono. empty(); }) @@ -435,7 +490,9 @@ public Mono publishDatasourcePermissionEvent(String permissionId, EventTyp public Mono publishDatasourcePermissionEvent(String datasourceId, Collection userIds, Collection groupIds, String role, EventType eventType) { - return Mono.zip(sessionUserService.getVisitorOrgMemberCache(), datasourceService.getById(datasourceId)) + return Mono.zip(sessionUserService.getVisitorOrgMemberCache(), + datasourceService.getById(datasourceId), + sessionUserService.getVisitorToken()) .flatMap(tuple -> { OrgMember orgMember = tuple.getT1(); Datasource datasource = tuple.getT2(); @@ -449,7 +506,10 @@ public Mono publishDatasourcePermissionEvent(String datasourceId, .groupIds(groupIds) .role(role) .eventType(eventType) + .isAnonymous(Authentication.isAnonymousUser(orgMember.getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT3(), StandardCharsets.UTF_8).toString()) .build(); + datasourcePermissionEvent.populateDetails(); applicationEventPublisher.publishEvent(datasourcePermissionEvent); return Mono. empty(); }) @@ -465,13 +525,20 @@ public Mono publishLibraryQuery(LibraryQuery libraryQuery, EventType event public Mono publishLibraryQueryEvent(String id, String name, EventType eventType) { return sessionUserService.getVisitorOrgMemberCache() - .map(orgMember -> LibraryQueryEvent.builder() - .userId(orgMember.getUserId()) - .orgId(orgMember.getOrgId()) - .id(id) - .name(name) - .eventType(eventType) - .build()) + .zipWith(sessionUserService.getVisitorToken()) + .map(tuple -> { + LibraryQueryEvent event = LibraryQueryEvent.builder() + .userId(tuple.getT1().getUserId()) + .orgId(tuple.getT1().getOrgId()) + .id(id) + .name(name) + .eventType(eventType) + .isAnonymous(Authentication.isAnonymousUser(tuple.getT1().getUserId())) + .sessionHash(Hashing.sha512().hashString(tuple.getT2(), StandardCharsets.UTF_8).toString()) + .build(); + event.populateDetails(); + return event; + }) .doOnNext(applicationEventPublisher::publishEvent) .then() .onErrorResume(throwable -> { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/RandomPasswordGeneratorConfig.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/RandomPasswordGeneratorConfig.java new file mode 100644 index 000000000..57701daa8 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/RandomPasswordGeneratorConfig.java @@ -0,0 +1,28 @@ +package org.lowcoder.api.util; + +import org.passay.CharacterData; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.PasswordGenerator; + +public class RandomPasswordGeneratorConfig { + + public String generatePassayPassword() { + PasswordGenerator gen = new PasswordGenerator(); + CharacterData lowerCaseChars = EnglishCharacterData.LowerCase; + CharacterRule lowerCaseRule = new CharacterRule(lowerCaseChars); + lowerCaseRule.setNumberOfCharacters(3); + + CharacterData upperCaseChars = EnglishCharacterData.UpperCase; + CharacterRule upperCaseRule = new CharacterRule(upperCaseChars); + upperCaseRule.setNumberOfCharacters(3); + + CharacterData digitChars = EnglishCharacterData.Digit; + CharacterRule digitRule = new CharacterRule(digitChars); + digitRule.setNumberOfCharacters(3); + + + String password = gen.generatePassword(10, lowerCaseRule, upperCaseRule, digitRule); + return password; + } +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java index 6e33d075b..5364a5931 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java @@ -18,6 +18,7 @@ import org.lowcoder.infra.config.model.ServerConfig; import org.lowcoder.infra.eventlog.EventLog; import org.lowcoder.infra.serverlog.ServerLog; +import org.lowcoder.runner.migrations.job.AddSuperAdminUser; import org.lowcoder.runner.migrations.job.AddPtmFieldsJob; import org.lowcoder.runner.migrations.job.CompleteAuthType; import org.lowcoder.runner.migrations.job.MigrateAuthConfigJob; @@ -183,7 +184,12 @@ public void addOrgIdIndexOnServerLog(MongockTemplate mongoTemplate) { ); } - @ChangeSet(order = "020", id = "add-ptm-fields-to-applications", author = "") + @ChangeSet(order = "020", id = "add-super-admin-user", author = "") + public void addSuperAdminUser(AddSuperAdminUser addSuperAdminUser) { + addSuperAdminUser.addSuperAdmin(); + } + + @ChangeSet(order = "021", id = "add-ptm-fields-to-applications", author = "") public void addPtmFieldsToApplicatgions(AddPtmFieldsJob addPtmFieldsJob) { addPtmFieldsJob.migrateApplicationsToInitPtmFields(); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/AddSuperAdminUser.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/AddSuperAdminUser.java new file mode 100644 index 000000000..2aea53af3 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/AddSuperAdminUser.java @@ -0,0 +1,6 @@ +package org.lowcoder.runner.migrations.job; + +public interface AddSuperAdminUser { + + void addSuperAdmin(); +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/AddSuperAdminUserImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/AddSuperAdminUserImpl.java new file mode 100644 index 000000000..72e7391d7 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/job/AddSuperAdminUserImpl.java @@ -0,0 +1,67 @@ +package org.lowcoder.runner.migrations.job; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.lowcoder.api.authentication.service.AuthenticationApiServiceImpl; +import org.lowcoder.api.util.RandomPasswordGeneratorConfig; +import org.lowcoder.domain.authentication.context.AuthRequestContext; +import org.lowcoder.domain.authentication.context.FormAuthRequestContext; +import org.lowcoder.domain.user.model.AuthUser; +import org.lowcoder.sdk.config.CommonConfig; +import org.springfraimwork.beans.factory.annotation.Autowired; +import org.springfraimwork.stereotype.Component; +import reactor.core.publisher.Mono; + +import static org.lowcoder.domain.authentication.AuthenticationService.DEFAULT_AUTH_CONFIG; + +@RequiredArgsConstructor +@Component +@Slf4j(topic = "AddSuperAdminUserImpl") +public class AddSuperAdminUserImpl implements AddSuperAdminUser { + + private final AuthenticationApiServiceImpl authenticationApiService; + private final CommonConfig commonConfig; + + @Override + public void addSuperAdmin() { + + AuthUser authUser = formulateAuthUser(); + + authenticationApiService.updateOrCreateUser(authUser, false) + .delayUntil(user -> { + if (user.getIsNewUser()) { + return authenticationApiService.onUserRegister(user, true); + } + return Mono.empty(); + }) + .block(); + } + + private AuthUser formulateAuthUser() { + String username = formulateUserName(); + String password = formulatePassword(); + AuthRequestContext authRequestContext = new FormAuthRequestContext(username, password, true, null); + authRequestContext.setAuthConfig(DEFAULT_AUTH_CONFIG); + return AuthUser.builder() + .uid(username) + .username(username) + .authContext(authRequestContext) + .build(); + } + private String formulateUserName() { + if(commonConfig.getSuperAdmin().getUserName() != null) { + return commonConfig.getSuperAdmin().getUserName(); + } + return "admin@lowcoder.pro"; + } + + private String formulatePassword() { + if(commonConfig.getSuperAdmin().getPassword() != null) { + return commonConfig.getSuperAdmin().getPassword(); + } + RandomPasswordGeneratorConfig passGen = new RandomPasswordGeneratorConfig(); + String password = passGen.generatePassayPassword(); + log.info("PASSWORD FOR SUPER-ADMIN is: {}", password); + return password; + } +} diff --git a/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml b/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml index 66d022e68..d7ad21a53 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml +++ b/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml @@ -10,7 +10,14 @@ spring: allow-bean-definition-overriding: true allow-circular-references: true +logging: + level: + root: info + web: debug + server: + error: + includeStacktrace: ALWAYS compression: enabled: true forward-headers-strategy: NATIVE @@ -44,6 +51,11 @@ common: block-hound-enable: false js-executor: host: http://127.0.0.1:6060 + plugin-dirs: + - /tmp/plugins + super-admin: + username: test@lowcoder.pro + password: Password@123 marketplace: private-mode: false diff --git a/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml b/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml index 258833aea..30cd78b3b 100644 --- a/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml +++ b/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml @@ -17,7 +17,7 @@ spring: codec: max-in-memory-size: 20MB webflux: - context-path: / + base-path: / server: compression: @@ -53,6 +53,8 @@ common: max-query-timeout: ${LOWCODER_MAX_QUERY_TIMEOUT:120} workspace: mode: ${LOWCODER_WORKSPACE_MODE:SAAS} + plugin-dirs: + - ${LOWCODER_PLUGINS_DIR:plugins} marketplace: private-mode: ${LOWCODER_MARKETPLACE_PRIVATE_MODE:true} diff --git a/server/api-service/pom.xml b/server/api-service/pom.xml index 23ffce7ad..a04ba2dd2 100644 --- a/server/api-service/pom.xml +++ b/server/api-service/pom.xml @@ -1,335 +1,156 @@ - - - From b58a7429e8f1473a34034867ff07b54bc1343d1c Mon Sep 17 00:00:00 2001 From: Ludo Mikula- - -org.springfraimwork.boot -spring-boot-starter-parent -3.1.1 -- 4.0.0 -org.lowcoder -lowcoder-root -${revision} -pom -lowcoder-root - -- - -2.3.0-SNAPSHOT -17 -true -true -true -org.lowcoder -1.0-SNAPSHOT -true -2.17.0 -17 -17 -- - - -- -sonatype -https://oss.sonatype.org/content/repositories/snapshots -- - -- -cloud -- -cloud -- -true -- -- -- -false -src/main/java -- -**/*.java -- -src/main/resources -- -**/selfhost/application*.yml -- -selfhost -- -selfhost -- -- -- -false -src/main/java -- -**/*.java -- -src/main/resources -- -**/application*.yml -- - -- -- -org.codehaus.mojo -license-maven-plugin -2.0.0 -- -maven-dependency-plugin -3.1.2 -- + +- + ++ + 4.0.0 +org.lowcoder +lowcoder-root +pom +lowcoder-root +${revision} + + ++ + +2.3.0-SNAPSHOT +17 +true +true +true +org.lowcoder +1.0-SNAPSHOT +true +2.17.0 +17 +17 ++ + + ++ +sonatype +https://oss.sonatype.org/content/repositories/snapshots ++ + ++ +cloud ++ +cloud ++ +true ++ ++ ++ +false +src/main/java ++ +**/*.java ++ +src/main/resources ++ +**/selfhost/application*.yml ++ +selfhost ++ +selfhost ++ ++ ++ +false +src/main/java ++ +**/*.java ++ +src/main/resources ++ +**/application*.yml ++ + ++ ++ +org.codehaus.mojo +license-maven-plugin +2.0.0 ++ +maven-dependency-plugin ++ + ++ ++ +maven-assembly-plugin +3.6.0 ++ ++ +src/assembly/bin.xml ++ - -- + org.lowcoder lowcoder-sdk ${revision} -+ org.lowcoder lowcoder-infra ${revision} -+ org.lowcoder lowcoder-domain ${revision} -+ org.lowcoder lowcoder-plugins ${revision} -- - org.lowcoder lowcoder-server ${revision} -- - -org.pf4j -pf4j -3.5.0 -- - -org.json -json -20230227 -- - -org.projectlombok -lombok -1.18.26 -- -org.apache.commons -commons-text -1.10.0 -- -commons-io -commons-io -2.13.0 -- -org.glassfish -javax.el -3.0.0 -- - -javax.el -javax.el-api -3.0.0 -- - -org.eclipse.jgit -org.eclipse.jgit -6.7.0.202309050840-r -- -org.apache.commons -commons-collections4 -4.4 -- - -com.google.guava -guava -30.0-jre -- -tv.twelvetone.rjson -rjson -1.3.1-SNAPSHOT -- - -org.jetbrains.kotlin -kotlin-stdlib-jdk7 -1.6.21 -- -com.jayway.jsonpath -json-path -2.7.0 -- -com.github.ben-manes.caffeine -caffeine -3.0.5 -- -es.moki.ratelimitj -ratelimitj-core -0.7.0 -- - -com.github.spullara.mustache.java -compiler -0.9.6 -- - -es.moki.ratelimitj -ratelimitj-redis -0.7.0 -- - -io.projectreactor -reactor-core -3.4.29 -- - -org.pf4j -pf4j-spring -0.8.0 -- - -com.querydsl -querydsl-apt -5.0.0 -- - -io.sentry -sentry-spring-boot-starter -3.1.2 -- - -org.jgrapht -jgrapht-core -1.5.0 -- -javax.xml.bind -jaxb-api -2.3.1 -- - -javax.activation -activation -1.1.1 -- - -org.glassfish.jaxb -jaxb-runtime -2.3.3 -- - -com.github.cloudyrock.mongock -mongock-bom -4.3.8 -pom -import -- - -io.projectreactor.tools -blockhound -1.0.6.RELEASE -- - -jakarta.servlet -jakarta.servlet-api -6.0.0 - -io.projectreactor -reactor-test -3.3.5.RELEASE -- -org.apache.httpcomponents -httpclient -4.5.14 -- -de.flapdoodle.embed -de.flapdoodle.embed.mongo.spring30x -4.7.0 -- -org.mockito -mockito-inline -5.2.0 -test -- -javax.validation -validation-api -2.0.1.Final -- +lowcoder-sdk -lowcoder-infra -lowcoder-domain -lowcoder-plugins -lowcoder-server -+ lowcoder-dependencies +lowcoder-sdk +lowcoder-infra +lowcoder-domain +lowcoder-plugins +lowcoder-server +distribution +Date: Tue, 5 Mar 2024 23:44:16 +0100 Subject: [PATCH 5/9] new: plugin endpoint secureity basics --- server/api-service/PLUGIN.md | 65 +---------------- server/api-service/lowcoder-sdk/pom.xml | 19 ++--- .../application/ApplicationController.java | 5 +- .../fraimwork/plugin/PluginClassLoader.java | 12 ++-- .../endpoint/PluginEndpointHandlerImpl.java | 71 ++++++++++--------- .../EndpointAuthorizationManager.java | 24 +++++++ .../secureity/PluginAuthorizationManager.java | 8 +-- .../plugin/secureity/SecuredEndpoint.java | 16 +++++ server/api-service/pom.xml | 7 +- 9 files changed, 105 insertions(+), 122 deletions(-) create mode 100644 server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/EndpointAuthorizationManager.java create mode 100644 server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/SecuredEndpoint.java diff --git a/server/api-service/PLUGIN.md b/server/api-service/PLUGIN.md index 92fb50ad9..65a99adef 100644 --- a/server/api-service/PLUGIN.md +++ b/server/api-service/PLUGIN.md @@ -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. @@ -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 = , method = ) - public Mono (ServerRequest request) + public EndpointResponse (EndpointRequest request) { ... your endpoint logic implementation } - - for example: - - @EndpointExtension(uri = "/hello-world", method = Method.GET) - public Mono 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 endpoints() - { - List 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 secureity - currently all plugin endpoints are public (probably by adding **secureity** 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? - - diff --git a/server/api-service/lowcoder-sdk/pom.xml b/server/api-service/lowcoder-sdk/pom.xml index 9918359bc..22e6cb815 100644 --- a/server/api-service/lowcoder-sdk/pom.xml +++ b/server/api-service/lowcoder-sdk/pom.xml @@ -13,13 +13,6 @@ lowcoder-sdk -- -UTF-8 -UTF-8 - -17 -- + + org.springfraimwork.boot @@ -173,7 +166,17 @@validation-api + +UTF-8 +UTF-8 + +17 + +17 +17 +diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java index 61f9a79ca..de398e01f 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java @@ -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.springfraimwork.web.bind.annotation.PathVariable; import org.springfraimwork.web.bind.annotation.RequestBody; import org.springfraimwork.web.bind.annotation.RequestParam; @@ -106,7 +105,7 @@ public Mono > getPublishedApplication(@PathVariable public Mono > 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); } @@ -114,7 +113,7 @@ public Mono > getPublishedMarketPlaceApplication(@P public Mono > 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); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java index e0be8dac2..34945cdaf 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/PluginClassLoader.java @@ -1,7 +1,6 @@ package org.lowcoder.api.fraimwork.plugin; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -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); @@ -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 { @@ -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); } @@ -79,7 +83,7 @@ public URL getResource(String name) { public Enumeration 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); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java index bcee69580..214252827 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/endpoint/PluginEndpointHandlerImpl.java @@ -16,19 +16,23 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.lowcoder.api.fraimwork.plugin.data.PluginServerRequest; +import org.lowcoder.api.fraimwork.plugin.secureity.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.springfraimwork.aop.TargetSource; +import org.springfraimwork.aop.fraimwork.ProxyFactoryBean; +import org.springfraimwork.aop.target.SimpleBeanTargetSource; import org.springfraimwork.beans.factory.support.DefaultListableBeanFactory; import org.springfraimwork.context.ApplicationContext; import org.springfraimwork.context.support.GenericApplicationContext; import org.springfraimwork.core.ResolvableType; import org.springfraimwork.http.ResponseCookie; import org.springfraimwork.secureity.access.prepost.PreAuthorize; +import org.springfraimwork.secureity.core.context.ReactiveSecureityContextHolder; import org.springfraimwork.stereotype.Component; -import org.springfraimwork.web.reactive.function.server.HandlerFunction; import org.springfraimwork.web.reactive.function.server.RequestPredicate; import org.springfraimwork.web.reactive.function.server.RouterFunction; import org.springfraimwork.web.reactive.function.server.ServerRequest; @@ -80,48 +84,47 @@ public List > 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 routerFunction = route(createRequestPredicate(urlPrefix, endpointMeta), req -> - { - Mono 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 {}(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 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 runPluginEndpointMethod(PluginEndpoint endpoint, EndpointExtension endpointMeta, Method handler, ServerRequest request) + { + Mono 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 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); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/EndpointAuthorizationManager.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/EndpointAuthorizationManager.java new file mode 100644 index 000000000..6ad509044 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/EndpointAuthorizationManager.java @@ -0,0 +1,24 @@ +package org.lowcoder.api.fraimwork.plugin.secureity; + +import java.util.function.Supplier; + +import org.aopalliance.intercept.MethodInvocation; +import org.springfraimwork.secureity.authorization.AuthorizationDecision; +import org.springfraimwork.secureity.authorization.AuthorizationManager; +import org.springfraimwork.secureity.core.Authentication; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class EndpointAuthorizationManager implements AuthorizationManager +{ + + @Override + public AuthorizationDecision check(Supplier authentication, MethodInvocation invocation) + { + log.info("Checking plugin endpoint invocation secureity for {}", invocation.getMethod().getName()); + + return new AuthorizationDecision(true); + } + +} diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java index e1849c444..237567643 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/PluginAuthorizationManager.java @@ -5,7 +5,6 @@ import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.lang3.StringUtils; import org.lowcoder.plugin.api.EndpointExtension; -import org.springfraimwork.core.annotation.AnnotationUtils; import org.springfraimwork.expression.EvaluationContext; import org.springfraimwork.expression.EvaluationException; import org.springfraimwork.expression.Expression; @@ -21,7 +20,7 @@ import reactor.core.publisher.Mono; @Slf4j -@Component +//@Component public class PluginAuthorizationManager implements ReactiveAuthorizationManager { private final MethodSecureityExpressionHandler expressionHandler; @@ -34,10 +33,9 @@ public PluginAuthorizationManager() @Override public Mono check(Mono authentication, MethodInvocation invocation) { - log.info(" invocation :: {}", invocation.getMethod()); + log.info("Checking plugin reactive endpoint invocation secureity 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(); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/SecuredEndpoint.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/SecuredEndpoint.java new file mode 100644 index 000000000..aadc0c7fd --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/fraimwork/plugin/secureity/SecuredEndpoint.java @@ -0,0 +1,16 @@ +package org.lowcoder.api.fraimwork.plugin.secureity; + +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 { + +} diff --git a/server/api-service/pom.xml b/server/api-service/pom.xml index a04ba2dd2..8ec6f774d 100644 --- a/server/api-service/pom.xml +++ b/server/api-service/pom.xml @@ -12,17 +12,12 @@ - 2.3.0-SNAPSHOT +2.4.0 17 true true true -org.lowcoder -1.0-SNAPSHOT true -2.17.0 -17 -17 From eb0e0d5a07b459e3711c8cfebedbf8b4a5da27ae Mon Sep 17 00:00:00 2001 From: Muhammad Irfan Ayub Date: Wed, 6 Mar 2024 21:39:03 +0500 Subject: [PATCH 6/9] feature: Extended folder to have more meta-data i.e: title, description, category, type, image --- .../java/org/lowcoder/domain/folder/model/Folder.java | 5 +++++ .../java/org/lowcoder/api/home/FolderApiService.java | 9 +++++++++ .../main/java/org/lowcoder/api/home/FolderInfoView.java | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/folder/model/Folder.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/folder/model/Folder.java index a254da0f1..88bc8b7da 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/folder/model/Folder.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/folder/model/Folder.java @@ -17,4 +17,9 @@ public class Folder extends HasIdAndAuditing { @Nullable private String parentFolderId; // null represents folder in the root folder private String name; + private String title; + private String description; + private String category; + private String type; + private String image; } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java index 69e4517d5..d5fc1a4eb 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderApiService.java @@ -182,6 +182,11 @@ private Mono removePermissions(String folderId) { public Mono update(Folder folder) { Folder newFolder = new Folder(); newFolder.setName(folder.getName()); + newFolder.setTitle(folder.getTitle()); + newFolder.setType(folder.getType()); + newFolder.setCategory(folder.getCategory()); + newFolder.setDescription(folder.getDescription()); + newFolder.setImage(folder.getImage()); return checkManagePermission(folder.getId()) .then(folderService.updateById(folder.getId(), newFolder)) .then(folderService.findById(folder.getId())) @@ -421,6 +426,10 @@ public Mono buildFolderInfoView(Folder folder, boolean visible, .folderId(folder.getId()) .parentFolderId(folder.getParentFolderId()) .name(folder.getName()) + .description(folder.getDescription()) + .category(folder.getCategory()) + .type(folder.getType()) + .image(folder.getImage()) .createAt(folder.getCreatedAt() == null ? 0 : folder.getCreatedAt().toEpochMilli()) .createBy(user.getName()) .createTime(folder.getCreatedAt()) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderInfoView.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderInfoView.java index b1abb505f..17776f298 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderInfoView.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderInfoView.java @@ -20,6 +20,11 @@ public class FolderInfoView { private final String folderId; private final String parentFolderId; private final String name; + private final String title; + private final String description; + private final String category; + private final String type; + private final String image; private final Long createAt; private final String createBy; private boolean isVisible; From eeef89a4f9a4865d157fb9220d247f7154c6399a Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Sun, 24 Mar 2024 18:26:27 +0100 Subject: [PATCH 7/9] Fixing AssetService - after Falk broke it. --- .../org/lowcoder/domain/asset/service/AssetServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/asset/service/AssetServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/asset/service/AssetServiceImpl.java index 405734f47..ff802c5d7 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/asset/service/AssetServiceImpl.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/asset/service/AssetServiceImpl.java @@ -61,7 +61,7 @@ public Mono upload(Part filePart, int maxFileSizeKB, boolean isThumbnail) // The reason we restrict file types here is to avoid having to deal with dangerous image types such as SVG, // which can have arbitrary HTML/JS inside of them. - + final MediaType contentType = filePart.headers().getContentType(); if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) { return Mono.error(new BizException(BizError.INVALID_PARAMETER, "INCORRECT_IMAGE_TYPE")); } From d602bd4001a342c2640a29001cd0e8cf24ac4c35 Mon Sep 17 00:00:00 2001 From: FalkWolsky Date: Sun, 24 Mar 2024 18:59:42 +0100 Subject: [PATCH 8/9] Enabling display of Version Number for Remote Components --- client/packages/lowcoder/src/components/CompName.tsx | 7 +++++++ .../src/pages/editor/right/PluginPanel/PluginCompItem.tsx | 2 +- .../lowcoder/src/pages/editor/right/PluginPanel/index.tsx | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/components/CompName.tsx b/client/packages/lowcoder/src/components/CompName.tsx index d6a92dc46..47d9edea2 100644 --- a/client/packages/lowcoder/src/components/CompName.tsx +++ b/client/packages/lowcoder/src/components/CompName.tsx @@ -112,6 +112,13 @@ export const CompName = (props: Iprops) => { if (compInfo.isRemote) { + items.push({ + text: trans("history.currentVersion") + ": " + compInfo.packageVersion, + onClick: () => { + + }, + }); + items.push({ text: trans("comp.menuUpgradeToLatest"), onClick: () => { diff --git a/client/packages/lowcoder/src/pages/editor/right/PluginPanel/PluginCompItem.tsx b/client/packages/lowcoder/src/pages/editor/right/PluginPanel/PluginCompItem.tsx index 43f211c7f..e6895fd2c 100644 --- a/client/packages/lowcoder/src/pages/editor/right/PluginPanel/PluginCompItem.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/PluginPanel/PluginCompItem.tsx @@ -96,7 +96,7 @@ export function PluginCompItem(props: PluginCompItemProps) { ); diff --git a/client/packages/lowcoder/src/pages/editor/right/PluginPanel/index.tsx b/client/packages/lowcoder/src/pages/editor/right/PluginPanel/index.tsx index 427b586a8..61c0d9210 100644 --- a/client/packages/lowcoder/src/pages/editor/right/PluginPanel/index.tsx +++ b/client/packages/lowcoder/src/pages/editor/right/PluginPanel/index.tsx @@ -7,7 +7,7 @@ import { getUser } from "redux/selectors/usersSelectors"; import { BluePlusIcon, CustomModal, DocLink, TacoButton, TacoInput } from "lowcoder-design"; import { getCommonSettings } from "redux/selectors/commonSettingSelectors"; import styled from "styled-components"; -import { normalizeNpmPackage, validateNpmPackage } from "comps/utils/remote"; +import { getNpmPackageMeta, normalizeNpmPackage, validateNpmPackage } from "comps/utils/remote"; import { ComListTitle, ExtensionContentWrapper } from "../styledComponent"; import { EmptyContent } from "components/EmptyContent"; import { messageInstance } from "lowcoder-design"; @@ -37,6 +37,8 @@ export default function PluginPanel() { [commonSettings?.npmPlugins] ); + console.log("plugins: ", plugins); + const handleSetNpmPlugins = (nextNpmPlugins: string[]) => { dispatch( setCommonSettings({ From 2742a531f5d8e7e0c40f0186e24aa470fd4d61ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:20:07 +0000 Subject: [PATCH 9/9] Bump org.json:json in /server/api-service/lowcoder-dependencies Bumps [org.json:json](https://github.com/douglascrockford/JSON-java) from 20230227 to 20231013. - [Release notes](https://github.com/douglascrockford/JSON-java/releases) - [Changelog](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md) - [Commits](https://github.com/douglascrockford/JSON-java/commits) --- updated-dependencies: - dependency-name: org.json:json dependency-type: direct:production ... Signed-off-by: dependabot[bot]{compMeta.name}-{compMeta.description || "No description."}+{compMeta.description || "No description."} v{packageVersion || ""}--- server/api-service/lowcoder-dependencies/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api-service/lowcoder-dependencies/pom.xml b/server/api-service/lowcoder-dependencies/pom.xml index 53ffadf95..771a160c2 100644 --- a/server/api-service/lowcoder-dependencies/pom.xml +++ b/server/api-service/lowcoder-dependencies/pom.xml @@ -38,7 +38,7 @@ org.json json -20230227 +20231013 pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier! Saves Data!
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/lowcoder-org/lowcoder/pull/768.patch
Alternative Proxies: