chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:52 +08:00
commit 980028369b
6111 changed files with 1026474 additions and 0 deletions
@@ -0,0 +1,147 @@
/*
* Copyright 1999-2025 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.JustForTest;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.lifecycle.Closeable;
import com.alibaba.nacos.common.remote.client.ServerListFactory;
import com.alibaba.nacos.common.spi.NacosServiceLoader;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* Server list Manager.
*
* @author totalo
*/
public abstract class AbstractServerListManager implements ServerListFactory, Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractServerListManager.class);
protected ServerListProvider serverListProvider;
protected NacosClientProperties properties;
public AbstractServerListManager(NacosClientProperties properties) {
this(properties, null);
}
public AbstractServerListManager(NacosClientProperties properties, String namespace) {
// To avoid set operation affect the original properties.
NacosClientProperties tmpProperties = properties.derive();
if (StringUtils.isNotBlank(namespace)) {
tmpProperties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
}
tmpProperties.setProperty(Constants.CLIENT_MODULE_TYPE, getModuleName());
this.properties = tmpProperties;
}
@Override
public List<String> getServerList() {
return serverListProvider.getServerList();
}
@Override
public void shutdown() throws NacosException {
String className = this.getClass().getName();
LOGGER.info("{} do shutdown begin", className);
if (null != serverListProvider) {
serverListProvider.shutdown();
}
serverListProvider = null;
LOGGER.info("{} do shutdown stop", className);
}
/**
* Start server list manager.
*
* @throws NacosException during start and initialize.
*/
public void start() throws NacosException {
Collection<ServerListProvider> serverListProviders =
NacosServiceLoader.load(ServerListProvider.class);
Collection<ServerListProvider> sorted = serverListProviders.stream()
.sorted((a, b) -> b.getOrder() - a.getOrder()).collect(Collectors.toList());
for (ServerListProvider each : sorted) {
boolean matchResult = each.match(properties);
LOGGER.info("Load and match ServerListProvider {}, match result: {}",
each.getClass().getCanonicalName(),
matchResult);
if (matchResult) {
this.serverListProvider = each;
LOGGER.info("Will use {} as ServerListProvider",
this.serverListProvider.getClass().getCanonicalName());
break;
}
}
if (null == serverListProvider) {
LOGGER.error("No server list provider found, SPI load size: {}", sorted.size());
throw new NacosException(NacosException.CLIENT_INVALID_PARAM,
"No server list provider found.");
}
this.serverListProvider.init(properties, getNacosRestTemplate());
}
public String getServerName() {
return getModuleName() + "-" + serverListProvider.getServerName();
}
public String getContextPath() {
return serverListProvider.getContextPath();
}
public String getNamespace() {
return serverListProvider.getNamespace();
}
public String getAddressSource() {
return serverListProvider.getAddressSource();
}
public boolean isFixed() {
return serverListProvider.isFixed();
}
/**
* get module name.
*
* @return module name
*/
protected abstract String getModuleName();
/**
* get nacos rest template.
*
* @return nacos rest template
*/
protected abstract NacosRestTemplate getNacosRestTemplate();
@JustForTest
NacosClientProperties getProperties() {
return properties;
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 1999-2024 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.List;
/**
* Address server list provider.
*
* @author totalo
*/
public abstract class AbstractServerListProvider implements ServerListProvider {
protected String contextPath = ClientBasicParamUtil.getDefaultContextPath();
protected String namespace = "";
@Override
public void init(final NacosClientProperties properties,
final NacosRestTemplate nacosRestTemplate) throws NacosException {
if (null == properties) {
throw new NacosException(NacosException.INVALID_PARAM, "properties is null");
}
initContextPath(properties);
initNameSpace(properties);
}
/**
* Get server list.
* @return server list
*/
@Override
public abstract List<String> getServerList();
/**
* Get server name.
* @return server name
*/
@Override
public abstract String getServerName();
/**
* Get order.
* @return order
*/
@Override
public abstract int getOrder();
public String getContextPath() {
return contextPath;
}
public String getNamespace() {
return namespace;
}
private void initContextPath(NacosClientProperties properties) {
String contentPathTmp = properties.getProperty(PropertyKeyConst.CONTEXT_PATH);
if (!StringUtils.isBlank(contentPathTmp)) {
this.contextPath = contentPathTmp;
}
}
private void initNameSpace(NacosClientProperties properties) {
String namespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
if (StringUtils.isNotBlank(namespace)) {
this.namespace = namespace;
}
}
}
@@ -0,0 +1,303 @@
/*
* Copyright 1999-2025 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.constant.Constants.Address;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
import com.alibaba.nacos.client.utils.ContextPathUtil;
import com.alibaba.nacos.client.utils.TemplateUtils;
import com.alibaba.nacos.common.executor.NameThreadFactory;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.HttpUtils;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.InternetAddressUtil;
import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Endpoint server list provider.
*
* @author totalo
*/
public class EndpointServerListProvider extends AbstractServerListProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(EndpointServerListProvider.class);
private static final boolean USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE = true;
private NacosRestTemplate nacosRestTemplate;
private static final String CUSTOM_NAME = "custom";
private final long refreshServerListInternal = TimeUnit.SECONDS.toMillis(30);
private final int initServerListRetryTimes = 5;
private long lastServerListRefreshTime = 0L;
private ScheduledExecutorService refreshServerListExecutor;
private String endpoint;
private int endpointPort = 8080;
private String endpointContextPath;
private String serverListName = ClientBasicParamUtil.getDefaultNodesPath();
private volatile List<String> serversFromEndpoint = new ArrayList<>();
private String addressServerUrl;
private String moduleName = "default";
@Override
public void init(final NacosClientProperties properties,
final NacosRestTemplate nacosRestTemplate)
throws NacosException {
super.init(properties, nacosRestTemplate);
this.nacosRestTemplate = nacosRestTemplate;
initEndpoint(properties);
initEndpointPort(properties);
initEndpointContextPath(properties);
initServerListName(properties);
initAddressServerUrl(properties);
initModuleName(properties);
startRefreshServerListTask(properties);
}
@Override
public List<String> getServerList() {
return serversFromEndpoint;
}
@Override
public String getServerName() {
String contextPathTmp =
StringUtils.isNotBlank(this.endpointContextPath) ? this.endpointContextPath
: this.contextPath;
return CUSTOM_NAME + "-"
+ String.join("_", endpoint, String.valueOf(endpointPort), contextPathTmp,
serverListName)
+ (StringUtils.isNotBlank(namespace) ? ("_" + StringUtils.trim(namespace)) : "");
}
@Override
public int getOrder() {
return Address.ENDPOINT_SERVER_LIST_PROVIDER_ORDER;
}
@Override
public boolean match(final NacosClientProperties properties) {
String endpointTmp = getEndPointTmp(properties);
return StringUtils.isNotBlank(endpointTmp);
}
@Override
public String getAddressSource() {
return this.addressServerUrl;
}
private String getEndPointTmp(NacosClientProperties properties) {
String endpointTmp = properties.getProperty(PropertyKeyConst.ENDPOINT);
String isUseEndpointRuleParsing =
properties.getProperty(PropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
properties.getProperty(SystemPropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
String.valueOf(USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE)));
if (Boolean.parseBoolean(isUseEndpointRuleParsing)) {
endpointTmp = ClientBasicParamUtil.parsingEndpointRule(endpointTmp);
}
return endpointTmp;
}
/**
* Start refresh server list task.
*
* @throws NacosException nacos exception
*/
public void startRefreshServerListTask(NacosClientProperties properties) throws NacosException {
for (int i = 0; i < initServerListRetryTimes && getServerList().isEmpty(); ++i) {
refreshServerListIfNeed();
if (!serversFromEndpoint.isEmpty()) {
break;
}
try {
this.wait((i + 1) * 100L);
} catch (Exception e) {
LOGGER.warn("get serverlist fail,url: {}", addressServerUrl);
}
}
if (serversFromEndpoint.isEmpty()) {
LOGGER.error("[init-serverlist] fail to get NACOS-server serverlist! url: {}",
addressServerUrl);
throw new NacosException(NacosException.SERVER_ERROR,
"fail to get NACOS-server serverlist! not connnect url:" + addressServerUrl);
}
refreshServerListExecutor = new ScheduledThreadPoolExecutor(1,
new NameThreadFactory(
"com.alibaba.nacos.client.address.EndpointServerListProvider.refreshServerList"));
// executor schedules the timer task
long refreshInterval = Long.parseLong(
properties.getProperty(PropertyKeyConst.ENDPOINT_REFRESH_INTERVAL_SECONDS, "30"));
refreshServerListExecutor.scheduleWithFixedDelay(this::refreshServerListIfNeed, 0L,
refreshInterval,
TimeUnit.SECONDS);
}
private void refreshServerListIfNeed() {
try {
if (System.currentTimeMillis()
- lastServerListRefreshTime < refreshServerListInternal) {
return;
}
List<String> list = getServerListFromEndpoint();
if (CollectionUtils.isEmpty(list)) {
throw new Exception("Can not acquire Nacos list");
}
list.sort(String::compareTo);
if (!CollectionUtils.isEqualCollection(list, serversFromEndpoint)) {
LOGGER.info("[SERVER-LIST] server list is updated: {}", list);
serversFromEndpoint = list;
lastServerListRefreshTime = System.currentTimeMillis();
NotifyCenter.publishEvent(new ServerListChangeEvent());
}
} catch (Throwable e) {
LOGGER.warn("failed to update server list", e);
}
}
private List<String> getServerListFromEndpoint() {
try {
HttpRestResult<String> httpResult = nacosRestTemplate.get(addressServerUrl,
HttpUtils.builderHeader(moduleName), Query.EMPTY, String.class);
if (!httpResult.ok()) {
LOGGER.error("[check-serverlist] error. addressServerUrl: {}, code: {}",
addressServerUrl,
httpResult.getCode());
return null;
}
List<String> lines = IoUtils.readLines(new StringReader(httpResult.getData()));
List<String> result = new ArrayList<>(lines.size());
for (String serverAddr : lines) {
String[] ipPort = InternetAddressUtil.splitIpPortStr(serverAddr);
String ip = ipPort[0].trim();
if (ipPort.length == 1) {
result.add(ip + InternetAddressUtil.IP_PORT_SPLITER
+ ClientBasicParamUtil.getDefaultServerPort());
} else {
result.add(serverAddr);
}
}
return result;
} catch (Exception e) {
LOGGER.error("[check-serverlist] exception. url: {}", addressServerUrl, e);
return null;
}
}
private void initEndpoint(NacosClientProperties properties) {
// Endpoint should not be null or empty, because the match has return `true`.
this.endpoint = getEndPointTmp(properties);
}
private void initEndpointPort(NacosClientProperties properties) {
String endpointPortTmp = TemplateUtils.stringEmptyAndThenExecute(
properties.getProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_PORT),
() -> properties.getProperty(PropertyKeyConst.ENDPOINT_PORT));
if (StringUtils.isNotBlank(endpointPortTmp)) {
this.endpointPort = Integer.parseInt(endpointPortTmp);
}
}
private void initEndpointContextPath(NacosClientProperties properties) {
String endpointContextPathTmp = TemplateUtils.stringEmptyAndThenExecute(
properties.getProperty(
PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_CONTEXT_PATH),
() -> properties.getProperty(PropertyKeyConst.ENDPOINT_CONTEXT_PATH));
if (StringUtils.isNotBlank(endpointContextPathTmp)) {
this.endpointContextPath = endpointContextPathTmp;
}
}
private void initServerListName(NacosClientProperties properties) {
String serverListNameTmp = properties.getProperty(PropertyKeyConst.ENDPOINT_CLUSTER_NAME);
boolean isUseClusterName = Boolean.parseBoolean(
properties.getProperty(PropertyKeyConst.IS_ADAPT_CLUSTER_NAME_USAGE));
if (StringUtils.isBlank(serverListNameTmp) && isUseClusterName) {
serverListNameTmp = properties.getProperty(PropertyKeyConst.CLUSTER_NAME);
}
if (!StringUtils.isBlank(serverListNameTmp)) {
this.serverListName = serverListNameTmp;
}
}
private void initAddressServerUrl(NacosClientProperties properties) {
String contextPathTmp = StringUtils.isNotBlank(this.endpointContextPath)
? ContextPathUtil.normalizeContextPath(
this.endpointContextPath)
: ContextPathUtil.normalizeContextPath(this.contextPath);
StringBuilder addressServerUrlTem = new StringBuilder(
String.format("http://%s:%d%s/%s", this.endpoint, this.endpointPort, contextPathTmp,
this.serverListName));
boolean hasQueryString = false;
if (StringUtils.isNotBlank(namespace)) {
addressServerUrlTem.append("?namespace=").append(namespace);
hasQueryString = true;
}
if (properties.containsKey(PropertyKeyConst.ENDPOINT_QUERY_PARAMS)) {
addressServerUrlTem.append(hasQueryString ? "&" : "?");
addressServerUrlTem
.append(properties.getProperty(PropertyKeyConst.ENDPOINT_QUERY_PARAMS));
}
this.addressServerUrl = addressServerUrlTem.toString();
LOGGER.info("address server url = {}", this.addressServerUrl);
}
private void initModuleName(NacosClientProperties properties) {
String moduleNameTmp = properties.getProperty(Constants.CLIENT_MODULE_TYPE);
if (StringUtils.isNotBlank(moduleNameTmp)) {
this.moduleName = moduleNameTmp;
}
}
@Override
public void shutdown() throws NacosException {
if (null != refreshServerListExecutor) {
refreshServerListExecutor.shutdown();
}
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 1999-2024 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.constant.Constants.Address;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.utils.InternetAddressUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import static com.alibaba.nacos.common.constant.RequestUrlConstants.HTTPS_PREFIX;
import static com.alibaba.nacos.common.constant.RequestUrlConstants.HTTP_PREFIX;
/**
* Properties server list provider.
*
* @author totalo
*/
public class PropertiesListProvider extends AbstractServerListProvider {
private static final String FIXED_NAME = "fixed";
private List<String> serverList;
@Override
public void init(final NacosClientProperties properties,
final NacosRestTemplate nacosRestTemplate) throws NacosException {
super.init(properties, nacosRestTemplate);
serverList = new ArrayList<>();
String serverAddrsStr = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
StringTokenizer serverAddrsTokens = new StringTokenizer(serverAddrsStr, ",;");
while (serverAddrsTokens.hasMoreTokens()) {
String serverAddr = serverAddrsTokens.nextToken().trim();
if (serverAddr.startsWith(HTTP_PREFIX) || serverAddr.startsWith(HTTPS_PREFIX)) {
this.serverList.add(serverAddr);
} else {
String[] serverAddrArr = InternetAddressUtil.splitIpPortStr(serverAddr);
if (serverAddrArr.length == 1) {
this.serverList
.add(serverAddrArr[0] + InternetAddressUtil.IP_PORT_SPLITER
+ ClientBasicParamUtil.getDefaultServerPort());
} else {
this.serverList.add(serverAddr);
}
}
}
}
@Override
public List<String> getServerList() {
return serverList;
}
@Override
public String getServerName() {
return FIXED_NAME + "-"
+ (StringUtils.isNotBlank(namespace) ? (StringUtils.trim(namespace) + "-")
: "")
+ ClientBasicParamUtil.getNameSuffixByServerIps(serverList.toArray(new String[0]));
}
@Override
public int getOrder() {
return Address.ADDRESS_SERVER_LIST_PROVIDER_ORDER;
}
@Override
public boolean match(final NacosClientProperties properties) {
return StringUtils.isNotBlank(properties.getProperty(PropertyKeyConst.SERVER_ADDR));
}
@Override
public boolean isFixed() {
return true;
}
@Override
public void shutdown() throws NacosException {
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 1999-2018 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.client.address;
import com.alibaba.nacos.common.notify.SlowEvent;
/**
* Server List Change Event.
*
* @author zongtanghu
*/
public class ServerListChangeEvent extends SlowEvent {
private static final long serialVersionUID = -1655577508567092395L;
}
@@ -0,0 +1,102 @@
/*
* Copyright 1999-2025 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.client.address;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.lifecycle.Closeable;
import java.util.List;
/**
* Server list provider.
*
* @author totalo
*/
public interface ServerListProvider extends Closeable {
/**
* Init.
* @param properties nacos client properties
* @param nacosRestTemplate nacos rest template
* @throws NacosException nacos exception
*/
void init(final NacosClientProperties properties, final NacosRestTemplate nacosRestTemplate)
throws NacosException;
/**
* Get server list.
* @return server list
*/
List<String> getServerList();
/**
* Get server name.
* @return server name
*/
default String getServerName() {
return "";
}
/**
* Get namespace.
* @return namespace
*/
default String getNamespace() {
return "";
}
/**
* Get context path.
* @return context path
*/
default String getContextPath() {
return ClientBasicParamUtil.getDefaultContextPath();
}
/**
* Get order.
* @return order
*/
int getOrder();
/**
* Match.
* @param properties nacos client properties
* @return match
*/
boolean match(final NacosClientProperties properties);
/**
* check the server list is fixed or not.
* @return true if the server list is fixed
*/
default boolean isFixed() {
return false;
}
/**
* Get address source.
* @return address source
*/
default String getAddressSource() {
return "";
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 1999-2021 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.client.auth.impl;
/**
* Nacos auth login constants.
*
* @author Nacos
*/
public class NacosAuthLoginConstant {
public static final String ACCESSTOKEN = "accessToken";
public static final String TOKENTTL = "tokenTtl";
public static final String TOKENREFRESHWINDOW = "tokenRefreshWindow";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
public static final String COLON = ":";
public static final String SERVER = "server";
public static final String RELOGINFLAG = "reLoginFlag";
}
@@ -0,0 +1,143 @@
/*
* Copyright 1999-2021 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.client.auth.impl;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.impl.process.HttpLoginProcessor;
import com.alibaba.nacos.common.utils.RandomUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/**
* a ClientAuthService implement.
*
* @author wuyfee
*/
@SuppressWarnings("checkstyle:SummaryJavadoc")
public class NacosClientAuthServiceImpl extends AbstractClientAuthService {
private static final Logger SECURITY_LOGGER =
LoggerFactory.getLogger(NacosClientAuthServiceImpl.class);
/**
* TTL of token in seconds.
*/
private long tokenTtl;
/**
* Last timestamp refresh security info from server.
*/
private long lastRefreshTime;
/**
* time window to refresh security info in seconds.
*/
private long tokenRefreshWindow;
/**
* A context to take with when sending request to Nacos server.
*/
private volatile LoginIdentityContext loginIdentityContext = new LoginIdentityContext();
/**
* Re-login window in milliseconds.
*/
private final long reLoginWindow = 60000;
/**
* Login to servers.
*
* @return true if login successfully
*/
@Override
public Boolean login(Properties properties) {
try {
boolean reLoginFlag = Boolean.parseBoolean(
loginIdentityContext.getParameter(NacosAuthLoginConstant.RELOGINFLAG, "false"));
if (reLoginFlag) {
if ((System.currentTimeMillis() - lastRefreshTime) < reLoginWindow) {
return true;
}
} else {
if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS
.toMillis(tokenTtl - tokenRefreshWindow)) {
return true;
}
}
if (StringUtils.isBlank(properties.getProperty(PropertyKeyConst.USERNAME))) {
lastRefreshTime = System.currentTimeMillis();
return true;
}
for (String server : this.serverList) {
HttpLoginProcessor httpLoginProcessor = new HttpLoginProcessor(nacosRestTemplate);
properties.setProperty(NacosAuthLoginConstant.SERVER, server);
LoginIdentityContext identityContext = httpLoginProcessor.getResponse(properties);
if (identityContext != null) {
if (identityContext.getAllKey().contains(NacosAuthLoginConstant.ACCESSTOKEN)) {
tokenTtl = Long.parseLong(
identityContext.getParameter(NacosAuthLoginConstant.TOKENTTL));
tokenRefreshWindow = generateTokenRefreshWindow(tokenTtl);
lastRefreshTime = System.currentTimeMillis();
LoginIdentityContext newCtx = new LoginIdentityContext();
newCtx.setParameter(NacosAuthLoginConstant.ACCESSTOKEN,
identityContext.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
this.loginIdentityContext = newCtx;
}
return true;
}
}
} catch (Throwable throwable) {
SECURITY_LOGGER.warn("[SecurityProxy] login failed, error: ", throwable);
return false;
}
return false;
}
@Override
public LoginIdentityContext getLoginIdentityContext(RequestResource resource) {
return this.loginIdentityContext;
}
@Override
public void shutdown() throws NacosException {
}
/**
* Randomly generate TokenRefreshWindow, Avoid a large number of logins causing pressure on the Nacos server.
* @param tokenTtl TTL of token in seconds.
* @return tokenRefreshWindow, numerical range [tokenTtl/15 ~ tokenTtl/10]
*/
public long generateTokenRefreshWindow(long tokenTtl) {
long startNumber = tokenTtl / 15;
long endNumber = tokenTtl / 10;
return RandomUtils.nextLong(startNumber, endNumber);
}
}
@@ -0,0 +1,133 @@
/*
* Copyright 1999-2021 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.client.auth.impl.process;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant;
import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
import com.alibaba.nacos.client.utils.ContextPathUtil;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.tls.TlsSystemConfig;
import com.alibaba.nacos.common.utils.InternetAddressUtil;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static com.alibaba.nacos.common.constant.RequestUrlConstants.HTTPS_PREFIX;
import static com.alibaba.nacos.common.constant.RequestUrlConstants.HTTP_PREFIX;
/**
* Login processor for Http.
*
* @author Nacos
*/
public class HttpLoginProcessor implements LoginProcessor {
private static final Logger SECURITY_LOGGER = LoggerFactory.getLogger(HttpLoginProcessor.class);
private static final String LOGIN_V1_URL = "/v1/auth/users/login";
private static final String LOGIN_V3_URL = "/v3/auth/user/login";
public static final String DEFAULT_NACOS_WEB_CONTEXT = "/nacos";
private final NacosRestTemplate nacosRestTemplate;
public HttpLoginProcessor(NacosRestTemplate nacosRestTemplate) {
this.nacosRestTemplate = nacosRestTemplate;
}
@Override
public LoginIdentityContext getResponse(Properties properties) {
String contextPath = ContextPathUtil.normalizeContextPath(
properties.getProperty(PropertyKeyConst.CONTEXT_PATH, DEFAULT_NACOS_WEB_CONTEXT));
String server = properties.getProperty(NacosAuthLoginConstant.SERVER, StringUtils.EMPTY);
if (!server.startsWith(HTTPS_PREFIX) && !server.startsWith(HTTP_PREFIX)) {
if (!InternetAddressUtil.containsPort(server)) {
server = server + InternetAddressUtil.IP_PORT_SPLITER
+ ClientBasicParamUtil.getDefaultServerPort();
}
server = getProtocolPrefix() + server;
}
String url = server + contextPath + LOGIN_V3_URL;
Map<String, String> params = new HashMap<>(2);
Map<String, String> bodyMap = new HashMap<>(2);
params.put(PropertyKeyConst.USERNAME,
properties.getProperty(PropertyKeyConst.USERNAME, StringUtils.EMPTY));
bodyMap.put(PropertyKeyConst.PASSWORD,
properties.getProperty(PropertyKeyConst.PASSWORD, StringUtils.EMPTY));
try {
HttpRestResult<String> restResult = nacosRestTemplate.postForm(url, Header.EMPTY,
Query.newInstance().initParams(params), bodyMap, String.class);
int code = restResult.getCode();
if (code == NacosException.NOT_FOUND || code == NacosException.SERVER_NOT_IMPLEMENTED) {
url = server + contextPath + LOGIN_V1_URL;
restResult = nacosRestTemplate.postForm(url, Header.EMPTY,
Query.newInstance().initParams(params),
bodyMap, String.class);
}
if (!restResult.ok()) {
SECURITY_LOGGER.error("login failed: {}", JacksonUtils.toJson(restResult));
return null;
}
JsonNode obj = JacksonUtils.toObj(restResult.getData());
LoginIdentityContext loginIdentityContext = new LoginIdentityContext();
if (obj.has(Constants.ACCESS_TOKEN)) {
loginIdentityContext.setParameter(NacosAuthLoginConstant.ACCESSTOKEN,
obj.get(Constants.ACCESS_TOKEN).asText());
loginIdentityContext.setParameter(NacosAuthLoginConstant.TOKENTTL,
obj.get(Constants.TOKEN_TTL).asText());
} else {
SECURITY_LOGGER
.info("[NacosClientAuthServiceImpl] ACCESS_TOKEN is empty from response");
}
return loginIdentityContext;
} catch (Exception e) {
Map<String, String> newBodyMap = new HashMap<>(bodyMap);
newBodyMap.put(PropertyKeyConst.PASSWORD,
ClientBasicParamUtil
.desensitiseParameter(bodyMap.get(PropertyKeyConst.PASSWORD)));
SECURITY_LOGGER.error("[NacosClientAuthServiceImpl] login http request failed"
+ " url: {}, params: {}, bodyMap: {}, errorMsg: {}", url, params, newBodyMap,
e.getMessage());
return null;
}
}
private static String getProtocolPrefix() {
return Boolean.getBoolean(TlsSystemConfig.TLS_ENABLE) ? HTTPS_PREFIX : HTTP_PREFIX;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 1999-2021 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.client.auth.impl.process;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import java.util.Properties;
/**
* Nacos login processor.
*
* @author Nacos
*/
public interface LoginProcessor {
/**
* send request to server and get result.
*
* @param properties request properties.
* @return login identity context.
*/
LoginIdentityContext getResponse(Properties properties);
}
@@ -0,0 +1,143 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.constant.OidcProtocolConstants;
import com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
/**
* OIDC Client Authentication Service implementation.
*
* <p>Implements the {@link AbstractClientAuthService} SPI to provide OIDC-based
* authentication for Nacos clients. Uses the OAuth2 Client Credentials Grant
* to obtain access tokens from the Identity Provider.
*
* <p>Flow:
* <ol>
* <li>{@link #login(Properties)} is called periodically by the framework</li>
* <li>On first call with OIDC configured: performs OIDC Discovery and obtains access token</li>
* <li>On subsequent calls: checks if token needs refresh and refreshes if needed</li>
* <li>{@link #getLoginIdentityContext(RequestResource)} returns the context with accessToken</li>
* </ol>
*
* @author wangzji
*/
public class OidcClientAuthServiceImpl extends AbstractClientAuthService {
private static final Logger LOGGER = LoggerFactory.getLogger(OidcClientAuthServiceImpl.class);
private final Object refreshLock = new Object();
private volatile OidcClientContext context;
private volatile OidcTokenHolder tokenHolder;
private volatile LoginIdentityContext loginIdentityContext = new LoginIdentityContext();
/**
* Whether OIDC has been determined to be unconfigured.
* Once set to true, subsequent login() calls skip OIDC processing.
*/
private volatile boolean oidcNotConfigured = false;
@Override
public Boolean login(Properties properties) {
try {
// Fast path: if OIDC is already known to be unconfigured, skip
if (oidcNotConfigured) {
return true;
}
// Step 1: Initialize context on first call (double-check locking)
if (context == null) {
synchronized (refreshLock) {
if (context == null) {
OidcClientContext newContext = new OidcClientContext();
boolean configured = newContext.init(properties);
if (!configured) {
oidcNotConfigured = true;
LOGGER.debug(
"[OIDC-CLIENT] OIDC not configured (missing client-id/client-secret), skipping");
return true;
}
this.tokenHolder = new OidcTokenHolder();
this.context = newContext;
LOGGER.info("[OIDC-CLIENT] OIDC client configured, client-id: {}",
context.getClientId());
}
}
}
// Step 2: Perform OIDC Discovery if not yet done
if (!context.isDiscovered()) {
boolean discoveryResult = context.discover();
if (!discoveryResult) {
LOGGER.warn(
"[OIDC-CLIENT] OIDC Discovery failed, will retry on next login cycle");
return false;
}
}
// Step 3: Fetch or refresh token if needed (double-check locking)
if (tokenHolder.isExpiredOrNeedRefresh()) {
synchronized (refreshLock) {
if (tokenHolder.isExpiredOrNeedRefresh()) {
boolean tokenResult = tokenHolder.fetchToken(context);
if (!tokenResult) {
LOGGER.warn(
"[OIDC-CLIENT] Token fetch failed, will retry on next login cycle");
return false;
}
// Step 4: Update LoginIdentityContext with new access token (dual header)
LoginIdentityContext newCtx = new LoginIdentityContext();
String token = tokenHolder.getAccessToken();
newCtx.setParameter(OidcProtocolConstants.AUTHORIZATION_HEADER,
OidcProtocolConstants.BEARER_PREFIX + token);
newCtx.setParameter(OidcProtocolConstants.ACCESS_TOKEN_PARAM, token);
this.loginIdentityContext = newCtx;
LOGGER.debug(
"[OIDC-CLIENT] LoginIdentityContext updated with new access token");
}
}
}
return true;
} catch (Throwable throwable) {
LOGGER.warn("[OIDC-CLIENT] login failed, error: ", throwable);
return false;
}
}
@Override
public LoginIdentityContext getLoginIdentityContext(RequestResource resource) {
return this.loginIdentityContext;
}
@Override
public void shutdown() throws NacosException {
LOGGER.info("[OIDC-CLIENT] Shutting down OIDC client auth service");
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
/**
* Client-specific constants for OIDC client authentication.
*
* <p>Protocol-level constants (Discovery fields, OAuth2 parameters, HTTP headers, etc.)
* are defined in {@link com.alibaba.nacos.plugin.auth.constant.OidcProtocolConstants}.
*
* @author wangzji
*/
public final class OidcClientConstants {
private OidcClientConstants() {
}
// ----- Properties configuration keys (client-specific) -----
/**
* OIDC Issuer URI, used for OIDC Discovery.
*/
public static final String PROP_ISSUER_URI = "nacos.client.auth.oidc.issuer-uri";
/**
* OAuth2 Client ID for Client Credentials Grant.
*/
public static final String PROP_CLIENT_ID = "nacos.client.auth.oidc.client-id";
/**
* OAuth2 Client Secret for Client Credentials Grant.
*/
public static final String PROP_CLIENT_SECRET = "nacos.client.auth.oidc.client-secret";
/**
* OAuth2 scopes, defaults to "openid".
*/
public static final String PROP_SCOPE = "nacos.client.auth.oidc.scope";
/**
* Token endpoint override. If set, OIDC Discovery is skipped.
*/
public static final String PROP_TOKEN_ENDPOINT = "nacos.client.auth.oidc.token-endpoint";
// ----- Default values -----
public static final String DEFAULT_SCOPE = "openid";
}
@@ -0,0 +1,203 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.constant.OidcProtocolConstants;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
/**
* OIDC client context that holds configuration and performs OIDC Discovery.
*
* <p>Reads OIDC configuration from {@link Properties} and optionally performs
* OIDC Discovery to resolve the token endpoint from the issuer's
* {@code .well-known/openid-configuration}.
*
* @author wangzji
*/
public class OidcClientContext {
private static final Logger LOGGER = LoggerFactory.getLogger(OidcClientContext.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private String issuerUri;
private String clientId;
private String clientSecret;
private String scope;
private volatile String tokenEndpoint;
private volatile boolean discovered;
/**
* Initialize context from properties.
*
* @param properties the client properties
* @return true if OIDC is configured (client-id and client-secret are present)
*/
public boolean init(Properties properties) {
this.issuerUri = properties.getProperty(OidcClientConstants.PROP_ISSUER_URI);
this.clientId = properties.getProperty(OidcClientConstants.PROP_CLIENT_ID);
this.clientSecret = properties.getProperty(OidcClientConstants.PROP_CLIENT_SECRET);
this.scope = properties.getProperty(OidcClientConstants.PROP_SCOPE,
OidcClientConstants.DEFAULT_SCOPE);
// Allow direct token endpoint override, skipping discovery
String directTokenEndpoint =
properties.getProperty(OidcClientConstants.PROP_TOKEN_ENDPOINT);
if (StringUtils.isNotBlank(directTokenEndpoint)) {
this.tokenEndpoint = directTokenEndpoint;
this.discovered = true;
}
return isConfigured();
}
/**
* Check if OIDC is configured with sufficient credentials.
*
* @return true if client-id, client-secret and (issuer-uri or token-endpoint) are present
*/
public boolean isConfigured() {
return StringUtils.isNotBlank(clientId) && StringUtils.isNotBlank(clientSecret)
&& (StringUtils.isNotBlank(issuerUri) || StringUtils.isNotBlank(tokenEndpoint));
}
/**
* Perform OIDC Discovery to retrieve the token endpoint.
*
* <p>Fetches {@code {issuer-uri}/.well-known/openid-configuration} and
* extracts the {@code token_endpoint} field.
*
* @return true if discovery succeeds
*/
public boolean discover() {
if (discovered) {
return true;
}
if (StringUtils.isBlank(issuerUri)) {
LOGGER.warn(
"[OIDC-CLIENT] issuer-uri is not configured, cannot perform OIDC Discovery");
return false;
}
String discoveryUrl = issuerUri.endsWith("/")
? issuerUri + ".well-known/openid-configuration"
: issuerUri + OidcProtocolConstants.WELL_KNOWN_PATH;
LOGGER.info("[OIDC-CLIENT] Performing OIDC Discovery from: {}", discoveryUrl);
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(discoveryUrl).openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(OidcProtocolConstants.DEFAULT_CONNECT_TIMEOUT_MS);
connection.setReadTimeout(OidcProtocolConstants.DEFAULT_READ_TIMEOUT_MS);
connection.setRequestProperty("Accept", OidcProtocolConstants.CONTENT_TYPE_JSON);
int responseCode = connection.getResponseCode();
if (responseCode != OidcProtocolConstants.HTTP_STATUS_OK) {
LOGGER.error("[OIDC-CLIENT] OIDC Discovery failed, HTTP status: {}", responseCode);
return false;
}
String responseBody;
try (InputStream responseStream = connection.getInputStream()) {
responseBody = readInputStreamAsString(responseStream);
}
JsonNode root = OBJECT_MAPPER.readTree(responseBody);
JsonNode tokenEndpointNode = root.get(OidcProtocolConstants.DISCOVERY_TOKEN_ENDPOINT);
if (tokenEndpointNode == null || tokenEndpointNode.isNull()) {
LOGGER.error("[OIDC-CLIENT] OIDC Discovery response missing 'token_endpoint'");
return false;
}
this.tokenEndpoint = tokenEndpointNode.asText();
this.discovered = true;
LOGGER.info("[OIDC-CLIENT] OIDC Discovery success, token_endpoint: {}",
this.tokenEndpoint);
return true;
} catch (IOException e) {
LOGGER.error("[OIDC-CLIENT] OIDC Discovery failed", e);
return false;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
public String getIssuerUri() {
return issuerUri;
}
public String getClientId() {
return clientId;
}
public String getClientSecret() {
return clientSecret;
}
public String getScope() {
return scope;
}
public String getTokenEndpoint() {
return tokenEndpoint;
}
public boolean isDiscovered() {
return discovered;
}
/**
* Read an InputStream fully into a String (Java 8 compatible).
*
* @param inputStream the input stream to read
* @return the string content
* @throws IOException if reading fails
*/
static String readInputStreamAsString(InputStream inputStream) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
return result.toString(StandardCharsets.UTF_8.name());
}
}
@@ -0,0 +1,254 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
import com.alibaba.nacos.common.utils.RandomUtils;
import com.alibaba.nacos.plugin.auth.constant.OidcProtocolConstants;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.StringJoiner;
/**
* Manages the OIDC Access Token lifecycle using the Client Credentials Grant.
*
* <p>Responsible for:
* <ul>
* <li>Fetching access tokens from the IdP token endpoint</li>
* <li>Tracking token expiration</li>
* <li>Determining when a proactive refresh is needed (within 6.7%-10% of expiry window)</li>
* </ul>
*
* @author wangzji
*/
public class OidcTokenHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(OidcTokenHolder.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private volatile String accessToken;
private volatile long expiresInSeconds;
private volatile long obtainedAtMs;
/**
* Fetch a new access token using Client Credentials Grant.
*
* @param context the OIDC client context with configuration
* @return true if the token was fetched successfully
*/
public boolean fetchToken(OidcClientContext context) {
String tokenEndpoint = context.getTokenEndpoint();
if (tokenEndpoint == null) {
LOGGER.error("[OIDC-CLIENT] Token endpoint is not available");
return false;
}
LOGGER.debug("[OIDC-CLIENT] Requesting access token from: {}", tokenEndpoint);
HttpURLConnection connection = null;
try {
// Build form-encoded request body
String requestBody = buildTokenRequestBody(context);
connection = (HttpURLConnection) new URL(tokenEndpoint).openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setConnectTimeout(OidcProtocolConstants.DEFAULT_CONNECT_TIMEOUT_MS);
connection.setReadTimeout(OidcProtocolConstants.DEFAULT_READ_TIMEOUT_MS);
connection.setRequestProperty("Content-Type", OidcProtocolConstants.CONTENT_TYPE_FORM);
connection.setRequestProperty("Accept", OidcProtocolConstants.CONTENT_TYPE_JSON);
byte[] bodyBytes = requestBody.getBytes(StandardCharsets.UTF_8);
connection.setRequestProperty("Content-Length", String.valueOf(bodyBytes.length));
try (OutputStream os = connection.getOutputStream()) {
os.write(bodyBytes);
os.flush();
}
int responseCode = connection.getResponseCode();
if (responseCode != OidcProtocolConstants.HTTP_STATUS_OK) {
String errorBody = "";
try (InputStream errorStream = connection.getErrorStream()) {
if (errorStream != null) {
errorBody = OidcClientContext.readInputStreamAsString(errorStream);
}
}
LOGGER.error("[OIDC-CLIENT] Token request failed, HTTP status: {}, body: {}",
responseCode, errorBody);
return false;
}
String responseBody;
try (InputStream responseStream = connection.getInputStream()) {
responseBody = OidcClientContext.readInputStreamAsString(responseStream);
}
return parseTokenResponse(responseBody);
} catch (IOException e) {
LOGGER.error("[OIDC-CLIENT] Token request failed", e);
return false;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
/**
* Parse the token response JSON and update internal state.
*
* @param responseBody the JSON response body
* @return true if parsing succeeds
*/
private boolean parseTokenResponse(String responseBody) {
try {
JsonNode root = OBJECT_MAPPER.readTree(responseBody);
JsonNode accessTokenNode = root.get(OidcProtocolConstants.TOKEN_RESPONSE_ACCESS_TOKEN);
if (accessTokenNode == null || accessTokenNode.isNull()) {
LOGGER.error("[OIDC-CLIENT] Token response missing 'access_token'");
return false;
}
JsonNode expiresInNode = root.get(OidcProtocolConstants.TOKEN_RESPONSE_EXPIRES_IN);
long newExpiresIn = 300; // default 5 minutes if not provided
if (expiresInNode != null && !expiresInNode.isNull()) {
newExpiresIn = expiresInNode.asLong(300);
}
this.accessToken = accessTokenNode.asText();
this.expiresInSeconds = newExpiresIn;
this.obtainedAtMs = System.currentTimeMillis();
LOGGER.info("[OIDC-CLIENT] Access token obtained successfully, expires_in: {}s",
newExpiresIn);
return true;
} catch (IOException e) {
LOGGER.error("[OIDC-CLIENT] Failed to parse token response", e);
return false;
}
}
/**
* Build the form-encoded request body for Client Credentials Grant.
*
* @param context the OIDC client context
* @return the URL-encoded form body
*/
private String buildTokenRequestBody(OidcClientContext context) {
try {
String charsetName = StandardCharsets.UTF_8.name();
StringJoiner joiner = new StringJoiner("&");
joiner.add(OidcProtocolConstants.GRANT_TYPE + "="
+ OidcProtocolConstants.GRANT_TYPE_CLIENT_CREDENTIALS);
joiner.add(OidcProtocolConstants.PARAM_CLIENT_ID + "="
+ URLEncoder.encode(context.getClientId(), charsetName));
joiner.add(OidcProtocolConstants.PARAM_CLIENT_SECRET + "="
+ URLEncoder.encode(context.getClientSecret(), charsetName));
if (context.getScope() != null && !context.getScope().isEmpty()) {
joiner.add(OidcProtocolConstants.PARAM_SCOPE + "="
+ URLEncoder.encode(context.getScope(), charsetName));
}
return joiner.toString();
} catch (UnsupportedEncodingException e) {
// Should never happen since UTF-8 is always supported
throw new IllegalStateException("UTF-8 encoding not supported", e);
}
}
/**
* Check whether the token is expired or needs proactive refresh.
*
* <p>Proactive refresh occurs within a random window of [tokenTtl/15, tokenTtl/10]
* before expiration (i.e., approximately 6.7%-10% of the token TTL before expiry).
* This is consistent with the refresh strategy used by {@code NacosClientAuthServiceImpl}.
*
* @return true if the token should be refreshed
*/
public boolean isExpiredOrNeedRefresh() {
if (accessToken == null) {
return true;
}
long elapsedMs = System.currentTimeMillis() - obtainedAtMs;
long elapsedSeconds = elapsedMs / 1000;
long refreshWindow = generateTokenRefreshWindow(expiresInSeconds);
return elapsedSeconds >= (expiresInSeconds - refreshWindow);
}
/**
* Generate a random refresh window, consistent with NacosClientAuthServiceImpl.
*
* @param tokenTtl the token TTL in seconds
* @return the refresh window in seconds, range [tokenTtl/15, tokenTtl/10]
*/
long generateTokenRefreshWindow(long tokenTtl) {
if (tokenTtl <= 0) {
return 0;
}
long startNumber = tokenTtl / 15;
long endNumber = tokenTtl / 10;
if (startNumber >= endNumber) {
return startNumber;
}
return RandomUtils.nextLong(startNumber, endNumber);
}
/**
* Get the current access token.
*
* @return the access token, or null if not yet obtained
*/
public String getAccessToken() {
return accessToken;
}
/**
* Get the token TTL in seconds.
*
* @return expires_in value from the token response
*/
public long getExpiresInSeconds() {
return expiresInSeconds;
}
/**
* Get the timestamp when the token was obtained.
*
* @return time in milliseconds
*/
public long getObtainedAtMs() {
return obtainedAtMs;
}
}
@@ -0,0 +1,119 @@
/*
* Copyright 1999-2021 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.client.auth.ram;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.injector.AbstractResourceInjector;
import com.alibaba.nacos.client.auth.ram.injector.AiResourceInjector;
import com.alibaba.nacos.client.auth.ram.injector.ConfigResourceInjector;
import com.alibaba.nacos.client.auth.ram.injector.LockResourceInjector;
import com.alibaba.nacos.client.auth.ram.injector.NamingResourceInjector;
import com.alibaba.nacos.client.auth.ram.utils.RamUtil;
import com.alibaba.nacos.client.auth.ram.utils.SpasAdapter;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.constant.SignType;
import com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Client Auth service implementation for aliyun RAM.
*
* @author xiweng.yy
*/
public class RamClientAuthServiceImpl extends AbstractClientAuthService {
private static final Logger LOGGER = LoggerFactory.getLogger(RamClientAuthServiceImpl.class);
private final RamContext ramContext;
private final Map<String, AbstractResourceInjector> resourceInjectors;
public RamClientAuthServiceImpl() {
ramContext = new RamContext();
resourceInjectors = new HashMap<>();
resourceInjectors.put(SignType.NAMING, new NamingResourceInjector());
resourceInjectors.put(SignType.CONFIG, new ConfigResourceInjector());
resourceInjectors.put(SignType.LOCK, new LockResourceInjector());
resourceInjectors.put(SignType.AI, new AiResourceInjector());
}
@Override
public Boolean login(Properties properties) {
if (ramContext.validate()) {
return true;
}
loadRoleName(properties);
loadAccessKey(properties);
loadSecretKey(properties);
loadRegionId(properties);
return true;
}
private void loadRoleName(Properties properties) {
String ramRoleName = properties.getProperty(PropertyKeyConst.RAM_ROLE_NAME);
if (!StringUtils.isBlank(ramRoleName)) {
StsConfig.getInstance().setRamRoleName(ramRoleName);
ramContext.setRamRoleName(ramRoleName);
}
}
private void loadAccessKey(Properties properties) {
ramContext.setAccessKey(RamUtil.getAccessKey(properties));
}
private void loadSecretKey(Properties properties) {
ramContext.setSecretKey(RamUtil.getSecretKey(properties));
}
private void loadRegionId(Properties properties) {
String regionId = properties.getProperty(PropertyKeyConst.SIGNATURE_REGION_ID);
ramContext.setRegionId(regionId);
}
@Override
public LoginIdentityContext getLoginIdentityContext(RequestResource resource) {
LoginIdentityContext result = new LoginIdentityContext();
if (!ramContext.validate() || notFountInjector(resource.getType())) {
return result;
}
resourceInjectors.get(resource.getType()).doInject(resource, ramContext, result);
return result;
}
private boolean notFountInjector(String type) {
if (!resourceInjectors.containsKey(type)) {
LOGGER.warn("Injector for type {} not found, will use default ram identity context.",
type);
return true;
}
return false;
}
@Override
public void shutdown() throws NacosException {
SpasAdapter.freeCredentialInstance();
}
}
@@ -0,0 +1,34 @@
/*
* 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.client.auth.ram;
/**
* Ram auth plugin constants.
*
* @author xiweng.yy
*/
public class RamConstants {
public static final String SIGNATURE_VERSION = "signatureVersion";
public static final String V4 = "v4";
public static final String SIGNATURE_V4_METHOD = "HmacSHA256";
public static final String SIGNATURE_V4_PRODUCE = "mse-nacos";
}
@@ -0,0 +1,78 @@
/*
* Copyright 1999-2021 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.client.auth.ram;
import com.alibaba.nacos.common.utils.StringUtils;
/**
* Aliyun RAM context.
*
* @author xiweng.yy
*/
public class RamContext {
private String accessKey;
private String secretKey;
private String ramRoleName;
private String regionId;
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getRamRoleName() {
return ramRoleName;
}
public void setRamRoleName(String ramRoleName) {
this.ramRoleName = ramRoleName;
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
/**
* Validate the RAM context.
*
* @return true if the context is valid
*/
public boolean validate() {
return StringUtils.isNotBlank(ramRoleName)
|| StringUtils.isNotBlank(accessKey) && StringUtils
.isNotBlank(secretKey);
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
/**
* Credential Listener.
*
* @author Nacos
*/
public interface CredentialListener {
/**
* update Credential.
*/
void onUpdateCredential();
}
@@ -0,0 +1,120 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
/**
* Credential Service.
*
* @author Nacos
*/
public final class CredentialService implements SpasCredentialLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(CredentialService.class);
private static final ConcurrentHashMap<String, CredentialService> INSTANCES =
new ConcurrentHashMap<>();
private final String appName;
private Credentials credentials = new Credentials();
private final CredentialWatcher watcher;
private CredentialListener listener;
private CredentialService(String appName) {
if (appName == null) {
String value = NacosClientProperties.PROTOTYPE
.getProperty(IdentifyConstants.PROJECT_NAME_PROPERTY);
if (StringUtils.isNotEmpty(value)) {
appName = value;
}
}
this.appName = appName;
watcher = new CredentialWatcher(appName, this);
}
public static CredentialService getInstance() {
return getInstance(null);
}
public static CredentialService getInstance(String appName) {
String key = appName != null ? appName : IdentifyConstants.NO_APP_NAME;
return INSTANCES.computeIfAbsent(key, k -> new CredentialService(appName));
}
public static CredentialService freeInstance() {
return freeInstance(null);
}
/**
* Free instance.
*
* @param appName app name
* @return {@link CredentialService}
*/
public static CredentialService freeInstance(String appName) {
String key = appName != null ? appName : IdentifyConstants.NO_APP_NAME;
CredentialService instance = INSTANCES.remove(key);
if (instance != null) {
instance.free();
}
return instance;
}
/**
* Free service.
*/
public void free() {
if (watcher != null) {
watcher.stop();
}
LOGGER.info("[{}] {} is freed", appName, this.getClass().getSimpleName());
}
@Override
public Credentials getCredential() {
return credentials;
}
public void setCredential(Credentials credential) {
boolean changed = !(credentials == credential
|| (credentials != null && credentials.identical(credential)));
credentials = credential;
if (changed && listener != null) {
listener.onUpdateCredential();
}
}
public void setStaticCredential(Credentials credential) {
if (watcher != null) {
watcher.stop();
}
setCredential(credential);
}
public void registerCredentialListener(CredentialListener listener) {
this.listener = listener;
}
}
@@ -0,0 +1,277 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.executor.ExecutorFactory;
import com.alibaba.nacos.common.executor.NameThreadFactory;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Credential Watcher.
*
* @author Nacos
*/
public class CredentialWatcher {
private static final Logger LOGGER = LoggerFactory.getLogger(CredentialWatcher.class);
private static final long REFRESH_INTERVAL = 10 * 1000L;
private final CredentialService serviceInstance;
private final String appName;
private String propertyPath;
private boolean stopped;
private final ScheduledExecutorService executor;
public CredentialWatcher(String appName, CredentialService serviceInstance) {
this.appName = appName;
this.serviceInstance = serviceInstance;
loadCredential(true);
executor = ExecutorFactory.newSingleScheduledExecutorService(
new NameThreadFactory("com.alibaba.nacos.client.auth.ram.identify.watcher"));
executor.scheduleWithFixedDelay(new Runnable() {
private long modified = 0;
@Override
public void run() {
synchronized (this) {
if (stopped) {
return;
}
boolean reload = false;
if (propertyPath == null) {
reload = true;
} else {
File file = new File(propertyPath);
long lastModified = file.lastModified();
if (modified != lastModified) {
reload = true;
modified = lastModified;
}
}
if (reload) {
loadCredential(false);
}
}
}
}, REFRESH_INTERVAL, REFRESH_INTERVAL, TimeUnit.MILLISECONDS);
}
/**
* Stop watcher.
*/
public void stop() {
if (stopped) {
return;
}
if (executor != null) {
synchronized (executor) {
stopped = true;
executor.shutdown();
}
}
LOGGER.info("[{}] {} is stopped", appName, this.getClass().getSimpleName());
}
private void loadCredential(boolean init) {
loadPropertyPath(init);
InputStream propertiesIs = loadPropertyPathToStream();
Credentials credentials = new Credentials();
boolean loadResult = Objects.isNull(propertiesIs) ? loadCredentialFromEnv(init, credentials)
: loadCredentialFromProperties(propertiesIs, init, credentials);
if (!loadResult) {
return;
}
if (!credentials.valid()) {
LOGGER
.warn(
"[1] Credential file missing required property {} Credential file missing {} or {}",
appName,
IdentifyConstants.ACCESS_KEY, IdentifyConstants.SECRET_KEY);
propertyPath = null;
// return;
}
serviceInstance.setCredential(credentials);
}
private boolean loadCredentialFromProperties(InputStream propertiesIs, boolean init,
Credentials credentials) {
Properties properties = new Properties();
try {
properties.load(propertiesIs);
} catch (IOException e) {
LOGGER
.error("[26] Unable to load credential file, appName:" + appName
+ "Unable to load credential file "
+ propertyPath, e);
propertyPath = null;
return false;
} finally {
try {
propertiesIs.close();
} catch (IOException e) {
LOGGER.error("[27] Unable to close credential file, appName:" + appName
+ "Unable to close credential file " + propertyPath, e);
}
}
if (init) {
LOGGER.info("[{}] Load credential file {}", appName, propertyPath);
}
String accessKey = null;
String secretKey = null;
String tenantId = null;
if (!IdentifyConstants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) {
if (properties.containsKey(IdentifyConstants.ACCESS_KEY)) {
accessKey = properties.getProperty(IdentifyConstants.ACCESS_KEY);
}
if (properties.containsKey(IdentifyConstants.SECRET_KEY)) {
secretKey = properties.getProperty(IdentifyConstants.SECRET_KEY);
}
if (properties.containsKey(IdentifyConstants.TENANT_ID)) {
tenantId = properties.getProperty(IdentifyConstants.TENANT_ID);
}
} else {
if (properties.containsKey(IdentifyConstants.DOCKER_ACCESS_KEY)) {
accessKey = properties.getProperty(IdentifyConstants.DOCKER_ACCESS_KEY);
}
if (properties.containsKey(IdentifyConstants.DOCKER_SECRET_KEY)) {
secretKey = properties.getProperty(IdentifyConstants.DOCKER_SECRET_KEY);
}
if (properties.containsKey(IdentifyConstants.DOCKER_TENANT_ID)) {
tenantId = properties.getProperty(IdentifyConstants.DOCKER_TENANT_ID);
}
}
setAccessKey(credentials, accessKey);
setSecretKey(credentials, secretKey);
setTenantId(credentials, tenantId);
return true;
}
private boolean loadCredentialFromEnv(boolean init, Credentials credentials) {
propertyPath = null;
String accessKey =
NacosClientProperties.PROTOTYPE.getProperty(IdentifyConstants.ENV_ACCESS_KEY);
String secretKey =
NacosClientProperties.PROTOTYPE.getProperty(IdentifyConstants.ENV_SECRET_KEY);
if (accessKey == null && secretKey == null) {
if (init) {
LOGGER.info("{} No credential found", appName);
}
return false;
}
setAccessKey(credentials, accessKey);
setSecretKey(credentials, secretKey);
return true;
}
private void loadPropertyPath(boolean init) {
if (propertyPath == null) {
URL url = ClassLoader.getSystemResource(IdentifyConstants.PROPERTIES_FILENAME);
if (url != null) {
propertyPath = url.getPath();
}
if (propertyPath == null || propertyPath.isEmpty()) {
String value = NacosClientProperties.PROTOTYPE.getProperty("spas.identity");
if (StringUtils.isNotEmpty(value)) {
propertyPath = value;
}
if (propertyPath == null || propertyPath.isEmpty()) {
propertyPath =
IdentifyConstants.CREDENTIAL_PATH
+ (appName == null ? IdentifyConstants.CREDENTIAL_DEFAULT
: appName);
} else {
if (init) {
LOGGER.info("[{}] Defined credential file: -Dspas.identity={}", appName,
propertyPath);
}
}
} else {
if (init) {
LOGGER.info("[{}] Load credential file from classpath: {}", appName,
IdentifyConstants.PROPERTIES_FILENAME);
}
}
}
}
private InputStream loadPropertyPathToStream() {
InputStream propertiesIs = null;
do {
try {
propertiesIs = new FileInputStream(propertyPath);
} catch (FileNotFoundException e) {
if (appName != null && !appName.equals(IdentifyConstants.CREDENTIAL_DEFAULT)
&& propertyPath
.equals(IdentifyConstants.CREDENTIAL_PATH + appName)) {
propertyPath = IdentifyConstants.CREDENTIAL_PATH
+ IdentifyConstants.CREDENTIAL_DEFAULT;
continue;
}
if (!IdentifyConstants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) {
propertyPath = IdentifyConstants.DOCKER_CREDENTIAL_PATH;
continue;
}
}
break;
} while (true);
return propertiesIs;
}
private void setAccessKey(Credentials credentials, String accessKey) {
if (!Objects.isNull(accessKey)) {
credentials.setAccessKey(accessKey.trim());
}
}
private void setSecretKey(Credentials credentials, String secretKey) {
if (!Objects.isNull(secretKey)) {
credentials.setSecretKey(secretKey.trim());
}
}
private void setTenantId(Credentials credentials, String tenantId) {
if (!Objects.isNull(tenantId)) {
credentials.setTenantId(tenantId.trim());
}
}
}
@@ -0,0 +1,85 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
/**
* Credentials.
*
* @author Nacos
*/
public class Credentials implements SpasCredential {
private volatile String accessKey;
private volatile String secretKey;
private volatile String tenantId;
public Credentials() {
this(null, null, null);
}
public Credentials(String accessKey, String secretKey, String tenantId) {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.tenantId = tenantId;
}
@Override
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
@Override
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
public boolean valid() {
return accessKey != null && !accessKey.isEmpty() && secretKey != null
&& !secretKey.isEmpty();
}
/**
* Identical.
*
* @param other other
* @return true if identical
*/
public boolean identical(Credentials other) {
return this == other || (other != null && (accessKey == null && other.accessKey == null
|| accessKey != null && accessKey.equals(other.accessKey))
&& (secretKey == null && other.secretKey == null || secretKey != null && secretKey
.equals(other.secretKey)));
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
/**
* Identify Constants.
*
* @author Nacos
*/
public class IdentifyConstants {
public static final String ACCESS_KEY = "accessKey";
public static final String SECRET_KEY = "secretKey";
public static final String SECURITY_TOKEN_HEADER = "Spas-SecurityToken";
public static final String TENANT_ID = "tenantId";
public static final String PROPERTIES_FILENAME = "spas.properties";
public static final String CREDENTIAL_PATH = "/home/admin/.spas_key/";
public static final String CREDENTIAL_DEFAULT = "default";
public static final String DOCKER_CREDENTIAL_PATH = "/etc/instanceInfo";
public static final String DOCKER_ACCESS_KEY = "env_spas_accessKey";
public static final String DOCKER_SECRET_KEY = "env_spas_secretKey";
public static final String DOCKER_TENANT_ID = "ebv_spas_tenantId";
public static final String ENV_ACCESS_KEY = "spas_accessKey";
public static final String ENV_SECRET_KEY = "spas_secretKey";
public static final String ENV_TENANT_ID = "tenant.id";
public static final String NO_APP_NAME = "";
public static final String PROJECT_NAME_PROPERTY = "project.name";
public static final String RAM_ROLE_NAME_PROPERTY = "ram.role.name";
public static final String REFRESH_TIME_PROPERTY = "time.to.refresh.in.millisecond";
public static final String SECURITY_PROPERTY = "security.credentials";
public static final String SECURITY_URL_PROPERTY = "security.credentials.url";
public static final String SECURITY_CACHE_PROPERTY = "cache.security.credentials";
}
@@ -0,0 +1,39 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
/**
* Spas Credential Interface.
*
* @author Nacos
*/
public interface SpasCredential {
/**
* get AccessKey.
*
* @return AccessKey
*/
String getAccessKey();
/**
* get SecretKey.
*
* @return SecretKey
*/
String getSecretKey();
}
@@ -0,0 +1,32 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
/**
* Spas Credential Loader.
*
* @author Nacos
*/
public interface SpasCredentialLoader {
/**
* get Credential.
*
* @return Credential
*/
SpasCredential getCredential();
}
@@ -0,0 +1,143 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.utils.StringUtils;
/**
* Sts config.
*
* @author Nacos
*/
public class StsConfig {
private static final String RAM_SECURITY_CREDENTIALS_URL =
"http://100.100.100.200/latest/meta-data/ram/security-credentials/";
private String ramRoleName;
/**
* The STS temporary certificate will be refreshed when the validity period of
* the temporary certificate is left (allow the local time to be at most slower than the STS service time).
*/
private int timeToRefreshInMillisecond = 3 * 60 * 1000;
/**
* Metadata interface for obtaining STS temporary credentials (including role name).
*/
private String securityCredentialsUrl;
/**
* Set the STS temporary certificate and no longer obtain it through the metadata interface.
*/
private String securityCredentials;
/**
* Whether to cache.
*/
private boolean cacheSecurityCredentials = true;
private static class Singleton {
private static final StsConfig INSTANCE = new StsConfig();
}
private StsConfig() {
String ramRoleName = NacosClientProperties.PROTOTYPE
.getProperty(IdentifyConstants.RAM_ROLE_NAME_PROPERTY);
if (!StringUtils.isBlank(ramRoleName)) {
setRamRoleName(ramRoleName);
}
String timeToRefreshInMillisecond = NacosClientProperties.PROTOTYPE
.getProperty(IdentifyConstants.REFRESH_TIME_PROPERTY);
if (!StringUtils.isBlank(timeToRefreshInMillisecond)) {
setTimeToRefreshInMillisecond(Integer.parseInt(timeToRefreshInMillisecond));
}
String securityCredentials =
NacosClientProperties.PROTOTYPE.getProperty(IdentifyConstants.SECURITY_PROPERTY);
if (!StringUtils.isBlank(securityCredentials)) {
setSecurityCredentials(securityCredentials);
}
String securityCredentialsUrl = NacosClientProperties.PROTOTYPE
.getProperty(IdentifyConstants.SECURITY_URL_PROPERTY);
if (!StringUtils.isBlank(securityCredentialsUrl)) {
setSecurityCredentialsUrl(securityCredentialsUrl);
}
String cacheSecurityCredentials = NacosClientProperties.PROTOTYPE
.getProperty(IdentifyConstants.SECURITY_CACHE_PROPERTY);
if (!StringUtils.isBlank(cacheSecurityCredentials)) {
setCacheSecurityCredentials(Boolean.parseBoolean(cacheSecurityCredentials));
}
}
public static StsConfig getInstance() {
return Singleton.INSTANCE;
}
public String getRamRoleName() {
return ramRoleName;
}
public void setRamRoleName(String ramRoleName) {
this.ramRoleName = ramRoleName;
}
public int getTimeToRefreshInMillisecond() {
return timeToRefreshInMillisecond;
}
public void setTimeToRefreshInMillisecond(int timeToRefreshInMillisecond) {
this.timeToRefreshInMillisecond = timeToRefreshInMillisecond;
}
public String getSecurityCredentialsUrl() {
if (securityCredentialsUrl == null && ramRoleName != null) {
return RAM_SECURITY_CREDENTIALS_URL + ramRoleName;
}
return securityCredentialsUrl;
}
public void setSecurityCredentialsUrl(String securityCredentialsUrl) {
this.securityCredentialsUrl = securityCredentialsUrl;
}
public String getSecurityCredentials() {
return securityCredentials;
}
public void setSecurityCredentials(String securityCredentials) {
this.securityCredentials = securityCredentials;
}
public boolean isStsOn() {
return StringUtils.isNotEmpty(getSecurityCredentials())
|| StringUtils.isNotEmpty(getSecurityCredentialsUrl());
}
public boolean isCacheSecurityCredentials() {
return cacheSecurityCredentials;
}
public void setCacheSecurityCredentials(boolean cacheSecurityCredentials) {
this.cacheSecurityCredentials = cacheSecurityCredentials;
}
}
@@ -0,0 +1,103 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Date;
/**
* Sts credential for aliyun RAM.
*
* @author xiweng.yy
*/
public class StsCredential {
@JsonProperty(value = "AccessKeyId")
private String accessKeyId;
@JsonProperty(value = "AccessKeySecret")
private String accessKeySecret;
@JsonProperty(value = "Expiration")
private Date expiration;
@JsonProperty(value = "SecurityToken")
private String securityToken;
@JsonProperty(value = "LastUpdated")
private Date lastUpdated;
@JsonProperty(value = "Code")
private String code;
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
public Date getExpiration() {
return expiration;
}
public void setExpiration(Date expiration) {
this.expiration = expiration;
}
public String getSecurityToken() {
return securityToken;
}
public void setSecurityToken(String securityToken) {
this.securityToken = securityToken;
}
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "STSCredential{" + "accessKeyId='" + accessKeyId + '\'' + ", accessKeySecret='"
+ accessKeySecret
+ '\'' + ", expiration=" + expiration + ", securityToken='" + securityToken + '\''
+ ", lastUpdated=" + lastUpdated + ", code='" + code + '\'' + '}';
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.client.remote.HttpClientManager;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sts credential holder.
*
* @author xiweng.yy
*/
public class StsCredentialHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(StsCredentialHolder.class);
private static final StsCredentialHolder INSTANCE = new StsCredentialHolder();
private StsCredential stsCredential;
private StsCredentialHolder() {
}
public static StsCredentialHolder getInstance() {
return INSTANCE;
}
/**
* Get Sts Credential.
*
* @return StsCredential
*/
public StsCredential getStsCredential() {
boolean cacheSecurityCredentials = StsConfig.getInstance().isCacheSecurityCredentials();
if (cacheSecurityCredentials && stsCredential != null) {
long currentTime = System.currentTimeMillis();
long expirationTime = stsCredential.getExpiration().getTime();
int timeToRefreshInMillisecond =
StsConfig.getInstance().getTimeToRefreshInMillisecond();
if (expirationTime - currentTime > timeToRefreshInMillisecond) {
return stsCredential;
}
}
String stsResponse = getStsResponse();
stsCredential = JacksonUtils.toObj(stsResponse, new TypeReference<StsCredential>() {
});
LOGGER.info("[getSTSCredential] code:{}, accessKeyId:{}, lastUpdated:{}, expiration:{}",
stsCredential.getCode(), stsCredential.getAccessKeyId(),
stsCredential.getLastUpdated(),
stsCredential.getExpiration());
return stsCredential;
}
private static String getStsResponse() {
String securityCredentials = StsConfig.getInstance().getSecurityCredentials();
if (securityCredentials != null) {
return securityCredentials;
}
String securityCredentialsUrl = StsConfig.getInstance().getSecurityCredentialsUrl();
try {
HttpRestResult<String> result = HttpClientManager.getInstance().getNacosRestTemplate()
.get(securityCredentialsUrl, Header.EMPTY, Query.EMPTY, String.class);
if (!result.ok()) {
LOGGER.error(
"can not get security credentials, securityCredentialsUrl: {}, responseCode: {}, response: {}",
securityCredentialsUrl, result.getCode(), result.getMessage());
throw new NacosRuntimeException(NacosException.SERVER_ERROR,
"can not get security credentials, responseCode: " + result.getCode()
+ ", response: " + result
.getMessage());
}
return result.getData();
} catch (Exception e) {
LOGGER.error("can not get security credentials", e);
throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 1999-2021 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.client.auth.ram.injector;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
/**
* Abstract aliyun RAM resource injector.
*
* @author xiweng.yy
*/
public abstract class AbstractResourceInjector {
/**
* Generate and inject resource into context. Default impl will do nothing.
*
* @param resource request resource
* @param context ram context
* @param result the result identity context
*/
public void doInject(RequestResource resource, RamContext context,
LoginIdentityContext result) {
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 1999-2025 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.client.auth.ram.injector;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.client.auth.ram.utils.CalculateV4SigningKeyUtil;
import com.alibaba.nacos.client.auth.ram.utils.SpasAdapter;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import java.util.Map;
/**
* AI module aliyun ram reousce injector.
*
* @author xiweng.yy
*/
public class AiResourceInjector extends AbstractResourceInjector {
private static final String ACCESS_KEY_HEADER = "Spas-AccessKey";
@Override
public void doInject(RequestResource resource, RamContext context,
LoginIdentityContext result) {
if (!context.validate()) {
return;
}
String accessKey = context.getAccessKey();
String secretKey = context.getSecretKey();
if (StsConfig.getInstance().isStsOn()) {
StsCredential stsCredential = StsCredentialHolder.getInstance().getStsCredential();
accessKey = stsCredential.getAccessKeyId();
secretKey = stsCredential.getAccessKeySecret();
result.setParameter(IdentifyConstants.SECURITY_TOKEN_HEADER,
stsCredential.getSecurityToken());
}
result.setParameter(ACCESS_KEY_HEADER, accessKey);
String signatureKey = secretKey;
if (StringUtils.isNotEmpty(context.getRegionId())) {
signatureKey = CalculateV4SigningKeyUtil.finalSigningKeyStringWithDefaultInfo(secretKey,
context.getRegionId());
result.setParameter(RamConstants.SIGNATURE_VERSION, RamConstants.V4);
}
Map<String, String> signHeaders =
SpasAdapter.getSignHeaders(buildResourceString(resource), signatureKey);
result.setParameters(signHeaders);
}
private String buildResourceString(RequestResource resource) {
return resource.getNamespace() + "+" + resource.getGroup();
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 1999-2021 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.client.auth.ram.injector;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.client.auth.ram.utils.CalculateV4SigningKeyUtil;
import com.alibaba.nacos.client.auth.ram.utils.SpasAdapter;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import java.util.Map;
/**
* Resource Injector for naming module.
*
* @author xiweng.yy
*/
public class ConfigResourceInjector extends AbstractResourceInjector {
private static final String ACCESS_KEY_HEADER = "Spas-AccessKey";
private static final String DEFAULT_RESOURCE = "";
@Override
public void doInject(RequestResource resource, RamContext context,
LoginIdentityContext result) {
String accessKey = context.getAccessKey();
String secretKey = context.getSecretKey();
// STS 临时凭证鉴权的优先级高于 AK/SK 鉴权
if (StsConfig.getInstance().isStsOn()) {
StsCredential stsCredential = StsCredentialHolder.getInstance().getStsCredential();
accessKey = stsCredential.getAccessKeyId();
secretKey = stsCredential.getAccessKeySecret();
result.setParameter(IdentifyConstants.SECURITY_TOKEN_HEADER,
stsCredential.getSecurityToken());
}
if (StringUtils.isNotEmpty(accessKey) && StringUtils.isNotBlank(secretKey)) {
result.setParameter(ACCESS_KEY_HEADER, accessKey);
}
String signatureKey = secretKey;
if (StringUtils.isNotEmpty(context.getRegionId())) {
signatureKey = CalculateV4SigningKeyUtil
.finalSigningKeyStringWithDefaultInfo(secretKey, context.getRegionId());
result.setParameter(RamConstants.SIGNATURE_VERSION, RamConstants.V4);
}
Map<String, String> signHeaders = SpasAdapter
.getSignHeaders(getResource(resource.getNamespace(), resource.getGroup()),
signatureKey);
result.setParameters(signHeaders);
}
private String getResource(String tenant, String group) {
if (StringUtils.isBlank(tenant)) {
if (StringUtils.isBlank(group)) {
return DEFAULT_RESOURCE;
} else {
return group;
}
} else {
return tenant + "+" + group;
}
}
}
@@ -0,0 +1,56 @@
/*
* 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.client.auth.ram.injector;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
/**
* lock resource injector.
*
* @author 985492783@qq.com
* @date 2023/9/17 1:10
*/
public class LockResourceInjector extends AbstractResourceInjector {
private static final String AK_FIELD = "ak";
@Override
public void doInject(RequestResource resource, RamContext context,
LoginIdentityContext result) {
String accessKey = context.getAccessKey();
String secretKey = context.getSecretKey();
// STS 临时凭证鉴权的优先级高于 AK/SK 鉴权
if (StsConfig.getInstance().isStsOn()) {
StsCredential stsCredential = StsCredentialHolder.getInstance().getStsCredential();
accessKey = stsCredential.getAccessKeyId();
secretKey = stsCredential.getAccessKeySecret();
result.setParameter(IdentifyConstants.SECURITY_TOKEN_HEADER,
stsCredential.getSecurityToken());
}
if (StringUtils.isNotEmpty(accessKey) && StringUtils.isNotBlank(secretKey)) {
result.setParameter(AK_FIELD, accessKey);
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 1999-2021 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.client.auth.ram.injector;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.client.auth.ram.utils.CalculateV4SigningKeyUtil;
import com.alibaba.nacos.client.auth.ram.utils.SignUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Resource Injector for naming module.
*
* @author xiweng.yy
*/
public class NamingResourceInjector extends AbstractResourceInjector {
private static final Logger LOGGER = LoggerFactory.getLogger(NamingResourceInjector.class);
private static final String SIGNATURE_FILED = "signature";
private static final String DATA_FILED = "data";
private static final String AK_FILED = "ak";
@Override
public void doInject(RequestResource resource, RamContext context,
LoginIdentityContext result) {
if (context.validate()) {
try {
String accessKey = context.getAccessKey();
String secretKey = context.getSecretKey();
// STS 临时凭证鉴权的优先级高于 AK/SK 鉴权
if (StsConfig.getInstance().isStsOn()) {
StsCredential stsCredential =
StsCredentialHolder.getInstance().getStsCredential();
accessKey = stsCredential.getAccessKeyId();
secretKey = stsCredential.getAccessKeySecret();
result.setParameter(IdentifyConstants.SECURITY_TOKEN_HEADER,
stsCredential.getSecurityToken());
}
String signatureKey = secretKey;
if (StringUtils.isNotEmpty(context.getRegionId())) {
signatureKey = CalculateV4SigningKeyUtil
.finalSigningKeyStringWithDefaultInfo(secretKey, context.getRegionId());
result.setParameter(RamConstants.SIGNATURE_VERSION, RamConstants.V4);
}
String signData = getSignData(getGroupedServiceName(resource));
String signature = SignUtil.sign(signData, signatureKey);
result.setParameter(SIGNATURE_FILED, signature);
result.setParameter(DATA_FILED, signData);
result.setParameter(AK_FILED, accessKey);
} catch (Exception e) {
LOGGER.error("inject ak/sk failed.", e);
}
}
}
private String getGroupedServiceName(RequestResource resource) {
if (resource.getResource().contains(Constants.SERVICE_INFO_SPLITER) || StringUtils
.isBlank(resource.getGroup())) {
return resource.getResource();
}
return NamingUtils.getGroupedNameOptional(resource.getResource(), resource.getGroup());
}
private String getSignData(String serviceName) {
return StringUtils.isNotEmpty(serviceName)
? System.currentTimeMillis() + Constants.SERVICE_INFO_SPLITER
+ serviceName
: String.valueOf(System.currentTimeMillis());
}
}
@@ -0,0 +1,117 @@
/*
* 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.client.auth.ram.utils;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
/**
* CalculateV4SigningKeyUtil.
*
* @author xiweng.yy
*/
public class CalculateV4SigningKeyUtil {
private static final String PREFIX = "aliyun_v4";
private static final String CONSTANT = "aliyun_v4_request";
private static final DateTimeFormatter V4_SIGN_DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd");
private static final ZoneId UTC_0 = ZoneId.of("GMT+00:00");
private static byte[] firstSigningKey(String secret, String date, String signMethod)
throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(signMethod);
mac.init(new SecretKeySpec((PREFIX + secret).getBytes(StandardCharsets.UTF_8), signMethod));
return mac.doFinal(date.getBytes(StandardCharsets.UTF_8));
}
private static byte[] regionSigningKey(String secret, String date, String region,
String signMethod)
throws NoSuchAlgorithmException, InvalidKeyException {
byte[] firstSignkey = firstSigningKey(secret, date, signMethod);
Mac mac = Mac.getInstance(signMethod);
mac.init(new SecretKeySpec(firstSignkey, signMethod));
return mac.doFinal(region.getBytes(StandardCharsets.UTF_8));
}
private static byte[] finalSigningKey(String secret, String date, String region,
String productCode,
String signMethod) {
try {
byte[] secondSignkey = regionSigningKey(secret, date, region, signMethod);
Mac mac = Mac.getInstance(signMethod);
mac.init(new SecretKeySpec(secondSignkey, signMethod));
byte[] thirdSigningKey = mac.doFinal(productCode.getBytes(StandardCharsets.UTF_8));
// 计算最终派生秘钥
mac = Mac.getInstance(signMethod);
mac.init(new SecretKeySpec(thirdSigningKey, signMethod));
return mac.doFinal(CONSTANT.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("unsupported Algorithm:" + signMethod);
} catch (InvalidKeyException e) {
throw new RuntimeException("InvalidKey");
}
}
/**
* Return V4 signature key with base64 encode.
*
* @param secret secret key
* @param date date with utc format, like 20211222
* @param region region id
* @param productCode cloud product code
* @param signMethod sign method
* @return V4 signature key with base64 encode
*/
public static String finalSigningKeyString(String secret, String date, String region,
String productCode,
String signMethod) {
return Base64.getEncoder()
.encodeToString(finalSigningKey(secret, date, region, productCode, signMethod));
}
/**
* Return V4 signature key with base64 encode for some default information.
*
* <li>
* <ul>date = current date</ul>
* <ul>produceCode = mse</ul>
* <ul>signMethod = HMAC-SHA256</ul>
* </li>
*
* @param secret secret key
* @param region region id
* @return V4 signature key with base64 encode
*/
public static String finalSigningKeyStringWithDefaultInfo(String secret, String region) {
String signDate = LocalDateTime.now(UTC_0).format(V4_SIGN_DATE_FORMATTER);
return finalSigningKeyString(secret, signDate, region, RamConstants.SIGNATURE_V4_PRODUCE,
RamConstants.SIGNATURE_V4_METHOD);
}
}
@@ -0,0 +1,58 @@
/*
* 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.client.auth.ram.utils;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.Properties;
/**
* Util to get ram info, such as AK, SK and RAM role.
*
* @author xiweng.yy
*/
public class RamUtil {
public static String getAccessKey(Properties properties) {
boolean isUseRamInfoParsing = Boolean.parseBoolean(properties
.getProperty(PropertyKeyConst.IS_USE_RAM_INFO_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_RAM_INFO_PARSING,
Constants.DEFAULT_USE_RAM_INFO_PARSING)));
String result = properties.getProperty(PropertyKeyConst.ACCESS_KEY);
if (isUseRamInfoParsing && StringUtils.isBlank(result)) {
result = SpasAdapter.getAk();
}
return result;
}
public static String getSecretKey(Properties properties) {
boolean isUseRamInfoParsing = Boolean.parseBoolean(properties
.getProperty(PropertyKeyConst.IS_USE_RAM_INFO_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_RAM_INFO_PARSING,
Constants.DEFAULT_USE_RAM_INFO_PARSING)));
String result = properties.getProperty(PropertyKeyConst.SECRET_KEY);
if (isUseRamInfoParsing && StringUtils.isBlank(result)) {
result = SpasAdapter.getSk();
}
return result;
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 1999-2021 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.client.auth.ram.utils;
import com.alibaba.nacos.common.codec.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Sign util.
*
* @author pbting
* @date 2019-01-22 10:20 PM
*/
public class SignUtil {
private static final Charset UTF8 = StandardCharsets.UTF_8;
/**
* Sign.
*
* @param data data
* @param key key
* @return signature
* @throws Exception exception
*/
public static String sign(String data, String key) throws Exception {
try {
byte[] signature = sign(data.getBytes(UTF8), key.getBytes(UTF8),
SignUtil.SigningAlgorithm.HmacSHA1);
return new String(Base64.encodeBase64(signature), UTF8);
} catch (Exception ex) {
throw new Exception("Unable to calculate a request signature: " + ex.getMessage(), ex);
}
}
static byte[] sign(byte[] data, byte[] key, SignUtil.SigningAlgorithm algorithm)
throws Exception {
try {
Mac mac = Mac.getInstance(algorithm.toString());
mac.init(new SecretKeySpec(key, algorithm.toString()));
return mac.doFinal(data);
} catch (Exception ex) {
throw new Exception("Unable to calculate a request signature: " + ex.getMessage(), ex);
}
}
public enum SigningAlgorithm {
// Hmac SHA1 algorithm
HmacSHA1;
SigningAlgorithm() {
}
}
}
@@ -0,0 +1,133 @@
/*
* Copyright 1999-2021 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.client.auth.ram.utils;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.auth.ram.identify.CredentialService;
import com.alibaba.nacos.common.codec.Base64;
import com.alibaba.nacos.common.utils.StringUtils;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* adapt spas interface.
*
* @author Nacos
*/
public class SpasAdapter {
private static final String TIMESTAMP_HEADER = "Timestamp";
private static final String SIGNATURE_HEADER = "Spas-Signature";
private static final String GROUP_KEY = "group";
public static final String TENANT_KEY = "tenant";
private static final String SHA_ENCRYPT = "HmacSHA1";
public static Map<String, String> getSignHeaders(String resource, String secretKey) {
Map<String, String> header = new HashMap<>(2);
String timeStamp = String.valueOf(System.currentTimeMillis());
header.put(TIMESTAMP_HEADER, timeStamp);
if (secretKey != null) {
String signature;
if (StringUtils.isBlank(resource)) {
signature = signWithHmacSha1Encrypt(timeStamp, secretKey);
} else {
signature = signWithHmacSha1Encrypt(resource + "+" + timeStamp, secretKey);
}
header.put(SIGNATURE_HEADER, signature);
}
return header;
}
public static Map<String, String> getSignHeaders(String groupKey, String tenant,
String secretKey) {
if (StringUtils.isBlank(groupKey) && StringUtils.isBlank(tenant)) {
return null;
}
String resource = "";
if (StringUtils.isNotBlank(groupKey) && StringUtils.isNotBlank(tenant)) {
resource = tenant + "+" + groupKey;
} else {
if (!StringUtils.isBlank(groupKey)) {
resource = groupKey;
}
}
return getSignHeaders(resource, secretKey);
}
public static Map<String, String> getSignHeaders(Map<String, String> paramValues,
String secretKey) {
if (null == paramValues) {
return null;
}
String resource = "";
if (paramValues.containsKey(TENANT_KEY) && paramValues.containsKey(GROUP_KEY)) {
resource = paramValues.get(TENANT_KEY) + "+" + paramValues.get(GROUP_KEY);
} else {
if (!StringUtils.isBlank(paramValues.get(GROUP_KEY))) {
resource = paramValues.get(GROUP_KEY);
}
}
return getSignHeaders(resource, secretKey);
}
public static String getSk() {
return CredentialService.getInstance().getCredential().getSecretKey();
}
public static String getAk() {
return CredentialService.getInstance().getCredential().getAccessKey();
}
public static void freeCredentialInstance() {
CredentialService.freeInstance();
}
/**
* Sign with hmac SHA1 encrtpt.
*
* @param encryptText encrypt text
* @param encryptKey encrypt key
* @return base64 string
*/
public static String signWithHmacSha1Encrypt(String encryptText, String encryptKey) {
try {
byte[] data = encryptKey.getBytes(Constants.ENCODE);
// Construct a key according to the given byte array, and the second parameter specifies the name of a key algorithm
SecretKey secretKey = new SecretKeySpec(data, SHA_ENCRYPT);
// Generate a Mac object specifying Mac algorithm
Mac mac = Mac.getInstance(SHA_ENCRYPT);
// Initialize the Mac object with the given key
mac.init(secretKey);
byte[] text = encryptText.getBytes(Constants.ENCODE);
byte[] textFinal = mac.doFinal(text);
// Complete Mac operation, base64 encoding, convert byte array to string
return new String(Base64.encodeBase64(textFinal), Constants.ENCODE);
} catch (Exception e) {
throw new RuntimeException("signWithhmacSHA1Encrypt fail", e);
}
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 1999-2018 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.client.constant;
import java.util.concurrent.TimeUnit;
/**
* All the constants.
*
* @author onew
*/
public class Constants {
public static class SysEnv {
public static final String USER_HOME = "user.home";
public static final String PROJECT_NAME = "project.name";
public static final String JM_LOG_PATH = "JM.LOG.PATH";
public static final String JM_SNAPSHOT_PATH = "JM.SNAPSHOT.PATH";
public static final String NACOS_ENV_FIRST = "nacos.env.first";
}
public static class Security {
public static final long SECURITY_INFO_REFRESH_INTERVAL_MILLS =
TimeUnit.SECONDS.toMillis(5);
}
public static class Address {
public static final int ENDPOINT_SERVER_LIST_PROVIDER_ORDER = 500;
public static final int ADDRESS_SERVER_LIST_PROVIDER_ORDER = 499;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 1999-2022 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.client.env;
import java.util.Properties;
abstract class AbstractPropertySource {
/**
* get property's type.
* @return name
*/
abstract SourceType getType();
/**
* get property, if the value can not be got by the special key, the null will be returned.
* @param key special key
* @return value or null
*/
abstract String getProperty(String key);
/**
* Tests if the specified object is a key in this propertySource.
* @param key key possible key
* @return true if and only if the specified object is a key in this propertySource, false otherwise.
*/
abstract boolean containsKey(String key);
/**
* to properties.
* @return properties
*/
abstract Properties asProperties();
}
@@ -0,0 +1,50 @@
/*
* Copyright 1999-2022 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.client.env;
import java.util.Properties;
class JvmArgsPropertySource extends AbstractPropertySource {
private final Properties properties;
JvmArgsPropertySource() {
this.properties = System.getProperties();
}
@Override
SourceType getType() {
return SourceType.JVM;
}
@Override
String getProperty(String key) {
return properties.getProperty(key);
}
@Override
boolean containsKey(String key) {
return properties.containsKey(key);
}
@Override
Properties asProperties() {
Properties properties = new Properties();
properties.putAll(this.properties);
return properties;
}
}
@@ -0,0 +1,169 @@
/*
* Copyright 1999-2022 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.client.env;
import java.util.Properties;
/**
* NacosClientProperties interface. include all the properties from jvm args, system environment, default setting. more
* details you can see https://github.com/alibaba/nacos/issues/8622
*
* @author onewe
*/
public interface NacosClientProperties {
/**
* all the NacosClientProperties object must be created by PROTOTYPE, so child NacosClientProperties can read
* properties from the PROTOTYPE. it looks like this: |-PROTOTYPE----------------> ip=127.0.0.1
* |---|-child1---------------> port=6379 if you search key called "port" from child1, certainly you will get 6379
* if you search key called "ip" from child1, you will get 127.0.0.1. because the child can read properties from
* parent NacosClientProperties
*/
NacosClientProperties PROTOTYPE = SearchableProperties.INSTANCE;
/**
* get property, if the value can not be got by the special key, the null will be returned.
*
* @param key special key
* @return string value or null.
*/
String getProperty(String key);
/**
* get property, if the value can not be got by the special key, the default value will be returned.
*
* @param key special key
* @param defaultValue default value
* @return string value or default value.
*/
String getProperty(String key, String defaultValue);
/**
* get property from special property source.
*
* @param source source type
* @param key special key
* @return string value or null.
* @see SourceType
*/
String getPropertyFrom(SourceType source, String key);
/**
* get property from special property source.
*
* @param source source type
* @return string value or null.
* @see SourceType
*/
Properties getProperties(SourceType source);
/**
* get boolean, if the value can not be got by the special key, the null will be returned.
*
* @param key special key
* @return boolean value or null.
*/
Boolean getBoolean(String key);
/**
* get boolean, if the value can not be got by the special key, the default value will be returned.
*
* @param key special key
* @param defaultValue default value
* @return boolean value or defaultValue.
*/
Boolean getBoolean(String key, Boolean defaultValue);
/**
* get integer, if the value can not be got by the special key, the null will be returned.
*
* @param key special key
* @return integer value or null
*/
Integer getInteger(String key);
/**
* get integer, if the value can not be got by the special key, the default value will be returned.
*
* @param key special key
* @param defaultValue default value
* @return integer value or default value
*/
Integer getInteger(String key, Integer defaultValue);
/**
* get long, if the value can not be got by the special key, the null will be returned.
*
* @param key special key
* @return long value or null
*/
Long getLong(String key);
/**
* get long, if the value can not be got by the special key, the default value will be returned.
*
* @param key special key
* @param defaultValue default value
* @return long value or default value
*/
Long getLong(String key, Long defaultValue);
/**
* set property.
*
* @param key key
* @param value value
*/
void setProperty(String key, String value);
/**
* add properties.
*
* @param properties properties
*/
void addProperties(Properties properties);
/**
* Tests if the specified object is a key in this NacosClientProperties.
*
* @param key key possible key
* @return true if and only if the specified object is a key in this NacosClientProperties, false otherwise.
*/
boolean containsKey(String key);
/**
* get properties from NacosClientProperties.
*
* @return properties
*/
Properties asProperties();
/**
* create a new NacosClientProperties which scope is itself.
*
* @return NacosClientProperties
*/
NacosClientProperties derive();
/**
* create a new NacosClientProperties from NacosClientProperties#PROTOTYPE and init.
*
* @param properties properties
* @return NacosClientProperties
*/
NacosClientProperties derive(Properties properties);
}
@@ -0,0 +1,110 @@
/*
* Copyright 1999-2022 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.client.env;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;
class PropertiesPropertySource extends AbstractPropertySource {
private final Properties properties = new Properties();
private final PropertiesPropertySource parent;
PropertiesPropertySource() {
this.parent = null;
}
PropertiesPropertySource(PropertiesPropertySource parent) {
this.parent = parent;
}
@Override
SourceType getType() {
return SourceType.PROPERTIES;
}
@Override
String getProperty(String key) {
return getProperty(this, key);
}
private String getProperty(PropertiesPropertySource propertiesPropertySource, String key) {
final String value = propertiesPropertySource.properties.getProperty(key);
if (value != null) {
return value;
}
final PropertiesPropertySource parent = propertiesPropertySource.parent;
if (parent == null) {
return null;
}
return getProperty(parent, key);
}
@Override
boolean containsKey(String key) {
return containsKey(this, key);
}
boolean containsKey(PropertiesPropertySource propertiesPropertySource, String key) {
final boolean exist = propertiesPropertySource.properties.containsKey(key);
if (exist) {
return true;
}
final PropertiesPropertySource parent = propertiesPropertySource.parent;
if (parent == null) {
return false;
}
return containsKey(parent, key);
}
@Override
Properties asProperties() {
List<Properties> propertiesList = new ArrayList<>(8);
propertiesList = lookForProperties(this, propertiesList);
Properties ret = new Properties();
final ListIterator<Properties> iterator =
propertiesList.listIterator(propertiesList.size());
while (iterator.hasPrevious()) {
final Properties properties = iterator.previous();
ret.putAll(properties);
}
return ret;
}
List<Properties> lookForProperties(PropertiesPropertySource propertiesPropertySource,
List<Properties> propertiesList) {
propertiesList.add(propertiesPropertySource.properties);
final PropertiesPropertySource parent = propertiesPropertySource.parent;
if (parent == null) {
return propertiesList;
}
return lookForProperties(parent, propertiesList);
}
synchronized void setProperty(String key, String value) {
properties.setProperty(key, value);
}
synchronized void addProperties(Properties source) {
properties.putAll(source);
}
}
@@ -0,0 +1,243 @@
/*
* Copyright 1999-2022 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.client.env;
import com.alibaba.nacos.client.constant.Constants;
import com.alibaba.nacos.client.env.convert.CompositeConverter;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
/**
* Searchable NacosClientProperties. the SearchableProperties that it can be specified search order by nacos.env.first
*
* @author onewe
*/
class SearchableProperties implements NacosClientProperties {
private static final Logger LOGGER = LoggerFactory.getLogger(SearchableProperties.class);
private static final JvmArgsPropertySource JVM_ARGS_PROPERTY_SOURCE =
new JvmArgsPropertySource();
private static final SystemEnvPropertySource SYSTEM_ENV_PROPERTY_SOURCE =
new SystemEnvPropertySource();
private static final List<SourceType> SEARCH_ORDER;
private static final CompositeConverter CONVERTER = new CompositeConverter();
static {
SEARCH_ORDER = init();
StringBuilder orderInfo = new StringBuilder("properties search order:");
for (int i = 0; i < SEARCH_ORDER.size(); i++) {
orderInfo.append(SEARCH_ORDER.get(i).toString());
if (i < SEARCH_ORDER.size() - 1) {
orderInfo.append("->");
}
}
LOGGER.debug(orderInfo.toString());
}
private static List<SourceType> init() {
List<SourceType> initOrder =
Arrays.asList(SourceType.PROPERTIES, SourceType.JVM, SourceType.ENV);
String firstEnv = JVM_ARGS_PROPERTY_SOURCE.getProperty(Constants.SysEnv.NACOS_ENV_FIRST);
if (StringUtils.isBlank(firstEnv)) {
firstEnv = SYSTEM_ENV_PROPERTY_SOURCE.getProperty(Constants.SysEnv.NACOS_ENV_FIRST);
}
if (StringUtils.isNotBlank(firstEnv)) {
try {
final SourceType sourceType = SourceType.valueOf(firstEnv.toUpperCase());
if (!sourceType.equals(SourceType.PROPERTIES)) {
final int index = initOrder.indexOf(sourceType);
final SourceType replacedSourceType = initOrder.set(0, sourceType);
initOrder.set(index, replacedSourceType);
}
} catch (Exception e) {
LOGGER.warn("first source type parse error, it will be used default order!", e);
}
}
return initOrder;
}
static final SearchableProperties INSTANCE = new SearchableProperties();
private final List<AbstractPropertySource> propertySources;
private final PropertiesPropertySource propertiesPropertySource;
private SearchableProperties() {
this(new PropertiesPropertySource());
}
private SearchableProperties(PropertiesPropertySource propertiesPropertySource) {
this.propertiesPropertySource = propertiesPropertySource;
this.propertySources = build(propertiesPropertySource, JVM_ARGS_PROPERTY_SOURCE,
SYSTEM_ENV_PROPERTY_SOURCE);
}
@Override
public String getProperty(String key) {
return getProperty(key, null);
}
@Override
public String getProperty(String key, String defaultValue) {
return this.search(key, String.class).orElse(defaultValue);
}
@Override
public String getPropertyFrom(SourceType source, String key) {
if (source == null) {
return this.getProperty(key);
}
switch (source) {
case JVM:
return JVM_ARGS_PROPERTY_SOURCE.getProperty(key);
case ENV:
return SYSTEM_ENV_PROPERTY_SOURCE.getProperty(key);
case PROPERTIES:
return this.propertiesPropertySource.getProperty(key);
default:
return this.getProperty(key);
}
}
@Override
public Properties getProperties(SourceType source) {
if (source == null) {
return null;
}
switch (source) {
case JVM:
return JVM_ARGS_PROPERTY_SOURCE.asProperties();
case ENV:
return SYSTEM_ENV_PROPERTY_SOURCE.asProperties();
case PROPERTIES:
return this.propertiesPropertySource.asProperties();
default:
return null;
}
}
@Override
public Boolean getBoolean(String key) {
return getBoolean(key, null);
}
@Override
public Boolean getBoolean(String key, Boolean defaultValue) {
return this.search(key, Boolean.class).orElse(defaultValue);
}
@Override
public Integer getInteger(String key) {
return getInteger(key, null);
}
@Override
public Integer getInteger(String key, Integer defaultValue) {
return this.search(key, Integer.class).orElse(defaultValue);
}
@Override
public Long getLong(String key) {
return getLong(key, null);
}
@Override
public Long getLong(String key, Long defaultValue) {
return this.search(key, Long.class).orElse(defaultValue);
}
@Override
public void setProperty(String key, String value) {
propertiesPropertySource.setProperty(key, value);
}
@Override
public void addProperties(Properties properties) {
propertiesPropertySource.addProperties(properties);
}
@Override
public Properties asProperties() {
Properties properties = new Properties();
final ListIterator<AbstractPropertySource> iterator =
propertySources.listIterator(propertySources.size());
while (iterator.hasPrevious()) {
final AbstractPropertySource previous = iterator.previous();
properties.putAll(previous.asProperties());
}
return properties;
}
@Override
public boolean containsKey(String key) {
for (AbstractPropertySource propertySource : propertySources) {
final boolean containing = propertySource.containsKey(key);
if (containing) {
return true;
}
}
return false;
}
private <T> Optional<T> search(String key, Class<T> targetType) {
for (AbstractPropertySource propertySource : propertySources) {
final String value = propertySource.getProperty(key);
if (value != null) {
if (targetType.isAssignableFrom(String.class)) {
return (Optional<T>) Optional.of(value);
}
return Optional.ofNullable(CONVERTER.convert(value, targetType));
}
}
return Optional.empty();
}
private List<AbstractPropertySource> build(AbstractPropertySource... propertySources) {
final Map<SourceType, AbstractPropertySource> sourceMap = Arrays.stream(propertySources)
.collect(Collectors.toMap(AbstractPropertySource::getType,
propertySource -> propertySource));
return SEARCH_ORDER.stream().map(sourceMap::get).collect(Collectors.toList());
}
@Override
public NacosClientProperties derive() {
return new SearchableProperties(
new PropertiesPropertySource(this.propertiesPropertySource));
}
@Override
public NacosClientProperties derive(Properties properties) {
final NacosClientProperties nacosClientProperties = this.derive();
nacosClientProperties.addProperties(properties);
return nacosClientProperties;
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 1999-2022 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.client.env;
/**
* properties source type enum.
* @author onewe
*/
public enum SourceType {
/**
* get value from properties.
*/
PROPERTIES,
/**
* get value from jvm args.
*/
JVM,
/**
* get value from system environment.
*/
ENV,
/**
* get value from unknown environment, will be search in all properties by orders.
*/
UNKNOWN
}
@@ -0,0 +1,89 @@
/*
* Copyright 1999-2022 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.client.env;
import java.util.Map;
import java.util.Properties;
class SystemEnvPropertySource extends AbstractPropertySource {
private final Map<String, String> env = System.getenv();
@Override
SourceType getType() {
return SourceType.ENV;
}
@Override
String getProperty(String key) {
String checkedKey = checkPropertyName(key);
if (checkedKey == null) {
final String upperCaseKey = key.toUpperCase();
if (!upperCaseKey.equals(key)) {
checkedKey = checkPropertyName(upperCaseKey);
}
}
if (checkedKey == null) {
return null;
}
return env.get(checkedKey);
}
/**
* copy from https://github.com/spring-projects/spring-framework.git
* Copyright 2002-2021 the original author or authors.
* Since:
* 3.1
* Author:
* Chris Beams, Juergen Hoeller
*/
private String checkPropertyName(String name) {
// Check name as-is
if (containsKey(name)) {
return name;
}
// Check name with just dots replaced
String noDotName = name.replace('.', '_');
if (!name.equals(noDotName) && containsKey(noDotName)) {
return noDotName;
}
// Check name with just hyphens replaced
String noHyphenName = name.replace('-', '_');
if (!name.equals(noHyphenName) && containsKey(noHyphenName)) {
return noHyphenName;
}
// Check name with dots and hyphens replaced
String noDotNoHyphenName = noDotName.replace('-', '_');
if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) {
return noDotNoHyphenName;
}
// Give up
return null;
}
@Override
boolean containsKey(String name) {
return this.env.containsKey(name);
}
@Override
Properties asProperties() {
Properties properties = new Properties();
properties.putAll(this.env);
return properties;
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 1999-2022 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.client.env.convert;
abstract class AbstractPropertyConverter<T> {
/**
* convert property to target object.
* @param property the property gets from environments
* @return target object
*/
abstract T convert(String property);
}
@@ -0,0 +1,56 @@
/*
* Copyright 1999-2022 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.client.env.convert;
import com.alibaba.nacos.common.utils.StringUtils;
import java.util.HashSet;
import java.util.Set;
class BooleanConverter extends AbstractPropertyConverter<Boolean> {
private static final Set<String> TRUE_VALUES = new HashSet<>(8);
private static final Set<String> FALSE_VALUES = new HashSet<>(8);
static {
TRUE_VALUES.add("true");
TRUE_VALUES.add("on");
TRUE_VALUES.add("yes");
TRUE_VALUES.add("1");
FALSE_VALUES.add("false");
FALSE_VALUES.add("off");
FALSE_VALUES.add("no");
FALSE_VALUES.add("0");
}
@Override
Boolean convert(String property) {
if (StringUtils.isEmpty(property)) {
return null;
}
property = property.toLowerCase();
if (TRUE_VALUES.contains(property)) {
return Boolean.TRUE;
} else if (FALSE_VALUES.contains(property)) {
return Boolean.FALSE;
} else {
throw new IllegalArgumentException("Invalid boolean value '" + property + "'");
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 1999-2022 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.client.env.convert;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingFormatArgumentException;
/**
* default converters.
* @author onewe
*/
public class CompositeConverter {
private final Map<Class<?>, AbstractPropertyConverter<?>> converterRegistry = new HashMap<>();
public CompositeConverter() {
converterRegistry.put(Boolean.class, new BooleanConverter());
converterRegistry.put(Integer.class, new IntegerConverter());
converterRegistry.put(Long.class, new LongConverter());
}
/**
* convert property to target type.
* @param property the property gets from environments
* @param targetClass target class object
* @param <T> target type
* @return the object of target type
*/
public <T> T convert(String property, Class<T> targetClass) {
final AbstractPropertyConverter<?> converter = converterRegistry.get(targetClass);
if (converter == null) {
throw new MissingFormatArgumentException(
"converter not found, can't convert from String to "
+ targetClass.getCanonicalName());
}
return (T) converter.convert(property);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 1999-2022 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.client.env.convert;
import com.alibaba.nacos.common.utils.StringUtils;
class IntegerConverter extends AbstractPropertyConverter<Integer> {
@Override
Integer convert(String property) {
if (StringUtils.isEmpty(property)) {
return null;
}
try {
return Integer.valueOf(property);
} catch (Exception e) {
throw new IllegalArgumentException(
"Cannot convert String [" + property + "] to Integer");
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 1999-2022 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.client.env.convert;
import com.alibaba.nacos.common.utils.StringUtils;
class LongConverter extends AbstractPropertyConverter<Long> {
@Override
Long convert(String property) {
if (StringUtils.isEmpty(property)) {
return null;
}
try {
return Long.valueOf(property);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot convert String [" + property + "] to Long");
}
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 1999-2018 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.client.remote;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.http.AbstractHttpClientFactory;
import com.alibaba.nacos.common.http.HttpClientBeanHolder;
import com.alibaba.nacos.common.http.HttpClientConfig;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.lifecycle.Closeable;
import com.alibaba.nacos.common.utils.ExceptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* http Manager.
*
* @author Nacos
*/
public class HttpClientManager implements Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientManager.class);
private static final HttpClientFactory HTTP_CLIENT_FACTORY = new HttpClientFactory();
private static final int CON_TIME_OUT_MILLIS = 1000;
private static final int READ_TIME_OUT_MILLIS = 3000;
private static class HttpClientManagerInstance {
private static final HttpClientManager INSTANCE = new HttpClientManager();
}
public static HttpClientManager getInstance() {
return HttpClientManagerInstance.INSTANCE;
}
@Override
public void shutdown() throws NacosException {
LOGGER.info("[HttpClientManager] Start destroying NacosRestTemplate");
try {
HttpClientBeanHolder.shutdownNacosSyncRest(HTTP_CLIENT_FACTORY.getClass().getName());
} catch (Exception ex) {
LOGGER.error(
"[HttpClientManager] An exception occurred when the HTTP client was closed : {}",
ExceptionUtil.getStackTrace(ex));
}
LOGGER.info("[HttpClientManager] Completed destruction of NacosRestTemplate");
}
/**
* get connectTimeout.
*
* @param connectTimeout connectTimeout
* @return int return max timeout
*/
public int getConnectTimeoutOrDefault(int connectTimeout) {
return Math.max(CON_TIME_OUT_MILLIS, connectTimeout);
}
/**
* get NacosRestTemplate Instance.
*
* @return NacosRestTemplate
*/
public NacosRestTemplate getNacosRestTemplate() {
return HttpClientBeanHolder.getNacosRestTemplate(HTTP_CLIENT_FACTORY);
}
/**
* HttpClientFactory.
*/
private static class HttpClientFactory extends AbstractHttpClientFactory {
@Override
protected HttpClientConfig buildHttpClientConfig() {
return HttpClientConfig.builder().setConTimeOutMillis(CON_TIME_OUT_MILLIS)
.setReadTimeOutMillis(READ_TIME_OUT_MILLIS).build();
}
@Override
protected Logger assignLogger() {
return LOGGER;
}
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 1999-2018 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.client.utils;
import com.alibaba.nacos.client.constant.Constants;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.utils.StringUtils;
import java.io.File;
/**
* appName util.
*
* @author Nacos
*/
public class AppNameUtils {
private static final String PARAM_MARKING_JBOSS = "jboss.server.home.dir";
private static final String PARAM_MARKING_JETTY = "jetty.home";
private static final String PARAM_MARKING_TOMCAT = "catalina.base";
private static final String LINUX_ADMIN_HOME = "/home/admin/";
private static final String SERVER_JBOSS = "jboss";
private static final String SERVER_JETTY = "jetty";
private static final String SERVER_TOMCAT = "tomcat";
private static final String SERVER_UNKNOWN = "unknown server";
private static final String DEFAULT_APP_NAME = "unknown";
public static String getAppName() {
String appName;
appName = getAppNameByProjectName();
if (appName != null) {
return appName;
}
appName = getAppNameByServerHome();
if (appName != null) {
return appName;
}
return DEFAULT_APP_NAME;
}
private static String getAppNameByProjectName() {
return NacosClientProperties.PROTOTYPE.getProperty(Constants.SysEnv.PROJECT_NAME);
}
private static String getAppNameByServerHome() {
String serverHome = null;
if (SERVER_JBOSS.equals(getServerType())) {
serverHome = NacosClientProperties.PROTOTYPE.getProperty(PARAM_MARKING_JBOSS);
} else if (SERVER_JETTY.equals(getServerType())) {
serverHome = NacosClientProperties.PROTOTYPE.getProperty(PARAM_MARKING_JETTY);
} else if (SERVER_TOMCAT.equals(getServerType())) {
serverHome = NacosClientProperties.PROTOTYPE.getProperty(PARAM_MARKING_TOMCAT);
}
if (serverHome != null && serverHome.startsWith(LINUX_ADMIN_HOME)) {
return StringUtils.substringBetween(serverHome, LINUX_ADMIN_HOME, File.separator);
}
return null;
}
private static String getServerType() {
String serverType;
if (NacosClientProperties.PROTOTYPE.getProperty(PARAM_MARKING_JBOSS) != null) {
serverType = SERVER_JBOSS;
} else if (NacosClientProperties.PROTOTYPE.getProperty(PARAM_MARKING_JETTY) != null) {
serverType = SERVER_JETTY;
} else if (NacosClientProperties.PROTOTYPE.getProperty(PARAM_MARKING_TOMCAT) != null) {
serverType = SERVER_TOMCAT;
} else {
serverType = SERVER_UNKNOWN;
}
return serverType;
}
}
@@ -0,0 +1,274 @@
/*
* Copyright 1999-2025 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.client.utils;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.utils.ConvertUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.common.utils.VersionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
import java.util.regex.Pattern;
/**
* Nacos client basic parameters utils.
*
* @author xiweng.yy
*/
public class ClientBasicParamUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientBasicParamUtil.class);
private static final Pattern PATTERN = Pattern.compile("\\$\\{[^}]+\\}");
private static final int DESENSITISE_PARAMETER_MIN_LENGTH = 2;
private static final int DESENSITISE_PARAMETER_KEEP_ONE_CHAR_LENGTH = 8;
private static final String NACOS_CLIENT_APP_KEY = "nacos.client.appKey";
private static final String NACOS_CLIENT_CONTEXT_PATH_KEY = "nacos.client.contextPath";
private static final String DEFAULT_NACOS_CLIENT_CONTEXT_PATH = "nacos";
private static final String NACOS_SERVER_PORT_KEY = "nacos.server.port";
private static final String DEFAULT_SERVER_PORT = "8848";
private static final String BLANK_STR = "";
private static String defaultContextPath;
private static String appKey;
private static String clientVersion = "unknown";
private static String serverPort;
private static String defaultNodesPath = "serverlist";
static {
// Client identity information
appKey = NacosClientProperties.PROTOTYPE.getProperty(NACOS_CLIENT_APP_KEY, BLANK_STR);
defaultContextPath =
NacosClientProperties.PROTOTYPE.getProperty(NACOS_CLIENT_CONTEXT_PATH_KEY,
DEFAULT_NACOS_CLIENT_CONTEXT_PATH);
serverPort = NacosClientProperties.PROTOTYPE.getProperty(NACOS_SERVER_PORT_KEY,
DEFAULT_SERVER_PORT);
LOGGER.info("[settings] [req-serv] nacos-server port:{}", serverPort);
clientVersion = VersionUtils.version;
}
public static String getAppKey() {
return appKey;
}
public static void setAppKey(String appKey) {
ClientBasicParamUtil.appKey = appKey;
}
public static String getDefaultContextPath() {
return defaultContextPath;
}
public static void setDefaultContextPath(String defaultContextPath) {
ClientBasicParamUtil.defaultContextPath = defaultContextPath;
}
public static String getClientVersion() {
return clientVersion;
}
public static void setClientVersion(String clientVersion) {
ClientBasicParamUtil.clientVersion = clientVersion;
}
public static String getDefaultServerPort() {
return serverPort;
}
public static String getDefaultNodesPath() {
return defaultNodesPath;
}
public static void setDefaultNodesPath(String defaultNodesPath) {
ClientBasicParamUtil.defaultNodesPath = defaultNodesPath;
}
/**
* Parse namespace from properties and environment.
*
* @param properties properties
* @return namespace
*/
public static String parseNamespace(NacosClientProperties properties) {
String namespaceTmp = null;
String isUseCloudNamespaceParsing =
properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
properties.getProperty(
SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));
if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {
namespaceTmp = TenantUtil.getUserTenantForAcm();
namespaceTmp = TemplateUtils.stringBlankAndThenExecute(namespaceTmp, () -> {
String namespace = properties
.getProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
return StringUtils.isNotBlank(namespace) ? namespace : StringUtils.EMPTY;
});
}
if (StringUtils.isBlank(namespaceTmp)) {
namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE);
}
return StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim()
: Constants.DEFAULT_NAMESPACE_ID;
}
/**
* Parse end point rule.
*
* @param endpointUrl endpoint url
* @return end point rule
*/
public static String parsingEndpointRule(String endpointUrl) {
// If entered in the configuration file, the priority in ENV will be given priority.
if (endpointUrl == null || !PATTERN.matcher(endpointUrl).find()) {
// skip retrieve from system property and retrieve directly from system env
String endpointUrlSource = NacosClientProperties.PROTOTYPE.getProperty(
PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL);
if (StringUtils.isNotBlank(endpointUrlSource)) {
endpointUrl = endpointUrlSource;
}
return StringUtils.isNotBlank(endpointUrl) ? endpointUrl : "";
}
endpointUrl =
endpointUrl.substring(endpointUrl.indexOf("${") + 2, endpointUrl.lastIndexOf("}"));
int defStartOf = endpointUrl.indexOf(":");
String defaultEndpointUrl = null;
if (defStartOf != -1) {
defaultEndpointUrl = endpointUrl.substring(defStartOf + 1);
endpointUrl = endpointUrl.substring(0, defStartOf);
}
String endpointUrlSource = TemplateUtils.stringBlankAndThenExecute(
NacosClientProperties.PROTOTYPE.getProperty(endpointUrl),
() -> NacosClientProperties.PROTOTYPE.getProperty(
PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL));
if (StringUtils.isBlank(endpointUrlSource)) {
if (StringUtils.isNotBlank(defaultEndpointUrl)) {
endpointUrl = defaultEndpointUrl;
}
} else {
endpointUrl = endpointUrlSource;
}
return StringUtils.isNotBlank(endpointUrl) ? endpointUrl : "";
}
public static String getInputParameters(Properties properties) {
boolean logAllParameters =
ConvertUtils.toBoolean(properties.getProperty(PropertyKeyConst.LOG_ALL_PROPERTIES),
false);
StringBuilder result = new StringBuilder();
if (logAllParameters) {
result.append(
"Log nacos client init properties with Full mode, This mode is only used for debugging and troubleshooting. ");
result.append(
"Please close this mode by removing properties `logAllProperties` after finishing debug or troubleshoot.\n");
result.append("Nacos client all init properties: \n");
properties.forEach(
(key, value) -> result.append("\t").append(key.toString()).append("=")
.append(value.toString())
.append("\n"));
} else {
result.append("Nacos client key init properties: \n");
appendKeyParameters(result, properties, PropertyKeyConst.SERVER_ADDR, false);
appendKeyParameters(result, properties, PropertyKeyConst.NAMESPACE, false);
appendKeyParameters(result, properties, PropertyKeyConst.ENDPOINT, false);
appendKeyParameters(result, properties, PropertyKeyConst.ENDPOINT_PORT, false);
appendKeyParameters(result, properties, PropertyKeyConst.USERNAME, false);
appendKeyParameters(result, properties, PropertyKeyConst.PASSWORD, true);
appendKeyParameters(result, properties, PropertyKeyConst.ACCESS_KEY, true);
appendKeyParameters(result, properties, PropertyKeyConst.SECRET_KEY, true);
appendKeyParameters(result, properties, PropertyKeyConst.RAM_ROLE_NAME, false);
appendKeyParameters(result, properties, PropertyKeyConst.SIGNATURE_REGION_ID, false);
}
return result.toString();
}
private static void appendKeyParameters(StringBuilder result, Properties properties,
String propertyKey,
boolean needDesensitise) {
String propertyValue = properties.getProperty(propertyKey);
if (StringUtils.isBlank(propertyValue)) {
return;
}
result.append("\t").append(propertyKey).append("=")
.append(needDesensitise ? desensitiseParameter(propertyValue) : propertyValue)
.append("\n");
}
/**
* Do desensitise for parameters with `*` to replace inner content.
*
* @param parameterValue parameter value which need be desensitised.
* @return desensitised parameter value.
*/
public static String desensitiseParameter(String parameterValue) {
if (parameterValue.length() <= DESENSITISE_PARAMETER_MIN_LENGTH) {
return parameterValue;
}
if (parameterValue.length() < DESENSITISE_PARAMETER_KEEP_ONE_CHAR_LENGTH) {
return doDesensitiseParameter(parameterValue, 1);
}
return doDesensitiseParameter(parameterValue, 2);
}
private static String doDesensitiseParameter(String parameterValue, int keepCharCount) {
StringBuilder result = new StringBuilder(parameterValue);
for (int i = keepCharCount; i < parameterValue.length() - keepCharCount; i++) {
result.setCharAt(i, '*');
}
return result.toString();
}
public static String getNameSuffixByServerIps(String... serverIps) {
StringBuilder sb = new StringBuilder();
String split = "";
for (String serverIp : serverIps) {
sb.append(split);
serverIp = serverIp.replaceAll("http(s)?://", "");
sb.append(serverIp.replaceAll(":", "_"));
split = "-";
}
return sb.toString();
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 1999-2018 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.client.utils;
import com.alibaba.nacos.common.utils.StringUtils;
/**
* Context path Util.
*
* @author Wei.Wang
*/
public class ContextPathUtil {
private static final String ROOT_WEB_CONTEXT_PATH = "/";
/**
* normalize context path.
*
* @param contextPath origin context path
* @return normalized context path
*/
public static String normalizeContextPath(String contextPath) {
if (StringUtils.isBlank(contextPath) || ROOT_WEB_CONTEXT_PATH.equals(contextPath)) {
return StringUtils.EMPTY;
}
return contextPath.startsWith(ROOT_WEB_CONTEXT_PATH) ? contextPath
: ROOT_WEB_CONTEXT_PATH + contextPath;
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 1999-2018 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.client.utils;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Callable;
/**
* Template Utils.
*
* @author Nacos
*/
public class TemplateUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(TemplateUtils.class);
/**
* Execute if string not empty.
*
* @param source source
* @param runnable execute runnable
*/
public static void stringNotEmptyAndThenExecute(String source, Runnable runnable) {
if (StringUtils.isNotEmpty(source)) {
try {
runnable.run();
} catch (Exception e) {
LOGGER.error("string not empty and then execute cause an exception.", e);
}
}
}
/**
* Execute if string empty.
*
* @param source empty source
* @param callable execute callable
* @return result
*/
public static String stringEmptyAndThenExecute(String source, Callable<String> callable) {
if (StringUtils.isEmpty(source)) {
try {
return callable.call();
} catch (Exception e) {
LOGGER.error("string empty and then execute cause an exception.", e);
}
}
return source == null ? null : source.trim();
}
/**
* Execute if string blank.
*
* @param source empty source
* @param callable execute callable
* @return result
*/
public static String stringBlankAndThenExecute(String source, Callable<String> callable) {
if (StringUtils.isBlank(source)) {
try {
return callable.call();
} catch (Exception e) {
LOGGER.error("string empty and then execute cause an exception.", e);
}
}
return source == null ? null : source.trim();
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 1999-2018 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.client.utils;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.utils.StringUtils;
/**
* Tenant Util.
*
* @author Nacos
*/
public class TenantUtil {
private static final String USER_TENANT;
private static final String DEFAULT_ACM_NAMESPACE = "";
private static final String TENANT_ID = "tenant.id";
private static final String ACM_NAMESPACE_PROPERTY = "acm.namespace";
static {
USER_TENANT = NacosClientProperties.PROTOTYPE.getProperty(TENANT_ID, "");
}
/**
* Adapt the way ACM gets tenant on the cloud.
* <p>
* Note the difference between getting and getting ANS. Since the processing logic on the server side is different,
* the default value returns differently.
* </p>
*
* @return user tenant for acm
*/
public static String getUserTenantForAcm() {
String tmp = USER_TENANT;
if (StringUtils.isBlank(USER_TENANT)) {
tmp = NacosClientProperties.PROTOTYPE.getProperty(ACM_NAMESPACE_PROPERTY,
DEFAULT_ACM_NAMESPACE);
}
return tmp;
}
/**
* Adapt the way ANS gets tenant on the cloud.
*
* @return user tenant for ans
*/
public static String getUserTenantForAns() {
String tmp = USER_TENANT;
if (StringUtils.isBlank(USER_TENANT)) {
tmp = NacosClientProperties.PROTOTYPE.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
}
return tmp;
}
}
@@ -0,0 +1,19 @@
#
# Copyright 1999-2024 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.
#
#
com.alibaba.nacos.client.address.EndpointServerListProvider
com.alibaba.nacos.client.address.PropertiesListProvider
@@ -0,0 +1,20 @@
#
# Copyright 1999-2021 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.
#
#
com.alibaba.nacos.client.auth.impl.NacosClientAuthServiceImpl
com.alibaba.nacos.client.auth.ram.RamClientAuthServiceImpl
com.alibaba.nacos.client.auth.oidc.OidcClientAuthServiceImpl
@@ -0,0 +1,211 @@
/*
* 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class AbstractServerListManagerTest {
@Mock
NacosRestTemplate restTemplate;
NacosClientProperties properties;
AbstractServerListManager serverListManager;
@BeforeEach
void setUp() {
properties = NacosClientProperties.PROTOTYPE.derive();
}
@AfterEach
void tearDown() throws NacosException {
if (null != serverListManager) {
serverListManager.shutdown();
}
}
@Test
void testConstructorWithNamespace() {
serverListManager = new MockServerListManager(properties, "test-namespace");
assertFalse(properties.containsKey(PropertyKeyConst.NAMESPACE));
assertFalse(properties.containsKey(Constants.CLIENT_MODULE_TYPE));
assertTrue(serverListManager.getProperties().containsKey(PropertyKeyConst.NAMESPACE));
assertEquals("testModule",
serverListManager.getProperties().getProperty(Constants.CLIENT_MODULE_TYPE));
}
@Test
void testConstructorWithoutNamespace() {
serverListManager = new MockServerListManager(properties);
assertFalse(properties.containsKey(PropertyKeyConst.NAMESPACE));
assertFalse(properties.containsKey(Constants.CLIENT_MODULE_TYPE));
assertFalse(serverListManager.getProperties().containsKey(PropertyKeyConst.NAMESPACE));
assertEquals("testModule",
serverListManager.getProperties().getProperty(Constants.CLIENT_MODULE_TYPE));
}
@Test
void testStartWithoutProvider() {
serverListManager = new MockServerListManager(properties);
assertThrows(NacosException.class, () -> serverListManager.start());
}
@Test
void testGetServerList() throws NacosException {
properties.setProperty("MockTest", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
// Mock provider will call this method in init.
verify(restTemplate).getInterceptors();
assertEquals(1, serverListManager.getServerList().size());
assertEquals("mock-server-list", serverListManager.getServerList().get(0));
}
@Test
void testGetServerNameDefault() throws NacosException {
properties.setProperty("MockTest", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("testModule-", serverListManager.getServerName());
}
@Test
void testGetServerName() throws NacosException {
properties.setProperty("MockTest", "true");
properties.setProperty("ReturnMock", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("testModule-MockServerName", serverListManager.getServerName());
}
@Test
void testGetContextPathDefault() throws NacosException {
properties.setProperty("MockTest", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("nacos", serverListManager.getContextPath());
}
@Test
void testGetContextPath() throws NacosException {
properties.setProperty("MockTest", "true");
properties.setProperty("ReturnMock", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("MockContextPath", serverListManager.getContextPath());
}
@Test
void testGetNamespaceDefault() throws NacosException {
properties.setProperty("MockTest", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("", serverListManager.getNamespace());
}
@Test
void testGetNamespace() throws NacosException {
properties.setProperty("MockTest", "true");
properties.setProperty("ReturnMock", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("MockNamespace", serverListManager.getNamespace());
}
@Test
void testGetAddressSourceDefault() throws NacosException {
properties.setProperty("MockTest", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("", serverListManager.getAddressSource());
}
@Test
void testGetAddressSource() throws NacosException {
properties.setProperty("MockTest", "true");
properties.setProperty("ReturnMock", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertEquals("MockAddressSource", serverListManager.getAddressSource());
}
@Test
void testIsFixedDefault() throws NacosException {
properties.setProperty("MockTest", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertFalse(serverListManager.isFixed());
}
@Test
void testIsFixed() throws NacosException {
properties.setProperty("MockTest", "true");
properties.setProperty("ReturnMock", "true");
serverListManager = new MockServerListManager(properties);
serverListManager.start();
assertTrue(serverListManager.isFixed());
}
private class MockServerListManager extends AbstractServerListManager {
public MockServerListManager(NacosClientProperties properties) {
super(properties);
}
public MockServerListManager(NacosClientProperties properties, String namespace) {
super(properties, namespace);
}
@Override
protected String getModuleName() {
return "testModule";
}
@Override
protected NacosRestTemplate getNacosRestTemplate() {
return restTemplate;
}
@Override
public String genNextServer() {
return "";
}
@Override
public String getCurrentServer() {
return "";
}
}
}
@@ -0,0 +1,461 @@
/*
* 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.SystemPropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.constant.Constants;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
import com.alibaba.nacos.client.utils.ContextPathUtil;
import com.alibaba.nacos.common.constant.HttpHeaderConsts;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.StringUtils;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class EndpointServerListProviderTest {
@Mock
NacosRestTemplate nacosRestTemplate;
private EndpointServerListProvider serverListProvider;
private NacosClientProperties properties;
private HttpRestResult requestSuccess;
@BeforeEach
void setUp() {
requestSuccess =
new HttpRestResult<>(Header.EMPTY, 200, "\n127.0.0.1\nlocalhost:9848", "success");
serverListProvider = new EndpointServerListProvider();
properties = NacosClientProperties.PROTOTYPE.derive();
}
@AfterEach
void tearDown() throws NacosException {
System.clearProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL);
System.clearProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_PORT);
System.clearProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_CONTEXT_PATH);
System.clearProperty(PropertyKeyConst.ENDPOINT_CLUSTER_NAME);
System.clearProperty(SystemPropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE);
serverListProvider.shutdown();
}
@Test
void testInitWithoutProperties() throws NacosException {
assertThrows(NacosException.class, () -> serverListProvider.init(null, nacosRestTemplate));
}
@Test
void testMatchAndInitForPropertiesEndpoint() throws Exception {
assertFalse(serverListProvider.match(properties));
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
assertTrue(serverListProvider.match(properties));
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"", ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testMatchAndInitForSystemEndpoint() throws Exception {
assertFalse(serverListProvider.match(properties));
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL,
"endpointFromSystem");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
assertTrue(serverListProvider.match(properties));
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromSystem", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(), "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testMatchAndInitByParsingFalseFromProperties() throws Exception {
assertFalse(serverListProvider.match(properties));
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL,
"endpointFromSystem");
properties.setProperty(PropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE, "false");
assertTrue(serverListProvider.match(properties));
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"", ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testMatchAndInitByParsingFalseFromSystem() throws Exception {
assertFalse(serverListProvider.match(properties));
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL,
"endpointFromSystem");
System.setProperty(SystemPropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE, "false");
assertTrue(serverListProvider.match(properties));
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"", ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testMatchAndInitByParsingTrue() throws Exception {
assertFalse(serverListProvider.match(properties));
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL,
"endpointFromSystem");
assertTrue(serverListProvider.match(properties));
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromSystem", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(), "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithPropertiesEndpointPort() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.ENDPOINT_PORT, "80");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 80, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(), "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithSystemEndpointPort() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_PORT, "443");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 443, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"", ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithPropertiesEndpointContextPath() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.ENDPOINT_CONTEXT_PATH, "address");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, "address",
ClientBasicParamUtil.getDefaultNodesPath(), "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithSystemEndpointContextPath() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_CONTEXT_PATH,
"addresses");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, "addresses",
ClientBasicParamUtil.getDefaultNodesPath(), "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitContextPathWithFull() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.CONTEXT_PATH, "globalContextPath");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, "globalContextPath",
ClientBasicParamUtil.getDefaultNodesPath(), "",
"globalContextPath");
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithPropertiesEndpointClusterName() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.ENDPOINT_CLUSTER_NAME, "endpointClusterName");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
"endpointClusterName", "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithPropertiesEndpointClusterNameWithFull() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.CLUSTER_NAME, "clusterName");
properties.setProperty(PropertyKeyConst.ENDPOINT_CLUSTER_NAME, "endpointClusterName");
properties.setProperty(PropertyKeyConst.IS_ADAPT_CLUSTER_NAME_USAGE, "true");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
"endpointClusterName", "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithSystemEndpointClusterNameByOldWay() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.CLUSTER_NAME, "clusterName");
properties.setProperty(PropertyKeyConst.IS_ADAPT_CLUSTER_NAME_USAGE, "true");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
"clusterName", "",
ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithSystemEndpointClusterWithoutAdapt() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.CLUSTER_NAME, "clusterName");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"", ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithNamespace() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.NAMESPACE, "customNamespace");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"customNamespace", ClientBasicParamUtil.getDefaultContextPath());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithQuery() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.ENDPOINT_QUERY_PARAMS, "nofix=1");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"", ClientBasicParamUtil.getDefaultContextPath(), "nofix=1");
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithNamespaceAndQuery() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.NAMESPACE, "customNamespace");
properties.setProperty(PropertyKeyConst.ENDPOINT_QUERY_PARAMS, "nofix=1");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertInit("endpointFromProperties", 8080, ClientBasicParamUtil.getDefaultContextPath(),
ClientBasicParamUtil.getDefaultNodesPath(),
"customNamespace", ClientBasicParamUtil.getDefaultContextPath(), "nofix=1");
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitWithModuleType() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(com.alibaba.nacos.api.common.Constants.CLIENT_MODULE_TYPE, "naming");
when(nacosRestTemplate.get(anyString(),
argThat(header -> "naming"
.equals(header.getValue(HttpHeaderConsts.REQUEST_MODULE))),
any(Query.class),
eq(String.class))).thenReturn(requestSuccess);
serverListProvider.init(properties, nacosRestTemplate);
assertFalse(serverListProvider.getServerList().isEmpty());
assertEquals(2, serverListProvider.getServerList().size());
}
@Test
void testInitGetServerListWithException() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenThrow(
new IOException("test"));
assertThrows(NacosException.class,
() -> serverListProvider.init(properties, nacosRestTemplate));
}
@Test
void testInitGetServerListWithError() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
HttpRestResult failedResult = new HttpRestResult<>(Header.EMPTY, 500, null, "test");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
failedResult);
assertThrows(NacosException.class,
() -> serverListProvider.init(properties, nacosRestTemplate));
}
@Test
void testRefreshServerList() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.ENDPOINT_REFRESH_INTERVAL_SECONDS, "1");
HttpRestResult newResult =
new HttpRestResult<>(Header.EMPTY, 200, "\n1.1.1.1 \nlocalhost:9848", "success");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess, newResult);
serverListProvider.init(properties, nacosRestTemplate);
assertEquals(2, serverListProvider.getServerList().size());
assertEquals("127.0.0.1:8848", serverListProvider.getServerList().get(0));
assertEquals("localhost:9848", serverListProvider.getServerList().get(1));
Field field =
EndpointServerListProvider.class.getDeclaredField("lastServerListRefreshTime");
field.setAccessible(true);
field.set(serverListProvider, 0L);
// wait refresh
TimeUnit.MILLISECONDS.sleep(2000);
assertEquals(2, serverListProvider.getServerList().size());
assertEquals("1.1.1.1:8848", serverListProvider.getServerList().get(0));
assertEquals("localhost:9848", serverListProvider.getServerList().get(1));
}
@Test
void testRefreshServerListWithDiffSort() throws Exception {
properties.setProperty(PropertyKeyConst.ENDPOINT, "endpointFromProperties");
properties.setProperty(PropertyKeyConst.ENDPOINT_REFRESH_INTERVAL_SECONDS, "1");
HttpRestResult newResult =
new HttpRestResult<>(Header.EMPTY, 200, "\nlocalhost:9848\n127.0.0.1", "success");
when(nacosRestTemplate.get(anyString(), any(Header.class), any(Query.class),
eq(String.class))).thenReturn(
requestSuccess, newResult);
serverListProvider.init(properties, nacosRestTemplate);
assertEquals(2, serverListProvider.getServerList().size());
assertEquals("127.0.0.1:8848", serverListProvider.getServerList().get(0));
assertEquals("localhost:9848", serverListProvider.getServerList().get(1));
Field field =
EndpointServerListProvider.class.getDeclaredField("lastServerListRefreshTime");
field.setAccessible(true);
field.set(serverListProvider, 0L);
// wait refresh
TimeUnit.MILLISECONDS.sleep(2000);
assertEquals(2, serverListProvider.getServerList().size());
assertEquals("127.0.0.1:8848", serverListProvider.getServerList().get(0));
assertEquals("localhost:9848", serverListProvider.getServerList().get(1));
}
private void assertInit(String expectedEndpoint, int expectEndpointPort,
String expectedEndpointContext,
String expectedServiceName, String expectedNamespace, String expectedContextPath) {
assertInit(expectedEndpoint, expectEndpointPort, expectedEndpointContext,
expectedServiceName,
expectedNamespace, expectedContextPath, null);
}
private void assertInit(String expectedEndpoint, int expectEndpointPort,
String expectedEndpointContext,
String expectedServiceName, String expectedNamespace, String expectedContextPath,
String expectedQuery) {
String expectedAddressServerUrl = String.format("http://%s:%d%s/%s", expectedEndpoint,
expectEndpointPort,
ContextPathUtil.normalizeContextPath(expectedEndpointContext), expectedServiceName);
assertEquals(Constants.Address.ENDPOINT_SERVER_LIST_PROVIDER_ORDER,
serverListProvider.getOrder());
String expectedServerName =
String.format("%s-%s_%d_%s_%s", "custom", expectedEndpoint, expectEndpointPort,
expectedEndpointContext, expectedServiceName);
if (StringUtils.isNotBlank(expectedNamespace)) {
expectedServerName = String.format("%s_%s", expectedServerName, expectedNamespace);
expectedAddressServerUrl += "?namespace=" + expectedNamespace;
}
if (StringUtils.isNotBlank(expectedQuery)) {
String queryTag = StringUtils.isBlank(expectedNamespace) ? "?" : "&";
expectedAddressServerUrl += queryTag + expectedQuery;
}
assertEquals(expectedAddressServerUrl, serverListProvider.getAddressSource());
assertEquals(expectedServerName, serverListProvider.getServerName());
assertEquals(expectedNamespace, serverListProvider.getNamespace());
assertEquals(expectedContextPath, serverListProvider.getContextPath());
}
}
@@ -0,0 +1,83 @@
/*
* 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.client.address;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.constant.Constants;
import com.alibaba.nacos.client.env.NacosClientProperties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PropertiesListProviderTest {
private PropertiesListProvider propertiesListProvider;
@BeforeEach
void setUp() {
propertiesListProvider = new PropertiesListProvider();
}
@AfterEach
void tearDown() throws NacosException {
propertiesListProvider.shutdown();
}
@Test
void testInitWithoutProperties() throws NacosException {
assertThrows(NacosException.class, () -> propertiesListProvider.init(null, null));
}
@Test
void testInit() throws NacosException {
NacosClientProperties properties = NacosClientProperties.PROTOTYPE.derive();
assertFalse(propertiesListProvider.match(properties));
properties.setProperty(PropertyKeyConst.SERVER_ADDR,
"localhost:1111,http://127.0.0.1:2222;https://1.1.1.1:3333,2.2.2.2;http://3.3.3.3,https://4.4.4.4");
assertTrue(propertiesListProvider.match(properties));
propertiesListProvider.init(properties, null);
assertEquals(6, propertiesListProvider.getServerList().size());
assertEquals("localhost:1111", propertiesListProvider.getServerList().get(0));
assertEquals("http://127.0.0.1:2222", propertiesListProvider.getServerList().get(1));
assertEquals("https://1.1.1.1:3333", propertiesListProvider.getServerList().get(2));
assertEquals("2.2.2.2:8848", propertiesListProvider.getServerList().get(3));
assertEquals("http://3.3.3.3", propertiesListProvider.getServerList().get(4));
assertEquals("https://4.4.4.4", propertiesListProvider.getServerList().get(5));
assertTrue(propertiesListProvider.isFixed());
assertEquals(Constants.Address.ADDRESS_SERVER_LIST_PROVIDER_ORDER,
propertiesListProvider.getOrder());
assertEquals(
"fixed-localhost_1111-127.0.0.1_2222-1.1.1.1_3333-2.2.2.2_8848-3.3.3.3-4.4.4.4",
propertiesListProvider.getServerName());
}
@Test
void testGetServerNameWithNamespace() throws NacosException {
NacosClientProperties properties = NacosClientProperties.PROTOTYPE.derive();
properties.setProperty(PropertyKeyConst.NAMESPACE, "test_namespace");
properties.setProperty(PropertyKeyConst.SERVER_ADDR, "localhost:1111");
propertiesListProvider.init(properties, null);
assertEquals("fixed-test_namespace-localhost_1111", propertiesListProvider.getServerName());
assertEquals("test_namespace", propertiesListProvider.getNamespace());
}
}
@@ -0,0 +1,103 @@
/*
* 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.client.address.mock;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.address.ServerListProvider;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import java.util.Collections;
import java.util.List;
public class MockServerListProvider implements ServerListProvider {
private NacosClientProperties properties;
@Override
public void init(NacosClientProperties properties, NacosRestTemplate nacosRestTemplate)
throws NacosException {
this.properties = properties;
nacosRestTemplate.getInterceptors();
}
@Override
public List<String> getServerList() {
if (properties.containsKey("EmptyList")) {
return Collections.emptyList();
}
return Collections.singletonList("mock-server-list");
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
@Override
public boolean match(NacosClientProperties properties) {
return properties.containsKey("MockTest");
}
@Override
public void shutdown() throws NacosException {
}
@Override
public String getServerName() {
if (isReturnMock()) {
return "MockServerName";
}
return ServerListProvider.super.getServerName();
}
@Override
public String getNamespace() {
if (isReturnMock()) {
return "MockNamespace";
}
return ServerListProvider.super.getNamespace();
}
@Override
public String getContextPath() {
if (isReturnMock()) {
return "MockContextPath";
}
return ServerListProvider.super.getContextPath();
}
@Override
public boolean isFixed() {
if (isReturnMock()) {
return true;
}
return ServerListProvider.super.isFixed();
}
@Override
public String getAddressSource() {
if (isReturnMock()) {
return "MockAddressSource";
}
return ServerListProvider.super.getAddressSource();
}
private boolean isReturnMock() {
return properties.getBoolean("ReturnMock", false);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 1999-2026 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.client.auth.impl;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class NacosAuthLoginConstantTest {
@Test
void testConstructor() {
assertNotNull(new NacosAuthLoginConstant());
}
@Test
void testFields() {
assertEquals("accessToken", NacosAuthLoginConstant.ACCESSTOKEN);
assertEquals("tokenTtl", NacosAuthLoginConstant.TOKENTTL);
assertEquals("tokenRefreshWindow", NacosAuthLoginConstant.TOKENREFRESHWINDOW);
assertEquals("username", NacosAuthLoginConstant.USERNAME);
assertEquals("password", NacosAuthLoginConstant.PASSWORD);
assertEquals(":", NacosAuthLoginConstant.COLON);
assertEquals("server", NacosAuthLoginConstant.SERVER);
assertEquals("reLoginFlag", NacosAuthLoginConstant.RELOGINFLAG);
}
}
@@ -0,0 +1,280 @@
/*
* Copyright 1999-2021 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.client.auth.impl;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class NacosClientAuthServiceImplTest {
@Test
void testLoginSuccess() throws Exception {
//given
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"ttttttttttttttttt\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
boolean ret = nacosClientAuthService.login(properties);
//then
assertTrue(ret);
}
@Test
void testTestLoginFailCode() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setCode(400);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
boolean ret = nacosClientAuthService.login(properties);
assertFalse(ret);
}
@Test
void testTestLoginFailHttp() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenThrow(new Exception());
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
boolean ret = nacosClientAuthService.login(properties);
assertFalse(ret);
}
@Test
void testTestLoginServerListSuccess() throws Exception {
//given
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"ttttttttttttttttt\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
boolean ret = nacosClientAuthService.login(properties);
assertTrue(ret);
}
@Test
void testTestLoginServerListLoginInWindow() throws Exception {
//given
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"ttttttttttttttttt\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
nacosClientAuthService.login(properties);
//then
boolean ret = nacosClientAuthService.login(properties);
assertTrue(ret);
}
@Test
void testGetAccessToken() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"abc\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
assertTrue(nacosClientAuthService.login(properties));
//then
assertEquals("abc",
nacosClientAuthService.getLoginIdentityContext(null)
.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
}
@Test
void testGetAccessEmptyToken() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"\",\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
assertTrue(nacosClientAuthService.login(properties));
//then
assertEquals("",
nacosClientAuthService.getLoginIdentityContext(null)
.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
}
@Test
void testGetAccessTokenWithoutToken() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"tokenTtl\":1000}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
assertTrue(nacosClientAuthService.login(properties));
//then
assertNull(
nacosClientAuthService.getLoginIdentityContext(null)
.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
}
@Test
void testGetAccessTokenWithInvalidTtl() throws Exception {
NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class);
HttpRestResult<Object> result = new HttpRestResult<>();
result.setData("{\"accessToken\":\"abc\",\"tokenTtl\":\"abc\"}");
result.setCode(200);
when(nacosRestTemplate.postForm(any(), (Header) any(), any(), any(), any()))
.thenReturn(result);
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.setServerList(serverList);
nacosClientAuthService.setNacosRestTemplate(nacosRestTemplate);
//when
assertFalse(nacosClientAuthService.login(properties));
}
@Test
void testReLogin() {
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
nacosClientAuthService.login(new Properties());
// reLogin
nacosClientAuthService.getLoginIdentityContext(null)
.setParameter(NacosAuthLoginConstant.RELOGINFLAG, "true");
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.USERNAME, "aaa");
properties.setProperty(PropertyKeyConst.PASSWORD, "123456");
List<String> serverList = new ArrayList<>();
serverList.add("localhost");
//when
assertTrue(nacosClientAuthService.login(properties));
}
@Test
void testGenerateTokenWithInvalidToken() {
NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl();
long tokenTtl = 18000L;
long tokenRefreshWindow = nacosClientAuthService.generateTokenRefreshWindow(tokenTtl);
assertTrue(tokenRefreshWindow <= tokenTtl / 10);
}
@Test
void testShutdownDoesNotThrow() {
NacosClientAuthServiceImpl service = new NacosClientAuthServiceImpl();
org.junit.jupiter.api.Assertions.assertDoesNotThrow(service::shutdown);
}
}
@@ -0,0 +1,143 @@
/*
* 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.client.auth.impl.process;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.tls.TlsSystemConfig;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HttpLoginProcessorTest {
@Mock
NacosRestTemplate restTemplate;
@Mock
HttpRestResult result;
Properties properties;
HttpLoginProcessor loginProcessor;
@BeforeEach
void setUp() {
loginProcessor = new HttpLoginProcessor(restTemplate);
properties = new Properties();
}
@Test
void testGetResponseSuccess() throws Exception {
properties.setProperty(NacosAuthLoginConstant.SERVER, "http://localhost:8848");
when(restTemplate.postForm(eq("http://localhost:8848/nacos/v3/auth/user/login"),
eq(Header.EMPTY),
any(Query.class), anyMap(), eq(String.class))).thenReturn(result);
when(result.ok()).thenReturn(true);
Map<String, String> mockMap = new HashMap<>();
mockMap.put(Constants.ACCESS_TOKEN, "mock_access_token");
mockMap.put(Constants.TOKEN_TTL, "100L");
when(result.getData()).thenReturn(JacksonUtils.toJson(mockMap));
LoginIdentityContext actual = loginProcessor.getResponse(properties);
assertEquals("mock_access_token", actual.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
assertEquals("100L", actual.getParameter(NacosAuthLoginConstant.TOKENTTL));
}
@Test
void testGetResponseFailed() throws Exception {
properties.setProperty(NacosAuthLoginConstant.SERVER, "localhost");
when(restTemplate.postForm(eq("http://localhost:8848/nacos/v3/auth/user/login"),
eq(Header.EMPTY),
any(Query.class), anyMap(), eq(String.class))).thenReturn(result);
assertNull(loginProcessor.getResponse(properties));
}
@Test
void testGetResponseException() throws Exception {
properties.setProperty(NacosAuthLoginConstant.SERVER, "localhost");
when(restTemplate.postForm(eq("http://localhost:8848/nacos/v3/auth/user/login"),
eq(Header.EMPTY),
any(Query.class), anyMap(), eq(String.class)))
.thenThrow(new RuntimeException("test"));
assertNull(loginProcessor.getResponse(properties));
}
@Test
void testGetResponseUsesHttpsWhenTlsEnabled() throws Exception {
String previousTlsEnable = System.getProperty(TlsSystemConfig.TLS_ENABLE);
System.setProperty(TlsSystemConfig.TLS_ENABLE, "true");
try {
properties.setProperty(NacosAuthLoginConstant.SERVER, "localhost");
when(restTemplate.postForm(anyString(),
eq(Header.EMPTY),
any(Query.class), anyMap(), eq(String.class))).thenReturn(result);
assertNull(loginProcessor.getResponse(properties));
verify(restTemplate).postForm(eq("https://localhost:8848/nacos/v3/auth/user/login"),
eq(Header.EMPTY), any(Query.class), anyMap(), eq(String.class));
} finally {
if (previousTlsEnable == null) {
System.clearProperty(TlsSystemConfig.TLS_ENABLE);
} else {
System.setProperty(TlsSystemConfig.TLS_ENABLE, previousTlsEnable);
}
}
}
@Test
void testGetResponseSuccessFromV1() throws Exception {
properties.setProperty(NacosAuthLoginConstant.SERVER, "localhost");
HttpRestResult httpRes = new HttpRestResult<>();
httpRes.setCode(NacosException.SERVER_NOT_IMPLEMENTED);
when(restTemplate.postForm(eq("http://localhost:8848/nacos/v3/auth/user/login"),
eq(Header.EMPTY),
any(Query.class), anyMap(), eq(String.class))).thenReturn(httpRes);
when(result.ok()).thenReturn(true);
Map<String, String> mockMap = new HashMap<>();
mockMap.put(Constants.ACCESS_TOKEN, "mock_access_token");
mockMap.put(Constants.TOKEN_TTL, "100L");
when(result.getData()).thenReturn(JacksonUtils.toJson(mockMap));
when(restTemplate.postForm(eq("http://localhost:8848/nacos/v1/auth/users/login"),
eq(Header.EMPTY),
any(Query.class), anyMap(), eq(String.class))).thenReturn(result);
LoginIdentityContext actual = loginProcessor.getResponse(properties);
assertEquals("mock_access_token", actual.getParameter(NacosAuthLoginConstant.ACCESSTOKEN));
assertEquals("100L", actual.getParameter(NacosAuthLoginConstant.TOKENTTL));
}
}
@@ -0,0 +1,237 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.constant.OidcProtocolConstants;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test for {@link OidcClientAuthServiceImpl}.
*
* @author wangzji
*/
class OidcClientAuthServiceImplTest {
private OidcClientAuthServiceImpl oidcClientAuthService;
@BeforeEach
void setUp() {
oidcClientAuthService = new OidcClientAuthServiceImpl();
}
@Test
void testLoginWithoutOidcConfig() {
// Given: no OIDC properties configured
Properties properties = new Properties();
properties.setProperty("serverAddr", "localhost:8848");
properties.setProperty("username", "nacos");
properties.setProperty("password", "nacos");
// When
boolean result = oidcClientAuthService.login(properties);
// Then: should succeed silently (OIDC not configured, let other plugins handle)
assertTrue(result);
// LoginIdentityContext should not contain accessToken
LoginIdentityContext ctx = oidcClientAuthService.getLoginIdentityContext(
RequestResource.configBuilder().build());
assertNotNull(ctx);
assertNull(ctx.getParameter(OidcProtocolConstants.ACCESS_TOKEN_PARAM));
}
@Test
void testLoginWithoutOidcConfigSkipsOnSubsequentCalls() {
// Given: no OIDC properties configured
Properties properties = new Properties();
// When: call login multiple times
boolean result1 = oidcClientAuthService.login(properties);
boolean result2 = oidcClientAuthService.login(properties);
// Then: both should succeed
assertTrue(result1);
assertTrue(result2);
}
@Test
void testLoginWithPartialOidcConfigMissingSecret() {
// Given: only client-id but no client-secret
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, "https://idp.example.com");
// When
boolean result = oidcClientAuthService.login(properties);
// Then: should succeed (incomplete OIDC config, skip)
assertTrue(result);
LoginIdentityContext ctx = oidcClientAuthService.getLoginIdentityContext(
RequestResource.configBuilder().build());
assertNull(ctx.getParameter(OidcProtocolConstants.ACCESS_TOKEN_PARAM));
}
@Test
void testLoginWithPartialOidcConfigMissingEndpoint() {
// Given: client-id and secret but no issuer-uri or token-endpoint
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
// When
boolean result = oidcClientAuthService.login(properties);
// Then: should succeed (incomplete OIDC config, skip)
assertTrue(result);
LoginIdentityContext ctx = oidcClientAuthService.getLoginIdentityContext(
RequestResource.configBuilder().build());
assertNull(ctx.getParameter(OidcProtocolConstants.ACCESS_TOKEN_PARAM));
}
@Test
void testGetLoginIdentityContextReturnsEmptyByDefault() {
LoginIdentityContext ctx = oidcClientAuthService.getLoginIdentityContext(
RequestResource.configBuilder().build());
assertNotNull(ctx);
}
private HttpServer httpServer;
@AfterEach
void afterEach() {
if (httpServer != null) {
httpServer.stop(0);
httpServer = null;
}
}
private static void writeJson(HttpExchange exchange, int status, String body)
throws IOException {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
private void startServer(String path, HttpHandler handler) throws IOException {
httpServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
httpServer.createContext(path, handler);
httpServer.start();
}
private String baseUri() {
return "http://127.0.0.1:" + httpServer.getAddress().getPort();
}
@Test
void testLoginFullFlowSuccess() throws Exception {
startServer("/", exchange -> {
String path = exchange.getRequestURI().getPath();
if (path.endsWith("/.well-known/openid-configuration")) {
writeJson(exchange, 200, "{\"token_endpoint\":\"" + baseUri() + "/token\"}");
} else if (path.endsWith("/token")) {
writeJson(exchange, 200, "{\"access_token\":\"abc\",\"expires_in\":3600}");
} else {
writeJson(exchange, 404, "");
}
});
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
assertTrue(oidcClientAuthService.login(properties));
LoginIdentityContext ctx = oidcClientAuthService.getLoginIdentityContext(
RequestResource.configBuilder().build());
assertEquals("abc", ctx.getParameter(OidcProtocolConstants.ACCESS_TOKEN_PARAM));
assertEquals(OidcProtocolConstants.BEARER_PREFIX + "abc",
ctx.getParameter(OidcProtocolConstants.AUTHORIZATION_HEADER));
// second call: token still fresh, no refresh needed → still success
assertTrue(oidcClientAuthService.login(properties));
}
@Test
void testLoginDiscoveryFails() throws Exception {
startServer("/.well-known/openid-configuration",
exchange -> writeJson(exchange, 500, ""));
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
assertFalse(oidcClientAuthService.login(properties));
}
@Test
void testLoginTokenFetchFails() throws Exception {
startServer("/", exchange -> {
String path = exchange.getRequestURI().getPath();
if (path.endsWith("/.well-known/openid-configuration")) {
writeJson(exchange, 200, "{\"token_endpoint\":\"" + baseUri() + "/token\"}");
} else {
writeJson(exchange, 500, "");
}
});
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
assertFalse(oidcClientAuthService.login(properties));
}
@Test
void testLoginCatchesUnexpectedException() {
// Pass a Properties impl that throws on getProperty to trigger the catch (Throwable) block
Properties properties = new Properties() {
@Override
public String getProperty(String key) {
throw new RuntimeException("boom");
}
};
assertFalse(oidcClientAuthService.login(properties));
}
@Test
void testShutdownDoesNotThrow() {
Assertions.assertDoesNotThrow(oidcClientAuthService::shutdown);
}
}
@@ -0,0 +1,268 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test for {@link OidcClientContext}.
*
* @author wangzji
*/
class OidcClientContextTest {
private OidcClientContext context;
@BeforeEach
void setUp() {
context = new OidcClientContext();
}
@Test
void testInitWithFullConfig() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI,
"https://idp.example.com/realms/test");
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
properties.setProperty(OidcClientConstants.PROP_SCOPE, "openid profile");
boolean configured = context.init(properties);
assertTrue(configured);
assertTrue(context.isConfigured());
assertEquals("https://idp.example.com/realms/test", context.getIssuerUri());
assertEquals("my-client", context.getClientId());
assertEquals("my-secret", context.getClientSecret());
assertEquals("openid profile", context.getScope());
assertFalse(context.isDiscovered());
}
@Test
void testInitWithDefaultScope() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, "https://idp.example.com");
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
boolean configured = context.init(properties);
assertTrue(configured);
assertEquals(OidcClientConstants.DEFAULT_SCOPE, context.getScope());
}
@Test
void testInitWithDirectTokenEndpoint() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
properties.setProperty(OidcClientConstants.PROP_TOKEN_ENDPOINT,
"https://idp.example.com/token");
boolean configured = context.init(properties);
assertTrue(configured);
assertTrue(context.isDiscovered()); // discovery is skipped
assertEquals("https://idp.example.com/token", context.getTokenEndpoint());
}
@Test
void testInitWithEmptyConfig() {
Properties properties = new Properties();
boolean configured = context.init(properties);
assertFalse(configured);
assertFalse(context.isConfigured());
}
@Test
void testInitWithOnlyClientId() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
boolean configured = context.init(properties);
assertFalse(configured);
}
@Test
void testInitWithClientCredentialsButNoEndpoint() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
boolean configured = context.init(properties);
assertFalse(configured, "Should not be configured without issuer-uri or token-endpoint");
}
@Test
void testDiscoverWithoutIssuerUri() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
boolean result = context.discover();
assertFalse(result, "Discovery should fail without issuer-uri");
}
@Test
void testDiscoverSkipsIfAlreadyDone() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
properties.setProperty(OidcClientConstants.PROP_TOKEN_ENDPOINT,
"https://idp.example.com/token");
context.init(properties);
// Already discovered via direct token endpoint
assertTrue(context.isDiscovered());
boolean result = context.discover();
assertTrue(result, "Should return true when already discovered");
}
@Test
void testDiscoverWithInvalidUrl() {
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, "http://0.0.0.0");
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
boolean result = context.discover();
assertFalse(result, "Discovery should fail with invalid URL");
}
private HttpServer httpServer;
@AfterEach
void afterEach() {
if (httpServer != null) {
httpServer.stop(0);
httpServer = null;
}
}
private void startServer(String path, HttpHandler handler) throws IOException {
httpServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
httpServer.createContext(path, handler);
httpServer.start();
}
private String baseIssuerUri() {
return "http://127.0.0.1:" + httpServer.getAddress().getPort();
}
private static void writeJson(HttpExchange exchange, int status, String body)
throws IOException {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
@Test
void testDiscoverSuccess() throws Exception {
startServer("/.well-known/openid-configuration", exchange -> writeJson(exchange, 200,
"{\"token_endpoint\":\"http://idp.example.com/token\"}"));
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseIssuerUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
assertTrue(context.discover());
assertTrue(context.isDiscovered());
assertEquals("http://idp.example.com/token", context.getTokenEndpoint());
}
@Test
void testDiscoverSuccessWithTrailingSlashIssuer() throws Exception {
startServer("/.well-known/openid-configuration", exchange -> writeJson(exchange, 200,
"{\"token_endpoint\":\"http://idp.example.com/token\"}"));
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseIssuerUri() + "/");
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
assertTrue(context.discover());
}
@Test
void testDiscoverHttpErrorReturnsFalse() throws Exception {
startServer("/.well-known/openid-configuration",
exchange -> writeJson(exchange, 500, "boom"));
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseIssuerUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
assertFalse(context.discover());
}
@Test
void testDiscoverMissingTokenEndpointReturnsFalse() throws Exception {
startServer("/.well-known/openid-configuration",
exchange -> writeJson(exchange, 200, "{\"issuer\":\"x\"}"));
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseIssuerUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
assertFalse(context.discover());
}
@Test
void testDiscoverNullTokenEndpointReturnsFalse() throws Exception {
startServer("/.well-known/openid-configuration",
exchange -> writeJson(exchange, 200, "{\"token_endpoint\":null}"));
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, baseIssuerUri());
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "my-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "my-secret");
context.init(properties);
assertFalse(context.discover());
}
@Test
void testReadInputStreamAsString() throws Exception {
String payload = "abcdef-数据";
ByteArrayInputStream bis =
new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
assertEquals(payload, OidcClientContext.readInputStreamAsString(bis));
}
}
@@ -0,0 +1,251 @@
/*
* Copyright 1999-2024 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.client.auth.oidc;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test for {@link OidcTokenHolder}.
*
* @author wangzji
*/
class OidcTokenHolderTest {
private OidcTokenHolder tokenHolder;
private HttpServer httpServer;
@BeforeEach
void setUp() {
tokenHolder = new OidcTokenHolder();
}
@AfterEach
void tearDown() {
if (httpServer != null) {
httpServer.stop(0);
httpServer = null;
}
}
private OidcClientContext newContextWithEndpoint(String tokenEndpoint) {
OidcClientContext context = new OidcClientContext();
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, "http://example.com");
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "test-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "test-secret");
properties.setProperty(OidcClientConstants.PROP_TOKEN_ENDPOINT, tokenEndpoint);
context.init(properties);
return context;
}
private OidcClientContext newContextWithEndpointAndScope(String tokenEndpoint, String scope) {
OidcClientContext context = new OidcClientContext();
Properties properties = new Properties();
properties.setProperty(OidcClientConstants.PROP_ISSUER_URI, "http://example.com");
properties.setProperty(OidcClientConstants.PROP_CLIENT_ID, "test-client");
properties.setProperty(OidcClientConstants.PROP_CLIENT_SECRET, "test-secret");
properties.setProperty(OidcClientConstants.PROP_TOKEN_ENDPOINT, tokenEndpoint);
properties.setProperty(OidcClientConstants.PROP_SCOPE, scope);
context.init(properties);
return context;
}
private void startServer(HttpHandler handler) throws IOException {
httpServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
httpServer.createContext("/token", handler);
httpServer.start();
}
private String tokenEndpointUrl() {
return "http://127.0.0.1:" + httpServer.getAddress().getPort() + "/token";
}
private static void writeResponse(HttpExchange exchange, int status, String body)
throws IOException {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
@Test
void testInitialStateNeedsRefresh() {
// Token holder starts with no token
assertNull(tokenHolder.getAccessToken());
assertTrue(tokenHolder.isExpiredOrNeedRefresh());
}
@Test
void testFetchTokenWithNullEndpoint() {
OidcClientContext context = new OidcClientContext();
// context has no token endpoint
boolean result = tokenHolder.fetchToken(context);
assertFalse(result, "Should fail when token endpoint is null");
}
@Test
void testGenerateTokenRefreshWindowWithZeroTtl() {
long window = tokenHolder.generateTokenRefreshWindow(0);
assertTrue(window == 0, "Window should be 0 for TTL 0");
}
@Test
void testGenerateTokenRefreshWindowWithNegativeTtl() {
long window = tokenHolder.generateTokenRefreshWindow(-1);
assertTrue(window == 0, "Window should be 0 for negative TTL");
}
@Test
void testGenerateTokenRefreshWindowWithNormalTtl() {
// TTL = 300s => startNumber = 300/15 = 20, endNumber = 300/10 = 30
long window = tokenHolder.generateTokenRefreshWindow(300);
assertTrue(window >= 20 && window < 30,
"Window should be in range [20, 30), got: " + window);
}
@Test
void testGenerateTokenRefreshWindowWithSmallTtl() {
// TTL = 10s => startNumber = 0, endNumber = 1
long window = tokenHolder.generateTokenRefreshWindow(10);
assertTrue(window >= 0 && window <= 1,
"Window should be 0 or 1 for TTL 10, got: " + window);
}
@Test
void testGenerateTokenRefreshWindowWithVerySmallTtl() {
// TTL = 5s => startNumber = 0, endNumber = 0 => falls to startNumber
long window = tokenHolder.generateTokenRefreshWindow(5);
assertTrue(window == 0, "Window should be 0 for very small TTL, got: " + window);
}
@Test
void testFetchTokenSuccess() throws Exception {
startServer(exchange -> writeResponse(exchange, 200,
"{\"access_token\":\"abc123\",\"expires_in\":600}"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertTrue(tokenHolder.fetchToken(context));
assertEquals("abc123", tokenHolder.getAccessToken());
assertEquals(600, tokenHolder.getExpiresInSeconds());
assertTrue(tokenHolder.getObtainedAtMs() > 0);
assertFalse(tokenHolder.isExpiredOrNeedRefresh());
}
@Test
void testFetchTokenSuccessWithScope() throws Exception {
AtomicReference<String> bodyRef = new AtomicReference<>();
startServer(exchange -> {
byte[] buf = new byte[4096];
int n = exchange.getRequestBody().read(buf);
bodyRef.set(new String(buf, 0, Math.max(n, 0), StandardCharsets.UTF_8));
writeResponse(exchange, 200,
"{\"access_token\":\"with-scope\",\"expires_in\":120}");
});
OidcClientContext context = newContextWithEndpointAndScope(tokenEndpointUrl(),
"openid profile");
assertTrue(tokenHolder.fetchToken(context));
assertEquals("with-scope", tokenHolder.getAccessToken());
assertNotNull(bodyRef.get());
assertTrue(bodyRef.get().contains("scope=openid"));
}
@Test
void testFetchTokenWithMissingExpiresInUsesDefault() throws Exception {
startServer(exchange -> writeResponse(exchange, 200,
"{\"access_token\":\"no-expires\"}"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertTrue(tokenHolder.fetchToken(context));
assertEquals(300L, tokenHolder.getExpiresInSeconds());
}
@Test
void testFetchTokenServerErrorReturnsFalse() throws Exception {
startServer(exchange -> writeResponse(exchange, 500, "{\"error\":\"server\"}"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertFalse(tokenHolder.fetchToken(context));
assertNull(tokenHolder.getAccessToken());
}
@Test
void testFetchTokenServerErrorWithoutBodyReturnsFalse() throws Exception {
startServer(exchange -> {
// 401 with no body
exchange.sendResponseHeaders(401, -1);
exchange.close();
});
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertFalse(tokenHolder.fetchToken(context));
}
@Test
void testFetchTokenIoExceptionReturnsFalse() {
// Use unreachable port (0 means kernel-chosen, but here we use 1 which usually fails)
OidcClientContext context = newContextWithEndpoint("http://127.0.0.1:1/token");
assertFalse(tokenHolder.fetchToken(context));
}
@Test
void testFetchTokenInvalidJsonReturnsFalse() throws Exception {
startServer(exchange -> writeResponse(exchange, 200, "not-json{"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertFalse(tokenHolder.fetchToken(context));
}
@Test
void testFetchTokenMissingAccessTokenReturnsFalse() throws Exception {
startServer(exchange -> writeResponse(exchange, 200, "{\"expires_in\":300}"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertFalse(tokenHolder.fetchToken(context));
}
@Test
void testFetchTokenAccessTokenIsNullReturnsFalse() throws Exception {
startServer(exchange -> writeResponse(exchange, 200,
"{\"access_token\":null,\"expires_in\":300}"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertFalse(tokenHolder.fetchToken(context));
}
@Test
void testIsExpiredOrNeedRefreshAfterRecentFetch() throws Exception {
startServer(exchange -> writeResponse(exchange, 200,
"{\"access_token\":\"fresh\",\"expires_in\":3600}"));
OidcClientContext context = newContextWithEndpoint(tokenEndpointUrl());
assertTrue(tokenHolder.fetchToken(context));
// freshly fetched 1h token should not need refresh
assertFalse(tokenHolder.isExpiredOrNeedRefresh());
}
}
@@ -0,0 +1,128 @@
/*
* Copyright 1999-2021 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.client.auth.ram;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.auth.ram.injector.AbstractResourceInjector;
import com.alibaba.nacos.common.utils.ReflectUtils;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Map;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class RamClientAuthServiceImplTest {
private static final String MOCK = "mock";
@Mock
private AbstractResourceInjector mockResourceInjector;
private RamClientAuthServiceImpl ramClientAuthService;
private Properties akSkProperties;
private Properties roleProperties;
private RamContext ramContext;
private RequestResource resource;
@BeforeEach
void setUp() throws Exception {
ramClientAuthService = new RamClientAuthServiceImpl();
Map<String, AbstractResourceInjector> resourceInjectors =
(Map<String, AbstractResourceInjector>) ReflectUtils.getFieldValue(
ramClientAuthService, "resourceInjectors");
resourceInjectors.clear();
resourceInjectors.put(MOCK, mockResourceInjector);
ramContext = (RamContext) ReflectUtils.getFieldValue(ramClientAuthService, "ramContext");
akSkProperties = new Properties();
roleProperties = new Properties();
akSkProperties.setProperty(PropertyKeyConst.ACCESS_KEY, PropertyKeyConst.ACCESS_KEY);
akSkProperties.setProperty(PropertyKeyConst.SECRET_KEY, PropertyKeyConst.SECRET_KEY);
roleProperties.setProperty(PropertyKeyConst.RAM_ROLE_NAME, PropertyKeyConst.RAM_ROLE_NAME);
resource = new RequestResource();
}
@AfterEach
void tearDown() throws NacosException {
ramClientAuthService.shutdown();
}
@Test
void testLoginWithAkSk() {
assertTrue(ramClientAuthService.login(akSkProperties));
assertEquals(PropertyKeyConst.ACCESS_KEY, ramContext.getAccessKey());
assertEquals(PropertyKeyConst.SECRET_KEY, ramContext.getSecretKey());
assertNull(ramContext.getRamRoleName());
assertTrue(ramClientAuthService.login(roleProperties));
assertEquals(PropertyKeyConst.ACCESS_KEY, ramContext.getAccessKey());
assertEquals(PropertyKeyConst.SECRET_KEY, ramContext.getSecretKey());
assertNull(ramContext.getRamRoleName());
}
@Test
void testLoginWithRoleName() {
assertTrue(ramClientAuthService.login(roleProperties));
assertNull(ramContext.getAccessKey(), PropertyKeyConst.ACCESS_KEY);
assertNull(ramContext.getSecretKey(), PropertyKeyConst.SECRET_KEY);
assertEquals(PropertyKeyConst.RAM_ROLE_NAME, ramContext.getRamRoleName());
assertTrue(ramClientAuthService.login(akSkProperties));
assertNull(ramContext.getAccessKey(), PropertyKeyConst.ACCESS_KEY);
assertNull(ramContext.getSecretKey(), PropertyKeyConst.SECRET_KEY);
assertEquals(PropertyKeyConst.RAM_ROLE_NAME, ramContext.getRamRoleName());
}
@Test
void testGetLoginIdentityContextWithoutLogin() {
LoginIdentityContext actual = ramClientAuthService.getLoginIdentityContext(resource);
assertTrue(actual.getAllKey().isEmpty());
verify(mockResourceInjector, never()).doInject(resource, ramContext, actual);
}
@Test
void testGetLoginIdentityContextWithoutInjector() {
ramClientAuthService.login(akSkProperties);
LoginIdentityContext actual = ramClientAuthService.getLoginIdentityContext(resource);
assertTrue(actual.getAllKey().isEmpty());
verify(mockResourceInjector, never()).doInject(resource, ramContext, actual);
}
@Test
void testGetLoginIdentityContextWithInjector() {
ramClientAuthService.login(akSkProperties);
resource.setType(MOCK);
LoginIdentityContext actual = ramClientAuthService.getLoginIdentityContext(resource);
assertTrue(actual.getAllKey().isEmpty());
verify(mockResourceInjector).doInject(resource, ramContext, actual);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 1999-2026 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.client.auth.ram;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class RamConstantsTest {
@Test
void testConstructor() {
assertNotNull(new RamConstants());
}
@Test
void testFields() {
assertEquals("signatureVersion", RamConstants.SIGNATURE_VERSION);
assertEquals("v4", RamConstants.V4);
assertEquals("HmacSHA256", RamConstants.SIGNATURE_V4_METHOD);
assertEquals("mse-nacos", RamConstants.SIGNATURE_V4_PRODUCE);
}
}
@@ -0,0 +1,143 @@
/*
*
* Copyright 1999-2018 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.client.auth.ram.identify;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
class CredentialServiceTest {
private static final String APP_NAME = "app";
@BeforeEach
void setUp() throws Exception {
CredentialService.freeInstance();
CredentialService.freeInstance(APP_NAME);
}
@AfterEach
void tearDown() throws Exception {
System.clearProperty(IdentifyConstants.PROJECT_NAME_PROPERTY);
CredentialService.freeInstance();
CredentialService.freeInstance(APP_NAME);
}
@Test
void testGetInstance() {
CredentialService credentialService1 = CredentialService.getInstance();
CredentialService credentialService2 = CredentialService.getInstance();
assertEquals(credentialService1, credentialService2);
}
@Test
void testGetInstance2() {
CredentialService credentialService1 = CredentialService.getInstance(APP_NAME);
CredentialService credentialService2 = CredentialService.getInstance(APP_NAME);
assertEquals(credentialService1, credentialService2);
}
@Test
void testGetInstance3() throws NoSuchFieldException, IllegalAccessException {
System.setProperty(IdentifyConstants.PROJECT_NAME_PROPERTY, APP_NAME);
CredentialService credentialService1 = CredentialService.getInstance();
Field appNameField = credentialService1.getClass().getDeclaredField("appName");
appNameField.setAccessible(true);
String appName = (String) appNameField.get(credentialService1);
assertEquals(APP_NAME, appName);
}
@Test
void testFreeInstance() {
CredentialService credentialService1 = CredentialService.getInstance();
CredentialService credentialService2 = CredentialService.freeInstance();
assertEquals(credentialService1, credentialService2);
}
@Test
void testFreeInstance2() {
CredentialService credentialService1 = CredentialService.getInstance();
CredentialService credentialService2 = CredentialService.freeInstance();
assertEquals(credentialService1, credentialService2);
}
@Test
void testFree() throws NoSuchFieldException, IllegalAccessException {
CredentialService credentialService1 = CredentialService.getInstance();
CredentialWatcher mockWatcher = mock(CredentialWatcher.class);
Field watcherField = CredentialService.class.getDeclaredField("watcher");
watcherField.setAccessible(true);
watcherField.set(credentialService1, mockWatcher);
//when
credentialService1.free();
//then
verify(mockWatcher, times(1)).stop();
}
@Test
void testGetCredential() {
CredentialService credentialService1 = CredentialService.getInstance();
Credentials credential = credentialService1.getCredential();
assertNotNull(credential);
}
@Test
void testSetCredential() {
CredentialService credentialService1 = CredentialService.getInstance();
Credentials credential = new Credentials();
//when
credentialService1.setCredential(credential);
//then
assertEquals(credential, credentialService1.getCredential());
}
@Test
void testSetStaticCredential() throws NoSuchFieldException, IllegalAccessException {
CredentialService credentialService1 = CredentialService.getInstance();
CredentialWatcher mockWatcher = mock(CredentialWatcher.class);
Field watcherField = CredentialService.class.getDeclaredField("watcher");
watcherField.setAccessible(true);
watcherField.set(credentialService1, mockWatcher);
Credentials credential = new Credentials();
//when
credentialService1.setStaticCredential(credential);
//then
assertEquals(credential, credentialService1.getCredential());
verify(mockWatcher, times(1)).stop();
}
@Test
void testRegisterCredentialListener() {
CredentialListener expect = mock(CredentialListener.class);
CredentialService credentialService1 = CredentialService.getInstance();
credentialService1.registerCredentialListener(expect);
Credentials newCredentials = new Credentials();
newCredentials.setAccessKey("ak");
credentialService1.setCredential(newCredentials);
verify(expect, times(1)).onUpdateCredential();
}
}
@@ -0,0 +1,233 @@
/*
* Copyright 1999-2018 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.client.auth.ram.identify;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CredentialWatcherTest {
@Mock
private CredentialService credentialService;
private CredentialWatcher credentialWatcher;
private Method loadCredentialMethod;
private Method loadCredentialFromPropertiesMethod;
@BeforeEach
void setUp() throws Exception {
credentialWatcher = new CredentialWatcher("testApp", credentialService);
loadCredentialMethod =
CredentialWatcher.class.getDeclaredMethod("loadCredential", boolean.class);
loadCredentialMethod.setAccessible(true);
loadCredentialFromPropertiesMethod =
CredentialWatcher.class.getDeclaredMethod("loadCredentialFromProperties",
InputStream.class, boolean.class, Credentials.class);
loadCredentialFromPropertiesMethod.setAccessible(true);
}
@AfterEach
void tearDown() throws Exception {
credentialWatcher.stop();
System.clearProperty("spas.identity");
System.clearProperty(IdentifyConstants.ENV_ACCESS_KEY);
System.clearProperty(IdentifyConstants.ENV_SECRET_KEY);
CredentialService.freeInstance();
}
@Test
void testStop() throws NoSuchFieldException, IllegalAccessException {
credentialWatcher.stop();
Field executorField = CredentialWatcher.class.getDeclaredField("executor");
executorField.setAccessible(true);
ScheduledExecutorService executor =
(ScheduledExecutorService) executorField.get(credentialWatcher);
assertTrue(executor.isShutdown());
}
@Test
void testLoadCredentialByEnv() throws InvocationTargetException, IllegalAccessException {
System.setProperty(IdentifyConstants.ENV_ACCESS_KEY, "testAk");
System.setProperty(IdentifyConstants.ENV_SECRET_KEY, "testSk");
final AtomicReference<String> readAk = new AtomicReference<>("");
final AtomicReference<String> readSK = new AtomicReference<>("");
final AtomicReference<String> readTenantId = new AtomicReference<>("");
doAnswer(invocationOnMock -> {
Credentials credentials = invocationOnMock.getArgument(0, Credentials.class);
readAk.set(credentials.getAccessKey());
readSK.set(credentials.getSecretKey());
readTenantId.set(credentials.getTenantId());
return null;
}).when(credentialService).setCredential(any());
loadCredentialMethod.invoke(credentialWatcher, true);
assertEquals("testAk", readAk.get());
assertEquals("testSk", readSK.get());
assertNull(readTenantId.get());
}
@Test
void testLoadCredentialByIdentityFile()
throws InvocationTargetException, IllegalAccessException {
URL url = CredentialWatcherTest.class.getClassLoader().getResource("spas.identity");
System.setProperty("spas.identity", url.getPath());
final AtomicReference<String> readAk = new AtomicReference<>("");
final AtomicReference<String> readSK = new AtomicReference<>("");
final AtomicReference<String> readTenantId = new AtomicReference<>("");
doAnswer(invocationOnMock -> {
Credentials credentials = invocationOnMock.getArgument(0, Credentials.class);
readAk.set(credentials.getAccessKey());
readSK.set(credentials.getSecretKey());
readTenantId.set(credentials.getTenantId());
return null;
}).when(credentialService).setCredential(any());
loadCredentialMethod.invoke(credentialWatcher, true);
assertEquals("testAk", readAk.get());
assertEquals("testSk", readSK.get());
assertEquals("testTenantId", readTenantId.get());
}
@Test
void testLoadCredentialByInvalidIdentityFile()
throws InvocationTargetException, IllegalAccessException {
URL url = CredentialWatcherTest.class.getClassLoader().getResource("spas_invalid.identity");
System.setProperty("spas.identity", url.getPath());
final AtomicReference<String> readAk = new AtomicReference<>("");
final AtomicReference<String> readSK = new AtomicReference<>("");
final AtomicReference<String> readTenantId = new AtomicReference<>("");
doAnswer(invocationOnMock -> {
Credentials credentials = invocationOnMock.getArgument(0, Credentials.class);
readAk.set(credentials.getAccessKey());
readSK.set(credentials.getSecretKey());
readTenantId.set(credentials.getTenantId());
return null;
}).when(credentialService).setCredential(any());
loadCredentialMethod.invoke(credentialWatcher, true);
assertEquals("", readAk.get());
assertEquals("testSk", readSK.get());
assertEquals("testTenantId", readTenantId.get());
}
/**
* The docker file is need /etc permission, which depend environment. So use mock InputStream to test.
*/
@Test
void testLoadCredentialByDockerFile()
throws FileNotFoundException, InvocationTargetException, IllegalAccessException,
NoSuchFieldException {
URL url = CredentialWatcherTest.class.getClassLoader().getResource("spas_docker.identity");
InputStream propertiesIS = new FileInputStream(url.getPath());
Credentials actual = new Credentials();
Field propertyPathField = CredentialWatcher.class.getDeclaredField("propertyPath");
propertyPathField.setAccessible(true);
propertyPathField.set(credentialWatcher, IdentifyConstants.DOCKER_CREDENTIAL_PATH);
loadCredentialFromPropertiesMethod.invoke(credentialWatcher, propertiesIS, true, actual);
assertEquals("testAk", actual.getAccessKey());
assertEquals("testSk", actual.getSecretKey());
assertEquals("testTenantId", actual.getTenantId());
}
@Test
void testLoadCredentialByFileWithIoException()
throws IOException, InvocationTargetException, IllegalAccessException {
InputStream propertiesIS = mock(InputStream.class);
when(propertiesIS.read(any())).thenThrow(new IOException("test"));
doThrow(new IOException("test")).when(propertiesIS).close();
Credentials actual = new Credentials();
loadCredentialFromPropertiesMethod.invoke(credentialWatcher, propertiesIS, true, actual);
assertNull(actual.getAccessKey());
assertNull(actual.getSecretKey());
assertNull(actual.getTenantId());
}
@Test
void testReLoadCredential()
throws InvocationTargetException, IllegalAccessException, InterruptedException {
URL url =
CredentialWatcherTest.class.getClassLoader().getResource("spas_modified.identity");
modifiedFile(url, true);
System.setProperty("spas.identity", url.getPath());
final AtomicReference<String> readAk = new AtomicReference<>("");
final AtomicReference<String> readSK = new AtomicReference<>("");
final AtomicReference<String> readTenantId = new AtomicReference<>("");
doAnswer(invocationOnMock -> {
Credentials credentials = invocationOnMock.getArgument(0, Credentials.class);
readAk.set(credentials.getAccessKey());
readSK.set(credentials.getSecretKey());
readTenantId.set(credentials.getTenantId());
return null;
}).when(credentialService).setCredential(any());
loadCredentialMethod.invoke(credentialWatcher, true);
assertEquals("testAk", readAk.get());
assertEquals("testSk", readSK.get());
assertNull(readTenantId.get());
// waiting reload thread work
modifiedFile(url, false);
TimeUnit.MILLISECONDS.sleep(10500);
assertEquals("testAk", readAk.get());
assertEquals("testSk", readSK.get());
assertEquals("testTenantId", readTenantId.get());
}
private boolean modifiedFile(URL url, boolean init) {
File file = new File(url.getPath());
boolean result;
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
if (init) {
bw.write("accessKey=testAk\nsecretKey=testSk");
} else {
bw.write("accessKey=testAk\nsecretKey=testSk\ntenantId=testTenantId");
}
bw.flush();
result = true;
} catch (IOException ignored) {
result = false;
}
return result;
}
}
@@ -0,0 +1,84 @@
/*
*
* Copyright 1999-2018 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.client.auth.ram.identify;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CredentialsTest {
@Test
void testGetter() {
// given
String ak = "ak";
String sk = "sk";
String tenantId = "100";
Credentials credentials = new Credentials(ak, sk, tenantId);
// when then
assertEquals(ak, credentials.getAccessKey());
assertEquals(sk, credentials.getSecretKey());
assertEquals(tenantId, credentials.getTenantId());
}
@Test
void testSetter() {
//given
String ak = "ak";
String sk = "sk";
String tenantId = "100";
Credentials credentials = new Credentials();
//when
credentials.setAccessKey(ak);
credentials.setSecretKey(sk);
credentials.setTenantId(tenantId);
//then
assertEquals(ak, credentials.getAccessKey());
assertEquals(sk, credentials.getSecretKey());
assertEquals(tenantId, credentials.getTenantId());
}
@Test
void testValid() {
//given
String ak = "ak";
String sk = "sk";
String tenantId = "100";
Credentials credentials = new Credentials(ak, sk, tenantId);
//when
boolean actual = credentials.valid();
//then
assertTrue(actual);
}
@Test
void testIdentical() {
//given
String ak = "ak";
String sk = "sk";
String tenantId = "100";
Credentials credentials1 = new Credentials(ak, sk, "101");
Credentials credentials2 = new Credentials(ak, sk, "100");
//then
boolean actual = credentials1.identical(credentials2);
//then
assertTrue(actual);
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 1999-2026 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.client.auth.ram.identify;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class IdentifyConstantsTest {
@Test
void testConstructor() {
assertNotNull(new IdentifyConstants());
}
@Test
void testFields() {
assertEquals("accessKey", IdentifyConstants.ACCESS_KEY);
assertEquals("secretKey", IdentifyConstants.SECRET_KEY);
assertEquals("Spas-SecurityToken", IdentifyConstants.SECURITY_TOKEN_HEADER);
assertEquals("tenantId", IdentifyConstants.TENANT_ID);
assertEquals("spas.properties", IdentifyConstants.PROPERTIES_FILENAME);
assertEquals("/home/admin/.spas_key/", IdentifyConstants.CREDENTIAL_PATH);
assertEquals("default", IdentifyConstants.CREDENTIAL_DEFAULT);
assertEquals("/etc/instanceInfo", IdentifyConstants.DOCKER_CREDENTIAL_PATH);
}
}
@@ -0,0 +1,140 @@
/*
* Copyright 1999-2018 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.client.auth.ram.identify;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class StsConfigTest {
private static void resetStsConfig() {
StsConfig.getInstance().setRamRoleName(null);
StsConfig.getInstance().setTimeToRefreshInMillisecond(3 * 60 * 1000);
StsConfig.getInstance().setCacheSecurityCredentials(true);
StsConfig.getInstance().setSecurityCredentials(null);
StsConfig.getInstance().setSecurityCredentialsUrl(null);
System.clearProperty(IdentifyConstants.RAM_ROLE_NAME_PROPERTY);
System.clearProperty(IdentifyConstants.REFRESH_TIME_PROPERTY);
System.clearProperty(IdentifyConstants.SECURITY_PROPERTY);
System.clearProperty(IdentifyConstants.SECURITY_URL_PROPERTY);
System.clearProperty(IdentifyConstants.SECURITY_CACHE_PROPERTY);
}
@BeforeEach
void before() {
resetStsConfig();
}
@AfterEach
void after() {
resetStsConfig();
}
@Test
void testGetInstance() {
StsConfig instance1 = StsConfig.getInstance();
StsConfig instance2 = StsConfig.getInstance();
assertEquals(instance1, instance2);
}
@Test
void testGetRamRoleName() {
StsConfig.getInstance().setRamRoleName("test");
assertEquals("test", StsConfig.getInstance().getRamRoleName());
}
@Test
void testGetTimeToRefreshInMillisecond() {
assertEquals(3 * 60 * 1000, StsConfig.getInstance().getTimeToRefreshInMillisecond());
StsConfig.getInstance().setTimeToRefreshInMillisecond(3000);
assertEquals(3000, StsConfig.getInstance().getTimeToRefreshInMillisecond());
}
@Test
void testGetSecurityCredentialsUrl() {
assertNull(StsConfig.getInstance().getSecurityCredentialsUrl());
String expect = "localhost";
StsConfig.getInstance().setSecurityCredentialsUrl(expect);
assertEquals(expect, StsConfig.getInstance().getSecurityCredentialsUrl());
}
@Test
void testGetSecurityCredentialsUrlDefault() {
StsConfig.getInstance().setRamRoleName("test");
assertEquals("http://100.100.100.200/latest/meta-data/ram/security-credentials/test",
StsConfig.getInstance().getSecurityCredentialsUrl());
}
@Test
void testGetSecurityCredentials() {
assertNull(StsConfig.getInstance().getSecurityCredentials());
String expect = "abc";
StsConfig.getInstance().setSecurityCredentials(expect);
assertEquals(expect, StsConfig.getInstance().getSecurityCredentials());
}
@Test
void testIsCacheSecurityCredentials() {
assertTrue(StsConfig.getInstance().isCacheSecurityCredentials());
StsConfig.getInstance().setCacheSecurityCredentials(false);
assertFalse(StsConfig.getInstance().isCacheSecurityCredentials());
}
@Test
void testIsOnFalse() {
boolean stsOn = StsConfig.getInstance().isStsOn();
assertFalse(stsOn);
}
@Test
void testIsOnTrue() {
StsConfig.getInstance().setSecurityCredentials("abc");
boolean stsOn = StsConfig.getInstance().isStsOn();
assertTrue(stsOn);
}
@Test
void testFromEnv()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
Constructor<StsConfig> constructor = StsConfig.class.getDeclaredConstructor();
constructor.setAccessible(true);
System.setProperty(IdentifyConstants.RAM_ROLE_NAME_PROPERTY, "test");
System.setProperty(IdentifyConstants.REFRESH_TIME_PROPERTY, "3000");
System.setProperty(IdentifyConstants.SECURITY_PROPERTY, "abc");
System.setProperty(IdentifyConstants.SECURITY_URL_PROPERTY, "localhost");
System.setProperty(IdentifyConstants.SECURITY_CACHE_PROPERTY, "false");
StsConfig stsConfig = constructor.newInstance();
assertEquals("test", stsConfig.getRamRoleName());
assertEquals(3000, stsConfig.getTimeToRefreshInMillisecond());
assertEquals("abc", stsConfig.getSecurityCredentials());
assertEquals("localhost", stsConfig.getSecurityCredentialsUrl());
assertFalse(stsConfig.isCacheSecurityCredentials());
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 1999-2021 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.client.auth.ram.identify;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.common.http.HttpClientBeanHolder;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.utils.JacksonUtils;
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.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class StsCredentialHolderTest {
private String securityCredentialsUrl;
private NacosRestTemplate cachedNacosRestTemplate;
@Mock
private HttpRestResult mockResult;
@Mock
private NacosRestTemplate nacosRestTemplate;
@BeforeEach
void setUp() throws Exception {
securityCredentialsUrl = StsConfig.getInstance().getSecurityCredentialsUrl();
StsConfig.getInstance().setSecurityCredentialsUrl("url");
Field restMapField = HttpClientBeanHolder.class.getDeclaredField("SINGLETON_REST");
restMapField.setAccessible(true);
Map<String, NacosRestTemplate> restMap =
(Map<String, NacosRestTemplate>) restMapField.get(null);
cachedNacosRestTemplate = restMap.get(
"com.alibaba.nacos.client.remote.HttpClientManager$HttpClientFactory");
restMap.put("com.alibaba.nacos.client.remote.HttpClientManager$HttpClientFactory",
nacosRestTemplate);
}
@AfterEach
void tearDown() throws Exception {
StsConfig.getInstance().setSecurityCredentials(null);
StsConfig.getInstance().setSecurityCredentialsUrl(securityCredentialsUrl);
if (null != cachedNacosRestTemplate) {
Field restMapField = HttpClientBeanHolder.class.getDeclaredField("SINGLETON_REST");
restMapField.setAccessible(true);
Map<String, NacosRestTemplate> restMap =
(Map<String, NacosRestTemplate>) restMapField.get(null);
restMap.put("com.alibaba.nacos.client.remote.HttpClientManager$HttpClientFactory",
cachedNacosRestTemplate);
}
clearForSts();
}
private void clearForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(null);
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), null);
}
@Test
void testGetStsCredentialFromCache() throws NoSuchFieldException, IllegalAccessException {
StsCredential stsCredential = buildMockStsCredential();
setStsCredential(stsCredential);
assertEquals(stsCredential, StsCredentialHolder.getInstance().getStsCredential());
}
private void setStsCredential(StsCredential stsCredential)
throws NoSuchFieldException, IllegalAccessException {
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), stsCredential);
}
@Test
void testGetStsCredentialFromStringCache() throws NoSuchFieldException, IllegalAccessException {
StsCredential stsCredential = buildMockStsCredential();
StsConfig.getInstance().setSecurityCredentials(JacksonUtils.toJson(stsCredential));
assertEquals(stsCredential.toString(),
StsCredentialHolder.getInstance().getStsCredential().toString());
}
@Test
void testGetStsCredentialFromRequest() throws Exception {
StsCredential stsCredential = buildMockStsCredential();
mockResult = new HttpRestResult<String>();
mockResult.setData(JacksonUtils.toJson(stsCredential));
mockResult.setCode(200);
when(nacosRestTemplate.get(any(), any(), any(), any())).thenReturn(mockResult);
assertEquals(stsCredential.toString(),
StsCredentialHolder.getInstance().getStsCredential().toString());
}
@Test
void testGetStsCredentialFromRequestFailure() throws Exception {
assertThrows(NacosRuntimeException.class, () -> {
mockResult = new HttpRestResult<String>();
mockResult.setData("");
mockResult.setCode(500);
when(nacosRestTemplate.get(any(), any(), any(), any())).thenReturn(mockResult);
StsCredentialHolder.getInstance().getStsCredential();
});
}
@Test
void testGetStsCredentialFromRequestException() throws Exception {
assertThrows(NacosRuntimeException.class, () -> {
when(nacosRestTemplate.get(any(), any(), any(), any()))
.thenThrow(new RuntimeException("test"));
StsCredentialHolder.getInstance().getStsCredential();
});
}
private StsCredential buildMockStsCredential() {
StsCredential stsCredential = new StsCredential();
stsCredential.setAccessKeyId("test-sts-ak");
stsCredential.setAccessKeySecret("test-sts-sk");
stsCredential.setSecurityToken("test-sts-token");
stsCredential.setExpiration(new Date(System.currentTimeMillis() + 1000000));
stsCredential.setCode("200");
stsCredential.setLastUpdated(new Date());
return stsCredential;
}
}
@@ -0,0 +1,39 @@
/*
* 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.client.auth.ram.injector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class AbstractResourceInjectorTest {
AbstractResourceInjector injector;
@BeforeEach
void setUp() {
injector = new AbstractResourceInjector() {
};
}
/**
* TODO, fill test case after AbstractResourceInjector include default logic.
*/
@Test
void testDoInject() {
injector.doInject(null, null, null);
}
}
@@ -0,0 +1,137 @@
/*
* Copyright 1999-2026 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.client.auth.ram.injector;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.constant.SignType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AiResourceInjectorTest {
private AiResourceInjector aiResourceInjector;
private RamContext ramContext;
private RequestResource resource;
private String cachedSecurityCredentialsUrl;
private String cachedSecurityCredentials;
private StsCredential stsCredential;
@BeforeEach
void setUp() {
aiResourceInjector = new AiResourceInjector();
ramContext = new RamContext();
ramContext.setAccessKey(PropertyKeyConst.ACCESS_KEY);
ramContext.setSecretKey(PropertyKeyConst.SECRET_KEY);
resource = new RequestResource();
resource.setType(SignType.AI);
resource.setNamespace("ai-namespace");
resource.setGroup("ai-group");
cachedSecurityCredentialsUrl = StsConfig.getInstance().getSecurityCredentialsUrl();
cachedSecurityCredentials = StsConfig.getInstance().getSecurityCredentials();
StsConfig.getInstance().setSecurityCredentialsUrl("");
StsConfig.getInstance().setSecurityCredentials("");
stsCredential = new StsCredential();
}
@AfterEach
void tearDown() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(cachedSecurityCredentialsUrl);
StsConfig.getInstance().setSecurityCredentials(cachedSecurityCredentials);
clearForSts();
}
@Test
void testDoInjectWhenContextInvalid() {
RamContext invalidContext = new RamContext();
// missing ak/sk → invalid
LoginIdentityContext actual = new LoginIdentityContext();
aiResourceInjector.doInject(resource, invalidContext, actual);
assertEquals(0, actual.getAllKey().size());
}
@Test
void testDoInjectWithFullResource() {
LoginIdentityContext actual = new LoginIdentityContext();
aiResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
@Test
void testDoInjectForSts() throws NoSuchFieldException, IllegalAccessException {
prepareForSts();
LoginIdentityContext actual = new LoginIdentityContext();
aiResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals("test-sts-ak", actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
assertTrue(actual.getAllKey().contains(IdentifyConstants.SECURITY_TOKEN_HEADER));
}
@Test
void testDoInjectForV4Sign() {
ramContext.setRegionId("cn-hangzhou");
LoginIdentityContext actual = new LoginIdentityContext();
aiResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertEquals(RamConstants.V4, actual.getParameter(RamConstants.SIGNATURE_VERSION));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
private void prepareForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl("test");
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), stsCredential);
stsCredential.setAccessKeyId("test-sts-ak");
stsCredential.setAccessKeySecret("test-sts-sk");
stsCredential.setSecurityToken("test-sts-token");
stsCredential.setExpiration(new Date(System.currentTimeMillis() + 1_000_000));
}
private void clearForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(null);
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), null);
}
}
@@ -0,0 +1,161 @@
/*
* Copyright 1999-2021 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.client.auth.ram.injector;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import com.alibaba.nacos.plugin.auth.constant.SignType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ConfigResourceInjectorTest {
private ConfigResourceInjector configResourceInjector;
private RamContext ramContext;
private RequestResource resource;
private String cachedSecurityCredentialsUrl;
private String cachedSecurityCredentials;
private StsCredential stsCredential;
@BeforeEach
void setUp() throws Exception {
configResourceInjector = new ConfigResourceInjector();
ramContext = new RamContext();
ramContext.setAccessKey(PropertyKeyConst.ACCESS_KEY);
ramContext.setSecretKey(PropertyKeyConst.SECRET_KEY);
resource = new RequestResource();
resource.setType(SignType.CONFIG);
resource.setNamespace("tenant");
resource.setGroup("group");
cachedSecurityCredentialsUrl = StsConfig.getInstance().getSecurityCredentialsUrl();
cachedSecurityCredentials = StsConfig.getInstance().getSecurityCredentials();
StsConfig.getInstance().setSecurityCredentialsUrl("");
StsConfig.getInstance().setSecurityCredentials("");
stsCredential = new StsCredential();
}
@AfterEach
void tearDown() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(cachedSecurityCredentialsUrl);
StsConfig.getInstance().setSecurityCredentials(cachedSecurityCredentials);
clearForSts();
}
@Test
void testDoInjectWithFullResource() throws Exception {
LoginIdentityContext actual = new LoginIdentityContext();
configResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
@Test
void testDoInjectWithTenant() throws Exception {
resource.setGroup("");
LoginIdentityContext actual = new LoginIdentityContext();
configResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
@Test
void testDoInjectWithGroup() throws Exception {
resource.setNamespace("");
LoginIdentityContext actual = new LoginIdentityContext();
configResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
@Test
void testDoInjectWithoutResource() throws Exception {
resource = new RequestResource();
LoginIdentityContext actual = new LoginIdentityContext();
configResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
@Test
void testDoInjectForSts() throws NoSuchFieldException, IllegalAccessException {
prepareForSts();
LoginIdentityContext actual = new LoginIdentityContext();
configResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals("test-sts-ak", actual.getParameter("Spas-AccessKey"));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
assertTrue(actual.getAllKey().contains(IdentifyConstants.SECURITY_TOKEN_HEADER));
}
@Test
void testDoInjectForV4Sign() {
LoginIdentityContext actual = new LoginIdentityContext();
ramContext.setRegionId("cn-hangzhou");
configResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("Spas-AccessKey"));
assertEquals(RamConstants.V4, actual.getParameter(RamConstants.SIGNATURE_VERSION));
assertTrue(actual.getAllKey().contains("Timestamp"));
assertTrue(actual.getAllKey().contains("Spas-Signature"));
}
private void prepareForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl("test");
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), stsCredential);
stsCredential.setAccessKeyId("test-sts-ak");
stsCredential.setAccessKeySecret("test-sts-sk");
stsCredential.setSecurityToken("test-sts-token");
stsCredential.setExpiration(new Date(System.currentTimeMillis() + 1000000));
}
private void clearForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(null);
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), null);
}
}
@@ -0,0 +1,117 @@
/*
* Copyright 1999-2026 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.client.auth.ram.injector;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class LockResourceInjectorTest {
private LockResourceInjector lockResourceInjector;
private RamContext ramContext;
private RequestResource resource;
private String cachedSecurityCredentialsUrl;
private String cachedSecurityCredentials;
private StsCredential stsCredential;
@BeforeEach
void setUp() {
lockResourceInjector = new LockResourceInjector();
ramContext = new RamContext();
ramContext.setAccessKey(PropertyKeyConst.ACCESS_KEY);
ramContext.setSecretKey(PropertyKeyConst.SECRET_KEY);
resource = new RequestResource();
cachedSecurityCredentialsUrl = StsConfig.getInstance().getSecurityCredentialsUrl();
cachedSecurityCredentials = StsConfig.getInstance().getSecurityCredentials();
StsConfig.getInstance().setSecurityCredentialsUrl("");
StsConfig.getInstance().setSecurityCredentials("");
stsCredential = new StsCredential();
}
@AfterEach
void tearDown() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(cachedSecurityCredentialsUrl);
StsConfig.getInstance().setSecurityCredentials(cachedSecurityCredentials);
clearForSts();
}
@Test
void testDoInjectWithAkSk() {
LoginIdentityContext actual = new LoginIdentityContext();
lockResourceInjector.doInject(resource, ramContext, actual);
assertEquals(1, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("ak"));
}
@Test
void testDoInjectWithoutSecretKey() {
ramContext.setSecretKey(null);
LoginIdentityContext actual = new LoginIdentityContext();
lockResourceInjector.doInject(resource, ramContext, actual);
assertEquals(0, actual.getAllKey().size());
assertNull(actual.getParameter("ak"));
}
@Test
void testDoInjectForSts() throws NoSuchFieldException, IllegalAccessException {
prepareForSts();
LoginIdentityContext actual = new LoginIdentityContext();
lockResourceInjector.doInject(resource, ramContext, actual);
assertEquals(2, actual.getAllKey().size());
assertEquals("test-sts-ak", actual.getParameter("ak"));
assertTrue(actual.getAllKey().contains(IdentifyConstants.SECURITY_TOKEN_HEADER));
}
private void prepareForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl("test");
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), stsCredential);
stsCredential.setAccessKeyId("test-sts-ak");
stsCredential.setAccessKeySecret("test-sts-sk");
stsCredential.setSecurityToken("test-sts-token");
stsCredential.setExpiration(new Date(System.currentTimeMillis() + 1_000_000));
}
private void clearForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(null);
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), null);
}
}
@@ -0,0 +1,173 @@
/*
* Copyright 1999-2021 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.client.auth.ram.injector;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import com.alibaba.nacos.client.auth.ram.RamContext;
import com.alibaba.nacos.client.auth.ram.identify.StsConfig;
import com.alibaba.nacos.client.auth.ram.identify.StsCredential;
import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder;
import com.alibaba.nacos.client.auth.ram.utils.CalculateV4SigningKeyUtil;
import com.alibaba.nacos.client.auth.ram.utils.SignUtil;
import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext;
import com.alibaba.nacos.plugin.auth.api.RequestResource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class NamingResourceInjectorTest {
private NamingResourceInjector namingResourceInjector;
private RamContext ramContext;
private RequestResource resource;
private StsCredential stsCredential;
@BeforeEach
void setUp() throws Exception {
namingResourceInjector = new NamingResourceInjector();
ramContext = new RamContext();
ramContext.setAccessKey(PropertyKeyConst.ACCESS_KEY);
ramContext.setSecretKey(PropertyKeyConst.SECRET_KEY);
stsCredential = new StsCredential();
StsConfig.getInstance().setRamRoleName(null);
}
@AfterEach
void tearDown() throws NoSuchFieldException, IllegalAccessException {
clearForSts();
}
@Test
void testDoInjectWithEmpty() throws Exception {
resource = RequestResource.namingBuilder().setResource("").build();
LoginIdentityContext actual = new LoginIdentityContext();
namingResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("ak"));
assertTrue(Long.parseLong(actual.getParameter("data")) - System.currentTimeMillis() < 100);
String expectSign = SignUtil.sign(actual.getParameter("data"), PropertyKeyConst.SECRET_KEY);
assertEquals(expectSign, actual.getParameter("signature"));
}
@Test
void testDoInjectWithGroup() throws Exception {
resource =
RequestResource.namingBuilder().setResource("test@@aaa").setGroup("group").build();
LoginIdentityContext actual = new LoginIdentityContext();
namingResourceInjector.doInject(resource, ramContext, actual);
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("ak"));
assertTrue(actual.getParameter("data").endsWith("@@test@@aaa"));
String expectSign = SignUtil.sign(actual.getParameter("data"), PropertyKeyConst.SECRET_KEY);
assertEquals(expectSign, actual.getParameter("signature"));
}
@Test
void testDoInjectWithoutGroup() throws Exception {
resource = RequestResource.namingBuilder().setResource("aaa").setGroup("group").build();
LoginIdentityContext actual = new LoginIdentityContext();
namingResourceInjector.doInject(resource, ramContext, actual);
assertTrue(actual.getParameter("data").endsWith("@@group@@aaa"));
assertEquals(3, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("ak"));
String expectSign = SignUtil.sign(actual.getParameter("data"), PropertyKeyConst.SECRET_KEY);
assertEquals(expectSign, actual.getParameter("signature"));
}
@Test
void testDoInjectWithGroupForSts() throws Exception {
prepareForSts();
resource =
RequestResource.namingBuilder().setResource("test@@aaa").setGroup("group").build();
LoginIdentityContext actual = new LoginIdentityContext();
namingResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals("test-sts-ak", actual.getParameter("ak"));
assertTrue(actual.getParameter("data").endsWith("@@test@@aaa"));
String expectSign = SignUtil.sign(actual.getParameter("data"), "test-sts-sk");
assertEquals(expectSign, actual.getParameter("signature"));
}
@Test
void testDoInjectWithoutGroupForSts() throws Exception {
prepareForSts();
resource = RequestResource.namingBuilder().setResource("aaa").setGroup("group").build();
LoginIdentityContext actual = new LoginIdentityContext();
namingResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals("test-sts-ak", actual.getParameter("ak"));
assertTrue(actual.getParameter("data").endsWith("@@group@@aaa"));
String expectSign = SignUtil.sign(actual.getParameter("data"), "test-sts-sk");
assertEquals(expectSign, actual.getParameter("signature"));
}
@Test
void testDoInjectForStsWithException() throws Exception {
prepareForSts();
stsCredential.setExpiration(new Date());
resource = RequestResource.namingBuilder().setResource("aaa").setGroup("group").build();
LoginIdentityContext actual = new LoginIdentityContext();
namingResourceInjector.doInject(resource, ramContext, actual);
assertEquals(0, actual.getAllKey().size());
}
@Test
void testDoInjectForV4Sign() throws Exception {
resource =
RequestResource.namingBuilder().setResource("test@@aaa").setGroup("group").build();
LoginIdentityContext actual = new LoginIdentityContext();
ramContext.setRegionId("cn-hangzhou");
namingResourceInjector.doInject(resource, ramContext, actual);
assertEquals(4, actual.getAllKey().size());
assertEquals(PropertyKeyConst.ACCESS_KEY, actual.getParameter("ak"));
assertEquals(RamConstants.V4, actual.getParameter(RamConstants.SIGNATURE_VERSION));
assertTrue(actual.getParameter("data").endsWith("@@test@@aaa"));
String signatureKey = CalculateV4SigningKeyUtil.finalSigningKeyStringWithDefaultInfo(
PropertyKeyConst.SECRET_KEY, "cn-hangzhou");
String expectSign = SignUtil.sign(actual.getParameter("data"), signatureKey);
assertEquals(expectSign, actual.getParameter("signature"));
}
private void prepareForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl("test");
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), stsCredential);
stsCredential.setAccessKeyId("test-sts-ak");
stsCredential.setAccessKeySecret("test-sts-sk");
stsCredential.setSecurityToken("test-sts-token");
stsCredential.setExpiration(new Date(System.currentTimeMillis() + 1000000));
}
private void clearForSts() throws NoSuchFieldException, IllegalAccessException {
StsConfig.getInstance().setSecurityCredentialsUrl(null);
StsConfig.getInstance().setSecurityCredentials(null);
Field field = StsCredentialHolder.class.getDeclaredField("stsCredential");
field.setAccessible(true);
field.set(StsCredentialHolder.getInstance(), null);
}
}
@@ -0,0 +1,67 @@
/*
* 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.client.auth.ram.utils;
import com.alibaba.nacos.client.auth.ram.RamConstants;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import javax.crypto.Mac;
import java.security.InvalidKeyException;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
class CalculateV4SigningKeyUtilTest {
@Test
void testFinalSigningKeyStringWithException() {
assertThrows(RuntimeException.class,
() -> CalculateV4SigningKeyUtil.finalSigningKeyString("", "2021-01-01",
"cn-hangzhou",
RamConstants.SIGNATURE_V4_PRODUCE, "non-exist"));
}
@Test
void testFinalSigningKeyStringWithDefaultInfo() {
assertNotNull(
CalculateV4SigningKeyUtil.finalSigningKeyStringWithDefaultInfo("", "cn-hangzhou"));
}
@Test
void testFinalSigningKeyStringWithInvalidKey() throws Exception {
Mac macMock = mock(Mac.class);
doThrow(new InvalidKeyException("forced")).when(macMock).init(any());
try (MockedStatic<Mac> mocked = Mockito.mockStatic(Mac.class)) {
mocked.when(() -> Mac.getInstance(anyString())).thenReturn(macMock);
assertThrows(RuntimeException.class,
() -> CalculateV4SigningKeyUtil.finalSigningKeyString("secret", "20210101",
"cn-hangzhou", RamConstants.SIGNATURE_V4_PRODUCE,
RamConstants.SIGNATURE_V4_METHOD));
}
}
@Test
void testConstructor() {
new CalculateV4SigningKeyUtil();
}
}
@@ -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.client.auth.ram.utils;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.auth.ram.identify.CredentialService;
import com.alibaba.nacos.client.auth.ram.identify.Credentials;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class RamUtilTest {
private Properties properties;
@BeforeEach
public void setUp() throws Exception {
SpasAdapter.freeCredentialInstance();
Credentials credentials = new Credentials("spasAk", "spasSk", "spasNamespaceId");
CredentialService.getInstance().setStaticCredential(credentials);
properties = new Properties();
properties.setProperty(PropertyKeyConst.ACCESS_KEY, "userAk");
properties.setProperty(PropertyKeyConst.SECRET_KEY, "userSk");
}
@AfterEach
public void tearDown() throws Exception {
SpasAdapter.freeCredentialInstance();
}
@Test
public void testGetAccessKeyWithUserAkSk() {
assertEquals("userAk", RamUtil.getAccessKey(properties));
assertEquals("userSk", RamUtil.getSecretKey(properties));
}
@Test
public void testGetAccessKeyWithSpasAkSk() {
assertEquals("spasAk", RamUtil.getAccessKey(new Properties()));
assertEquals("spasSk", RamUtil.getSecretKey(new Properties()));
}
@Test
public void testGetAccessKeyWithoutSpasAkSk() {
Properties properties1 = new Properties();
properties1.setProperty(PropertyKeyConst.IS_USE_RAM_INFO_PARSING, "false");
assertNull(RamUtil.getAccessKey(properties1));
assertNull(RamUtil.getSecretKey(properties1));
}
@Test
public void testConstructor() {
new RamUtil();
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 1999-2021 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.client.auth.ram.utils;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class SignUtilTest {
@Test
void testSign() throws Exception {
String actual = SignUtil.sign("aaa", "b");
assertEquals("DxyaKScrqL26yXYOuHXE3OwfQ0Y=", actual);
}
@Test
void testSignWithException() throws Exception {
assertThrows(Exception.class, () -> {
SignUtil.sign(null, "b");
});
}
@Test
void testSignWithException2() throws Exception {
assertThrows(Exception.class, () -> {
SignUtil.sign("aaa".getBytes(StandardCharsets.UTF_8),
"b".getBytes(StandardCharsets.UTF_8), null);
});
}
@Test
void testConstructor() {
new SignUtil();
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 1999-2021 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.client.auth.ram.utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
class SpasAdapterTest {
@Test
void test() {
assertNull(SpasAdapter.getAk());
assertNull(SpasAdapter.getSk());
Assertions.assertDoesNotThrow(() -> {
SpasAdapter.freeCredentialInstance();
});
}
@Test
void testSign() {
assertNull(SpasAdapter.getSignHeaders("", "", "123"));
final Map<String, String> map1 = SpasAdapter.getSignHeaders("aa", "bb", "123");
assertEquals(2, map1.size());
assertEquals(SpasAdapter.signWithHmacSha1Encrypt("bb+aa+" + map1.get("Timestamp"), "123"),
map1.get("Spas-Signature"));
final Map<String, String> map2 = SpasAdapter.getSignHeaders("aa", "", "123");
assertEquals(2, map2.size());
assertEquals(SpasAdapter.signWithHmacSha1Encrypt("aa" + "+" + map2.get("Timestamp"), "123"),
map2.get("Spas-Signature"));
final Map<String, String> map3 = SpasAdapter.getSignHeaders("", "bb", "123");
assertEquals(2, map3.size());
assertEquals(SpasAdapter.signWithHmacSha1Encrypt(map3.get("Timestamp"), "123"),
map3.get("Spas-Signature"));
}
@Test
void testSign2() {
assertNull(SpasAdapter.getSignHeaders((Map) null, "123"));
Map<String, String> param1 = new HashMap<>();
param1.put("tenant", "bb");
param1.put("group", "aa");
final Map<String, String> map1 = SpasAdapter.getSignHeaders(param1, "123");
assertEquals(2, map1.size());
assertEquals(SpasAdapter.signWithHmacSha1Encrypt("bb+aa+" + map1.get("Timestamp"), "123"),
map1.get("Spas-Signature"));
}
@Test
void testGetSignHeadersWithoutTenant() {
Map<String, String> param1 = new HashMap<>();
param1.put("group", "aa");
final Map<String, String> map1 = SpasAdapter.getSignHeaders(param1, "123");
assertEquals(2, map1.size());
assertEquals(SpasAdapter.signWithHmacSha1Encrypt("aa+" + map1.get("Timestamp"), "123"),
map1.get("Spas-Signature"));
}
@Test
void testSignWithHmacSha1EncryptWithException() {
assertThrows(Exception.class, () -> {
SpasAdapter.signWithHmacSha1Encrypt(null, "123");
});
}
@Test
void testConstructor() {
new SpasAdapter();
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 1999-2026 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.client.constant;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class ConstantsTest {
@Test
void testConstructors() {
assertNotNull(new Constants());
assertNotNull(new Constants.SysEnv());
assertNotNull(new Constants.Security());
assertNotNull(new Constants.Address());
}
@Test
void testSysEnvFields() {
assertEquals("user.home", Constants.SysEnv.USER_HOME);
assertEquals("project.name", Constants.SysEnv.PROJECT_NAME);
assertEquals("JM.LOG.PATH", Constants.SysEnv.JM_LOG_PATH);
assertEquals("JM.SNAPSHOT.PATH", Constants.SysEnv.JM_SNAPSHOT_PATH);
assertEquals("nacos.env.first", Constants.SysEnv.NACOS_ENV_FIRST);
}
@Test
void testSecurityFields() {
assertEquals(5_000L, Constants.Security.SECURITY_INFO_REFRESH_INTERVAL_MILLS);
}
@Test
void testAddressFields() {
assertEquals(500, Constants.Address.ENDPOINT_SERVER_LIST_PROVIDER_ORDER);
assertEquals(499, Constants.Address.ADDRESS_SERVER_LIST_PROVIDER_ORDER);
}
}
@@ -0,0 +1,329 @@
/*
* Copyright 1999-2022 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.client.env;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class NacosClientPropertiesTest {
@BeforeAll
static void init() {
System.setProperty("nacos.env.first", "jvm");
}
@AfterAll
static void teardown() {
System.clearProperty("nacos.env.first");
}
@Test
void testGetProperty() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.home", "/home/nacos");
final String value = NacosClientProperties.PROTOTYPE.getProperty("nacos.home");
assertEquals("/home/nacos", value);
}
@Test
void testGetPropertyMultiLayer() {
NacosClientProperties.PROTOTYPE.setProperty("top.layer", "top");
final NacosClientProperties layerAEnv = NacosClientProperties.PROTOTYPE.derive();
layerAEnv.setProperty("a.layer", "a");
final NacosClientProperties layerBEnv = layerAEnv.derive();
layerBEnv.setProperty("b.layer", "b");
final NacosClientProperties layerCEnv = layerBEnv.derive();
layerCEnv.setProperty("c.layer", "c");
String value = layerCEnv.getProperty("c.layer");
assertEquals("c", value);
value = layerCEnv.getProperty("b.layer");
assertEquals("b", value);
value = layerCEnv.getProperty("a.layer");
assertEquals("a", value);
value = layerCEnv.getProperty("top.layer");
assertEquals("top", value);
}
@Test
void testGetPropertyDefaultValue() {
final String value = NacosClientProperties.PROTOTYPE.getProperty("nacos.home.default",
"/home/default_value");
assertEquals("/home/default_value", value);
}
@Test
void testGetBoolean() {
NacosClientProperties.PROTOTYPE.setProperty("use.cluster", "true");
final Boolean value = NacosClientProperties.PROTOTYPE.getBoolean("use.cluster");
assertTrue(value);
}
@Test
void testGetBooleanDefaultValue() {
final Boolean value =
NacosClientProperties.PROTOTYPE.getBoolean("use.cluster.default", false);
assertFalse(value);
}
@Test
void testGetInteger() {
NacosClientProperties.PROTOTYPE.setProperty("max.timeout", "200");
final Integer value = NacosClientProperties.PROTOTYPE.getInteger("max.timeout");
assertEquals(200, value.intValue());
}
@Test
void testGetIntegerDefaultValue() {
final Integer value =
NacosClientProperties.PROTOTYPE.getInteger("max.timeout.default", 400);
assertEquals(400, value.intValue());
}
@Test
void testGetLong() {
NacosClientProperties.PROTOTYPE.setProperty("connection.timeout", "200");
final Long value = NacosClientProperties.PROTOTYPE.getLong("connection.timeout");
assertEquals(200L, value.longValue());
}
@Test
void testGetLongDefault() {
final Long value =
NacosClientProperties.PROTOTYPE.getLong("connection.timeout.default", 400L);
assertEquals(400L, value.longValue());
}
@Test
void setProperty() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.set.property", "true");
final String ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.set.property");
assertEquals("true", ret);
}
@Test
void setPropertyWithScope() {
final NacosClientProperties properties = NacosClientProperties.PROTOTYPE.derive();
properties.setProperty("nacos.set.property.scope", "config");
String ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.set.property.scope");
assertNull(ret);
ret = properties.getProperty("nacos.set.property.scope");
assertEquals("config", ret);
}
@Test
void testAddProperties() {
Properties properties = new Properties();
properties.setProperty("nacos.add.properties", "true");
NacosClientProperties.PROTOTYPE.addProperties(properties);
final String ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.add.properties");
assertEquals("true", ret);
}
@Test
void testAddPropertiesWithScope() {
Properties properties = new Properties();
properties.setProperty("nacos.add.properties.scope", "config");
final NacosClientProperties nacosClientProperties =
NacosClientProperties.PROTOTYPE.derive();
nacosClientProperties.addProperties(properties);
String ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.add.properties.scope");
assertNull(ret);
ret = nacosClientProperties.getProperty("nacos.add.properties.scope");
assertEquals("config", ret);
}
@Test
void testTestDerive() {
Properties properties = new Properties();
properties.setProperty("nacos.derive.properties.scope", "derive");
final NacosClientProperties nacosClientProperties =
NacosClientProperties.PROTOTYPE.derive(properties);
final String value = nacosClientProperties.getProperty("nacos.derive.properties.scope");
assertEquals("derive", value);
}
@Test
void testContainsKey() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.contains.key", "true");
boolean ret = NacosClientProperties.PROTOTYPE.containsKey("nacos.contains.key");
assertTrue(ret);
ret = NacosClientProperties.PROTOTYPE.containsKey("nacos.contains.key.in.sys");
assertFalse(ret);
}
@Test
void testContainsKeyMultiLayers() {
NacosClientProperties.PROTOTYPE.setProperty("top.layer", "top");
final NacosClientProperties layerAEnv = NacosClientProperties.PROTOTYPE.derive();
layerAEnv.setProperty("a.layer", "a");
final NacosClientProperties layerBEnv = layerAEnv.derive();
layerBEnv.setProperty("b.layer", "b");
final NacosClientProperties layerCEnv = layerBEnv.derive();
layerCEnv.setProperty("c.layer", "c");
boolean exist = layerCEnv.containsKey("c.layer");
assertTrue(exist);
exist = layerCEnv.containsKey("b.layer");
assertTrue(exist);
exist = layerCEnv.containsKey("a.layer");
assertTrue(exist);
exist = layerCEnv.containsKey("top.layer");
assertTrue(exist);
}
@Test
void testContainsKeyWithScope() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.contains.global.scope", "global");
final NacosClientProperties namingProperties = NacosClientProperties.PROTOTYPE.derive();
namingProperties.setProperty("nacos.contains.naming.scope", "naming");
boolean ret = NacosClientProperties.PROTOTYPE.containsKey("nacos.contains.global.scope");
assertTrue(ret);
ret = NacosClientProperties.PROTOTYPE.containsKey("nacos.contains.naming.scope");
assertFalse(ret);
ret = namingProperties.containsKey("nacos.contains.naming.scope");
assertTrue(ret);
ret = namingProperties.containsKey("nacos.contains.global.scope");
assertTrue(ret);
}
@Test
void testAsProperties() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.as.properties", "true");
final Properties properties = NacosClientProperties.PROTOTYPE.asProperties();
assertNotNull(properties);
assertEquals("true", properties.getProperty("nacos.as.properties"));
}
@Test
void testAsPropertiesWithScope() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.as.properties.global.scope", "global");
NacosClientProperties.PROTOTYPE.setProperty("nacos.server.addr.scope", "global");
final NacosClientProperties configProperties = NacosClientProperties.PROTOTYPE.derive();
configProperties.setProperty("nacos.server.addr.scope", "config");
final Properties properties = configProperties.asProperties();
assertNotNull(properties);
String ret = properties.getProperty("nacos.as.properties.global.scope");
assertEquals("global", ret);
ret = properties.getProperty("nacos.server.addr.scope");
assertEquals("config", ret);
}
@Test
void testGetPropertyWithScope() {
NacosClientProperties.PROTOTYPE.setProperty("nacos.global.scope", "global");
final NacosClientProperties configProperties = NacosClientProperties.PROTOTYPE.derive();
configProperties.setProperty("nacos.config.scope", "config");
final NacosClientProperties namingProperties = NacosClientProperties.PROTOTYPE.derive();
namingProperties.setProperty("nacos.naming.scope", "naming");
String ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.global.scope");
assertEquals("global", ret);
ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.config.scope");
assertNull(ret);
ret = NacosClientProperties.PROTOTYPE.getProperty("nacos.naming.scope");
assertNull(ret);
ret = configProperties.getProperty("nacos.config.scope");
assertEquals("config", ret);
ret = configProperties.getProperty("nacos.global.scope");
assertEquals("global", ret);
ret = configProperties.getProperty("nacos.naming.scope");
assertNull(ret);
ret = namingProperties.getProperty("nacos.naming.scope");
assertEquals("naming", ret);
ret = namingProperties.getProperty("nacos.global.scope");
assertEquals("global", ret);
ret = namingProperties.getProperty("nacos.config.scope");
assertNull(ret);
}
@Test
void testGetPropertyFrom() {
System.setProperty("nacos.home.default.test", "/home/jvm_args");
NacosClientProperties.PROTOTYPE.setProperty("nacos.home.default.test",
"/home/properties_args");
assertEquals("/home/jvm_args",
NacosClientProperties.PROTOTYPE.getPropertyFrom(SourceType.JVM,
"nacos.home.default.test"));
assertEquals("/home/properties_args",
NacosClientProperties.PROTOTYPE.getPropertyFrom(SourceType.PROPERTIES,
"nacos.home.default.test"));
assertEquals(
NacosClientProperties.PROTOTYPE.getPropertyFrom(null, "nacos.home.default.test"),
NacosClientProperties.PROTOTYPE.getProperty("nacos.home.default.test"));
}
}
@@ -0,0 +1,129 @@
/*
* 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.client.env;
import com.alibaba.nacos.client.constant.Constants;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* Additional test cases for SearchableProperties.
*
* <p> Common cases see {@link NacosClientPropertiesTest}.</p>
*/
class SearchablePropertiesTest {
Method initMethod;
@BeforeAll
static void init() {
System.setProperty(Constants.SysEnv.NACOS_ENV_FIRST, "jvm");
}
@AfterAll
static void teardown() {
System.clearProperty(Constants.SysEnv.NACOS_ENV_FIRST);
}
@BeforeEach
void setUp() throws Exception {
initMethod = SearchableProperties.class.getDeclaredMethod("init");
initMethod.setAccessible(true);
}
@AfterEach
void tearDown() throws Exception {
init();
initMethod.invoke(null);
}
@Test
void testInitWithInvalidOrder() throws IllegalAccessException, InvocationTargetException {
System.setProperty(Constants.SysEnv.NACOS_ENV_FIRST, "invalid");
List<SourceType> order = (List<SourceType>) initMethod.invoke(null);
assertOrder(order, SourceType.PROPERTIES, SourceType.JVM, SourceType.ENV);
}
@Test
void testInitWithoutSpecifiedOrder() throws IllegalAccessException, InvocationTargetException {
System.clearProperty(Constants.SysEnv.NACOS_ENV_FIRST);
List<SourceType> order = (List<SourceType>) initMethod.invoke(null);
assertOrder(order, SourceType.PROPERTIES, SourceType.JVM, SourceType.ENV);
}
private void assertOrder(List<SourceType> order, SourceType... sourceTypes) {
assertEquals(sourceTypes.length, order.size());
for (int i = 0; i < sourceTypes.length; i++) {
assertEquals(sourceTypes[i], order.get(i));
}
}
@Test
void testGetPropertyFromEnv() {
System.setProperty("testFromSource", "jvm");
NacosClientProperties properties = SearchableProperties.INSTANCE.derive();
properties.setProperty("testFromSource", "properties");
assertNull(properties.getPropertyFrom(SourceType.ENV, "testFromSource"));
}
@Test
void testGetPropertyFromUnknown() {
System.setProperty("testFromSource", "jvm");
NacosClientProperties properties = SearchableProperties.INSTANCE.derive();
properties.setProperty("testFromSource", "properties");
assertEquals(properties.getProperty("testFromSource"),
properties.getPropertyFrom(SourceType.UNKNOWN, "testFromSource"));
}
@Test
void testGetPropertiesByJvmSource() {
NacosClientProperties properties = SearchableProperties.INSTANCE.derive();
java.util.Properties jvm = properties.getProperties(SourceType.JVM);
org.junit.jupiter.api.Assertions.assertNotNull(jvm);
}
@Test
void testGetPropertiesByPropertiesSource() {
NacosClientProperties properties = SearchableProperties.INSTANCE.derive();
properties.setProperty("propsKey", "propsVal");
java.util.Properties props = properties.getProperties(SourceType.PROPERTIES);
org.junit.jupiter.api.Assertions.assertNotNull(props);
assertEquals("propsVal", props.getProperty("propsKey"));
}
@Test
void testGetPropertiesByUnknownSource() {
NacosClientProperties properties = SearchableProperties.INSTANCE.derive();
assertNull(properties.getProperties(SourceType.UNKNOWN));
}
@Test
void testGetPropertiesByNullSource() {
NacosClientProperties properties = SearchableProperties.INSTANCE.derive();
assertNull(properties.getProperties(null));
}
}
@@ -0,0 +1,91 @@
/*
* 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.client.env;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Additional test cases for SystemEnvPropertySource.
*
* <p> Common cases see {@link NacosClientPropertiesTest}.</p>
*/
class SystemEnvPropertySourceTest {
SystemEnvPropertySource systemEnvPropertySource;
private Map<String, String> mockEnvMap;
@BeforeEach
void setUp() throws Exception {
systemEnvPropertySource = new SystemEnvPropertySource();
mockEnvMap = new HashMap<>();
Field envField = SystemEnvPropertySource.class.getDeclaredField("env");
envField.setAccessible(true);
envField.set(systemEnvPropertySource, mockEnvMap);
mockEnvMap.put("testcase1", "value1");
mockEnvMap.put("test_case_2", "value2");
mockEnvMap.put("TESTCASE3", "value3");
mockEnvMap.put("TEST_CASE_4", "value4");
}
@Test
void testGetEnvForLowerCaseKey() {
assertEquals("value1", systemEnvPropertySource.getProperty("testcase1"));
}
@Test
void testGetEnvForLowerCaseKeyWithDot() {
assertEquals("value2", systemEnvPropertySource.getProperty("test.case.2"));
}
@Test
void testGetEnvForLowerCaseKeyWithHyphen() {
assertEquals("value2", systemEnvPropertySource.getProperty("test-case-2"));
}
@Test
void testGetEnvForLowerCaseKeyWithHyphenAndDot() {
assertEquals("value2", systemEnvPropertySource.getProperty("test.case-2"));
}
@Test
void testGetEnvForUpperCaseKey() {
assertEquals("value3", systemEnvPropertySource.getProperty("TESTCASE3"));
}
@Test
void testGetEnvForUpperCaseKeyWithDot() {
assertEquals("value4", systemEnvPropertySource.getProperty("TEST.CASE.4"));
}
@Test
void testGetEnvForUpperCaseKeyWithHyphen() {
assertEquals("value4", systemEnvPropertySource.getProperty("TEST-CASE-4"));
}
@Test
void testGetEnvForUpperCaseKeyWithHyphenAndDot() {
assertEquals("value4", systemEnvPropertySource.getProperty("TEST_CASE.4"));
}
}
@@ -0,0 +1,120 @@
/*
* 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.client.env.convert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.MissingFormatArgumentException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CompositeConverterTest {
CompositeConverter compositeConverter;
@BeforeEach
void setUp() throws Exception {
compositeConverter = new CompositeConverter();
}
@AfterEach
void tearDown() throws Exception {
}
@Test
void testConvertNotSupportType() {
assertThrows(MissingFormatArgumentException.class, () -> {
compositeConverter.convert("test", CompositeConverter.class);
});
}
@Test
void testConvertBooleanForEmptyProperty() {
assertNull(compositeConverter.convert(null, Boolean.class));
}
@Test
void testConvertBooleanTrue() {
assertTrue(compositeConverter.convert("true", Boolean.class));
assertTrue(compositeConverter.convert("on", Boolean.class));
assertTrue(compositeConverter.convert("yes", Boolean.class));
assertTrue(compositeConverter.convert("1", Boolean.class));
}
@Test
void testConvertBooleanFalse() {
assertFalse(compositeConverter.convert("false", Boolean.class));
assertFalse(compositeConverter.convert("off", Boolean.class));
assertFalse(compositeConverter.convert("no", Boolean.class));
assertFalse(compositeConverter.convert("0", Boolean.class));
}
@Test
void testConvertBooleanIllegal() {
assertThrows(IllegalArgumentException.class, () -> {
compositeConverter.convert("aaa", Boolean.class);
});
}
@Test
void testConvertIntegerForEmptyProperty() {
assertNull(compositeConverter.convert(null, Integer.class));
}
@Test
void testConvertInteger() {
assertEquals(100, (int) compositeConverter.convert("100", Integer.class));
assertEquals(Integer.MAX_VALUE,
(int) compositeConverter.convert(String.valueOf(Integer.MAX_VALUE), Integer.class));
assertEquals(Integer.MIN_VALUE,
(int) compositeConverter.convert(String.valueOf(Integer.MIN_VALUE), Integer.class));
}
@Test
void testConvertIntegerIllegal() {
assertThrows(IllegalArgumentException.class, () -> {
compositeConverter.convert("aaa", Integer.class);
});
}
@Test
void testConvertLongForEmptyProperty() {
assertNull(compositeConverter.convert(null, Long.class));
}
@Test
void testConvertLong() {
assertEquals(100L, (long) compositeConverter.convert("100", Long.class));
assertEquals(Long.MAX_VALUE,
(long) compositeConverter.convert(String.valueOf(Long.MAX_VALUE), Long.class));
assertEquals(Long.MIN_VALUE,
(long) compositeConverter.convert(String.valueOf(Long.MIN_VALUE), Long.class));
}
@Test
void testConvertLongIllegal() {
assertThrows(IllegalArgumentException.class, () -> {
compositeConverter.convert("aaa", Long.class);
});
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 1999-2026 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.client.remote;
import com.alibaba.nacos.common.http.HttpClientBeanHolder;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.anyString;
class HttpClientManagerTest {
@Test
void testGetInstanceReturnsSingleton() {
HttpClientManager first = HttpClientManager.getInstance();
HttpClientManager second = HttpClientManager.getInstance();
assertNotNull(first);
assertSame(first, second);
}
@Test
void testGetConnectTimeoutOrDefaultUsesMin() {
// Below 1000 ms minimum → returns 1000
assertEquals(1000, HttpClientManager.getInstance().getConnectTimeoutOrDefault(500));
}
@Test
void testGetConnectTimeoutOrDefaultUsesProvidedWhenLarger() {
assertEquals(2000, HttpClientManager.getInstance().getConnectTimeoutOrDefault(2000));
}
@Test
void testGetNacosRestTemplate() {
NacosRestTemplate template = HttpClientManager.getInstance().getNacosRestTemplate();
assertNotNull(template);
}
@Test
void testShutdownDoesNotThrow() {
// Note: shutdown closes the shared NacosRestTemplate. Run this test last via
// method name ordering (JUnit runs in alphabetical order without explicit ordering).
Assertions.assertDoesNotThrow(() -> HttpClientManager.getInstance().shutdown());
}
@Test
void testShutdownSwallowsException() {
try (MockedStatic<HttpClientBeanHolder> mocked =
Mockito.mockStatic(HttpClientBeanHolder.class)) {
mocked.when(() -> HttpClientBeanHolder.shutdownNacosSyncRest(anyString()))
.thenThrow(new RuntimeException("forced"));
Assertions.assertDoesNotThrow(() -> HttpClientManager.getInstance().shutdown());
}
}
@Test
void testHttpClientFactoryInternalsViaReflection() throws Exception {
Field factoryField = HttpClientManager.class.getDeclaredField("HTTP_CLIENT_FACTORY");
factoryField.setAccessible(true);
Object factory = factoryField.get(null);
Method buildConfig = factory.getClass().getDeclaredMethod("buildHttpClientConfig");
buildConfig.setAccessible(true);
Object config = buildConfig.invoke(factory);
assertNotNull(config);
Method assignLogger = factory.getClass().getDeclaredMethod("assignLogger");
assignLogger.setAccessible(true);
assertNotNull(assignLogger.invoke(factory));
}
}
@@ -0,0 +1,80 @@
/*
*
* Copyright 1999-2018 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.client.utils;
import com.alibaba.nacos.client.constant.Constants;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class AppNameUtilsTest {
@BeforeEach
void setUp() throws Exception {
}
@AfterEach
void tearDown() throws Exception {
System.clearProperty(Constants.SysEnv.PROJECT_NAME);
System.clearProperty("jboss.server.home.dir");
System.clearProperty("jetty.home");
System.clearProperty("catalina.base");
}
@Test
void testGetAppNameByDefault() {
String appName = AppNameUtils.getAppName();
assertEquals("unknown", appName);
}
@Test
void testGetAppNameByProjectName() {
System.setProperty(Constants.SysEnv.PROJECT_NAME, "testAppName");
String appName = AppNameUtils.getAppName();
assertEquals("testAppName", appName);
}
@Test
void testGetAppNameByServerTypeForJboss() {
System.setProperty("jboss.server.home.dir", "/home/admin/testAppName/");
String appName = AppNameUtils.getAppName();
assertEquals("testAppName", appName);
}
@Test
void testGetAppNameByServerTypeForJetty() {
System.setProperty("jetty.home", "/home/admin/testAppName/");
String appName = AppNameUtils.getAppName();
assertEquals("testAppName", appName);
}
@Test
void testGetAppNameByServerTypeForTomcat() {
System.setProperty("catalina.base", "/home/admin/testAppName/");
String appName = AppNameUtils.getAppName();
assertEquals("testAppName", appName);
}
@Test
void testConstructor() {
new AppNameUtils();
}
}
@@ -0,0 +1,201 @@
/*
* Copyright 1999-2025 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.client.utils;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.client.env.SourceType;
import com.alibaba.nacos.common.utils.VersionUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ClientBasicParamUtilTest {
private String defaultAppKey;
private String defaultContextPath;
private String defaultVersion;
private String defaultNodesPath;
@BeforeEach
void before() {
defaultAppKey = "";
defaultContextPath = "nacos";
defaultVersion = VersionUtils.version;
defaultNodesPath = "serverlist";
}
@AfterEach
void after() {
ClientBasicParamUtil.setAppKey(defaultAppKey);
ClientBasicParamUtil.setDefaultContextPath(defaultContextPath);
ClientBasicParamUtil.setClientVersion(defaultVersion);
ClientBasicParamUtil.setDefaultNodesPath(defaultNodesPath);
System.clearProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL);
}
@Test
void testGetAppKey() {
String defaultVal = ClientBasicParamUtil.getAppKey();
assertEquals(defaultAppKey, defaultVal);
String expect = "test";
ClientBasicParamUtil.setAppKey(expect);
assertEquals(expect, ClientBasicParamUtil.getAppKey());
}
@Test
void testGetDefaultContextPath() {
String defaultVal = ClientBasicParamUtil.getDefaultContextPath();
assertEquals(defaultContextPath, defaultVal);
String expect = "test";
ClientBasicParamUtil.setDefaultContextPath(expect);
assertEquals(expect, ClientBasicParamUtil.getDefaultContextPath());
}
@Test
void testGetClientVersion() {
String defaultVal = ClientBasicParamUtil.getClientVersion();
assertEquals(defaultVersion, defaultVal);
String expect = "test";
ClientBasicParamUtil.setClientVersion(expect);
assertEquals(expect, ClientBasicParamUtil.getClientVersion());
}
@Test
void testGetDefaultServerPort() {
String actual = ClientBasicParamUtil.getDefaultServerPort();
assertEquals("8848", actual);
}
@Test
void testGetDefaultNodesPath() {
String defaultVal = ClientBasicParamUtil.getDefaultNodesPath();
assertEquals("serverlist", defaultVal);
String expect = "test";
ClientBasicParamUtil.setDefaultNodesPath(expect);
assertEquals(expect, ClientBasicParamUtil.getDefaultNodesPath());
}
@Test
void testParseNamespace() {
String expect = "test";
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.NAMESPACE, expect);
final NacosClientProperties nacosClientProperties =
NacosClientProperties.PROTOTYPE.derive(properties);
String actual = ClientBasicParamUtil.parseNamespace(nacosClientProperties);
assertEquals(expect, actual);
}
@Test
void testParsingEndpointRule() {
String url = "${test:www.example.com}";
String actual = ClientBasicParamUtil.parsingEndpointRule(url);
assertEquals("www.example.com", actual);
}
@Test
void testParsingEndpointRuleFromSystem() {
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL,
"alibaba_aliware_endpoint_url");
assertEquals("alibaba_aliware_endpoint_url",
ClientBasicParamUtil.parsingEndpointRule(null));
}
@Test
void testParsingEndpointRuleFromSystemWithParam() {
System.setProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL,
"alibaba_aliware_endpoint_url");
assertEquals("alibaba_aliware_endpoint_url",
ClientBasicParamUtil.parsingEndpointRule("${abc:xxx}"));
}
@Test
void testGetInputParametersWithFullMode() {
Properties properties = new Properties();
properties.setProperty("testKey", "testValue");
properties.setProperty(PropertyKeyConst.LOG_ALL_PROPERTIES, "true");
NacosClientProperties clientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
String actual = ClientBasicParamUtil.getInputParameters(clientProperties.asProperties());
assertTrue(actual.startsWith(
"Log nacos client init properties with Full mode, This mode is only used for debugging and troubleshooting."));
assertTrue(actual.contains("\ttestKey=testValue\n"));
Properties envProperties = clientProperties.getProperties(SourceType.ENV);
String envCaseKey = envProperties.stringPropertyNames().iterator().next();
String envCaseValue = envProperties.getProperty(envCaseKey);
assertTrue(actual.contains(String.format("\t%s=%s\n", envCaseKey, envCaseValue)));
}
@Test
void testGetInputParameters() {
Properties properties = new Properties();
properties.setProperty("testKey", "testValue");
properties.setProperty(PropertyKeyConst.SERVER_ADDR, "localhost:8848");
properties.setProperty(PropertyKeyConst.PASSWORD, "testPassword");
NacosClientProperties clientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
String actual = ClientBasicParamUtil.getInputParameters(clientProperties.asProperties());
assertEquals(
"Nacos client key init properties: \n\tserverAddr=localhost:8848\n\tpassword=te********rd\n",
actual);
}
@Test
void testDesensitiseParameter() {
String shortParameter = "aa";
assertEquals(shortParameter, ClientBasicParamUtil.desensitiseParameter(shortParameter));
String middleParameter = "aaa";
assertEquals("a*a", ClientBasicParamUtil.desensitiseParameter(middleParameter));
middleParameter = "aaaaaaa";
assertEquals("a*****a", ClientBasicParamUtil.desensitiseParameter(middleParameter));
String longParameter = "testPass";
assertEquals("te****ss", ClientBasicParamUtil.desensitiseParameter(longParameter));
}
@Test
void testGetNameSuffixByServerIps() {
assertEquals("1.1.1.1-2.2.2.2_8848",
ClientBasicParamUtil.getNameSuffixByServerIps("http://1.1.1.1", "2.2.2.2:8848"));
}
@Test
void testParseNamespaceCloudParsingDefaultsWhenAllBlank() {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING, "true");
NacosClientProperties clientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
// No tenant, no ALIBABA_ALIWARE_NAMESPACE, no NAMESPACE → returns DEFAULT_NAMESPACE_ID
assertEquals(com.alibaba.nacos.api.common.Constants.DEFAULT_NAMESPACE_ID,
ClientBasicParamUtil.parseNamespace(clientProperties));
}
@Test
void testConstructor() {
new ClientBasicParamUtil();
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 1999-2018 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.client.utils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* ContextPathUtil test.
*
* @author Wei.Wang
* @date 2020/11/26 3:13 PM
*/
class ContextPathUtilTest {
@Test
void testNormalizeContextPath() {
assertEquals("/nacos", ContextPathUtil.normalizeContextPath("/nacos"));
assertEquals("/nacos", ContextPathUtil.normalizeContextPath("nacos"));
assertEquals("", ContextPathUtil.normalizeContextPath("/"));
assertEquals("", ContextPathUtil.normalizeContextPath(""));
}
@Test
void testConstructor() {
new ContextPathUtil();
}
}
@@ -0,0 +1,110 @@
/*
*
* Copyright 1999-2018 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.client.utils;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.concurrent.Callable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class TemplateUtilsTest {
@Test
void testStringNotEmptyAndThenExecuteSuccess() {
String word = "run";
Runnable task = Mockito.mock(Runnable.class);
TemplateUtils.stringNotEmptyAndThenExecute(word, task);
Mockito.verify(task, Mockito.times(1)).run();
}
@Test
void testStringNotEmptyAndThenExecuteFail() {
String word = "";
Runnable task = Mockito.mock(Runnable.class);
TemplateUtils.stringNotEmptyAndThenExecute(word, task);
Mockito.verify(task, Mockito.times(0)).run();
}
@Test
void testStringNotEmptyAndThenExecuteException() {
String word = "run";
Runnable task = Mockito.mock(Runnable.class);
doThrow(new RuntimeException("test")).when(task).run();
TemplateUtils.stringNotEmptyAndThenExecute(word, task);
Mockito.verify(task, Mockito.times(1)).run();
// NO exception thrown
}
@Test
void testStringEmptyAndThenExecuteSuccess() {
String word = " ";
String actual = TemplateUtils.stringEmptyAndThenExecute(word, () -> "call");
assertEquals("", actual);
}
@Test
void testStringEmptyAndThenExecuteFail() {
String word = "";
final String expect = "call";
String actual = TemplateUtils.stringEmptyAndThenExecute(word, () -> expect);
assertEquals(expect, actual);
}
@Test
void testStringEmptyAndThenExecuteException() throws Exception {
Callable callable = mock(Callable.class);
when(callable.call()).thenThrow(new RuntimeException("test"));
String actual = TemplateUtils.stringEmptyAndThenExecute(null, callable);
assertNull(actual);
}
@Test
void testStringBlankAndThenExecuteSuccess() {
String word = "success";
String actual = TemplateUtils.stringBlankAndThenExecute(word, () -> "call");
assertEquals(word, actual);
}
@Test
void testStringBlankAndThenExecuteFail() {
String word = " ";
final String expect = "call";
String actual = TemplateUtils.stringBlankAndThenExecute(word, () -> expect);
assertEquals(expect, actual);
}
@Test
void testStringBlankAndThenExecuteException() throws Exception {
Callable callable = mock(Callable.class);
when(callable.call()).thenThrow(new RuntimeException("test"));
String actual = TemplateUtils.stringBlankAndThenExecute(null, callable);
assertNull(actual);
}
@Test
void testConstructor() {
new TemplateUtils();
}
}
@@ -0,0 +1,60 @@
/*
*
* Copyright 1999-2018 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.client.utils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TenantUtilTest {
@AfterEach
void tearDown() {
System.clearProperty("acm.namespace");
System.clearProperty("ans.namespace");
}
@Test
void testGetUserTenantForAcm() {
String expect = "test";
System.setProperty("acm.namespace", expect);
String actual = TenantUtil.getUserTenantForAcm();
assertEquals(expect, actual);
}
@Test
void testGetUserTenantForAns() {
String expect = "test";
System.setProperty("ans.namespace", expect);
String actual = TenantUtil.getUserTenantForAns();
assertEquals(expect, actual);
}
@Test
void testGetUserTenantForAcmWhenAbsent() {
System.clearProperty("acm.namespace");
assertEquals("", TenantUtil.getUserTenantForAcm());
}
@Test
void testConstructor() {
new TenantUtil();
}
}
@@ -0,0 +1,18 @@
#
# Copyright 1999-2024 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.
#
#
com.alibaba.nacos.client.address.mock.MockServerListProvider
@@ -0,0 +1,18 @@
#
# 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.
#
accessKey=testAk
secretKey=testSk
tenantId=testTenantId
@@ -0,0 +1,18 @@
#
# 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.
#
env_spas_accessKey=testAk
env_spas_secretKey=testSk
ebv_spas_tenantId=testTenantId
@@ -0,0 +1,18 @@
#
# 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.
#
accessKey=
secretKey=testSk
tenantId=testTenantId
@@ -0,0 +1,15 @@
#
# 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.
#