From 64a701144122eaca31afb2f3380c131fe78eaf92 Mon Sep 17 00:00:00 2001 From: wangshaoping Date: Wed, 31 Jul 2024 18:02:22 +0800 Subject: [PATCH] update --- app.platform/build.gradle | 9 +- .../main/java/app/platform/Application.java | 14 - build-version.gradle | 7 + .../build-common.gradle | 19 ++ .../build-jetty.gradle | 15 ++ .../build-tomcat.gradle | 7 + .../build-undertow.gradle | 15 ++ default-authorizationserver/build.gradle | 21 ++ default-authorizationserver/gradle.properties | 0 ...DefaultAuthorizationServerApplication.java | 32 +++ .../config/AuthorizationServerConfig.java | 147 +++++++++++ .../sample/config/DefaultSecurityConfig.java | 60 +++++ .../src/main/java/sample/jose/Jwks.java | 74 ++++++ .../java/sample/jose/KeyGeneratorUtils.java | 85 ++++++ .../src/main/resources/application.yml | 10 + ...ltAuthorizationServerApplicationTests.java | 137 ++++++++++ ...efaultAuthorizationServerConsentTests.java | 126 +++++++++ erm.frontend/package.json | 2 +- erm.frontend/public/index.html | 25 ++ gradle.properties | 2 +- io.sc.engine.mv.frontend/package.json | 2 +- io.sc.engine.mv.frontend/public/index.html | 25 ++ .../META-INF/platform/plugins/liquibase.json | 2 +- .../code/impl/support/ResourceWrapper.java | 14 +- .../rule/core/po/function/Function.java | 67 +++++ .../core/code/template/groovy/functions.ftl | 6 + .../rule/core/code/template/groovy/groovy.ftl | 2 + io.sc.engine.rule.frontend/package.json | 2 +- io.sc.engine.rule.frontend/public/index.html | 25 ++ .../src/components/index.ts | 8 +- .../src/i18n/messages.json | 8 +- .../src/i18n/messages_tw_CN.json | 8 +- .../src/i18n/messages_zh_CN.json | 8 +- .../src/menus/menus.json | 3 +- .../src/routes/routes.json | 10 +- .../src/views/function/Function.vue | 148 +++++++++++ .../src/views/function/ImportDialog.vue | 72 +++++ .../src/views/functions/Functions.vue | 4 - .../src/views/lib/ImportDialog.vue | 2 +- .../src/views/lib/IndicatorGrid.vue | 4 +- .../src/views/lib/Lib.vue | 6 +- .../src/views/lib/ProcessorGrid.vue | 28 ++ .../resources/designer/DesignerDialog.vue | 8 +- .../views/resources/designer/Processor.vue | 28 +- io.sc.engine.rule.server/build.gradle | 1 + .../controller/FunctionWebController.java | 69 +++++ .../converter/FunctionEntityConverter.java | 84 ++++++ .../function/entity/FunctionEntity.java | 163 ++++++++++++ .../repository/FunctionRepository.java | 10 + .../function/service/FunctionService.java | 18 ++ .../service/impl/FunctionServiceImpl.java | 37 +++ .../rule/server/function/vo/FunctionVo.java | 69 +++++ .../plugins/item/ResourceExampleItem.java | 12 + .../service/impl/ResourceServiceImpl.java | 25 +- .../META-INF/platform/plugins/components.json | 44 ++-- .../platform/plugins/repositories.json | 3 + .../plugins/rule-engine-sample-resource.json | 3 +- .../server/sample/用户自定义函数.json | 9 + ...20515__Rule Engine Database Schema DDL.xml | 31 +++ io.sc.engine.st.frontend/package.json | 2 +- io.sc.engine.st.frontend/public/index.html | 25 ++ .../META-INF/platform/plugins/liquibase.json | 4 +- .../META-INF/platform/plugins/liquibase.json | 2 +- io.sc.platform.core.frontend/package.json | 2 +- .../public/index.html | 25 ++ .../src/components/index.ts | 2 + .../src/menus/menus.json | 9 + .../components/form/elements/WCodeMirror.vue | 16 +- .../src/platform/components/math/WMath.vue | 9 +- .../components/math/toolbar/Toolbar.vue | 59 ++++- .../src/platform/css/quasar.sass | 2 + .../src/platform/i18n/messages.json | 2 +- .../src/platform/i18n/messages_tw_CN.json | 2 +- .../src/platform/i18n/messages_zh_CN.json | 4 +- .../src/routes/routes.json | 13 + .../testcase/code-mirror/code-mirror.vue | 9 +- .../src/views/testcase/math/MathEditor.vue | 7 +- .../src/views/testcase/maxgraph/maxgraph.vue | 51 ++++ .../template-project/package.json | 4 +- .../template-project/public/index.html | 25 ++ .../testcase/code-mirror/code-mirror.vue | 9 +- .../src/views/testcase/math/MathEditor.vue | 7 +- .../platform/core/plugins/PluginManager.java | 36 +++ .../core/plugins/item/ExportableResource.java | 131 ++++++++++ .../springboot/AfterEnvironmentProcessor.java | 66 ++++- .../BeforeEnvironmentProcessor.java | 7 +- .../platform/plugins/directories.json | 2 + .../plugins/exportable-resources.json | 9 + .../asciidoc/9999-appendix/appendix.adoc | 2 +- .../environment/environment.adoc | 62 ++--- .../asciidoc/9999-appendix/oauth2/oauth2.adoc | 1 + .../asciidoc/市场风险/市场风险.adoc | 52 ++++ .../package.json | 2 +- .../public/index.html | 25 ++ .../impl/ProcessOperationServiceImpl.java | 3 +- .../META-INF/platform/plugins/liquibase.json | 6 +- .../templates/pgp/app/build-common.gradle | 2 +- .../templates/pgp/setup/gradle.properties | 2 +- .../plugins/jdbc-connection-template.json | 4 +- io.sc.platform.lcdp.frontend/package.json | 2 +- .../public/index.html | 25 ++ .../src/views/bpm/CompleteTaskDialog.vue | 16 +- .../META-INF/platform/plugins/liquibase.json | 2 +- io.sc.platform.mvc.frontend/package.json | 2 +- io.sc.platform.mvc.frontend/public/index.html | 25 ++ .../src/main/java/io/sc/platform/poi/A.java | 18 -- .../java/io/sc/platform/poi/ExcelBuilder.java | 34 --- .../poi/controller/TestWebController.java | 18 -- .../sc/platform/poi/service/PoiService.java | 5 - .../poi/service/impl/PoiServiceImpl.java | 65 ----- .../main/resources/CS_SCRIPT_CONFIG.0.0.csv | 2 - io.sc.platform.security.frontend/package.json | 2 +- .../PlatformRegisteredClientRepository.java | 2 +- ...2AuthorizationServerAutoConfiguration.java | 33 ++- .../controller/TestWebController.java | 13 - .../Oauth2RegisteredClientInitializer.java | 5 +- .../authorization/jpa/entity/Client.java | 4 + .../plugins/application-properties.json | 18 ++ .../plugins/exportable-resources.json | 16 ++ .../META-INF/platform/plugins/security.json | 5 + .../{privateKey.txt => private-key.txt} | 0 .../{publicKey.txt => public-key.txt} | 0 ...Oauth2ResourceServerAutoConfiguration.java | 46 +--- .../plugins/application-properties.json | 11 + .../platform/security/SecurityProperties.java | 52 +++- .../META-INF/platform/plugins/liquibase.json | 4 +- .../sc/platform/system/api/user/UserVo.java | 6 +- io.sc.platform.system.frontend/package.json | 2 +- .../public/index.html | 25 ++ .../src/views/user/SetPasswordDialog.vue | 2 +- .../service/impl/AuditLogServiceImpl.java | 8 +- .../system/user/jpa/entity/UserEntity.java | 21 +- .../META-INF/platform/plugins/liquibase.json | 2 +- io.sc.standard.frontend/package.json | 2 +- io.sc.standard.frontend/public/index.html | 25 ++ .../META-INF/platform/plugins/liquibase.json | 4 +- org.webjars.mathcss-1.0.0/build.gradle | 37 +++ .../webjars/mathcss/1.0.0/mathml.css | 245 ++++++++++++++++++ .../resources/webjars/mathcss/1.0.0/mspace.js | 27 ++ settings.gradle | 3 + 140 files changed, 2998 insertions(+), 413 deletions(-) create mode 100644 default-authorizationserver/build-common.gradle create mode 100644 default-authorizationserver/build-jetty.gradle create mode 100644 default-authorizationserver/build-tomcat.gradle create mode 100644 default-authorizationserver/build-undertow.gradle create mode 100644 default-authorizationserver/build.gradle create mode 100644 default-authorizationserver/gradle.properties create mode 100644 default-authorizationserver/src/main/java/sample/DefaultAuthorizationServerApplication.java create mode 100644 default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java create mode 100644 default-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java create mode 100644 default-authorizationserver/src/main/java/sample/jose/Jwks.java create mode 100644 default-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java create mode 100644 default-authorizationserver/src/main/resources/application.yml create mode 100644 default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerApplicationTests.java create mode 100644 default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerConsentTests.java create mode 100644 io.sc.engine.rule.core/src/main/java/io/sc/engine/rule/core/po/function/Function.java create mode 100644 io.sc.engine.rule.core/src/main/resources/io/sc/engine/rule/core/code/template/groovy/functions.ftl create mode 100644 io.sc.engine.rule.frontend/src/views/function/Function.vue create mode 100644 io.sc.engine.rule.frontend/src/views/function/ImportDialog.vue delete mode 100644 io.sc.engine.rule.frontend/src/views/functions/Functions.vue create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/controller/FunctionWebController.java create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/converter/FunctionEntityConverter.java create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/entity/FunctionEntity.java create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/repository/FunctionRepository.java create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/service/FunctionService.java create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/service/impl/FunctionServiceImpl.java create mode 100644 io.sc.engine.rule.server/src/main/java/io/sc/engine/rule/server/function/vo/FunctionVo.java create mode 100644 io.sc.engine.rule.server/src/main/resources/io/sc/engine/rule/server/sample/用户自定义函数.json create mode 100644 io.sc.platform.core.frontend/src/platform/css/quasar.sass create mode 100644 io.sc.platform.core.frontend/src/views/testcase/maxgraph/maxgraph.vue create mode 100644 io.sc.platform.core/src/main/java/io/sc/platform/core/plugins/item/ExportableResource.java create mode 100644 io.sc.platform.core/src/main/resources/META-INF/platform/plugins/exportable-resources.json create mode 100644 io.sc.platform.developer.doc/asciidoc/市场风险/市场风险.adoc delete mode 100644 io.sc.platform.poi/src/main/java/io/sc/platform/poi/A.java delete mode 100644 io.sc.platform.poi/src/main/java/io/sc/platform/poi/ExcelBuilder.java delete mode 100644 io.sc.platform.poi/src/main/java/io/sc/platform/poi/controller/TestWebController.java delete mode 100644 io.sc.platform.poi/src/main/java/io/sc/platform/poi/service/PoiService.java delete mode 100644 io.sc.platform.poi/src/main/java/io/sc/platform/poi/service/impl/PoiServiceImpl.java delete mode 100644 io.sc.platform.poi/src/main/resources/CS_SCRIPT_CONFIG.0.0.csv delete mode 100644 io.sc.platform.security.oauth2.server.authorization/src/main/java/io/sc/platform/security/oauth2/server/authorization/controller/TestWebController.java create mode 100644 io.sc.platform.security.oauth2.server.authorization/src/main/resources/META-INF/platform/plugins/application-properties.json create mode 100644 io.sc.platform.security.oauth2.server.authorization/src/main/resources/META-INF/platform/plugins/exportable-resources.json create mode 100644 io.sc.platform.security.oauth2.server.authorization/src/main/resources/META-INF/platform/plugins/security.json rename io.sc.platform.security.oauth2.server.authorization/src/main/resources/io/sc/platform/security/{privateKey.txt => private-key.txt} (100%) rename io.sc.platform.security.oauth2.server.authorization/src/main/resources/io/sc/platform/security/{publicKey.txt => public-key.txt} (100%) create mode 100644 io.sc.platform.security.oauth2.server.resource/src/main/resources/META-INF/platform/plugins/application-properties.json create mode 100644 org.webjars.mathcss-1.0.0/build.gradle create mode 100644 org.webjars.mathcss-1.0.0/src/main/resources/META-INF/resources/webjars/mathcss/1.0.0/mathml.css create mode 100644 org.webjars.mathcss-1.0.0/src/main/resources/META-INF/resources/webjars/mathcss/1.0.0/mspace.js diff --git a/app.platform/build.gradle b/app.platform/build.gradle index a9ecd9b0..a71f9c0a 100644 --- a/app.platform/build.gradle +++ b/app.platform/build.gradle @@ -13,10 +13,11 @@ dependencies { implementation ( project(":io.sc.platform.app"), project(":io.sc.platform.developer"), - //project(":io.sc.platform.security.oauth2.server.authorization"), -// project(":io.sc.platform.security.oauth2.server.resource"), - project(":io.sc.platform.security.loginform"), + project(":io.sc.platform.security.oauth2.server.authorization"), + //project(":io.sc.platform.security.oauth2.server.resource"), + //project(":io.sc.platform.security.loginform"), + /* project(":io.sc.platform.scheduler.manager"), project(":io.sc.platform.scheduler.executor"), @@ -37,6 +38,7 @@ dependencies { // project(":erm.frontend"), project(":io.sc.standard"), + */ ) } @@ -77,7 +79,6 @@ bootJar{ } } - jib { outputPaths { tar = "build/libs/${project.name}-${project.version}-image.tar" diff --git a/app.platform/src/main/java/app/platform/Application.java b/app.platform/src/main/java/app/platform/Application.java index b6d116f0..e8a7f8e9 100644 --- a/app.platform/src/main/java/app/platform/Application.java +++ b/app.platform/src/main/java/app/platform/Application.java @@ -2,16 +2,9 @@ package app.platform; import io.sc.platform.core.ApplicationLauncher; import io.sc.platform.core.PlatformSpringBootServletInitializer; -import io.sc.platform.orm.entity.BaseEntity; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.web.WebApplicationInitializer; -import java.util.Locale; -import java.util.Set; - /** * 应用程序入口 */ @@ -19,12 +12,5 @@ import java.util.Set; public class Application extends PlatformSpringBootServletInitializer implements WebApplicationInitializer { public static void main(String[] args) throws Exception { ApplicationLauncher.run(Application.class,args); -// ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); -// provider.addIncludeFilter(new AssignableTypeFilter(BaseEntity.class)); -// Set components = provider.findCandidateComponents("io.sc"); -// for (BeanDefinition component : components) -// { -// System.out.println(component.getBeanClassName()); -// } } } diff --git a/build-version.gradle b/build-version.gradle index 80626d04..e62916d1 100755 --- a/build-version.gradle +++ b/build-version.gradle @@ -7,6 +7,13 @@ ext['PlatformDependencyVersions'] =[ "org.checkerframework:checker-qual" : "${checker_version}", "com.google.guava:guava" : "${guava_version}", "com.nimbusds:nimbus-jose-jwt" : "9.22", + "org.codehaus.groovy:groovy" : "3.0.19", + "org.codehaus.groovy:groovy-jsr223" : "3.0.19", + "org.codehaus.groovy:groovy-datetime" : "3.0.19", + "org.codehaus.groovy:groovy-dateutil" : "3.0.19", + "org.codehaus.groovy:groovy-json" : "3.0.19", + "org.codehaus.groovy:groovy-sql" : "3.0.19", + "org.codehaus.groovy:groovy-xml" : "3.0.19", ]; /*********************************************************************** diff --git a/default-authorizationserver/build-common.gradle b/default-authorizationserver/build-common.gradle new file mode 100644 index 00000000..b1acd61d --- /dev/null +++ b/default-authorizationserver/build-common.gradle @@ -0,0 +1,19 @@ +/** + * 设置打包文件的运行时目标环境(target) + * 设置方式: 通过命令行 -D 传入目标环境参数 + * 打包命令如下: + * 1. gradle bootwar # 默认, target=tomcat + * 2. gradle bootwar -Dtarget=undertow # undertow, target=undertow + * 3. gradle bootwar -Dtarget=jetty # jetty, target=jetty + */ +def target =System.getProperty("target") ?: "tomcat"; +System.setProperty('target',target); + +// 根据 targetRuntime 变量的值执行实际的 build.gradle +apply from: "build-${target}.gradle" + +// 应用启动项目无需发布到仓库中 +publishPublicationPublicationToMavenRepository.enabled=false + +// 开启 docker 镜像生成任务 +jibBuildTar.enabled =true \ No newline at end of file diff --git a/default-authorizationserver/build-jetty.gradle b/default-authorizationserver/build-jetty.gradle new file mode 100644 index 00000000..6cf02057 --- /dev/null +++ b/default-authorizationserver/build-jetty.gradle @@ -0,0 +1,15 @@ +println "[Jetty] 环境 ......" + +configurations { + all*.exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" + all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-core" + all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-websocket" +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-jetty") + + providedRuntime( + "org.springframework.boot:spring-boot-starter-jetty", + ) +} diff --git a/default-authorizationserver/build-tomcat.gradle b/default-authorizationserver/build-tomcat.gradle new file mode 100644 index 00000000..a9d85e43 --- /dev/null +++ b/default-authorizationserver/build-tomcat.gradle @@ -0,0 +1,7 @@ +println "[Tomcat] 环境 ......" + +dependencies { + providedRuntime( + "org.springframework.boot:spring-boot-starter-tomcat", + ) +} diff --git a/default-authorizationserver/build-undertow.gradle b/default-authorizationserver/build-undertow.gradle new file mode 100644 index 00000000..00d3b492 --- /dev/null +++ b/default-authorizationserver/build-undertow.gradle @@ -0,0 +1,15 @@ +println "[Undertow] 环境 ......" + +configurations { + all*.exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" + all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-core" + all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-websocket" +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-undertow") + + providedRuntime( + "org.springframework.boot:spring-boot-starter-undertow", + ) +} diff --git a/default-authorizationserver/build.gradle b/default-authorizationserver/build.gradle new file mode 100644 index 00000000..bb373641 --- /dev/null +++ b/default-authorizationserver/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'war' +apply plugin: 'com.google.cloud.tools.jib' + +apply from: "build-common.gradle" + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web"){ + exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" + } +} + +dependencies { + implementation( + "jakarta.servlet:jakarta.servlet-api", + "org.springframework.boot:spring-boot-starter-web", + "org.springframework.boot:spring-boot-starter-security", + "org.springframework.boot:spring-boot-starter-jdbc", + "org.springframework.security:spring-security-oauth2-authorization-server:0.4.5", + "com.h2database:h2" + ) +} diff --git a/default-authorizationserver/gradle.properties b/default-authorizationserver/gradle.properties new file mode 100644 index 00000000..e69de29b diff --git a/default-authorizationserver/src/main/java/sample/DefaultAuthorizationServerApplication.java b/default-authorizationserver/src/main/java/sample/DefaultAuthorizationServerApplication.java new file mode 100644 index 00000000..17a7ddcf --- /dev/null +++ b/default-authorizationserver/src/main/java/sample/DefaultAuthorizationServerApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@SpringBootApplication +public class DefaultAuthorizationServerApplication { + + public static void main(String[] args) { + SpringApplication.run(DefaultAuthorizationServerApplication.class, args); + } + +} diff --git a/default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java new file mode 100644 index 00000000..f1d9b7e4 --- /dev/null +++ b/default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample.config; + +import java.util.UUID; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; +import sample.jose.Jwks; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; +import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; + +/** + * @author Joe Grandja + * @since 0.0.1 + */ +@Configuration(proxyBeanMethods = false) +public class AuthorizationServerConfig { + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) + .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 + + // @formatter:off + http + .exceptionHandling(exceptions -> + exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) + ) + .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); + // @formatter:on + return http.build(); + } + + // @formatter:off + @Bean + public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { + RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("messaging-client") + .clientSecret("{noop}secret") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") + .redirectUri("http://127.0.0.1:8080/authorized") + .scope(OidcScopes.OPENID) + .scope(OidcScopes.PROFILE) + .scope("message.read") + .scope("message.write") + .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) + .build(); + + // Save registered client in db as if in-memory + JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); + registeredClientRepository.save(registeredClient); + + return registeredClientRepository; + } + // @formatter:on + + @Bean + public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { + return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); + } + + @Bean + public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { + return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); + } + + @Bean + public JWKSource jwkSource() { + RSAKey rsaKey = Jwks.generateRsa(); + JWKSet jwkSet = new JWKSet(rsaKey); + return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); + } + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().build(); + } + + @Bean + public EmbeddedDatabase embeddedDatabase() { + // @formatter:off + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.H2) + .setScriptEncoding("UTF-8") + .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") + .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") + .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") + .build(); + // @formatter:on + } + +} diff --git a/default-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java b/default-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java new file mode 100644 index 00000000..1eaca369 --- /dev/null +++ b/default-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +/** + * @author Joe Grandja + * @since 0.1.0 + */ +@EnableWebSecurity +public class DefaultSecurityConfig { + + // @formatter:off + @Bean + SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeRequests(authorizeRequests -> + authorizeRequests.anyRequest().authenticated() + ) + .formLogin(withDefaults()); + return http.build(); + } + // @formatter:on + + // @formatter:off + @Bean + UserDetailsService users() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("user1") + .password("password") + .roles("USER") + .build(); + return new InMemoryUserDetailsManager(user); + } + // @formatter:on + +} diff --git a/default-authorizationserver/src/main/java/sample/jose/Jwks.java b/default-authorizationserver/src/main/java/sample/jose/Jwks.java new file mode 100644 index 00000000..0a02e6cc --- /dev/null +++ b/default-authorizationserver/src/main/java/sample/jose/Jwks.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample.jose; + +import java.security.KeyPair; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.UUID; + +import javax.crypto.SecretKey; + +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.RSAKey; + +/** + * @author Joe Grandja + * @since 0.1.0 + */ +public final class Jwks { + + private Jwks() { + } + + public static RSAKey generateRsa() { + KeyPair keyPair = KeyGeneratorUtils.generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + // @formatter:off + return new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + // @formatter:on + } + + public static ECKey generateEc() { + KeyPair keyPair = KeyGeneratorUtils.generateEcKey(); + ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); + ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate(); + Curve curve = Curve.forECParameterSpec(publicKey.getParams()); + // @formatter:off + return new ECKey.Builder(curve, publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + // @formatter:on + } + + public static OctetSequenceKey generateSecret() { + SecretKey secretKey = KeyGeneratorUtils.generateSecretKey(); + // @formatter:off + return new OctetSequenceKey.Builder(secretKey) + .keyID(UUID.randomUUID().toString()) + .build(); + // @formatter:on + } +} diff --git a/default-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java b/default-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java new file mode 100644 index 00000000..babaf285 --- /dev/null +++ b/default-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample.jose; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +/** + * @author Joe Grandja + * @since 0.1.0 + */ +final class KeyGeneratorUtils { + + private KeyGeneratorUtils() { + } + + static SecretKey generateSecretKey() { + SecretKey hmacKey; + try { + hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey(); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + return hmacKey; + } + + static KeyPair generateRsaKey() { + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } + + static KeyPair generateEcKey() { + EllipticCurve ellipticCurve = new EllipticCurve( + new ECFieldFp( + new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")), + new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"), + new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291")); + ECPoint ecPoint = new ECPoint( + new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"), + new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109")); + ECParameterSpec ecParameterSpec = new ECParameterSpec( + ellipticCurve, + ecPoint, + new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), + 1); + + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + keyPairGenerator.initialize(ecParameterSpec); + keyPair = keyPairGenerator.generateKeyPair(); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } +} diff --git a/default-authorizationserver/src/main/resources/application.yml b/default-authorizationserver/src/main/resources/application.yml new file mode 100644 index 00000000..5e879a67 --- /dev/null +++ b/default-authorizationserver/src/main/resources/application.yml @@ -0,0 +1,10 @@ +server: + port: 9000 + +logging: + level: + root: INFO + org.springframework.web: INFO + org.springframework.security: INFO + org.springframework.security.oauth2: INFO +# org.springframework.boot.autoconfigure: DEBUG diff --git a/default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerApplicationTests.java b/default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerApplicationTests.java new file mode 100644 index 00000000..d9a0b2ac --- /dev/null +++ b/default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerApplicationTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample; + +import java.io.IOException; + +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.HtmlButton; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlInput; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.util.UriComponentsBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for the sample Authorization Server. + * + * @author Daniel Garnier-Moiroux + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +public class DefaultAuthorizationServerApplicationTests { + private static final String REDIRECT_URI = "http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc"; + + private static final String AUTHORIZATION_REQUEST = UriComponentsBuilder + .fromPath("/oauth2/authorize") + .queryParam("response_type", "code") + .queryParam("client_id", "messaging-client") + .queryParam("scope", "openid") + .queryParam("state", "some-state") + .queryParam("redirect_uri", REDIRECT_URI) + .toUriString(); + + @Autowired + private WebClient webClient; + + @BeforeEach + public void setUp() { + this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); + this.webClient.getOptions().setRedirectEnabled(true); + this.webClient.getCookieManager().clearCookies(); // log out + } + + @Test + public void whenLoginSuccessfulThenDisplayNotFoundError() throws IOException { + HtmlPage page = this.webClient.getPage("/"); + + assertLoginPage(page); + + this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); + WebResponse signInResponse = signIn(page, "user1", "password").getWebResponse(); + assertThat(signInResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); // there is no "default" index page + } + + @Test + public void whenLoginFailsThenDisplayBadCredentials() throws IOException { + HtmlPage page = this.webClient.getPage("/"); + + HtmlPage loginErrorPage = signIn(page, "user1", "wrong-password"); + + HtmlElement alert = loginErrorPage.querySelector("div[role=\"alert\"]"); + assertThat(alert).isNotNull(); + assertThat(alert.getTextContent()).isEqualTo("Bad credentials"); + } + + @Test + public void whenNotLoggedInAndRequestingTokenThenRedirectsToLogin() throws IOException { + HtmlPage page = this.webClient.getPage(AUTHORIZATION_REQUEST); + + assertLoginPage(page); + } + + @Test + public void whenLoggingInAndRequestingTokenThenRedirectsToClientApplication() throws IOException { + // Log in + this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); + this.webClient.getOptions().setRedirectEnabled(false); + signIn(this.webClient.getPage("/login"), "user1", "password"); + + // Request token + WebResponse response = this.webClient.getPage(AUTHORIZATION_REQUEST).getWebResponse(); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); + String location = response.getResponseHeaderValue("location"); + assertThat(location).startsWith(REDIRECT_URI); + assertThat(location).contains("code="); + } + + private static

P signIn(HtmlPage page, String username, String password) throws IOException { + HtmlInput usernameInput = page.querySelector("input[name=\"username\"]"); + HtmlInput passwordInput = page.querySelector("input[name=\"password\"]"); + HtmlButton signInButton = page.querySelector("button"); + + usernameInput.type(username); + passwordInput.type(password); + return signInButton.click(); + } + + private static void assertLoginPage(HtmlPage page) { + assertThat(page.getUrl().toString()).endsWith("/login"); + + HtmlInput usernameInput = page.querySelector("input[name=\"username\"]"); + HtmlInput passwordInput = page.querySelector("input[name=\"password\"]"); + HtmlButton signInButton = page.querySelector("button"); + + assertThat(usernameInput).isNotNull(); + assertThat(passwordInput).isNotNull(); + assertThat(signInButton.getTextContent()).isEqualTo("Sign in"); + } + +} diff --git a/default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerConsentTests.java b/default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerConsentTests.java new file mode 100644 index 00000000..641b63fa --- /dev/null +++ b/default-authorizationserver/src/test/java/sample/DefaultAuthorizationServerConsentTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package sample; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.DomElement; +import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.util.UriComponentsBuilder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * Consent screen integration tests for the sample Authorization Server. + * + * @author Dmitriy Dubson + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +public class DefaultAuthorizationServerConsentTests { + + @Autowired + private WebClient webClient; + + @MockBean + private OAuth2AuthorizationConsentService authorizationConsentService; + + private final String redirectUri = "http://127.0.0.1/login/oauth2/code/messaging-client-oidc"; + + private final String authorizationRequestUri = UriComponentsBuilder + .fromPath("/oauth2/authorize") + .queryParam("response_type", "code") + .queryParam("client_id", "messaging-client") + .queryParam("scope", "openid message.read message.write") + .queryParam("state", "state") + .queryParam("redirect_uri", this.redirectUri) + .toUriString(); + + @BeforeEach + public void setUp() { + this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); + this.webClient.getOptions().setRedirectEnabled(true); + this.webClient.getCookieManager().clearCookies(); + when(this.authorizationConsentService.findById(any(), any())).thenReturn(null); + } + + @Test + @WithMockUser("user1") + public void whenUserConsentsToAllScopesThenReturnAuthorizationCode() throws IOException { + final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri); + assertThat(consentPage.getTitleText()).isEqualTo("Consent required"); + + List scopes = new ArrayList<>(); + consentPage.querySelectorAll("input[name='scope']").forEach(scope -> + scopes.add((HtmlCheckBoxInput) scope)); + for (HtmlCheckBoxInput scope : scopes) { + scope.click(); + } + + List scopeIds = new ArrayList<>(); + scopes.forEach(scope -> { + assertThat(scope.isChecked()).isTrue(); + scopeIds.add(scope.getId()); + }); + assertThat(scopeIds).containsExactlyInAnyOrder("message.read", "message.write"); + + DomElement submitConsentButton = consentPage.querySelector("button[id='submit-consent']"); + this.webClient.getOptions().setRedirectEnabled(false); + + WebResponse approveConsentResponse = submitConsentButton.click().getWebResponse(); + assertThat(approveConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); + String location = approveConsentResponse.getResponseHeaderValue("location"); + assertThat(location).startsWith(this.redirectUri); + assertThat(location).contains("code="); + } + + @Test + @WithMockUser("user1") + public void whenUserCancelsConsentThenReturnAccessDeniedError() throws IOException { + final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri); + assertThat(consentPage.getTitleText()).isEqualTo("Consent required"); + + DomElement cancelConsentButton = consentPage.querySelector("button[id='cancel-consent']"); + this.webClient.getOptions().setRedirectEnabled(false); + + WebResponse cancelConsentResponse = cancelConsentButton.click().getWebResponse(); + assertThat(cancelConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); + String location = cancelConsentResponse.getResponseHeaderValue("location"); + assertThat(location).startsWith(this.redirectUri); + assertThat(location).contains("error=access_denied"); + } + +} diff --git a/erm.frontend/package.json b/erm.frontend/package.json index 608c88ad..c28bdbe4 100644 --- a/erm.frontend/package.json +++ b/erm.frontend/package.json @@ -92,7 +92,7 @@ "luckyexcel": "1.0.1", "mockjs": "1.1.0", "pinia": "2.1.7", - "platform-core": "8.1.279", + "platform-core": "8.1.287", "quasar": "2.15.4", "tailwindcss": "3.4.4", "vue": "3.4.31", diff --git a/erm.frontend/public/index.html b/erm.frontend/public/index.html index cd23f259..c3b9270d 100644 --- a/erm.frontend/public/index.html +++ b/erm.frontend/public/index.html @@ -26,6 +26,31 @@ + +