From aeaaded00a70d7ea11982a0826cef1d7b3203d32 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 18 Nov 2024 10:19:06 +0800 Subject: [PATCH 1/8] Fix the version comparison query SQL. (#12858) Fix the version comparison query SQL. --- .../plugin/datasource/mapper/HistoryConfigInfoMapper.java | 4 ++-- .../impl/derby/HistoryConfigInfoMapperByDerbyTest.java | 5 +++-- .../impl/mysql/HistoryConfigInfoMapperByMySqlTest.java | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java index cf7a4e74c08..f251ff5bd1b 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java @@ -107,8 +107,8 @@ default MapperResult findConfigHistoryFetchRows(MapperContext context) { */ default MapperResult detailPreviousConfigHistory(MapperContext context) { return new MapperResult( - "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified,encrypted_data_key " - + "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)", + "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type,ext_info,gmt_create" + + ",gmt_modified,encrypted_data_key FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)", Collections.singletonList(context.getWhereParameter(FieldConstant.ID))); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java index dab7cdc45a7..078cd8b44d1 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java @@ -109,8 +109,9 @@ void testDetailPreviousConfigHistory() { Object id = "1"; context.putWhereParameter(FieldConstant.ID, id); MapperResult mapperResult = historyConfigInfoMapperByDerby.detailPreviousConfigHistory(context); - assertEquals(mapperResult.getSql(), "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create," - + "gmt_modified,encrypted_data_key FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); + assertEquals(mapperResult.getSql(), "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type" + + ",ext_info,gmt_create,gmt_modified,encrypted_data_key " + + "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); assertArrayEquals(new Object[] {id}, mapperResult.getParamList().toArray()); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java index f416b899732..1817fb09840 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java @@ -107,8 +107,9 @@ void testDetailPreviousConfigHistory() { Object id = "1"; context.putWhereParameter(FieldConstant.ID, id); MapperResult mapperResult = historyConfigInfoMapperByMySql.detailPreviousConfigHistory(context); - assertEquals(mapperResult.getSql(), "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create," - + "gmt_modified,encrypted_data_key FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); + assertEquals(mapperResult.getSql(), "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type" + + ",ext_info,gmt_create,gmt_modified,encrypted_data_key " + + "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); assertArrayEquals(new Object[] {id}, mapperResult.getParamList().toArray()); } From a24e7ac6a7ad3a728fcf35df544773d4e1f0bfef Mon Sep 17 00:00:00 2001 From: zhouchunhai <111631755+zhouchunhai@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:56:03 +0800 Subject: [PATCH 2/8] The Taobao NPM mirror address is outdated. (#12869) (#12870) --- console-ui/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/console-ui/README.md b/console-ui/README.md index 236227a6413..ad651dbfbbf 100644 --- a/console-ui/README.md +++ b/console-ui/README.md @@ -1,13 +1,13 @@ # 开始项目 国内访问 npm 比较慢,我们可以使用阿里的镜像, 在 npm 或者 yarn 命令后面加参数: -> --registry=https://registry.npm.taobao.org +> --registry=https://registry.npmmirror.com 例: ``` -npm install --registry=https://registry.npm.taobao.org -yarn --registry=https://registry.npm.taobao.org +npm install --registry=https://registry.npmmirror.com +yarn --registry=https://registry.npmmirror.com ``` -[详情地址: http://npm.taobao.org/](http://npm.taobao.org/) +[详情地址: https://npmmirror.com/](http://npm.taobao.org/) ## Node安装 From bfc6f1b38c9926835d84934bf09f1c58972df1be Mon Sep 17 00:00:00 2001 From: 94pengchengxin Date: Thu, 21 Nov 2024 13:42:23 +0800 Subject: [PATCH 3/8] [ISSUE##12864] Fix missing semicolon on line 64 and remove extra comma on line 159 in mysql-schema.sql file (#12866) --- distribution/conf/mysql-schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/conf/mysql-schema.sql b/distribution/conf/mysql-schema.sql index 770961904bf..658b41da805 100644 --- a/distribution/conf/mysql-schema.sql +++ b/distribution/conf/mysql-schema.sql @@ -61,7 +61,7 @@ CREATE TABLE `config_info_gray` ( UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`), KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`), KEY `idx_gmt_modified` (`gmt_modified`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray' +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray'; /******************************************/ /* 表名称 = config_info_beta */ @@ -156,7 +156,7 @@ CREATE TABLE `his_config_info` ( `op_type` char(10) DEFAULT NULL COMMENT 'operation type', `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', - `publish_type` varchar(50) DEFAULT 'formal' COMMENT 'publish type gray or formal',, + `publish_type` varchar(50) DEFAULT 'formal' COMMENT 'publish type gray or formal', `ext_info` longtext DEFAULT NULL COMMENT 'ext info', PRIMARY KEY (`nid`), KEY `idx_gmt_create` (`gmt_create`), From 4be615cef43e6c003b1110c9b055b7d46caa9be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E7=BF=8A=20SionYang?= Date: Thu, 21 Nov 2024 14:05:50 +0800 Subject: [PATCH 4/8] Refactor Auth server identity check. (#12871) * Refactor Auth server identity check. * Fix Unit test. * For checkstyle. --- .../auth/AbstractProtocolAuthService.java | 37 +++ .../nacos/auth/GrpcProtocolAuthService.java | 9 + .../nacos/auth/HttpProtocolAuthService.java | 9 + .../nacos/auth/ProtocolAuthService.java | 10 + .../auth/serveridentity/DefaultChecker.java | 43 ++++ .../auth/serveridentity/ServerIdentity.java | 42 ++++ .../serveridentity/ServerIdentityChecker.java | 44 ++++ .../ServerIdentityCheckerHolder.java | 74 ++++++ .../serveridentity/ServerIdentityResult.java | 72 ++++++ .../auth/GrpcProtocolAuthServiceTest.java | 43 ++++ .../auth/HttpProtocolAuthServiceTest.java | 61 ++++- .../ServerIdentityCheckerHolderTest.java | 88 ++++++++ .../common/remote/client/grpc/GrpcClient.java | 6 +- .../remote/client/grpc/GrpcClusterClient.java | 8 + .../controller/v2/ConfigControllerV2Test.java | 18 +- .../src/main/resources/application.properties | 18 +- .../alibaba/nacos/core/auth/AuthFilter.java | 43 ++-- .../nacos/core/auth/AuthFilterTest.java | 212 ++++++++++++++---- 18 files changed, 735 insertions(+), 102 deletions(-) create mode 100644 auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java create mode 100644 auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java create mode 100644 auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java create mode 100644 auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java create mode 100644 auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java create mode 100644 auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java diff --git a/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java index 150738db186..6a1292d8a63 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java @@ -18,7 +18,12 @@ import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.serveridentity.ServerIdentity; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityChecker; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityCheckerHolder; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.auth.util.Loggers; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -42,8 +47,16 @@ public abstract class AbstractProtocolAuthService implements ProtocolAuthServ protected final AuthConfigs authConfigs; + protected final ServerIdentityChecker checker; + protected AbstractProtocolAuthService(AuthConfigs authConfigs) { this.authConfigs = authConfigs; + this.checker = ServerIdentityCheckerHolder.getInstance().getChecker(); + } + + @Override + public void initialize() { + this.checker.init(authConfigs); } @Override @@ -78,6 +91,30 @@ public boolean validateAuthority(IdentityContext identityContext, Permission per return true; } + @Override + public ServerIdentityResult checkServerIdentity(R request, Secured secured) { + if (isInvalidServerIdentity()) { + return ServerIdentityResult.fail( + "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); + } + ServerIdentity serverIdentity = parseServerIdentity(request); + return checker.check(serverIdentity, secured); + } + + private boolean isInvalidServerIdentity() { + return StringUtils.isBlank(authConfigs.getServerIdentityKey()) || StringUtils.isBlank( + authConfigs.getServerIdentityValue()); + } + + /** + * Parse server identity from protocol request. + * + * @param request protocol request + * @return nacos server identity. + */ + protected abstract ServerIdentity parseServerIdentity(R request); + /** * Get resource from secured annotation specified resource. * diff --git a/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java index 7b4c3197931..c8dd925d6df 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java @@ -18,6 +18,7 @@ import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.serveridentity.ServerIdentity; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Resource; import com.alibaba.nacos.auth.config.AuthConfigs; @@ -51,6 +52,7 @@ public GrpcProtocolAuthService(AuthConfigs authConfigs) { @Override public void initialize() { + super.initialize(); resourceParserMap.put(SignType.NAMING, new NamingGrpcResourceParser()); resourceParserMap.put(SignType.CONFIG, new ConfigGrpcResourceParser()); } @@ -73,4 +75,11 @@ public Resource parseResource(Request request, Secured secured) { public IdentityContext parseIdentity(Request request) { return identityContextBuilder.build(request); } + + @Override + protected ServerIdentity parseServerIdentity(Request request) { + String serverIdentityKey = authConfigs.getServerIdentityKey(); + String serverIdentity = request.getHeader(serverIdentityKey); + return new ServerIdentity(serverIdentityKey, serverIdentity); + } } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java index dfd99397167..131e0ac95c2 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.auth; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.serveridentity.ServerIdentity; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Resource; import com.alibaba.nacos.auth.config.AuthConfigs; @@ -51,6 +52,7 @@ public HttpProtocolAuthService(AuthConfigs authConfigs) { @Override public void initialize() { + super.initialize(); resourceParserMap.put(SignType.NAMING, new NamingHttpResourceParser()); resourceParserMap.put(SignType.CONFIG, new ConfigHttpResourceParser()); } @@ -72,4 +74,11 @@ public Resource parseResource(HttpServletRequest request, Secured secured) { public IdentityContext parseIdentity(HttpServletRequest request) { return identityContextBuilder.build(request); } + + @Override + protected ServerIdentity parseServerIdentity(HttpServletRequest request) { + String serverIdentityKey = authConfigs.getServerIdentityKey(); + String serverIdentity = request.getHeader(serverIdentityKey); + return new ServerIdentity(serverIdentityKey, serverIdentity); + } } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java index 0815db4d224..f2f99366931 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.auth; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -85,4 +86,13 @@ public interface ProtocolAuthService { * @throws AccessException exception during validating */ boolean validateAuthority(IdentityContext identityContext, Permission permission) throws AccessException; + + /** + * check server identity. + * + * @param request protocol request + * @param secured secured api secured annotation + * @return server identity result + */ + ServerIdentityResult checkServerIdentity(R request, Secured secured); } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java new file mode 100644 index 00000000000..d475a439a5e --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * 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 + * + * http://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 com.alibaba.nacos.auth.serveridentity; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.AuthConfigs; + +/** + * Nacos default server identity checker. + * + * @author xiweng.yy + */ +public class DefaultChecker implements ServerIdentityChecker { + + private AuthConfigs authConfigs; + + @Override + public void init(AuthConfigs authConfigs) { + this.authConfigs = authConfigs; + } + + @Override + public ServerIdentityResult check(ServerIdentity serverIdentity, Secured secured) { + if (authConfigs.getServerIdentityValue().equals(serverIdentity.getIdentityValue())) { + return ServerIdentityResult.success(); + } + return ServerIdentityResult.noMatched(); + } +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java new file mode 100644 index 00000000000..2c846e3f87d --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * 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 + * + * http://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 com.alibaba.nacos.auth.serveridentity; + +/** + * Nacos server identity. + * + * @author xiweng.yy + */ +public class ServerIdentity { + + private final String identityKey; + + private final String identityValue; + + public ServerIdentity(String identityKey, String identityValue) { + this.identityKey = identityKey; + this.identityValue = identityValue; + } + + public String getIdentityKey() { + return identityKey; + } + + public String getIdentityValue() { + return identityValue; + } +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java new file mode 100644 index 00000000000..4bb8031709c --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * 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 + * + * http://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 com.alibaba.nacos.auth.serveridentity; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.AuthConfigs; + +/** + * Nacos server identity checker for nacos inner/admin API identity check. + * + * @author xiweng.yy + */ +public interface ServerIdentityChecker { + + /** + * Do init checker. + * + * @param authConfigs config for nacos auth. + */ + void init(AuthConfigs authConfigs); + + /** + * Do check nacos server identity. + * + * @param serverIdentity server identity + * @param secured secured api secured annotation + * @return result of checking server identity + */ + ServerIdentityResult check(ServerIdentity serverIdentity, Secured secured); +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java new file mode 100644 index 00000000000..77469df5e32 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * 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 + * + * http://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 com.alibaba.nacos.auth.serveridentity; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; + +/** + * Server Identity Checker SPI holder. + * + * @author xiweng.yy + */ +public class ServerIdentityCheckerHolder { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerIdentityCheckerHolder.class); + + private static final ServerIdentityCheckerHolder INSTANCE = new ServerIdentityCheckerHolder(); + + private ServerIdentityChecker checker; + + private ServerIdentityCheckerHolder() { + tryGetCheckerBySpi(); + } + + public static ServerIdentityCheckerHolder getInstance() { + return INSTANCE; + } + + public ServerIdentityChecker getChecker() { + return checker; + } + + private synchronized void tryGetCheckerBySpi() { + Collection checkers = NacosServiceLoader.load(ServerIdentityChecker.class); + if (checkers.isEmpty()) { + checker = new DefaultChecker(); + LOGGER.info("Not found ServerIdentityChecker implementation from SPI, use default."); + return; + } + if (checkers.size() > 1) { + checker = showAllImplementations(checkers); + return; + } + checker = checkers.iterator().next(); + LOGGER.info("Found ServerIdentityChecker implementation {}", checker.getClass().getCanonicalName()); + } + + private ServerIdentityChecker showAllImplementations(Collection checkers) { + ServerIdentityChecker result = checkers.iterator().next(); + for (ServerIdentityChecker each : checkers) { + LOGGER.warn("Found ServerIdentityChecker implementation {}", each.getClass().getCanonicalName()); + } + LOGGER.warn("Found more than one ServerIdentityChecker implementation from SPI, use the first one {}.", + result.getClass().getCanonicalName()); + return result; + } +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java new file mode 100644 index 00000000000..68093d24f07 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * 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 + * + * http://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 com.alibaba.nacos.auth.serveridentity; + +/** + * Nacos server identity check result. + * + * @author xiweng.yy + */ +public class ServerIdentityResult { + + private final ResultStatus status; + + private final String message; + + private ServerIdentityResult(ResultStatus status, String message) { + this.status = status; + this.message = message; + } + + public ResultStatus getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public static ServerIdentityResult success() { + return new ServerIdentityResult(ResultStatus.MATCHED, "Server identity matched."); + } + + public static ServerIdentityResult noMatched() { + return new ServerIdentityResult(ResultStatus.NOT_MATCHED, "Server identity not matched."); + } + + public static ServerIdentityResult fail(String message) { + return new ServerIdentityResult(ResultStatus.FAIL, message); + } + + public enum ResultStatus { + + /** + * Nacos server identity matched. + */ + MATCHED, + + /** + * Nacos server identity not matched, need authentication. + */ + NOT_MATCHED, + + /** + * Nacos server identity check failed. + */ + FAIL; + } +} diff --git a/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java b/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java index b9a937d1833..034914f71ca 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.auth.mock.MockAuthPluginService; import com.alibaba.nacos.auth.mock.MockResourceParser; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -41,6 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GrpcProtocolAuthServiceTest { @@ -178,6 +180,47 @@ void testEnabledAuthWithoutPlugin() throws NoSuchMethodException { assertFalse(protocolAuthService.enableAuth(secured)); } + @Test + @Secured + void testCheckServerIdentityWithoutIdentityConfig() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityWithoutIdentityConfig"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + } + + @Test + @Secured + void testCheckServerIdentityNotMatched() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityNotMatched"); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + namingRequest.putHeader("1", "3"); + result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + } + + @Test + @Secured + void testCheckServerIdentityMatched() throws NoSuchMethodException { + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + namingRequest.putHeader("1", "2"); + Secured secured = getMethodSecure("testCheckServerIdentityMatched"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.MATCHED, result.getStatus()); + } + private Secured getMethodSecure(String methodName) throws NoSuchMethodException { Method method = GrpcProtocolAuthServiceTest.class.getDeclaredMethod(methodName); return method.getAnnotation(Secured.class); diff --git a/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java b/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java index 42e59024458..d27ab63bcc6 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.auth.mock.MockAuthPluginService; import com.alibaba.nacos.auth.mock.MockResourceParser; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -31,7 +32,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -45,6 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) // todo remove this @@ -63,12 +64,12 @@ class HttpProtocolAuthServiceTest { void setUp() throws Exception { protocolAuthService = new HttpProtocolAuthService(authConfigs); protocolAuthService.initialize(); - Mockito.when(request.getParameter(eq(CommonParams.NAMESPACE_ID))).thenReturn("testNNs"); - Mockito.when(request.getParameter(eq(CommonParams.GROUP_NAME))).thenReturn("testNG"); - Mockito.when(request.getParameter(eq(CommonParams.SERVICE_NAME))).thenReturn("testS"); - Mockito.when(request.getParameter(eq("tenant"))).thenReturn("testCNs"); - Mockito.when(request.getParameter(eq(Constants.GROUP))).thenReturn("testCG"); - Mockito.when(request.getParameter(eq(Constants.DATA_ID))).thenReturn("testD"); + when(request.getParameter(eq(CommonParams.NAMESPACE_ID))).thenReturn("testNNs"); + when(request.getParameter(eq(CommonParams.GROUP_NAME))).thenReturn("testNG"); + when(request.getParameter(eq(CommonParams.SERVICE_NAME))).thenReturn("testS"); + when(request.getParameter(eq("tenant"))).thenReturn("testCNs"); + when(request.getParameter(eq(Constants.GROUP))).thenReturn("testCG"); + when(request.getParameter(eq(Constants.DATA_ID))).thenReturn("testD"); } @Test @@ -139,7 +140,7 @@ void testValidateIdentityWithoutPlugin() throws AccessException { @Test void testValidateIdentityWithPlugin() throws AccessException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); IdentityContext identityContext = new IdentityContext(); assertFalse(protocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE)); } @@ -152,7 +153,7 @@ void testValidateAuthorityWithoutPlugin() throws AccessException { @Test void testValidateAuthorityWithPlugin() throws AccessException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); assertFalse(protocolAuthService.validateAuthority(new IdentityContext(), new Permission(Resource.EMPTY_RESOURCE, ""))); } @@ -160,7 +161,7 @@ void testValidateAuthorityWithPlugin() throws AccessException { @Test @Secured(signType = SignType.CONFIG) void testEnabledAuthWithPlugin() throws NoSuchMethodException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); Secured secured = getMethodSecure("testEnabledAuthWithPlugin"); assertTrue(protocolAuthService.enableAuth(secured)); } @@ -168,11 +169,49 @@ void testEnabledAuthWithPlugin() throws NoSuchMethodException { @Test @Secured(signType = SignType.CONFIG) void testEnabledAuthWithoutPlugin() throws NoSuchMethodException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist-plugin"); + when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist-plugin"); Secured secured = getMethodSecure("testEnabledAuthWithoutPlugin"); assertFalse(protocolAuthService.enableAuth(secured)); } + @Test + void testCheckServerIdentityWithoutIdentityConfig() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityWithoutIdentityConfig"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + } + + @Test + void testCheckServerIdentityNotMatched() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityNotMatched"); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + when(request.getHeader("1")).thenReturn("3"); + result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + } + + @Test + void testCheckServerIdentityMatched() throws NoSuchMethodException { + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + when(request.getHeader("1")).thenReturn("2"); + Secured secured = getMethodSecure("testCheckServerIdentityMatched"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.MATCHED, result.getStatus()); + } + private Secured getMethodSecure(String methodName) throws NoSuchMethodException { Method method = HttpProtocolAuthServiceTest.class.getDeclaredMethod(methodName); return method.getAnnotation(Secured.class); diff --git a/auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java b/auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java new file mode 100644 index 00000000000..3209a249bb0 --- /dev/null +++ b/auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * 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 + * + * http://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 com.alibaba.nacos.auth.serveridentity; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class ServerIdentityCheckerHolderTest { + + Map, Collection>> servicesMap; + + @BeforeEach + void setUp() { + servicesMap = (Map, Collection>>) ReflectionTestUtils.getField(NacosServiceLoader.class, + "SERVICES"); + } + + @AfterEach + void tearDown() { + servicesMap.remove(ServerIdentityChecker.class); + } + + @Test + void testConstructorWithSingleImplementation() + throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + ServerIdentityCheckerHolder holder = getNewHolder(1); + assertInstanceOf(MockChecker.class, holder.getChecker()); + } + + @Test + void testConstructorWithMultipleImplementation() + throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + ServerIdentityCheckerHolder holder = getNewHolder(2); + assertInstanceOf(MockChecker.class, holder.getChecker()); + } + + ServerIdentityCheckerHolder getNewHolder(int size) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + List> classes = new LinkedList<>(); + for (int i = 0; i < size; i++) { + classes.add(MockChecker.class); + } + servicesMap.put(ServerIdentityChecker.class, classes); + Constructor constructor = ServerIdentityCheckerHolder.class.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } + + public static class MockChecker implements ServerIdentityChecker { + + @Override + public void init(AuthConfigs authConfigs) { + } + + @Override + public ServerIdentityResult check(ServerIdentity serverIdentity, Secured secured) { + return ServerIdentityResult.success(); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java index 3cde9d830fa..e6827d10d1f 100644 --- a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java +++ b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java @@ -386,7 +386,7 @@ public Connection connectToServer(ServerInfo serverInfo) { grpcConn.setChannel(managedChannel); //send a setup request. ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest(); - conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion()); + conSetupRequest.setClientVersion(getClientVersion()); conSetupRequest.setLabels(super.getLabels()); // set ability table conSetupRequest.setAbilityTable( @@ -416,6 +416,10 @@ public Connection connectToServer(ServerInfo serverInfo) { return null; } + protected String getClientVersion() { + return VersionUtils.getFullClientVersion(); + } + /** * ability mode: sdk client or cluster client. * diff --git a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java index 69530afd833..b949352e5de 100644 --- a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java +++ b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.api.ability.constant.AbilityMode; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.common.remote.client.RpcClientTlsConfig; +import com.alibaba.nacos.common.utils.VersionUtils; import java.util.Map; @@ -30,6 +31,8 @@ */ public class GrpcClusterClient extends GrpcClient { + private static final String CLUSTER_CLIENT_VERSION_PREFIX = "Nacos-Server:v"; + /** * Empty constructor. * @@ -71,6 +74,11 @@ protected AbilityMode abilityMode() { return AbilityMode.CLUSTER_CLIENT; } + @Override + protected String getClientVersion() { + return CLUSTER_CLIENT_VERSION_PREFIX + VersionUtils.version; + } + @Override public int rpcPortOffset() { return Integer.parseInt(System.getProperty(GrpcConstants.NACOS_SERVER_GRPC_PORT_OFFSET_KEY, diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java index d53152f98c0..7eb34abfa8a 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java @@ -29,6 +29,7 @@ import com.alibaba.nacos.config.server.service.ConfigOperationService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.core.auth.AuthFilter; +import com.alibaba.nacos.core.code.ControllerMethodsCache; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.sys.env.EnvUtil; import com.fasterxml.jackson.databind.JsonNode; @@ -52,7 +53,9 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -91,6 +94,9 @@ class ConfigControllerV2Test { @Mock private AuthConfigs authConfigs; + @Mock + private ControllerMethodsCache controllerMethodsCache; + private ConfigControllerV2 configControllerV2; private MockMvc mockmvc; @@ -311,10 +317,14 @@ void testGetConfigFuzzyByDetail() throws Exception { @Test void testGetConfigAuthFilter() throws Exception { when(authConfigs.isAuthEnabled()).thenReturn(true); - - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail") - .param("search", "accurate").param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") - .param("config_tags", "").param("pageNo", "1").param("pageSize", "10").param("config_detail", "server.port"); + Method method = Arrays.stream(ConfigControllerV2.class.getMethods()) + .filter(m -> m.getName().equals("searchConfigByDetails")).findFirst().get(); + when(controllerMethodsCache.getMethod(any(HttpServletRequest.class))).thenReturn(method); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail").param("search", "accurate") + .param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") + .param("config_tags", "").param("pageNo", "1").param("pageSize", "10") + .param("config_detail", "server.port"); MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); diff --git a/console/src/main/resources/application.properties b/console/src/main/resources/application.properties index 0e40b041e6d..a3629d9118e 100644 --- a/console/src/main/resources/application.properties +++ b/console/src/main/resources/application.properties @@ -33,14 +33,14 @@ server.port=8848 ### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced. # spring.datasource.platform=mysql # nacos.plugin.datasource.log.enabled=true -spring.sql.init.platform=derby +#spring.sql.init.platform=mysql ### Count of DB: -db.num=1 +# db.num=1 ### Connect URL of DB: -db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai -db.user=root -db.password=11021102 +# db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC +# db.user=nacos +# db.password=nacos ### the maximum retry times for push nacos.config.push.maxRetryTime=50 @@ -113,7 +113,7 @@ nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/ nacos.core.auth.system.type=nacos ### If turn on auth system: -nacos.core.auth.enabled=true +nacos.core.auth.enabled=false ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=true @@ -123,8 +123,8 @@ nacos.core.auth.enable.userAgentAuthWhite=false ### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false. ### The two properties is the white list for auth and used by identity the request from other server. -nacos.core.auth.server.identity.key=servr123 -nacos.core.auth.server.identity.value=servr123value +nacos.core.auth.server.identity.key= +nacos.core.auth.server.identity.value= ### worked when nacos.core.auth.system.type=nacos ### The token expiration in seconds: @@ -132,7 +132,7 @@ nacos.core.auth.plugin.nacos.token.cache.enable=false nacos.core.auth.plugin.nacos.token.expire.seconds=18000 ### The default token (Base64 string): #nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 -nacos.core.auth.plugin.nacos.token.secret.key=key12345key12345key12345SecretKey012345678901234567890123456789012345678901234567890123456789 +nacos.core.auth.plugin.nacos.token.secret.key= ### worked when nacos.core.auth.system.type=ldap,{0} is Placeholder,replace login username #nacos.core.auth.ldap.url=ldap://localhost:389 diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java index 5bf98b5f1e6..9fdc0879169 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -19,18 +19,16 @@ import com.alibaba.nacos.auth.HttpProtocolAuthService; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.common.utils.ExceptionUtil; -import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.code.ControllerMethodsCache; import com.alibaba.nacos.core.context.RequestContext; import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.core.utils.Loggers; -import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.sys.env.Constants; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -75,30 +73,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; - if (authConfigs.isEnableUserAgentAuthWhite()) { - String userAgent = WebUtils.getUserAgent(req); - if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) { - chain.doFilter(request, response); - return; - } - } else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils.isNotBlank( - authConfigs.getServerIdentityValue())) { - String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey()); - if (StringUtils.isNotBlank(serverIdentity)) { - if (authConfigs.getServerIdentityValue().equals(serverIdentity)) { - chain.doFilter(request, response); - return; - } - Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(), - req.getRemoteHost()); - } - } else { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, - "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" - + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); - return; - } - try { Method method = methodsCache.getMethod(req); @@ -108,13 +82,26 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha return; } - if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { + if (method.isAnnotationPresent(Secured.class)) { if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); } Secured secured = method.getAnnotation(Secured.class); + + ServerIdentityResult serverIdentityResult = protocolAuthService.checkServerIdentity(req, secured); + switch (serverIdentityResult.getStatus()) { + case FAIL: + resp.sendError(HttpServletResponse.SC_FORBIDDEN, serverIdentityResult.getMessage()); + return; + case MATCHED: + chain.doFilter(request, response); + return; + default: + break; + } + if (!protocolAuthService.enableAuth(secured)) { chain.doFilter(request, response); return; diff --git a/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java index 5baa64bf268..62e38af1ddc 100644 --- a/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java @@ -17,30 +17,38 @@ package com.alibaba.nacos.core.auth; +import com.alibaba.nacos.auth.HttpProtocolAuthService; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.common.constant.HttpHeaderConsts; import com.alibaba.nacos.core.code.ControllerMethodsCache; import com.alibaba.nacos.core.context.RequestContextHolder; -import com.alibaba.nacos.sys.env.Constants; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Permission; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.exception.AccessException; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * {@link AuthFilter} unit test. @@ -51,7 +59,6 @@ @ExtendWith(MockitoExtension.class) class AuthFilterTest { - @InjectMocks private AuthFilter authFilter; @Mock @@ -60,52 +67,159 @@ class AuthFilterTest { @Mock private ControllerMethodsCache methodsCache; + @Mock + FilterChain filterChain; + + @Mock + HttpServletRequest request; + + @Mock + HttpServletResponse response; + + @BeforeEach + void setUp() { + authFilter = new AuthFilter(authConfigs, methodsCache); + } + @AfterEach void tearDown() { RequestContextHolder.removeContext(); } @Test - void testDoFilter() { - try { - FilterChain filterChain = new MockFilterChain(); - Mockito.when(authConfigs.isAuthEnabled()).thenReturn(true); - MockHttpServletRequest request = new MockHttpServletRequest(); - HttpServletResponse response = new MockHttpServletResponse(); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(authConfigs.isEnableUserAgentAuthWhite()).thenReturn(true); - request.addHeader(HttpHeaderConsts.USER_AGENT_HEADER, Constants.NACOS_SERVER_HEADER); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(authConfigs.isEnableUserAgentAuthWhite()).thenReturn(false); - Mockito.when(authConfigs.getServerIdentityKey()).thenReturn("1"); - Mockito.when(authConfigs.getServerIdentityValue()).thenReturn("2"); - request.addHeader("1", "2"); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(authConfigs.getServerIdentityValue()).thenReturn("3"); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(methodsCache.getMethod(Mockito.any())).thenReturn(filterChain.getClass().getMethod("testSecured")); - authFilter.doFilter(request, response, filterChain); - - } catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage()); - } + void testDoFilterDisabledAuth() throws ServletException, IOException { + when(authConfigs.isAuthEnabled()).thenReturn(false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithoutServerIdentity() throws ServletException, IOException, NoSuchMethodException { + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithoutServerIdentity")); + when(authConfigs.isAuthEnabled()).thenReturn(true); + authFilter.doFilter(request, response, filterChain); + verify(filterChain, never()).doFilter(request, response); + verify(response).sendError(403, + "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); + } + + @Test + @Secured + void testDoFilterWithServerIdentity() throws ServletException, IOException, NoSuchMethodException { + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithServerIdentity")); + when(authConfigs.isAuthEnabled()).thenReturn(true); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + when(request.getHeader("1")).thenReturn("2"); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithoutMethod() throws ServletException, IOException { + when(authConfigs.isAuthEnabled()).thenReturn(true); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + void testDoFilterWithoutSecured() throws ServletException, IOException, NoSuchMethodException { + when(authConfigs.isAuthEnabled()).thenReturn(true); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithoutSecured")); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithNoNeedAuthSecured() throws NoSuchMethodException, ServletException, IOException { + when(authConfigs.isAuthEnabled()).thenReturn(true); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNoNeedAuthSecured")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredSuccess() + throws NoSuchMethodException, ServletException, IOException, AccessException { + when(authConfigs.isAuthEnabled()).thenReturn(true); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNeedAuthSecuredSuccess")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(true); + when(protocolAuthService.validateAuthority(any(IdentityContext.class), any(Permission.class))).thenReturn(true); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredIdentityFailure() + throws NoSuchMethodException, ServletException, IOException, AccessException { + when(authConfigs.isAuthEnabled()).thenReturn(true); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNeedAuthSecuredIdentityFailure")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain, never()).doFilter(request, response); + verify(response).sendError(eq(403), anyString()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredAuthorityFailure() + throws NoSuchMethodException, ServletException, IOException, AccessException { + when(authConfigs.isAuthEnabled()).thenReturn(true); + when(authConfigs.getServerIdentityKey()).thenReturn("1"); + when(authConfigs.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNeedAuthSecuredAuthorityFailure")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(true); + when(protocolAuthService.validateAuthority(any(IdentityContext.class), any(Permission.class))).thenReturn( + false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain, never()).doFilter(request, response); + verify(response).sendError(eq(403), anyString()); } - class MockFilterChain implements FilterChain { - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException { - System.out.println("filter chain executed"); - } - - @Secured(resource = "xx") - public void testSecured() { - - } + private HttpProtocolAuthService injectMockPlugins() { + HttpProtocolAuthService protocolAuthService = new HttpProtocolAuthService(authConfigs); + protocolAuthService.initialize(); + HttpProtocolAuthService spyProtocolAuthService = spy(protocolAuthService); + ReflectionTestUtils.setField(authFilter, "protocolAuthService", spyProtocolAuthService); + return spyProtocolAuthService; } } From cdb9cce88c3e11efef810990af4430a5cc5f4a63 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:51:33 +0800 Subject: [PATCH 5/8] Bump org.apache.tomcat.embed:tomcat-embed-core from 9.0.93 to 9.0.96 (#12862) Bumps org.apache.tomcat.embed:tomcat-embed-core from 9.0.93 to 9.0.96. --- updated-dependencies: - dependency-name: org.apache.tomcat.embed:tomcat-embed-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1dd884543cf..0984998a812 100644 --- a/pom.xml +++ b/pom.xml @@ -154,7 +154,7 @@ 5.3.39 5.8.15 - 9.0.93 + 9.0.96 From 93ea0ddb137f703004a95ffa1744ee983d120483 Mon Sep 17 00:00:00 2001 From: littlesparklet <112364429+littlesparklet@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:05:44 +0800 Subject: [PATCH 6/8] Remove excess code.(related issue #12871 ) (#12874) --- console-ui/src/locales/en-US.js | 3 --- console-ui/src/locales/zh-CN.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/console-ui/src/locales/en-US.js b/console-ui/src/locales/en-US.js index c399b39b085..941cf8ef25a 100644 --- a/console-ui/src/locales/en-US.js +++ b/console-ui/src/locales/en-US.js @@ -254,9 +254,6 @@ const I18N_CONF = { lastUpdateTime: 'Last Modified At', operator: 'Operator', operation: 'Operation', - publishType: 'Publish Type', - formal: 'Formal Version', - gray: 'Gray Version', compare: 'Compare', historyCompareTitle: 'History Compare', historyCompareLastVersion: 'Lasted Release Version', diff --git a/console-ui/src/locales/zh-CN.js b/console-ui/src/locales/zh-CN.js index 001c49f48f3..9c75b6595f3 100644 --- a/console-ui/src/locales/zh-CN.js +++ b/console-ui/src/locales/zh-CN.js @@ -251,9 +251,6 @@ const I18N_CONF = { articleMeetRequirements: '条满足要求的配置。', lastUpdateTime: '最后更新时间', operator: '操作人', - publishType: '发布类型', - formal: '正式版本', - gray: '灰度版本', operation: '操作', compare: '比较', historyCompareTitle: '历史版本比较', From c5e5a8223167677a0df8d6e0cf9e16303faf58d6 Mon Sep 17 00:00:00 2001 From: shiyiyue1102 Date: Thu, 21 Nov 2024 15:11:31 +0800 Subject: [PATCH 7/8] fix type search on mysql model (#12875) --- .../plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java index dd4de5b6efe..cd6925531e9 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java @@ -250,7 +250,7 @@ public MapperResult findConfigInfoLike4PageFetchRows(MapperContext context) { where.and().like("content", content); } if (!ArrayUtils.isEmpty(types)) { - where.in("type", types); + where.and().in("type", types); } where.limit(context.getStartRow(), context.getPageSize()); return where.build(); From 2a0dafa2f4e1a8f1fac38e8bf750b77858c03af3 Mon Sep 17 00:00:00 2001 From: Daniella Hubble <68478626+DaniellaHubble@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:14:05 -0600 Subject: [PATCH 8/8] fix flaky tests (#12885) --- .../client/naming/selector/NamingSelectorFactoryTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java index 3c3aa27ad95..f4520de8801 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java @@ -24,7 +24,7 @@ import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -92,6 +92,7 @@ public void testNewIpSelector() { @Test public void testNewMetadataSelector() { Instance ins1 = new Instance(); + ins1.setMetadata(new LinkedHashMap<>()); ins1.addMetadata("a", "1"); ins1.addMetadata("b", "2"); Instance ins2 = new Instance(); @@ -102,7 +103,7 @@ public void testNewMetadataSelector() { NamingContext namingContext = mock(NamingContext.class); when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); - NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new HashMap() { + NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new LinkedHashMap() { { put("a", "1"); put("b", "2"); @@ -117,6 +118,7 @@ public void testNewMetadataSelector() { @Test public void testNewMetadataSelector2() { Instance ins1 = new Instance(); + ins1.setMetadata(new LinkedHashMap<>()); ins1.addMetadata("a", "1"); ins1.addMetadata("c", "3"); Instance ins2 = new Instance(); @@ -127,7 +129,7 @@ public void testNewMetadataSelector2() { NamingContext namingContext = mock(NamingContext.class); when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); - NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new HashMap() { + NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new LinkedHashMap() { { put("a", "1"); put("b", "2");