diff --git a/client/packages/lowcoder-design/src/components/ScrollBar.tsx b/client/packages/lowcoder-design/src/components/ScrollBar.tsx index b900b292a..7e589faed 100644 --- a/client/packages/lowcoder-design/src/components/ScrollBar.tsx +++ b/client/packages/lowcoder-design/src/components/ScrollBar.tsx @@ -58,18 +58,15 @@ interface IProps { hideScrollbar?: boolean; } -export const ScrollBar = ({ height = "100%", className, children, style, scrollableNodeProps, hideScrollbar, ...otherProps }: IProps) => { +export const ScrollBar = ({ height = "100%", className, children, style, scrollableNodeProps, hideScrollbar = false, ...otherProps }: IProps) => { // You can now use the style prop directly or pass it to SimpleBar const combinedStyle = { ...style, height }; // Example of combining height with passed style - return (hideScrollbar ?? false) ? ( + return hideScrollbar ? ( - - {children} - + {children} - ) - : ( + ) : ( {children} diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index 838a9665c..b9837726f 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -111,21 +111,16 @@ class AppIndex extends React.Component { {{this.props.brandName}} {} - - {isLowCoderDomain && ( - <> - {/* setting Meta Attributes to be able for embedding via iframely */} - - - - - - - {/* embedding analytics of Cleabits */} - - - )} - + {isLowCoderDomain && [ + // Adding Support for iframely to be able to embedd the component explorer in the docu + , + , + , + , + , + // adding Clearbit Support for Analytics + + ]} diff --git a/client/packages/lowcoder/src/components/CompName.tsx b/client/packages/lowcoder/src/components/CompName.tsx index d6a92dc46..78900c80e 100644 --- a/client/packages/lowcoder/src/components/CompName.tsx +++ b/client/packages/lowcoder/src/components/CompName.tsx @@ -83,6 +83,7 @@ export const CompName = (props: Iprops) => { const items: EditPopoverItemType[] = []; + // Falk: TODO - Implement upgrade for individual Version functionality const handleUpgrade = async () => { if (upgrading) { return; @@ -112,6 +113,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/comps/comps/allComp.test.tsx b/client/packages/lowcoder/src/comps/comps/allComp.test.tsx index 44292f3d7..aa11137f7 100644 --- a/client/packages/lowcoder/src/comps/comps/allComp.test.tsx +++ b/client/packages/lowcoder/src/comps/comps/allComp.test.tsx @@ -72,7 +72,7 @@ Object.keys(QueryMap).forEach((key) => { function logDiff(comp: any, newComp: any, prefix?: string) { if (_.isNil(comp.children)) { - console.info(`diff. comp.${prefix}: `, comp, ` newComp.${prefix}: `, newComp); + // console.info(`diff. comp.${prefix}: `, comp, ` newComp.${prefix}: `, newComp); return; } Object.keys(comp.children).forEach((key) => { diff --git a/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx b/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx index 8234e4231..f5f720569 100644 --- a/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx +++ b/client/packages/lowcoder/src/comps/comps/listViewComp/listView.tsx @@ -215,17 +215,11 @@ export function ListView(props: Props) { - {scrollbars ? ( - - <>{ { if (height) setListHeight(height); }} observerOptions={{ box: "border-box" }} > -
{renders}
-
} -
- ) : ( + <>{ { if (height) setListHeight(height); }} observerOptions={{ box: "border-box" }} >
{renders}
} - )} +
diff --git a/client/packages/lowcoder/src/comps/comps/moduleComp/moduleComp.tsx b/client/packages/lowcoder/src/comps/comps/moduleComp/moduleComp.tsx index f48918f30..b20f3c414 100644 --- a/client/packages/lowcoder/src/comps/comps/moduleComp/moduleComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/moduleComp/moduleComp.tsx @@ -505,6 +505,7 @@ class ModuleTmpComp extends ModuleCompBase { const ModuleCompWithView = withViewFn(ModuleTmpComp, (comp) => { const appId = comp.children.appId.getView(); const error = comp.children.error.getView(); + const scrollbars = comp.children.scrollbars.getView(); const moduleExternalState: ExternalEditorContextState = useMemo( () => ({ @@ -530,7 +531,7 @@ const ModuleCompWithView = withViewFn(ModuleTmpComp, (comp) => { if (comp.moduleRootComp && comp.isReady) { content = ( - + {comp.moduleRootComp.getView()} diff --git a/client/packages/lowcoder/src/comps/comps/remoteComp/loaders.tsx b/client/packages/lowcoder/src/comps/comps/remoteComp/loaders.tsx index 93011ee73..4353f9fbb 100644 --- a/client/packages/lowcoder/src/comps/comps/remoteComp/loaders.tsx +++ b/client/packages/lowcoder/src/comps/comps/remoteComp/loaders.tsx @@ -10,13 +10,17 @@ import { async function npmLoader( remoteInfo: RemoteCompInfo ): Promise { - const { packageName, packageVersion = "latest", compName } = remoteInfo; + + console.log("remoteInfo: ", remoteInfo); + + // Falk: removed "packageVersion = "latest" as default value fir packageVersion - to ensure no automatic version jumping. + + const { packageName, packageVersion, compName } = remoteInfo; const entry = `${NPM_PLUGIN_ASSETS_BASE_URL}/${packageName}@${packageVersion}/index.js`; // const entry = `../../../../../public/package/index.js`; // console.log("Entry", entry); try { const module = await import(/* webpackIgnore: true */ entry); - // console.log("Entry 1", module); const comp = module.default?.[compName]; if (!comp) { throw new Error(trans("npm.compNotFound", { compName })); diff --git a/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx b/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx index 1bdc2ea4e..4d7c189c4 100644 --- a/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tabs/tabbedContainerComp.tsx @@ -3,7 +3,7 @@ import { JSONObject, JSONValue } from "util/jsonTypes"; import { CompAction, CompActionTypes, deleteCompAction, wrapChildAction } from "lowcoder-core"; import { DispatchType, RecordConstructorToView, wrapDispatch } from "lowcoder-core"; import { AutoHeightControl } from "comps/controls/autoHeightControl"; -import { BooleanStateControl, booleanExposingStateControl, stringExposingStateControl } from "comps/controls/codeStateControl"; +import { stringExposingStateControl } from "comps/controls/codeStateControl"; import { eventHandlerControl } from "comps/controls/eventHandlerControl"; import { TabsOptionControl } from "comps/controls/optionsControl"; import { styleControl } from "comps/controls/styleControl"; @@ -12,7 +12,7 @@ import { sameTypeMap, UICompBuilder, withDefault } from "comps/generators"; import { addMapChildAction } from "comps/generators/sameTypeMap"; import { NameConfig, NameConfigHidden, withExposingConfigs } from "comps/generators/withExposing"; import { NameGenerator } from "comps/utils"; -import { ControlNode, Section, sectionNames } from "lowcoder-design"; +import { ScrollBar, Section, sectionNames } from "lowcoder-design"; import { HintPlaceHolder } from "lowcoder-design"; import _ from "lodash"; import React, { useCallback, useContext } from "react"; @@ -33,6 +33,7 @@ import { DisabledContext } from "comps/generators/uiCompBuilder"; import { EditorContext } from "comps/editorState"; import { checkIsMobile } from "util/commonUtils"; import { messageInstance } from "lowcoder-design"; +import { BoolControl } from "comps/controls/boolControl"; const EVENT_OPTIONS = [ { @@ -50,9 +51,10 @@ const childrenMap = { 1: { layout: {}, items: {} }, }), autoHeight: AutoHeightControl, + scrollbars: withDefault(BoolControl, false), onEvent: eventHandlerControl(EVENT_OPTIONS), disabled: BoolCodeControl, - showHeader: withDefault(BooleanStateControl, "true"), + showHeader: withDefault(BoolControl, true), style: styleControl(TabContainerStyle), headerStyle: styleControl(ContainerHeaderStyle), bodyStyle: styleControl(ContainerBodyStyle), @@ -211,7 +213,7 @@ const TabbedContainer = (props: TabbedContainerProps) => { const editorState = useContext(EditorContext); const maxWidth = editorState.getAppSettings().maxWidth; const isMobile = checkIsMobile(maxWidth); - const showHeader = props.showHeader.value; + const showHeader = props.showHeader.valueOf(); const paddingWidth = isMobile ? 8 : 0; const tabItems = visibleTabs.map((tab) => { @@ -236,14 +238,16 @@ const TabbedContainer = (props: TabbedContainerProps) => { forceRender: true, children: ( - + + + ) } @@ -307,6 +311,11 @@ export const TabbedContainerBaseComp = (function () { <>
{children.autoHeight.getPropertyView()} + {!children.autoHeight.getView() && ( + children.scrollbars.propertyView({ + label: trans("prop.scrollbar"), + }) + )}
{children.style.getPropertyView()} diff --git a/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainer.tsx b/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainer.tsx index 76e2dae74..1becd89e0 100644 --- a/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainer.tsx +++ b/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainer.tsx @@ -147,8 +147,7 @@ export function TriContainer(props: TriContainerProps) { )} {showBody && ( - {scrollbars ? ( - + - ) : ( - - )} )} {showFooter && ( diff --git a/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainerComp.tsx b/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainerComp.tsx index c32ba3d2e..61a64da16 100644 --- a/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainerComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/triContainerComp/triContainerComp.tsx @@ -125,12 +125,15 @@ export class TriContainerComp extends TriContainerBaseComp implements IContainer this.children.showHeader.propertyView({ label: trans("prop.showHeader") }), this.children.showBody.propertyView({ label: trans("prop.showBody") }), this.children.showFooter.propertyView({ label: trans("prop.showFooter") }), - (!this.children.autoHeight.getView()) && this.children.scrollbars.propertyView({ label: trans("prop.scrollbar") }), + ]; } heightPropertyView() { - return this.children.autoHeight.getPropertyView(); + return [ + this.children.autoHeight.getPropertyView(), + (!this.children.autoHeight.getView()) && this.children.scrollbars.propertyView({ label: trans("prop.scrollbar") }) + ]; } stylePropertyView() { diff --git a/client/packages/lowcoder/src/comps/controls/styleControl.tsx b/client/packages/lowcoder/src/comps/controls/styleControl.tsx index ef7b0813d..7648814bb 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControl.tsx @@ -747,10 +747,11 @@ export function styleControl(colorConfig preInputNode: , placeholder: props[name], }) - : (name === "padding" || + : name === "textSize" || + name === "padding" || name === "containerHeaderPadding" || name === "containerFooterPadding" || - name === "containerBodyPadding") + name === "containerBodyPadding" ? ( children[name] as InstanceType ).propertyView({ @@ -820,37 +821,15 @@ export function styleControl(colorConfig preInputNode: , placeholder: props[name], }) - : name === "backgroundImageSize" || name === "headerBackgroundImageSize" || name === "footerBackgroundImageSize" - ? ( - children[name] as InstanceType - ).propertyView({ - label: config.label, - preInputNode: , - placeholder: props[name], - }) - : name === "backgroundImagePosition" || name === "headerBackgroundImagePosition" || name === "footerBackgroundImagePosition" - ? ( - children[name] as InstanceType - ).propertyView({ - label: config.label, - preInputNode: , - placeholder: props[name], - }) - : name === "backgroundImageOrigin" || name === "headerBackgroundImageOrigin" || name === "footerBackgroundImageOrigin" - ? ( - children[name] as InstanceType - ).propertyView({ - label: config.label, - preInputNode: , - placeholder: props[name], - }) - : children[name].propertyView({ - label: config.label, - panelDefaultColor: props[name], - // isDep: isDepColorConfig(config), - isDep: true, - depMsg: depMsg, - })} + : children[name].propertyView({ + label: config.label, + panelDefaultColor: props[name], + // isDep: isDepColorConfig(config), + isDep: true, + depMsg: depMsg, + }) + + } ); })} diff --git a/client/packages/lowcoder/src/comps/hooks/agoraFunctions.tsx b/client/packages/lowcoder/src/comps/hooks/agoraFunctions.tsx index 155b9b739..129157e06 100644 --- a/client/packages/lowcoder/src/comps/hooks/agoraFunctions.tsx +++ b/client/packages/lowcoder/src/comps/hooks/agoraFunctions.tsx @@ -43,12 +43,12 @@ const useAgora = () => { const leaveChannel = async () => { if (isJoined) { if (!client) { - console.error("Agora client is not initialized"); + // console.error("Agora client is not initialized"); return; } if (!client.localTracks.length) { - console.error("No local tracks to unpublish"); + // console.error("No local tracks to unpublish"); return; } @@ -105,7 +105,7 @@ const useAgora = () => { setHeight(videoHeight!); // console.log(`Video width: ${videoWidth}px, height: ${videoHeight}px`); } else { - console.error("Media stream track not found"); + // console.error("Media stream track not found"); } }; diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index af1981c37..9048c0959 100644 --- a/client/packages/lowcoder/src/pages/editor/editorView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorView.tsx @@ -329,6 +329,9 @@ function EditorView(props: EditorViewProps) { const hideBodyHeader = useTemplateViewMode(); + // we check if we are on the public cloud + const isLowCoderDomain = window.location.hostname === 'app.lowcoder.cloud'; + if (readOnly && hideHeader) { return ( @@ -341,7 +344,19 @@ function EditorView(props: EditorViewProps) { if (readOnly && !showAppSnapshot) { return ( - {application && {application.name}} + + {application && {application.name}} + {isLowCoderDomain && [ + // Adding Support for iframely to be able to embedd the component explorer in the docu + , + , + , + , + , + // adding Clearbit Support for Analytics + + ]} + {!hideBodyHeader && } @@ -354,6 +369,7 @@ function EditorView(props: EditorViewProps) { ); } + // history mode, display with the right panel, a little trick const showRight = panelStatus.right || showAppSnapshot; let uiCompView; @@ -392,7 +408,19 @@ function EditorView(props: EditorViewProps) { toggleEditorModeStatus={toggleEditorModeStatus} editorModeStatus={editorModeStatus} /> - {application && {application.name}} + + {application && {application.name}} + {isLowCoderDomain && [ + // Adding Support for iframely to be able to embedd the component explorer in the docu + , + , + , + , + , + // adding Clearbit Support for Analytics + + ]} + {showNewUserGuide && }
{compMeta.name}
-
{compMeta.description || "No description."}
+
{compMeta.description || "No description."} v{packageVersion || ""}
); 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({ diff --git a/client/packages/lowcoder/src/util/dateTimeUtils.ts b/client/packages/lowcoder/src/util/dateTimeUtils.ts index 1cfdb99ae..4ec6cea67 100644 --- a/client/packages/lowcoder/src/util/dateTimeUtils.ts +++ b/client/packages/lowcoder/src/util/dateTimeUtils.ts @@ -1,6 +1,70 @@ +import { time } from "console"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; +import timezone from "dayjs/plugin/timezone"; +import duration from "dayjs/plugin/duration"; +import utc from "dayjs/plugin/utc"; +import quarterOfYear from "dayjs/plugin/quarterOfYear"; +import weekOfYear from "dayjs/plugin/weekOfYear"; +import isBetween from "dayjs/plugin/isBetween"; +import isToday from "dayjs/plugin/isToday"; +import isTomorrow from "dayjs/plugin/isTomorrow"; +import isYesterday from "dayjs/plugin/isYesterday"; +import customParseFormat from "dayjs/plugin/customParseFormat"; +import advancedFormat from "dayjs/plugin/advancedFormat"; +import updateLocale from "dayjs/plugin/updateLocale"; +import localeData from "dayjs/plugin/localeData"; +import localizedFormat from "dayjs/plugin/localizedFormat"; +import isLeapYear from "dayjs/plugin/isLeapYear"; +import isSameOrAfter from "dayjs/plugin/isSameOrAfter"; +import isSameOrBefore from "dayjs/plugin/isSameOrBefore"; +import isoWeek from "dayjs/plugin/isoWeek"; +import isoWeeksInYear from "dayjs/plugin/isoWeeksInYear"; +import weekYear from "dayjs/plugin/weekYear"; +import isMoment from "dayjs/plugin/isMoment"; +import calendar from "dayjs/plugin/calendar"; +import buddhistEra from "dayjs/plugin/buddhistEra"; +import minmax from "dayjs/plugin/minMax"; +import bigIntSupport from "dayjs/plugin/bigIntSupport"; +import objectSupport from "dayjs/plugin/objectSupport"; +import pluralGetSet from 'dayjs/plugin/pluralGetSet'; +import preParsePostFormat from 'dayjs/plugin/preParsePostFormat'; +import toObject from 'dayjs/plugin/toObject'; +import toArray from 'dayjs/plugin/toArray'; + +dayjs.extend(relativeTime); +dayjs.extend(timezone); +dayjs.extend(duration); +dayjs.extend(utc); +dayjs.extend(quarterOfYear); +dayjs.extend(weekOfYear); +dayjs.extend(isBetween); +dayjs.extend(isToday); +dayjs.extend(isTomorrow); +dayjs.extend(isYesterday); +dayjs.extend(customParseFormat); +dayjs.extend(advancedFormat); +dayjs.extend(updateLocale); +dayjs.extend(localeData); +dayjs.extend(localizedFormat); +dayjs.extend(isLeapYear); +dayjs.extend(isSameOrAfter); +dayjs.extend(isSameOrBefore); +dayjs.extend(isoWeek); +dayjs.extend(isoWeeksInYear); +dayjs.extend(weekYear); +dayjs.extend(isMoment); +dayjs.extend(calendar); +dayjs.extend(buddhistEra); +dayjs.extend(minmax); dayjs.extend(relativeTime); +dayjs.extend(bigIntSupport); +dayjs.extend(localizedFormat); +dayjs.extend(objectSupport); +dayjs.extend(pluralGetSet); +dayjs.extend(preParsePostFormat); +dayjs.extend(toObject); +dayjs.extend(toArray); export const TIME_FORMAT = "HH:mm:ss"; export const TIME_12_FORMAT = "HH:mm:ss:a"; diff --git a/client/packages/lowcoder/src/util/perfUtils.ts b/client/packages/lowcoder/src/util/perfUtils.ts index 570683377..76d2a5c2e 100644 --- a/client/packages/lowcoder/src/util/perfUtils.ts +++ b/client/packages/lowcoder/src/util/perfUtils.ts @@ -95,7 +95,7 @@ export function showCost(logstr: string, fn: () => T): T { } const startTime = performance.now(); const result = fn(); - console.info(`${logstr} cost: ${performance.now() - startTime}`); + // console.info(`${logstr} cost: ${performance.now() - startTime}`); return result; } diff --git a/server/api-service/lowcoder-dependencies/pom.xml b/server/api-service/lowcoder-dependencies/pom.xml index 43e02b367..23ad18c48 100644 --- a/server/api-service/lowcoder-dependencies/pom.xml +++ b/server/api-service/lowcoder-dependencies/pom.xml @@ -58,11 +58,18 @@ 2.13.0 + org.glassfish + javax.el + 3.0.0 + + + javax.el + javax.el-api + 3.0.0 jakarta.el jakarta.el-api 5.0.1 - org.eclipse.jgit org.eclipse.jgit @@ -159,6 +166,7 @@ 2.1.3 + org.glassfish.jaxb diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java index be6b2631f..c7feaae0f 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/configuration/PluginConfiguration.java @@ -4,6 +4,8 @@ import org.lowcoder.api.framework.plugin.LowcoderPluginManager; import org.lowcoder.api.framework.plugin.endpoint.PluginEndpointHandler; +// Falk: eventually not needed +import org.lowcoder.api.framework.plugin.security.PluginAuthorizationManager; import org.lowcoder.plugin.api.EndpointExtension; import org.springframework.aop.Advisor; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; @@ -21,7 +23,6 @@ import reactor.core.publisher.Mono; - @Configuration public class PluginConfiguration { @@ -42,5 +43,15 @@ RouterFunction pluginEndpoints(LowcoderPluginManager pluginManager, PluginEnd return (endpoints == null) ? pluginsList : pluginsList.andOther(endpoints); } - + + // Falk: eventually not needed + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + Advisor protectPluginEndpoints(PluginAuthorizationManager pluginAauthManager) + { + AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(EndpointExtension.class, true); + AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(pointcut, pluginAauthManager); + interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder() -1); + return interceptor; + } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/security/PluginAuthorizationManager.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/security/PluginAuthorizationManager.java new file mode 100644 index 000000000..237567643 --- /dev/null +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/plugin/security/PluginAuthorizationManager.java @@ -0,0 +1,92 @@ +package org.lowcoder.api.framework.plugin.security; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.lang3.StringUtils; +import org.lowcoder.plugin.api.EndpointExtension; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.ExpressionAuthorizationDecision; +import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +@Slf4j +//@Component +public class PluginAuthorizationManager implements ReactiveAuthorizationManager +{ + private final MethodSecurityExpressionHandler expressionHandler; + + public PluginAuthorizationManager() + { + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); + } + + @Override + public Mono check(Mono authentication, MethodInvocation invocation) + { + log.info("Checking plugin reactive endpoint invocation security for {}", invocation.getMethod().getName()); + + EndpointExtension endpointExtension = (EndpointExtension)invocation.getArguments()[1]; + 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/resources/application-lowcoder.yml b/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml index 29128279c..ee0ecf3e3 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 @@ -31,6 +31,11 @@ logging: root: info web: debug +logging: + level: + root: info + web: debug + server: error: includeStacktrace: ALWAYS diff --git a/server/node-service/src/plugins/openApi/index.ts b/server/node-service/src/plugins/openApi/index.ts index c99bcc9a3..31bc9d417 100644 --- a/server/node-service/src/plugins/openApi/index.ts +++ b/server/node-service/src/plugins/openApi/index.ts @@ -200,4 +200,4 @@ const openApiPlugin: DataSourcePlugin = { }, }; -export default openApiPlugin; +export default openApiPlugin; \ No newline at end of file