From b889d34858af64cb15666f751a441d7b19c5890e Mon Sep 17 00:00:00 2001 From: trydofor Date: Thu, 7 Dec 2023 17:55:18 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20StackOverflowError=20endless=20l?= =?UTF-8?q?oop=20#158?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- .../faceless/jooqgen/WingsCodeGenerator.java | 27 +-- .../faceless/jooqgen/WingsJavaGenerator.java | 27 +-- .../faceless/jooqgen/WingsJavaStrategy.java | 3 +- .../sample/TestWingsInitDatabaseSample.java | 3 +- .../wings/faceless/jooq/JooqShardingTest.java | 49 ++-- .../spring/boot/WingsEnabledTopFalseTest.java | 18 -- .../app/conf/TestSecurityConfiguration.java | 5 +- .../conf/WingsHttpPermitConfigurer.java | 11 +- .../spring/help/SecurityConfigHelper.java | 69 ++++-- .../app/conf/TestSecurityConfiguration.java | 3 +- .../request/FakeHttpServletRequest.java | 225 ++++++++++++++++++ .../bean/SlardarJacksonWebConfiguration.java | 17 +- .../bean/SlardarWebMvcConfiguration.java | 10 - .../slardar/webmvc/RequestMappingHelper.java | 2 + .../bean/SlardarMonitorConfiguration.java | 2 +- .../auth/impl/DefaultDaoAuthnCombo.java | 3 +- .../WarlockSecurityBeanConfiguration.java | 1 - .../WarlockSecurityConfConfiguration.java | 155 +++++++++++- .../spring/prop/WarlockSecurityProp.java | 8 + .../wings-warlock-security-77.properties | 2 + .../warlock/security/AccessDeny403Test.java | 3 +- .../spring/prop/WarlockEnabledProp.java | 9 + 23 files changed, 504 insertions(+), 150 deletions(-) create mode 100644 wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/FakeHttpServletRequest.java diff --git a/observe/docs b/observe/docs index a9ca9f274..469b6ee2d 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit a9ca9f274ee09d1442445fe9d209eec11dc3113e +Subproject commit 469b6ee2d35b8d70369a3cda764b976b3650321a diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java index 13d1f53e8..9ce3f4e17 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java @@ -1,6 +1,5 @@ package pro.fessional.wings.faceless.jooqgen; -import lombok.val; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jooq.Converter; @@ -66,11 +65,11 @@ public class WingsCodeGenerator { } try { - val src = conf.getGenerator().getTarget().getDirectory(); - val pkg = conf.getGenerator().getTarget().getPackageName().replace('.', '/'); + var src = conf.getGenerator().getTarget().getDirectory(); + var pkg = conf.getGenerator().getTarget().getPackageName().replace('.', '/'); // tmp dir - val tmp = Files.createTempDirectory("jooq-safe-gen").toFile(); - val tdr = tmp.getAbsolutePath(); + var tmp = Files.createTempDirectory("jooq-safe-gen").toFile(); + var tdr = tmp.getAbsolutePath(); conf.getGenerator().getTarget().setDirectory(tdr); log.info("safely generate, tmp-dir={}", tdr); @@ -137,8 +136,8 @@ private static Map walkDir(String root, String child) throws IOExc private static void safeCopy(String tmp, String src, String pkg, boolean inc) throws IOException { - val from = walkDir(tmp, pkg); - val dest = walkDir(src, pkg); + var from = walkDir(tmp, pkg); + var dest = walkDir(src, pkg); if (!inc && !dest.isEmpty()) { log.info("not incremental, Removing excess files in {}", new File(src, pkg).getCanonicalPath()); @@ -157,7 +156,7 @@ private static void safeCopy(String tmp, String src, String pkg, boolean inc) th // date = "2019-09-09T01:33:51.762Z", // schema version:2019090903 // serialVersionUID = 319604016; - val ignoreRegex = Pattern.compile(String.join("|", + var ignoreRegex = Pattern.compile(String.join("|", "(import +[^\r\n]+;[\r\n ]+)+", "The\\s+table\\s+[^.]+", "The\\s+schema\\s+[^<]+", @@ -166,19 +165,19 @@ private static void safeCopy(String tmp, String src, String pkg, boolean inc) th "[\r\n]+"), Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); for (Map.Entry entry : from.entrySet()) { - val k = entry.getKey(); - val f = entry.getValue(); - val d = dest.get(k); + var k = entry.getKey(); + var f = entry.getValue(); + var d = dest.get(k); if (d == null) { - val t = new File(src, k); + var t = new File(src, k); //noinspection ResultOfMethodCallIgnored t.getParentFile().mkdirs(); Files.copy(f.toPath(), t.toPath(), StandardCopyOption.REPLACE_EXISTING); log.info("create new file={}", k); } else { - val ft = ignoreRegex.matcher(InputStreams.readText(new FileInputStream(f))).replaceAll(Null.Str); - val dt = ignoreRegex.matcher(InputStreams.readText(new FileInputStream(d))).replaceAll(Null.Str); + var ft = ignoreRegex.matcher(InputStreams.readText(new FileInputStream(f))).replaceAll(Null.Str); + var dt = ignoreRegex.matcher(InputStreams.readText(new FileInputStream(d))).replaceAll(Null.Str); if (ft.equals(dt)) { log.info("skip main same file={}", k); } diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java index 6df7c3963..9beb27043 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java @@ -1,6 +1,5 @@ package pro.fessional.wings.faceless.jooqgen; -import lombok.val; import org.jetbrains.annotations.NotNull; import org.jooq.Condition; import org.jooq.codegen.GeneratorStrategy; @@ -92,8 +91,8 @@ public void printSingletonInstance(JavaWriter out, Definition definition) { // table is TableDefinition : SysCommitJournalTable, SysCommitJournal final String className = getStrategy().getJavaClassName(definition); final String identifier = getStrategy().getJavaIdentifier(definition); - val aliasName = genAlias(identifier); // N6 - val aliasLower = "pro.fessional.wings.faceless.database.jooq.WingsJooqEnv.uniqueAlias()"; // n6 + var aliasName = genAlias(identifier); // N6 + var aliasLower = "pro.fessional.wings.faceless.database.jooq.WingsJooqEnv.uniqueAlias()"; // n6 // public static final SysCommitJournalTable asN6 = SysCommitJournal.as(WingsJooqEnv.uniqueRuntimeAlias()); out.println("public static final %s %s = %s.as(%s);", className, aliasName, identifier, aliasLower); // 🦁<<< @@ -105,7 +104,7 @@ public void generateTableClassFooter(TableDefinition table, JavaWriter out) { // table is TableDefinition : SysCommitJournalTable, SysCommitJournal final String className = getStrategy().getJavaClassName(table); final String identifier = getStrategy().getJavaIdentifier(table); - val aliasName = genAlias(identifier); // N6 + var aliasName = genAlias(identifier); // N6 out.ref(NotNull.class); final List columns = table.getColumns(); @@ -130,8 +129,8 @@ public void generateTableClassFooter(TableDefinition table, JavaWriter out) { out.println("return %s;", aliasName); out.println("}"); - val logicCol = columns.stream().filter(it -> { - val col = it.getOutputName(); + var logicCol = columns.stream().filter(it -> { + var col = it.getOutputName(); return col.equalsIgnoreCase(COL_DELETE_DT) || col.equalsIgnoreCase(COL_IS_DELETED); }).findFirst(); @@ -142,14 +141,14 @@ public void generateTableClassFooter(TableDefinition table, JavaWriter out) { out.ref(Map.class); out.ref(HashMap.class); out.ref(JournalService.class); - val fldDel = reflectMethodRef(out, getStrategy().getJavaIdentifier(colDel), colRefSegments(colDel)); + var fldDel = reflectMethodRef(out, getStrategy().getJavaIdentifier(colDel), colRefSegments(colDel)); - val namDel = colDel.getOutputName(); + var namDel = colDel.getOutputName(); out.println(""); out.javadoc("The colDel %s condition", namDel); final String markDelete; if (namDel.equalsIgnoreCase(COL_DELETE_DT)) { - val colType = colDel.getDefinedType().getType().toLowerCase(); + var colType = colDel.getDefinedType().getType().toLowerCase(); if (colType.contains("time")) { markDelete = "commit.getCommitDt()"; if (WingsCodeGenConf.isLiveDataByMax()) { @@ -202,11 +201,11 @@ else if (colType.contains("int")) { out.println("Map, Object> map = new HashMap<>();"); out.println("map.put(%s, %s);", fldDel, markDelete); - val commitCol = columns.stream().filter(it -> + var commitCol = columns.stream().filter(it -> it.getOutputName().equalsIgnoreCase(COL_COMMIT_ID)).findFirst(); if (commitCol.isPresent()) { final ColumnDefinition colCid = commitCol.get(); - val fldCid = reflectMethodRef(out, getStrategy().getJavaIdentifier(colCid), colRefSegments(colCid)); + var fldCid = reflectMethodRef(out, getStrategy().getJavaIdentifier(colCid), colRefSegments(colCid)); out.println("map.put(%s, commit.getCommitId());", fldCid); } out.println("return map;"); @@ -269,9 +268,9 @@ public void generateDao(TableDefinition table, JavaWriter out) { private String genAlias(String id) { final String chr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - val ix = id.hashCode() % chr.length(); - val cd = ix < 0 ? chr.charAt(-ix) : chr.charAt(ix); - val sq = id.length() % 10; + var ix = id.hashCode() % chr.length(); + var cd = ix < 0 ? chr.charAt(-ix) : chr.charAt(ix); + var sq = id.length() % 10; return ("as" + cd) + sq; } diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaStrategy.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaStrategy.java index 63dfee4b2..fe7137bab 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaStrategy.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaStrategy.java @@ -1,6 +1,5 @@ package pro.fessional.wings.faceless.jooqgen; -import lombok.val; import org.jooq.codegen.DefaultGeneratorStrategy; import org.jooq.codegen.GeneratorStrategy; import org.jooq.meta.ColumnDefinition; @@ -22,7 +21,7 @@ public class WingsJavaStrategy extends DefaultGeneratorStrategy { @Override public List getJavaClassImplements(Definition definition, Mode mode) { - val impls = super.getJavaClassImplements(definition, mode); + var impls = super.getJavaClassImplements(definition, mode); if (!(definition instanceof TableDefinition)) return impls; List columns = ((TableDefinition) definition).getColumns(); diff --git a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/TestWingsInitDatabaseSample.java b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/TestWingsInitDatabaseSample.java index 3fd4260cf..21a997f03 100644 --- a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/TestWingsInitDatabaseSample.java +++ b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/TestWingsInitDatabaseSample.java @@ -2,7 +2,6 @@ import io.qameta.allure.TmsLink; import lombok.Setter; -import lombok.val; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -30,7 +29,7 @@ public class TestWingsInitDatabaseSample { @TmsLink("C12024") public void init0601() { // init - val sqls = scan(REVISION_PATH_MASTER, WingsRevision.V01_19_0521_01_EnumI18n.classpath()); + var sqls = scan(REVISION_PATH_MASTER, WingsRevision.V01_19_0521_01_EnumI18n.classpath()); schemaRevisionManager.publishRevision(WingsRevision.V00_19_0512_01_Schema.revision(), 0); schemaRevisionManager.checkAndInitSql(sqls, 0, false); diff --git a/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java b/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java index 6064a2925..7f3fa831f 100644 --- a/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java +++ b/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java @@ -3,7 +3,6 @@ import io.qameta.allure.TmsLink; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import lombok.val; import org.apache.shardingsphere.infra.hint.HintManager; import org.jooq.Field; import org.jooq.impl.DSL; @@ -14,11 +13,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; import org.springframework.boot.test.context.SpringBootTest; -import pro.fessional.wings.faceless.convention.EmptyValue; import pro.fessional.wings.faceless.app.database.autogen.tables.TstShardingTable; import pro.fessional.wings.faceless.app.database.autogen.tables.daos.TstShardingDao; import pro.fessional.wings.faceless.app.database.autogen.tables.pojos.TstSharding; import pro.fessional.wings.faceless.app.database.autogen.tables.records.TstShardingRecord; +import pro.fessional.wings.faceless.convention.EmptyValue; import pro.fessional.wings.faceless.flywave.SchemaRevisionManager; import pro.fessional.wings.faceless.flywave.SchemaShardingManager; import pro.fessional.wings.faceless.helper.WingsTestHelper; @@ -82,7 +81,7 @@ public void test3SplitTable5() { @Test @TmsLink("C12138") public void test4InsertSeeLog() { - val rd = new TstSharding(id, + var rd = new TstSharding(id, LocalDateTime.now(), EmptyValue.DATE_TIME, EmptyValue.DATE_TIME, @@ -105,9 +104,9 @@ public void test4InsertSeeLog() { @Test @TmsLink("C12139") public void test5UpdateSeeLog() { - val tp = TstShardingTable.TstSharding; + var tp = TstShardingTable.TstSharding; // update `tst_sharding` set `modify_dt` = ?, `login_info` = ? where `id` <= ? - val rp = dao.ctx().update(tp) + var rp = dao.ctx().update(tp) .set(tp.ModifyDt, LocalDateTime.now()) .set(tp.LoginInfo, "update 5") .where(tp.Id.eq(id)) @@ -115,8 +114,8 @@ public void test5UpdateSeeLog() { testcaseNotice("plain updated= $rp"); testcaseNotice("update `tst_sharding_1` set `modify_dt` = ?, `login_info` = ? where `id` = ?"); - val tw = dao.getTable(); - val rw = dao.ctx().update(tw) + var tw = dao.getTable(); + var rw = dao.ctx().update(tw) .set(tw.ModifyDt, LocalDateTime.now()) .set(tw.LoginInfo, "update 5") .where(tw.Id.eq(id)) @@ -124,8 +123,8 @@ public void test5UpdateSeeLog() { testcaseNotice("write updated= $rw"); testcaseNotice("update `tst_sharding_1` set `modify_dt` = ?, `login_info` = ? where `id` = ?"); - val tr = dao.getAlias(); - val rr = dao.ctx().update(tr) + var tr = dao.getAlias(); + var rr = dao.ctx().update(tr) .set(tr.ModifyDt, LocalDateTime.now()) .set(tr.LoginInfo, "update 5") .where(tr.Id.eq(id)) @@ -147,8 +146,8 @@ public void test5UpdateSeeLog() { public void test6SelectSeeLog() { try (HintManager it = HintManager.getInstance()) { it.setWriteRouteOnly(); - val ta = TstShardingTable.asP1; - val ra = dao.ctx().select(ta.Id) + var ta = TstShardingTable.asP1; + var ra = dao.ctx().select(ta.Id) .from(ta) .where(ta.Id.le(id)) .limit(DSL.inline(1)) // RC3 @@ -157,8 +156,8 @@ public void test6SelectSeeLog() { testcaseNotice("alias select", ra); testcaseNotice("select `y8`.`id` from `tst_sharding` as `y8` where `y8`.`id` <= ?"); - val tp = TstShardingTable.TstSharding; - val rp = dao.ctx().select(tp.Id) + var tp = TstShardingTable.TstSharding; + var rp = dao.ctx().select(tp.Id) .from(tp) .where(tp.Id.le(id)) // .limit(1) // https://github.com/apache/incubator-shardingsphere/issues/3330 @@ -167,8 +166,8 @@ public void test6SelectSeeLog() { testcaseNotice("plain select", rp); testcaseNotice("select `id` from `tst_sharding` where `id` <= ?"); - val da = dao.getAlias(); - val rd = dao.fetch(da, da.Id.eq(id)); + var da = dao.getAlias(); + var rd = dao.fetch(da, da.Id.eq(id)); testcaseNotice("dao select= $rd"); testcaseNotice("select `y8`.`id`, `y8`.`create_dt`, ... from `tst_sharding` as `y8` where `y8`.`id` = ?"); @@ -184,8 +183,8 @@ public void test6SelectSeeLog() { @Test @TmsLink("C12141") public void test7DeleteSeeLog() { - val tp = TstShardingTable.TstSharding; - val rp = dao.ctx().delete(tp) + var tp = TstShardingTable.TstSharding; + var rp = dao.ctx().delete(tp) .where(tp.Id.eq(id)) // Inline strategy cannot support range sharding. .and(tp.CommitId.isNotNull()) .getSQL(); @@ -193,8 +192,8 @@ public void test7DeleteSeeLog() { testcaseNotice("plain delete= $rp"); testcaseNotice("delete from `tst_sharding` where (`id` <= ? and `commit_id` is not null)"); - val dw = dao.getTable(); - val rw = dao.delete(dw, dw.Id.eq(id)); + var dw = dao.getTable(); + var rw = dao.delete(dw, dw.Id.eq(id)); testcaseNotice("dao delete= $rw"); testcaseNotice("delete from `tst_sharding_3` where `id` = ? "); @@ -212,17 +211,17 @@ public void test7DeleteSeeLog() { @Test @TmsLink("C12142") public void test8BatchSeeLog() { - val rds = Arrays.asList( + var rds = Arrays.asList( new TstShardingRecord(119L, now, now, now, 9L, "Batch merge 119", "test8", ZH_CN.getId()), new TstShardingRecord(308L, now, now, now, 9L, "Batch merge 308", "test8", ZH_CN.getId()), new TstShardingRecord(309L, now, now, now, 9L, "Batch merge 309", "test8", ZH_CN.getId()) ); testcaseNotice("Batch Insert, check log, ignore in 2 batch, 119 ignore; 308, 309 insert"); - val rs1 = dao.batchInsert(rds, 2, true); + var rs1 = dao.batchInsert(rds, 2, true); Assertions.assertArrayEquals(new int[]{1, 1, 1}, rs1); testcaseNotice("select first, then insert 310, or update 308, 309"); - val rs3 = dao.batchMerge(tbl, new Field[]{tbl.Id}, Arrays.asList( + var rs3 = dao.batchMerge(tbl, new Field[]{tbl.Id}, Arrays.asList( new TstShardingRecord(310L, now, now, now, 9L, "Batch merge 310", "Other 310", ZH_CN.getId()), new TstShardingRecord(308L, now, now, now, 9L, "Batch merge 308", "Other 308", ZH_CN.getId()), new TstShardingRecord(309L, now, now, now, 9L, "Batch merge 309", "Other 309", ZH_CN.getId()) @@ -233,14 +232,14 @@ public void test8BatchSeeLog() { @Test @TmsLink("C12143") public void test9BatchSeeLog() { - val rds = Arrays.asList( + var rds = Arrays.asList( new TstShardingRecord(119L, now, now, now, 9L, "Batch load 307", "test9", ZH_CN.getId()), new TstShardingRecord(318L, now, now, now, 9L, "Batch load 318", "test9", ZH_CN.getId()), new TstShardingRecord(319L, now, now, now, 9L, "Batch load 319", "test9", ZH_CN.getId()) ); testcaseNotice("Batch Insert, check log, replace 119, new318,319, in 2 batch, replace into"); try { - val rs2 = dao.batchInsert(rds, 2, false); + var rs2 = dao.batchInsert(rds, 2, false); log.info(Arrays.toString(rs2)); Assertions.assertArrayEquals(new int[]{2, 1, 1}, rs2); } @@ -252,7 +251,7 @@ public void test9BatchSeeLog() { testcaseNotice("Batch Merge, check log, new 320, on dupkey 318,319, in 2 batch, duplicate"); testcaseNotice("insert into `tst_sharding` (`id`, .., `other_info`) values (?,..., ?) on duplicate key update `login_info` = ?, `other_info` = ?"); try { - val rs3 = dao.batchMerge(tbl, Arrays.asList( + var rs3 = dao.batchMerge(tbl, Arrays.asList( new TstShardingRecord(320L, now, now, now, 9L, "Batch merge 320", "Other 320", ZH_CN.getId()), new TstShardingRecord(318L, now, now, now, 9L, "Batch merge 318", "Other 318", ZH_CN.getId()), new TstShardingRecord(319L, now, now, now, 9L, "Batch merge 319", "Other 319", ZH_CN.getId()) diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsEnabledTopFalseTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsEnabledTopFalseTest.java index 0ac58ed17..a0a795d64 100644 --- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsEnabledTopFalseTest.java +++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsEnabledTopFalseTest.java @@ -19,41 +19,23 @@ }) public class WingsEnabledTopFalseTest { - @Setter(onMethod_ = {@Autowired(required = false)}) - protected WingsEnabledCatConfiguration.InnerCatConfigDefault innerCatConfigDefault; - - @Setter(onMethod_ = {@Autowired(required = false)}) - protected WingsEnabledCatConfiguration wingsEnabledCatConfiguration; @Setter(onMethod_ = {@Autowired(required = false)}) protected WingsEnabledCatConfiguration.CatBean catBean; @Setter(onMethod_ = {@Autowired(required = false)}) - protected WingsEnabledCatConfiguration.InnerCatConfiguration innerCatConfiguration; - @Setter(onMethod_ = {@Autowired(required = false)}) protected WingsEnabledCatConfiguration.InnerCatBean innerCatBean; - - @Setter(onMethod_ = {@Autowired(required = false)}) - protected WingsEnabledDogConfiguration wingsEnabledDogConfiguration; @Setter(onMethod_ = {@Autowired(required = false)}) protected WingsEnabledDogConfiguration.DogBean dogBean; @Setter(onMethod_ = {@Autowired(required = false)}) - protected WingsEnabledDogConfiguration.InnerDogConfiguration innerDogConfiguration; - @Setter(onMethod_ = {@Autowired(required = false)}) protected WingsEnabledDogConfiguration.InnerDogBean innerDogBean; @Test @TmsLink("C11029") public void test() { - Assertions.assertNull(innerCatConfigDefault,"comment/unconment this to recompile"); - - Assertions.assertNull(wingsEnabledCatConfiguration); Assertions.assertNull(catBean); - Assertions.assertNull(innerCatConfiguration); Assertions.assertNull(innerCatBean); - Assertions.assertNull(wingsEnabledDogConfiguration); Assertions.assertNull(dogBean); - Assertions.assertNull(innerDogConfiguration); Assertions.assertNull(innerDogBean); } } \ No newline at end of file diff --git a/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java b/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java index 249e79682..5f580f28b 100644 --- a/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java +++ b/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java @@ -9,7 +9,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import pro.fessional.wings.slardar.security.handler.TestLoginHandler; @@ -47,10 +46,10 @@ public class TestSecurityConfiguration { * only non-API resources in the WebSecurityConfigurer above. */ @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvcMatcher) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { log.info("config HttpSecurity"); http.authorizeHttpRequests(conf -> conf - .requestMatchers(mvcMatcher.pattern("/authed/*")).authenticated() + .requestMatchers("/authed/*").authenticated() ) .formLogin(conf -> conf .loginPage("/user/login.json") // 401 page diff --git a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsHttpPermitConfigurer.java b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsHttpPermitConfigurer.java index 4b15f60b0..5e7094cde 100644 --- a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsHttpPermitConfigurer.java +++ b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsHttpPermitConfigurer.java @@ -3,11 +3,9 @@ import lombok.SneakyThrows; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.springframework.context.ApplicationContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import pro.fessional.wings.slardar.spring.help.SecurityConfigHelper; /** @@ -46,17 +44,10 @@ public WingsHttpPermitConfigurer permitTest() { return this; } - /** - * user mvcMatcher if exist MvcRequestMatcher.Builder, otherwise antMatcher - * - * @see SecurityConfigHelper#requestMatchers(MvcRequestMatcher.Builder, String...) - */ @SneakyThrows @NotNull public AuthorizeHttpRequestsConfigurer.AuthorizedUrl requestMatchers(String... paths) { final HttpSecurity http = getBuilder(); - final ApplicationContext ctx = http.getSharedObject(ApplicationContext.class); - final MvcRequestMatcher.Builder mat = ctx == null ? null : ctx.getBean(MvcRequestMatcher.Builder.class); - return http.authorizeHttpRequests().requestMatchers(SecurityConfigHelper.requestMatchers(mat, paths)); + return http.authorizeHttpRequests().requestMatchers(paths); } } diff --git a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/help/SecurityConfigHelper.java b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/help/SecurityConfigHelper.java index 19a329c06..3c5fda979 100644 --- a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/help/SecurityConfigHelper.java +++ b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/help/SecurityConfigHelper.java @@ -2,21 +2,26 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.springframework.context.ApplicationContext; import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.util.pattern.PathPattern; import pro.fessional.wings.slardar.security.WingsUserDetailsService; +import pro.fessional.wings.slardar.servlet.request.FakeHttpServletRequest; import pro.fessional.wings.slardar.spring.conf.WingsBindAuthnConfigurer; import pro.fessional.wings.slardar.spring.conf.WingsBindLoginConfigurer; import pro.fessional.wings.slardar.spring.conf.WingsHttpPermitConfigurer; -import java.util.Collection; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; /** *
@@ -30,7 +35,6 @@
  */
 public class SecurityConfigHelper {
 
-
     /**
      * 
      * protected void configure(HttpSecurity http) throws Exception {
@@ -42,6 +46,7 @@ public class SecurityConfigHelper {
      * }
      * 
*/ + @NotNull public static HttpHelper http() { return new HttpHelper(); } @@ -62,39 +67,59 @@ public HttpHelper bindLogin(Customizer customizer) thr } } + public static class MatcherHelper extends AbstractRequestMatcherRegistry { + private final Consumer> matchersConsumer; + + public MatcherHelper(ApplicationContext context, Consumer> matchersConsumer) { + this.matchersConsumer = matchersConsumer; + setApplicationContext(context); + } + + @Override + protected MatcherHelper chainRequestMatchers(List requestMatchers) { + matchersConsumer.accept(requestMatchers); + return this; + } + + public static MatcherHelper of(ApplicationContext context, AtomicReference ref) { + return new MatcherHelper(context, it -> ref.set(it.get(0))); + } + public static MatcherHelper of(ApplicationContext context, RequestMatcher[] ref) { + return new MatcherHelper(context, it -> it.toArray(ref)); + } + } + @NotNull public static WingsBindAuthnConfigurer auth() { return new WingsBindAuthnConfigurer<>(); } /** - * CVE-2023-34035: Authorization rules can be misconfigured when using multiple servlets + * encode PathPattern token to url + * + * @see PathPattern */ @NotNull - public static RequestMatcher[] requestMatchers(@Nullable MvcRequestMatcher.Builder mvcMatcher, String... path) { - RequestMatcher[] matchers = new RequestMatcher[path.length]; - for (int index = 0; index < path.length; index++) { - String p = path[index]; - matchers[index] = mvcMatcher == null ? new AntPathRequestMatcher(p, null) : mvcMatcher.pattern(p); - } - return matchers; + public static String encodePathPattern(@NotNull String path) { + path = URLEncoder.encode(path, StandardCharsets.UTF_8); + path = path.replace("%2F", "/"); + return path; } /** - * CVE-2023-34035: Authorization rules can be misconfigured when using multiple servlets + * fake the HttpServletRequest to test matcher */ @NotNull - public static RequestMatcher[] requestMatchers(@Nullable MvcRequestMatcher.Builder mvcMatcher, Collection path) { - RequestMatcher[] matchers = new RequestMatcher[path.size()]; - int index = 0; - for (String p : path) { - matchers[index++] = mvcMatcher == null ? new AntPathRequestMatcher(p, null) : mvcMatcher.pattern(p); - } - return matchers; + public static FakeHttpServletRequest fakeMatcherRequest(@NotNull String path) { + path = encodePathPattern(path); + FakeHttpServletRequest request = new FakeHttpServletRequest(); + request.setPathInfo(path); + request.setRequestURI(path); + return request; } - // //// + @NotNull public static CorsConfigurationSource corsPermitAll() { return request -> { CorsConfiguration conf = new CorsConfiguration(); diff --git a/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java b/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java index 3effbb26e..6cd8a7119 100644 --- a/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java +++ b/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/app/conf/TestSecurityConfiguration.java @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import pro.fessional.mirana.data.Null; import pro.fessional.wings.slardar.spring.help.SecurityConfigHelper; @@ -38,7 +37,7 @@ public class TestSecurityConfiguration { * only non-API resources in the WebSecurityConfigurer above. */ @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvcMatcher) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { log.info("config HttpSecurity"); http.apply(SecurityConfigHelper.http()) .httpPermit(conf -> conf diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/FakeHttpServletRequest.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/FakeHttpServletRequest.java new file mode 100644 index 000000000..93c8cbb2e --- /dev/null +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/FakeHttpServletRequest.java @@ -0,0 +1,225 @@ +package pro.fessional.wings.slardar.servlet.request; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.http.HttpUpgradeHandler; +import jakarta.servlet.http.Part; +import lombok.Getter; +import lombok.Setter; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import pro.fessional.mirana.data.Null; + +import java.io.BufferedReader; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/* + * @author trydofor + * @since 2023-12-06 + */ +@Getter +@Setter +public class FakeHttpServletRequest implements HttpServletRequest { + + private String authType; + private Cookie[] cookies; + private String method; + private String pathInfo = ""; + private String pathTranslated; + private String contextPath = ""; + private String queryString; + private String remoteUser; + private Principal userPrincipal; + private String requestedSessionId; + private String requestURI = ""; + private StringBuffer requestURL; + private String servletPath = ""; + private HttpSession session; + private boolean requestedSessionIdValid = true; + private boolean requestedSessionIdFromCookie = true; + private boolean requestedSessionIdFromURL = false; + private String characterEncoding; + private int contentLength = 0; + private String contentType = ""; + private ServletInputStream inputStream; + private String protocol = "HTTP/1.1"; + private String scheme = "http"; + private String serverName = "localhost"; + private int serverPort; + private BufferedReader reader; + private String remoteAddr; + private String remoteHost; + private Locale locale = Locale.US; + private boolean secure = false; + private RequestDispatcher requestDispatcher; + private int remotePort = 8080; + private String localName = "localhost"; + private String localAddr = "127.0.0.1"; + private int localPort = 1234; + private ServletContext servletContext; + private AsyncContext asyncContext; + private boolean asyncStarted = false; + private boolean asyncSupported = false; + private DispatcherType dispatcherType; + private String requestId; + private String protocolRequestId; + private ServletConnection servletConnection; + + private final Map parts = new LinkedHashMap<>(); + private final Map attributes = new LinkedHashMap<>(); + private final MultiValueMap headers = new LinkedMultiValueMap<>(); + private final Map parameterMap = new LinkedHashMap<>(); + + @Override + public long getDateHeader(String name) { + var h = getHeader(name); + return h == null ? 0 : Long.parseLong(h); + } + + @Override + public String getHeader(String name) { + var h = headers.get(name); + return h.isEmpty() ? null : h.get(0); + } + + @Override + public Enumeration getHeaders(String name) { + return Collections.enumeration(headers.get(name)); + } + + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(headers.keySet()); + } + + @Override + public int getIntHeader(String name) { + var h = getHeader(name); + return h == null ? 0 : Integer.parseInt(h); + } + + @Override + public boolean isUserInRole(String role) { + return false; + } + + @Override + public HttpSession getSession(boolean create) { + return getSession(); + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public boolean authenticate(HttpServletResponse response) { + return false; + } + + @Override + public void login(String username, String password) { + } + + @Override + public void logout() throws ServletException { + } + + @Override + public Collection getParts() { + return parts.values(); + } + + @Override + public Part getPart(String name) { + return parts.get(name); + } + + @Override + public T upgrade(Class handlerClass) { + return null; + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(attributes.keySet()); + } + + @Override + public long getContentLengthLong() { + return getContentLength(); + } + + @Override + public String getParameter(String name) { + var p = parameterMap.get(name); + return p == null || p.length == 0 ? null : p[0]; + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(parameterMap.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + var p = parameterMap.get(name); + return p == null ? Null.StrArr : p; + } + + @Override + public void setAttribute(String name, Object o) { + attributes.put(name, o); + } + + @Override + public void removeAttribute(String name) { + attributes.remove(name); + } + + @Override + public Enumeration getLocales() { + return Collections.enumeration(List.of(getLocale())); + } + + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + return getRequestDispatcher(); + } + + @Override + public AsyncContext startAsync() { + return getAsyncContext(); + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { + return getAsyncContext(); + } + +} diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java index 3ca2664a1..baea815bf 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; -import lombok.val; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -87,8 +86,8 @@ public Jackson2ObjectMapperBuilderCustomizer customizeJacksonDatetime(SlardarDat log.info("SlardarWebmvc spring-bean customizeJacksonDatetime"); return builder -> { // local - val date = DateTimeFormatter.ofPattern(prop.getDate().getFormat()); - val datePsr = prop.getDate() + var date = DateTimeFormatter.ofPattern(prop.getDate().getFormat()); + var datePsr = prop.getDate() .getSupport() .stream() .map(DateTimeFormatter::ofPattern) @@ -97,8 +96,8 @@ public Jackson2ObjectMapperBuilderCustomizer customizeJacksonDatetime(SlardarDat builder.deserializerByType(LocalDate.class, new JacksonLocalDateDeserializer(date, datePsr)); log.info("SlardarWebmvc conf Jackson2ObjectMapperBuilderCustomizer LocalDate"); - val time = DateTimeFormatter.ofPattern(prop.getTime().getFormat()); - val timePsr = prop.getTime() + var time = DateTimeFormatter.ofPattern(prop.getTime().getFormat()); + var timePsr = prop.getTime() .getSupport() .stream() .map(DateTimeFormatter::ofPattern) @@ -108,13 +107,13 @@ public Jackson2ObjectMapperBuilderCustomizer customizeJacksonDatetime(SlardarDat log.info("SlardarWebmvc conf Jackson2ObjectMapperBuilderCustomizer LocalTime"); // auto local - val full = DateTimeFormatter.ofPattern(prop.getDatetime().getFormat()); + var full = DateTimeFormatter.ofPattern(prop.getDatetime().getFormat()); final AutoZoneType autoLocal = AutoZoneType.valueOf(prop.getDatetime().isAuto()); JacksonLocalDateTimeSerializer.defaultFormatter = full; JacksonLocalDateTimeSerializer.defaultAutoZone = autoLocal; builder.serializerByType(LocalDateTime.class, new JacksonLocalDateTimeSerializer(full, autoLocal)); - val fullPsr = prop.getDatetime() + var fullPsr = prop.getDatetime() .getSupport() .stream() .map(DateTimeFormatter::ofPattern) @@ -129,7 +128,7 @@ public Jackson2ObjectMapperBuilderCustomizer customizeJacksonDatetime(SlardarDat JacksonZonedDateTimeSerializer.defaultAutoZone = autoZone; builder.serializerByType(ZonedDateTime.class, new JacksonZonedDateTimeSerializer(zoned, autoZone)); - val zonePsr = prop.getZoned() + var zonePsr = prop.getZoned() .getSupport() .stream() .map(DateTimeFormatter::ofPattern) @@ -145,7 +144,7 @@ public Jackson2ObjectMapperBuilderCustomizer customizeJacksonDatetime(SlardarDat JacksonOffsetDateTimeSerializer.defaultAutoZone = autoOffset; builder.serializerByType(OffsetDateTime.class, new JacksonOffsetDateTimeSerializer(offset, autoOffset)); - val offPsr = prop.getZoned() + var offPsr = prop.getZoned() .getSupport() .stream() .map(DateTimeFormatter::ofPattern) diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarWebMvcConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarWebMvcConfiguration.java index 0c54daffc..b80ade471 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarWebMvcConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarWebMvcConfiguration.java @@ -5,16 +5,13 @@ import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled; import pro.fessional.wings.slardar.webmvc.AutoRegisterInterceptor; import pro.fessional.wings.slardar.webmvc.PageQueryArgumentResolver; @@ -81,11 +78,4 @@ public void configureAsyncSupport(@NotNull AsyncSupportConfigurer configurer) { configurer.setTaskExecutor(applicationTaskExecutor); } } - - @Bean - @ConditionalWingsEnabled - public MvcRequestMatcher.Builder mvcRequestMatcherBuilder(HandlerMappingIntrospector introspector) { - log.info("SlardarWebmvc conf mvcRequestMatcherBuilder"); - return new MvcRequestMatcher.Builder(introspector); - } } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java index 0ab00cd4d..bf80d7e8a 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; @@ -24,6 +25,7 @@ * Note, not all mapping in the container * * @see DispatcherServlet#getHandlerMappings() + * @see HandlerMappingIntrospector#getHandlerMappings() */ public class RequestMappingHelper { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java index dfb2340a6..885d64723 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java @@ -81,7 +81,7 @@ public void postProcessBeanFactory(@NotNull ConfigurableListableBeanFactory bean log.info("Slardar spring-bean register dynamic LogMonitor bean=" + key); } else { - log.warn("Wings skip LogMonitor bean for file not exist, file=" + rf); + log.warn("Slardar skip LogMonitor bean for file not exist, bean=" + key + ", file=" + rf); } } else { diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java index ae11d5052..9205fe0b5 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java @@ -3,7 +3,6 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import lombok.val; import org.jetbrains.annotations.NotNull; import org.jooq.Condition; import org.springframework.beans.factory.annotation.Autowired; @@ -143,7 +142,7 @@ public void onFailure(@NotNull Enum authType, String username, String details final String at = wingsAuthTypeParser.parse(authType); final WinUserAuthnTable ta = winUserAuthnDao.getTable(); - val auth = winUserAuthnDao + var auth = winUserAuthnDao .ctx() .select(ta.UserId, ta.FailedCnt, ta.FailedMax, ta.Id) .from(ta) diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java index 1a7c5ee26..d00b863af 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java @@ -129,7 +129,6 @@ public LogoutSuccessHandler logoutSuccessHandler() { @Bean @ConditionalWingsEnabled - @ConditionalOnExpression("!'${" + WarlockSecurityProp.Key$logoutSuccessBody + "}'.isEmpty()") public AccessDeniedHandler accessDeniedHandler() { log.info("WarlockShadow spring-bean accessDeniedHandler"); return new AccessFailureHandler(); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityConfConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityConfConfiguration.java index 1279d0969..17eaa3b64 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityConfConfiguration.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityConfConfiguration.java @@ -1,10 +1,10 @@ package pro.fessional.wings.warlock.spring.bean; import jakarta.servlet.http.HttpServletResponse; -import lombok.val; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -19,23 +19,28 @@ import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.StringUtils; import pro.fessional.mirana.data.Null; +import pro.fessional.wings.silencer.runner.ApplicationRunnerOrdered; import pro.fessional.wings.silencer.spring.WingsOrdered; import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled; import pro.fessional.wings.silencer.spring.help.CommonPropHelper; import pro.fessional.wings.slardar.security.WingsAuthDetailsSource; +import pro.fessional.wings.slardar.servlet.request.FakeHttpServletRequest; import pro.fessional.wings.slardar.servlet.response.ResponseHelper; import pro.fessional.wings.slardar.spring.help.SecurityConfigHelper; +import pro.fessional.wings.slardar.spring.help.SecurityConfigHelper.MatcherHelper; import pro.fessional.wings.warlock.spring.conf.HttpSecurityCustomizer; import pro.fessional.wings.warlock.spring.prop.WarlockEnabledProp; import pro.fessional.wings.warlock.spring.prop.WarlockSecurityProp; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicReference; /** @@ -50,7 +55,7 @@ public class WarlockSecurityConfConfiguration { @Bean @ConditionalWingsEnabled(abs = WarlockEnabledProp.Key$secWebAuto) - public WebSecurityCustomizer warlockWebCustomizer(WarlockSecurityProp securityProp, ObjectProvider httpFirewall, ObjectProvider mvcMatcher) { + public WebSecurityCustomizer warlockWebCustomizer(WarlockSecurityProp securityProp, ObjectProvider httpFirewall) { log.info("WarlockShadow spring-bean warlockWebCustomizer"); return web -> { if (securityProp.isWebDebug()) { @@ -64,8 +69,7 @@ public WebSecurityCustomizer warlockWebCustomizer(WarlockSecurityProp securityPr if (!webIgnore.isEmpty()) { final Set ignores = CommonPropHelper.onlyValue(webIgnore.values()); log.info("WarlockShadow conf WebSecurity, ignoring=" + String.join("\n,", ignores)); - MvcRequestMatcher.Builder mvc = mvcMatcher.getIfAvailable(); - web.ignoring().requestMatchers(SecurityConfigHelper.requestMatchers(mvc, ignores)); + web.ignoring().requestMatchers(ignores.toArray(String[]::new)); } final HttpFirewall firewall = httpFirewall.getIfAvailable(); @@ -137,7 +141,13 @@ public HttpSecurityCustomizer warlockSecurityBindHttpConfigure( HttpServletResponse response = event.getResponse(); ResponseHelper.writeBodyUtf8(response, securityProp.getSessionExpiredBody()); }) - ); + ) + .anonymous(conf -> { + if (!securityProp.isAnonymous()) { + log.info("WarlockShadow conf HttpSecurity, disable anonymous"); + conf.disable(); + } + }); final AccessDeniedHandler deniedHandler = accessDeniedHandler.getIfAvailable(); if (deniedHandler != null) { @@ -150,23 +160,23 @@ public HttpSecurityCustomizer warlockSecurityBindHttpConfigure( @Bean @ConditionalWingsEnabled(abs = WarlockEnabledProp.Key$secHttpAuth) @Order(WingsOrdered.Lv4Application + 300) - public HttpSecurityCustomizer warlockSecurityAuthHttpConfigure(WarlockSecurityProp securityProp, ObjectProvider mvcMatcher) { + public HttpSecurityCustomizer warlockSecurityAuthHttpConfigure(WarlockSecurityProp securityProp) { log.info("WarlockShadow spring-bean warlockSecurityAuthHttpConfigure"); - MvcRequestMatcher.Builder mvc = mvcMatcher.getIfAvailable(); + return http -> { - val conf = http.authorizeHttpRequests(); + final var conf = http.authorizeHttpRequests(); // 1 PermitAll final Set permed = CommonPropHelper.onlyValue(securityProp.getPermitAll().values()); if (!permed.isEmpty()) { log.info("WarlockShadow conf HttpSecurity, bind PermitAll=" + String.join("\n,", permed)); - conf.requestMatchers(SecurityConfigHelper.requestMatchers(mvc, permed)).permitAll(); + conf.requestMatchers(permed.toArray(String[]::new)).permitAll(); } // 2 Authenticated final Set authed = CommonPropHelper.onlyValue(securityProp.getAuthenticated().values()); if (!authed.isEmpty()) { log.info("WarlockShadow conf HttpSecurity, bind Authenticated=" + String.join("\n,", authed)); - conf.requestMatchers(SecurityConfigHelper.requestMatchers(mvc, authed)).authenticated(); + conf.requestMatchers(authed.toArray(String[]::new)).authenticated(); } // 3 Authority @@ -187,7 +197,7 @@ public HttpSecurityCustomizer warlockSecurityAuthHttpConfigure(WarlockSecurityPr final String url = en.getKey(); final Set pms = CommonPropHelper.onlyValue(en.getValue()); log.info("WarlockShadow conf HttpSecurity, bind url=" + url + ", any-permit=[" + String.join(",", pms) + "]"); - conf.requestMatchers(SecurityConfigHelper.requestMatchers(mvc, url)).hasAnyAuthority(pms.toArray(Null.StrArr)); + conf.requestMatchers(url).hasAnyAuthority(pms.toArray(Null.StrArr)); } } }; @@ -266,7 +276,7 @@ public SecurityFilterChain securityFilterChain(WarlockSecurityProp securityProp, if (StringUtils.hasText(anyRequest)) { log.info("WarlockShadow conf securityFilterChain, anyRequest=" + anyRequest); String str = anyRequest.trim(); - if ("permitAll".equalsIgnoreCase(str)) { + if (!StringUtils.hasText(str) || "permitAll".equalsIgnoreCase(str)) { http.authorizeHttpRequests().anyRequest().permitAll(); } else if ("authenticated".equalsIgnoreCase(str)) { @@ -285,4 +295,123 @@ else if ("fullyAuthenticated".equalsIgnoreCase(str)) { log.info("WarlockShadow conf securityFilterChain, done"); return http.build(); } + + + @Bean + @ConditionalWingsEnabled(abs = WarlockEnabledProp.Key$secCheckUrl) + public ApplicationRunnerOrdered securityCheckUrlRunner(WarlockSecurityProp securityProp, ApplicationContext ctx) { + log.info("WarlockShadow spring-runs securityCheckUrlRunner"); + return new ApplicationRunnerOrdered(WingsOrdered.Lv1Config, ignored -> { + log.info("WarlockShadow check security url config"); + Map matchers = new LinkedHashMap<>(); + Map requests = new LinkedHashMap<>(); + + for (var en : securityProp.getWebIgnore().entrySet()) { + String ptn = en.getValue(); + if (!StringUtils.hasText(ptn)) continue; + matchers.put("WebIgnore:" + en.getKey(), ptn); + requests.put(ptn, SecurityConfigHelper.fakeMatcherRequest(ptn)); + } + for (var en : securityProp.getPermitAll().entrySet()) { + String ptn = en.getValue(); + if (!StringUtils.hasText(ptn)) continue; + matchers.put("PermitAll:" + en.getKey(), ptn); + requests.put(ptn, SecurityConfigHelper.fakeMatcherRequest(ptn)); + } + for (var en : securityProp.getAuthenticated().entrySet()) { + String ptn = en.getValue(); + if (!StringUtils.hasText(ptn)) continue; + matchers.put("Authenticated:" + en.getKey(), ptn); + requests.put(ptn, SecurityConfigHelper.fakeMatcherRequest(ptn)); + } + for (var en : securityProp.getAuthority().entrySet()) { + int c = 0; + String k = en.getKey(); + for (String ptn : en.getValue()) { + if (!StringUtils.hasText(ptn)) continue; + matchers.put("Authority:" + k + "[" + (c++) + "]", ptn); + requests.put(ptn, SecurityConfigHelper.fakeMatcherRequest(ptn)); + } + } + final AtomicReference opt = new AtomicReference<>(); + MatcherHelper matcherHelper = MatcherHelper.of(ctx, opt); + + // check including + for (var en : matchers.entrySet()) { + String ptn = en.getValue(); + requests.remove(ptn); + if (requests.isEmpty()) break; + + matcherHelper.requestMatchers(ptn); + RequestMatcher mt = opt.get(); + for (var er : requests.entrySet()) { + if (mt.matches(er.getValue())) { + log.warn(en.getKey() + "=" + ptn + " should not contain " + er.getKey()); + } + } + } + matchers.clear(); + requests.clear(); + + // check auth url + String loginPage = securityProp.getLoginPage(); + if (StringUtils.hasText(loginPage)) { + requests.put(loginPage, SecurityConfigHelper.fakeMatcherRequest(loginPage)); + } + String logoutUrl = securityProp.getLogoutUrl(); + if (StringUtils.hasText(logoutUrl)) { + requests.put(logoutUrl, SecurityConfigHelper.fakeMatcherRequest(logoutUrl)); + } + String loginProcUrl = securityProp.getLoginProcUrl(); + if (StringUtils.hasText(loginProcUrl)) { + requests.put(loginProcUrl, SecurityConfigHelper.fakeMatcherRequest(loginProcUrl)); + } + + StringBuilder err = new StringBuilder(); + for (var en : securityProp.getWebIgnore().entrySet()) { + String ptn = en.getValue(); + if (!StringUtils.hasText(ptn)) continue; + matcherHelper.requestMatchers(ptn); + RequestMatcher mt = opt.get(); + for (var e : requests.entrySet()) { + if (mt.matches(e.getValue())) { + err.append("\nWebIgnore:").append(en.getKey()).append(" should exclude ").append(e.getKey()); + } + } + } + + String anyRequest = securityProp.getAnyRequest(); + if (!StringUtils.hasText(anyRequest) + || "permitAll".equalsIgnoreCase(anyRequest) + || "anonymous".equalsIgnoreCase(anyRequest)) { + for (var en : securityProp.getPermitAll().entrySet()) { + String ptn = en.getValue(); + if (!StringUtils.hasText(ptn)) continue; + if (requests.isEmpty()) break; + + matcherHelper.requestMatchers(ptn); + RequestMatcher mt = opt.get(); + for (var it = requests.entrySet().iterator(); it.hasNext(); ) { + var er = it.next(); + if (mt.matches(er.getValue())) { + log.debug("WarlockShadow security url permit all include " + er.getKey()); + it.remove(); + } + } + } + if (!requests.isEmpty()) { + err.append("\nPermitAll should include urls: ").append(String.join(", ", requests.keySet())); + } + } + + if (!err.isEmpty()) { + String msg = err.toString(); + log.error(msg); + throw new IllegalStateException( + "\nWarlockSecurityConfConfiguration has security url conflict to fix." + + "\nor disable checking by `wings.enabled.warlock.sec-check-url=false`" + + msg); + } + }); + } } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java index 617e9a5d6..b77437f16 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java @@ -63,6 +63,14 @@ public class WarlockSecurityProp { private boolean authorityPerm = false; public static final String Key$authorityPerm = Key + ".authority-perm"; + /** + * whether to enable anonymous + * + * @see #Key$anonymous + */ + private boolean anonymous = false; + public static final String Key$anonymous = Key + ".anonymous"; + /** * true to forward in servlet, otherwise redirect(302) * diff --git a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties index e481c2aab..cac770ae2 100644 --- a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties +++ b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties @@ -4,6 +4,8 @@ wings.warlock.security.web-debug=false wings.warlock.security.authority-role=true ## whether to use Perm in AuthX. wings.warlock.security.authority-perm=true +## whether to enable anonymous +wings.warlock.security.anonymous=false ## true to forward in servlet, otherwise redirect(302) wings.warlock.security.login-forward=true diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny403Test.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny403Test.java index 71d17b8a8..cfac5fb6a 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny403Test.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny403Test.java @@ -23,7 +23,8 @@ * @since 2023-08-28 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "wings.warlock.security.anonymous=true") @Slf4j class AccessDeny403Test { diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockEnabledProp.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockEnabledProp.java index b1d180769..8d18a90b0 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockEnabledProp.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockEnabledProp.java @@ -25,6 +25,15 @@ public class WarlockEnabledProp { private boolean watching = false; public static final String Key$watching = Key + ".watching"; + + /** + * whether to check security url conflict + * + * @see #Key$secCheckUrl + */ + private boolean secCheckUrl = true; + public static final String Key$secCheckUrl = Key + ".sec-check-url"; + /** * whether to enable Web auto config, eg. firewall, debug, etc. *