This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new c85a5b607f5 branch-4.1: [fix](arrow-flight) Stop writing bearer tokens
to fe.log (#67146)
c85a5b607f5 is described below
commit c85a5b607f50e5e41d8745e2a267787887a7b8de
Author: Calvin Kirs <[email protected]>
AuthorDate: Fri Aug 28 01:22:42 2026 +0800
branch-4.1: [fix](arrow-flight) Stop writing bearer tokens to fe.log
(#67146)
#66572
---
fe/check/checkstyle/checkstyle.xml | 19 ++++++
.../main/java/org/apache/doris/catalog/Env.java | 5 +-
.../org/apache/doris/common/util/TokenMasker.java | 79 ++++++++++++++++++++++
.../org/apache/doris/httpv2/meta/MetaService.java | 24 +------
.../org/apache/doris/mysql/privilege/Auth.java | 5 +-
.../sessions/FlightSessionsWithTokenManager.java | 6 +-
.../sessions/FlightSqlConnectPoolMgr.java | 5 +-
.../arrowflight/tokens/FlightTokenManagerImpl.java | 40 ++++++-----
.../apache/doris/common/util/TokenMaskerTest.java | 63 +++++++++++++++++
9 files changed, 202 insertions(+), 44 deletions(-)
diff --git a/fe/check/checkstyle/checkstyle.xml
b/fe/check/checkstyle/checkstyle.xml
index 86f303d84c8..5cf7f608361 100644
--- a/fe/check/checkstyle/checkstyle.xml
+++ b/fe/check/checkstyle/checkstyle.xml
@@ -66,6 +66,25 @@ under the License.
<property name="message" value="Trailing whitespace found."/>
<property name="fileExtensions" value=".java"/>
</module>
+ <!--
+ Best effort guard against writing credentials to the log: catches a
value whose name says it
+ holds a token/password/secret/peer identity being passed straight to a
log call, either as a
+ parameter or concatenated into the message. Logs are routinely shipped
off the host, so a
+ credential in a log line has a far wider audience than the credential
store itself.
+
+ Matching is multiline because the argument often sits on a continuation
line, and [^;] bounds
+ each match to a single statement. It only recognizes the naming
convention, so it is a
+ backstop and not a substitute for review: an alias that does not carry
the noun (peerIdentity
+ needed to be added by hand once it turned out to be the Arrow Flight
bearer token) is invisible
+ to it.
+ -->
+ <module name="RegexpMultiline">
+ <property name="format"
+
value="LOG\.(?:info|warn|error|debug|trace)\([^;]*[,+]\s*(?![Mm]asked)(?:[A-Za-z0-9_]+\.)*(?:get)?[A-Za-z0-9_]*(?:[Tt]oken|[Pp]assword|[Ss]ecret|[Pp]eerIdentity)(?:Value|Str|String|Text)?(?:\(\))?\s*[,)+]"/>
+ <property name="message"
+ value="Do not log credentials. Mask the value with
org.apache.doris.common.util.TokenMasker (or log only a non-reversible id); if
the value is not a secret, name it so."/>
+ <property name="fileExtensions" value=".java"/>
+ </module>
<module name="TreeWalker">
<!-- filter -->
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 43235d9ac51..3dad10564d1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -92,6 +92,7 @@ import org.apache.doris.common.util.PrintableMap;
import org.apache.doris.common.util.PropertyAnalyzer;
import org.apache.doris.common.util.SmallFileMgr;
import org.apache.doris.common.util.TimeUtils;
+import org.apache.doris.common.util.TokenMasker;
import org.apache.doris.common.util.Util;
import org.apache.doris.consistency.ConsistencyChecker;
import org.apache.doris.cooldown.CooldownConfHandler;
@@ -1434,7 +1435,9 @@ public class Env {
}
String remoteToken =
conn.getHeaderField(MetaBaseAction.TOKEN);
if (token == null && remoteToken != null) {
- LOG.info("get token from helper node. token={}.",
remoteToken);
+ // Masked: the cluster token authenticates meta
access, so it must not
+ // reach fe.log. The prefix is enough to tell which
token was adopted.
+ LOG.info("get token from helper node. token={}.",
TokenMasker.maskPrefix(remoteToken));
token = remoteToken;
storage.writeClusterIdAndToken();
storage.reload();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/common/util/TokenMasker.java
b/fe/fe-core/src/main/java/org/apache/doris/common/util/TokenMasker.java
new file mode 100644
index 00000000000..cd6f47503c1
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/TokenMasker.java
@@ -0,0 +1,79 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.doris.common.util;
+
+import com.google.common.base.Strings;
+import com.google.common.hash.Hashing;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Helpers to render a secret (bearer token, auth token, ...) in log lines and
error messages
+ * without writing the secret itself, since logs are routinely shipped to
places that are far
+ * less protected than the credential store.
+ *
+ * <p>Two different renderings are offered, pick by what the reader of the
message needs:
+ * {@link #tokenId} when the reader only needs to correlate messages about the
same secret, and
+ * {@link #maskPrefix} when a human needs to recognize <i>which</i> configured
secret was used.
+ */
+public class TokenMasker {
+ public static final String EMPTY_TOKEN = "<empty>";
+
+ // A truncated digest: long enough that two live tokens are very unlikely
to collide,
+ // short enough that it is useless as a credential.
+ private static final int TOKEN_ID_LEN = 8;
+ private static final String TOKEN_ID_PREFIX = "sha256:";
+
+ // Minimum token length required before we reveal a masked prefix. Shorter
tokens would
+ // leak too large a fraction of the secret, so they are hidden entirely
with only a length hint.
+ private static final int MIN_TOKEN_LEN_FOR_PREFIX = 8;
+ private static final int TOKEN_PREFIX_LEN = 3;
+
+ private TokenMasker() {
+ }
+
+ /**
+ * Returns a stable, non-reversible handle for a token, e.g. {@code
sha256:1a2b3c4d}. The same
+ * token always renders to the same handle, so log lines and the error
message handed back to
+ * the client can be matched up, while no part of the secret is disclosed.
+ */
+ public static String tokenId(String token) {
+ if (Strings.isNullOrEmpty(token)) {
+ return EMPTY_TOKEN;
+ }
+ return TOKEN_ID_PREFIX + Hashing.sha256().hashString(token,
StandardCharsets.UTF_8).toString()
+ .substring(0, TOKEN_ID_LEN);
+ }
+
+ /**
+ * Masks a token by revealing only a short leading prefix (e.g. {@code
abc***}), so that a
+ * token mismatch is diagnosable during rotation, while never logging the
full secret. Empty
+ * tokens and tokens too short to safely show a prefix are hidden. Prefer
{@link #tokenId}
+ * unless the reader really needs to recognize the secret by sight.
+ */
+ public static String maskPrefix(String token) {
+ if (Strings.isNullOrEmpty(token)) {
+ return EMPTY_TOKEN;
+ }
+ if (token.length() < MIN_TOKEN_LEN_FOR_PREFIX) {
+ // Too short to reveal any prefix without leaking a large fraction
of the secret.
+ return "<hidden, token length " + token.length() + " < " +
MIN_TOKEN_LEN_FOR_PREFIX + ">";
+ }
+ return token.substring(0, TOKEN_PREFIX_LEN) + "***";
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/meta/MetaService.java
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/meta/MetaService.java
index 983d5301556..3e9cea55cdf 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/meta/MetaService.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/meta/MetaService.java
@@ -22,6 +22,7 @@ import org.apache.doris.common.Config;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.util.HttpURLUtil;
import org.apache.doris.common.util.NetUtils;
+import org.apache.doris.common.util.TokenMasker;
import org.apache.doris.ha.FrontendNodeType;
import
org.apache.doris.httpv2.controller.BaseController.ActionAuthorizationInfo;
import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
@@ -97,7 +98,7 @@ public class MetaService extends RestBaseController {
LOG.warn("reject meta request with invalid token. client: {},
{}, request from: {}, "
+ "expected: {}, actual: {}",
clientHost, clientPort, request.getRemoteAddr(),
- maskToken(clusterToken), maskToken(requestToken));
+ TokenMasker.maskPrefix(clusterToken),
TokenMasker.maskPrefix(requestToken));
throw unauthorized(clientHost, clientPort, request);
}
}
@@ -108,27 +109,6 @@ public class MetaService extends RestBaseController {
+ ", request from " + request.getRemoteAddr());
}
- // Minimum token length required before we reveal a masked prefix in logs.
Shorter tokens would
- // leak too large a fraction of the secret, so they are hidden entirely
with only a length hint.
- private static final int MIN_TOKEN_LEN_FOR_PREFIX = 8;
- private static final int TOKEN_PREFIX_LEN = 3;
-
- /**
- * Masks a token for logging: reveals only a short leading prefix (e.g.
"abc***") so that a
- * token mismatch is diagnosable during rotation, while never logging the
full secret. Empty
- * tokens and tokens too short to safely show a prefix are hidden.
- */
- private static String maskToken(String token) {
- if (Strings.isNullOrEmpty(token)) {
- return "<empty>";
- }
- if (token.length() < MIN_TOKEN_LEN_FOR_PREFIX) {
- // Too short to reveal any prefix without leaking a large fraction
of the secret.
- return "<hidden, token length " + token.length() + " < " +
MIN_TOKEN_LEN_FOR_PREFIX + ">";
- }
- return token.substring(0, TOKEN_PREFIX_LEN) + "***";
- }
-
@RequestMapping(path = "/image", method = RequestMethod.GET)
public Object image(HttpServletRequest request, HttpServletResponse
response) {
checkFromValidFe(request);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
index 08c0e8243e4..ce3faecc1ca 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
@@ -1746,8 +1746,9 @@ public class Auth implements Writable {
scramble = MysqlPassword.checkPassword(initialRootPassword);
} catch (AnalysisException e) {
// Skip set root password if `initial_root_password` is not valid
2-staged SHA-1 encrypted
- LOG.warn("initial_root_password [{}] is not valid 2-staged SHA-1
encrypted, ignore it",
- initialRootPassword);
+ // Do not echo the configured value: this branch is reached
precisely when it is not a
+ // 2-staged SHA-1 hash, which usually means a plaintext password
was configured.
+ LOG.warn("initial_root_password is not valid 2-staged SHA-1
encrypted, ignore it");
return;
}
UserIdentity rootUser = new UserIdentity(ROOT_USER, "%");
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSessionsWithTokenManager.java
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSessionsWithTokenManager.java
index 8001998a66e..02e1389111d 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSessionsWithTokenManager.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSessionsWithTokenManager.java
@@ -18,6 +18,7 @@
package org.apache.doris.service.arrowflight.sessions;
import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.util.TokenMasker;
import org.apache.doris.common.util.Util;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ConnectScheduler;
@@ -59,8 +60,9 @@ public class FlightSessionsWithTokenManager implements
FlightSessionsManager {
final FlightTokenDetails flightTokenDetails =
flightTokenManager.validateToken(peerIdentity);
if (flightTokenDetails.getCreatedSession()) {
flightTokenManager.invalidateToken(peerIdentity);
- throw new IllegalArgumentException("UserSession expire after
access, try reconnect, bearer token: "
- + peerIdentity + ", a peerIdentity(bearer token) can only
create a ConnectContext once. "
+ throw new IllegalArgumentException("UserSession expire after
access, try reconnect, bearer token id: "
+ + TokenMasker.tokenId(peerIdentity)
+ + ", a peerIdentity(bearer token) can only create a
ConnectContext once. "
+ "if ConnectContext is deleted without operation for a
long time, it needs to be reconnected "
+ "(at the same time obtain a new bearer token).");
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSqlConnectPoolMgr.java
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSqlConnectPoolMgr.java
index 04982a7fedc..c8854507e00 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSqlConnectPoolMgr.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/sessions/FlightSqlConnectPoolMgr.java
@@ -17,6 +17,7 @@
package org.apache.doris.service.arrowflight.sessions;
+import org.apache.doris.common.util.TokenMasker;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ConnectContext.ConnectType;
import org.apache.doris.qe.ConnectPoolMgr;
@@ -67,8 +68,10 @@ public class FlightSqlConnectPoolMgr extends ConnectPoolMgr {
// RootAllocator.close() marks the allocator closed before it
reports outstanding
// bytes. The error is actionable, but session teardown must
still release the
// coordinator, transaction and pool/token bookkeeping below.
+ // For an Arrow Flight SQL connection the peer identity IS the
bearer token, so it is
+ // logged as a masked id, the same one FlightTokenManagerImpl
uses.
LOG.warn("failed to close Flight SQL channel while
unregistering connection {}, peer identity {}",
- ctx.getConnectionId(), ctx.getPeerIdentity(), t);
+ ctx.getConnectionId(),
TokenMasker.tokenId(ctx.getPeerIdentity()), t);
}
}
// Finalize any Arrow Flight query whose coordinator was kept alive
across the
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/tokens/FlightTokenManagerImpl.java
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/tokens/FlightTokenManagerImpl.java
index 85d1a0bce35..a3c76da9f05 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/tokens/FlightTokenManagerImpl.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/tokens/FlightTokenManagerImpl.java
@@ -21,6 +21,7 @@ package org.apache.doris.service.arrowflight.tokens;
import org.apache.doris.catalog.Env;
import org.apache.doris.common.CustomThreadFactory;
+import org.apache.doris.common.util.TokenMasker;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.service.ExecuteEnv;
import org.apache.doris.service.arrowflight.auth2.FlightAuthResult;
@@ -79,12 +80,13 @@ public class FlightTokenManagerImpl implements
FlightTokenManager {
if (context != null) {
ExecuteEnv.getInstance().getScheduler().getFlightSqlConnectPoolMgr()
.unregisterConnection(context);
- LOG.info("evict bearer token: " + token + " from
tokenCache, reason: "
- + notification.getCause()
+ LOG.info("evict bearer token: " +
TokenMasker.tokenId(token) + " from tokenCache, "
+ + "reason: " + notification.getCause()
+ ", and unregister flight connection
context after evict bearer token");
} else {
- LOG.info("evict bearer token: " + token + " from
tokenCache, reason: "
- + notification.getCause() + ", and flight
connection context not exist");
+ LOG.info("evict bearer token: " +
TokenMasker.tokenId(token) + " from tokenCache, "
+ + "reason: " + notification.getCause()
+ + ", and flight connection context not
exist");
}
usersTokenLRU.get(tokenDetails.getUsername()).invalidate(token);
}
@@ -128,7 +130,7 @@ public class FlightTokenManagerImpl implements
FlightTokenManager {
public void onRemoval(@NotNull
RemovalNotification<String, Integer> notification) {
// TODO: broadcast this message to other FE
assert notification.getKey() != null;
- LOG.info("evict bearer token: " +
notification.getKey()
+ LOG.info("evict bearer token: " +
TokenMasker.tokenId(notification.getKey())
+ " from usersTokenLRU, reason: "
+ notification.getCause());
tokenCache.invalidate(notification.getKey());
}
@@ -141,7 +143,9 @@ public class FlightTokenManagerImpl implements
FlightTokenManager {
}));
}
usersTokenLRU.get(username).put(token, 1);
- LOG.info("Created flight token for user: {}, token: {}", username,
token);
+ // Never log the token itself: fe.log is routinely shipped off the FE
host, and the token is
+ // accepted as a full credential until it expires. The id is enough to
trace its lifecycle.
+ LOG.info("Created flight token for user: {}, token id: {}", username,
TokenMasker.tokenId(token));
return flightTokenDetails;
}
@@ -149,25 +153,28 @@ public class FlightTokenManagerImpl implements
FlightTokenManager {
public FlightTokenDetails validateToken(final String token) throws
IllegalArgumentException {
final FlightTokenDetails value = getTokenDetails(token);
if (value.getToken().equals("")) {
- throw new IllegalArgumentException("invalid bearer token: " + token
+ throw new IllegalArgumentException("invalid bearer token, token
id: " + TokenMasker.tokenId(token)
+ ", try reconnect, bearer token may not be created, or
may have been evict, search for this "
- + "token in fe.log to see the evict reason. currently in
fe.conf, `arrow_flight_max_connections`="
- + this.cacheSize + ",
`arrow_flight_token_alive_time_second`=" + this.cacheExpiration);
+ + "token id in fe.log to see the evict reason. currently
in fe.conf, "
+ + "`arrow_flight_max_connections`=" + this.cacheSize
+ + ", `arrow_flight_token_alive_time_second`=" +
this.cacheExpiration);
}
if (System.currentTimeMillis() >= value.getExpiresAt()) {
tokenCache.invalidate(token);
- throw new IllegalArgumentException("bearer token expired: " +
token + ", try reconnect, "
- + "currently in fe.conf,
`arrow_flight_token_alive_time_second`=" + this.cacheExpiration);
+ throw new IllegalArgumentException("bearer token expired, token
id: " + TokenMasker.tokenId(token)
+ + ", try reconnect, currently in fe.conf,
`arrow_flight_token_alive_time_second`="
+ + this.cacheExpiration);
}
if (usersTokenLRU.containsKey(value.getUsername())) {
try {
usersTokenLRU.get(value.getUsername()).get(token);
} catch (ExecutionException ignored) {
- throw new IllegalArgumentException("usersTokenLRU not exist
bearer token: " + token);
+ throw new IllegalArgumentException(
+ "usersTokenLRU not exist bearer token, token id: " +
TokenMasker.tokenId(token));
}
} else {
- throw new IllegalArgumentException(
- "bearer token not created: " + token + ", username: " +
value.getUsername());
+ throw new IllegalArgumentException("bearer token not created,
token id: " + TokenMasker.tokenId(token)
+ + ", username: " + value.getUsername());
}
LOG.info("Validated bearer token for user: {}", value.getUsername());
return value;
@@ -175,7 +182,7 @@ public class FlightTokenManagerImpl implements
FlightTokenManager {
@Override
public void invalidateToken(final String token) {
- LOG.info("Invalidate bearer token, {}", token);
+ LOG.info("Invalidate bearer token, token id: {}",
TokenMasker.tokenId(token));
tokenCache.invalidate(token);
}
@@ -185,7 +192,8 @@ public class FlightTokenManagerImpl implements
FlightTokenManager {
try {
value = tokenCache.getUnchecked(token);
} catch (CacheLoader.InvalidCacheLoadException ignored) {
- throw new IllegalArgumentException("InvalidCacheLoadException,
invalid bearer token: " + token);
+ throw new IllegalArgumentException(
+ "InvalidCacheLoadException, invalid bearer token, token
id: " + TokenMasker.tokenId(token));
}
return value;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/common/util/TokenMaskerTest.java
b/fe/fe-core/src/test/java/org/apache/doris/common/util/TokenMaskerTest.java
new file mode 100644
index 00000000000..fbd4e924ea9
--- /dev/null
+++ b/fe/fe-core/src/test/java/org/apache/doris/common/util/TokenMaskerTest.java
@@ -0,0 +1,63 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 org.apache.doris.common.util;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TokenMaskerTest {
+
+ // Same length and alphabet as the tokens FlightTokenManagerImpl mints
(130 random bits in
+ // base 32), but deliberately low entropy and readable, so that no secret
scanner has to
+ // decide whether a random-looking 26 character literal in the tree is a
real credential.
+ private static final String TOKEN = "notarealtokennotarealtoken";
+
+ @Test
+ public void testTokenIdHidesTheSecret() {
+ String id = TokenMasker.tokenId(TOKEN);
+ Assertions.assertFalse(id.contains(TOKEN));
+ // The id is a truncated digest and nothing else, so no part of the
token can survive in it.
+ Assertions.assertTrue(id.matches("sha256:[0-9a-f]{8}"), "unexpected
token id: " + id);
+ }
+
+ @Test
+ public void testTokenIdIsStableAndDistinguishing() {
+ // Same token always renders identically, so a log line and the error
message returned to
+ // the client can be matched up.
+ Assertions.assertEquals(TokenMasker.tokenId(TOKEN),
TokenMasker.tokenId(TOKEN));
+ Assertions.assertNotEquals(TokenMasker.tokenId(TOKEN),
TokenMasker.tokenId(TOKEN + "x"));
+ Assertions.assertEquals("sha256:",
TokenMasker.tokenId(TOKEN).substring(0, 7));
+ Assertions.assertEquals(15, TokenMasker.tokenId(TOKEN).length());
+ }
+
+ @Test
+ public void testTokenIdOfEmptyToken() {
+ Assertions.assertEquals(TokenMasker.EMPTY_TOKEN,
TokenMasker.tokenId(null));
+ Assertions.assertEquals(TokenMasker.EMPTY_TOKEN,
TokenMasker.tokenId(""));
+ }
+
+ @Test
+ public void testMaskPrefix() {
+ Assertions.assertEquals("not***", TokenMasker.maskPrefix(TOKEN));
+ Assertions.assertEquals(TokenMasker.EMPTY_TOKEN,
TokenMasker.maskPrefix(null));
+ Assertions.assertEquals(TokenMasker.EMPTY_TOKEN,
TokenMasker.maskPrefix(""));
+ // Too short to show a prefix: hidden entirely, only the length is
reported.
+ Assertions.assertEquals("<hidden, token length 7 < 8>",
TokenMasker.maskPrefix("1234567"));
+ Assertions.assertEquals("123***", TokenMasker.maskPrefix("12345678"));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]