This is an automated email from the ASF dual-hosted git repository.
dspavlov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git
The following commit(s) were added to refs/heads/master by this push:
new d80ba656 IGNITE-28679: [TCBot] Support JIRA tokens instead of basic
auth (#230)
d80ba656 is described below
commit d80ba65619b2c45b6f708965b644bc4c49874671
Author: ignitetcbot <[email protected]>
AuthorDate: Fri May 15 10:21:22 2026 +0300
IGNITE-28679: [TCBot] Support JIRA tokens instead of basic auth (#230)
Codex co-authored-by: Dmitriy Pavlov <[email protected]>
---
README.md | 7 +-
conf/branches.json | 22 ++-
.../tcbot/common/conf/IJiraServerConfig.java | 12 +-
.../ignite/tcbot/common/conf/PasswordEncoder.java | 45 +++++
.../apache/ignite/tcbot/common/util/HttpUtil.java | 15 +-
.../ignite/tcbot/engine/conf/GitHubConfig.java | 54 +++++-
.../ignite/tcbot/engine/conf/JiraServerConfig.java | 127 ++++++++++++-
.../tcbot/engine/conf/AuthTokenConfigTest.java | 201 +++++++++++++++++++++
tcbot-github/README.md | 6 +-
.../src/integrationTest/resources/branches.json | 2 +-
tcbot-jira/README.md | 8 +-
.../java/org/apache/ignite/jiraservice/Jira.java | 4 +-
12 files changed, 468 insertions(+), 35 deletions(-)
diff --git a/README.md b/README.md
index a9890fd4..b5856bf9 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,12 @@ Main config file is
[conf/branches.json](conf/branches.json). This file needs to
The running bot reloads `branches.json` lazily: configuration reads are cached
for up to 3 minutes, so most changes
become visible without a restart after the cache expires. Restart the bot only
when you need the change to take effect
immediately.
-Extra setup is required using security-sensitive information using
PasswordEncoder. No TeamCity credentials are required because TC bot asks users
to enter creds.
+JIRA and GitHub tokens can be specified as plain text in `branches.json` or
protected with `PasswordEncoder`.
+When `authTokEncoded` is not set, the bot auto-detects encoded hex values and
otherwise treats tokens as plain.
+Set `authTokEncoded` only when you need to force a mode. For JIRA Personal
Access Tokens, use
+`authScheme: "Bearer"`; legacy base64 username/password tokens can still use
`authScheme: "Basic"`. If
+JIRA `authScheme` is omitted, encoded tokens default to Basic for
compatibility and plain tokens default to Bearer.
+No TeamCity credentials are required because TC bot asks users to enter creds.
Minimal local run checklist:
* Import the Gradle project into IntelliJ IDEA.
diff --git a/conf/branches.json b/conf/branches.json
index efbc5da4..f9f8d1cf 100644
--- a/conf/branches.json
+++ b/conf/branches.json
@@ -66,12 +66,17 @@
"projectCode": "IGNITE",
/* Following prefix is to be specified only if it is necessary to
separate project code and branches markup in tickets. */
// "branchNumPrefix": "IGNITE-",
- /* JIRA Url, HTTPs is highly recommended because of Basic Auth used. */
+ /* JIRA Url, HTTPs is highly recommended. */
"url": "https://issues.apache.org/jira/",
- /** JIRA Auth token to access,
- Base-64 pre-encoded username and password, which will be included into
Basic Auth requests.
- Encoded token needs to be encrypted using
org.apache.ignite.ci.tcbot.conf.PasswordEncoder,
- use PasswordEncoder#encodeJiraTok to get from clear username/password
*/
+ /** JIRA authorization scheme. Use Bearer for personal access tokens, or
Basic for
+ base64 pre-encoded username/password tokens. If omitted, plain tokens
default to Bearer
+ and PasswordEncoder-protected tokens default to Basic for backward
compatibility. */
+ "authScheme": "Bearer",
+ /** JIRA Auth token to access. Personal Access Token can be specified as
plain text by default.
+ PasswordEncoder-protected hex values are auto-detected. Set
authTokEncoded to true or false
+ only to force a mode. For Basic auth, use
PasswordEncoder#userPwdToToken to get token value
+ from clear username/password. */
+ // "authTokEncoded": false,
"authTok": ""
//todo ^ specify token
}
@@ -88,10 +93,11 @@
/*
Specify GitHub Auth token (if needed), go to
User->Settings->Developers options->create new.
- Created token needs to be encrypted using
org.apache.ignite.ci.tcbot.conf.PasswordEncoder,
- use {@link org.apache.ignite.ci.conf.PasswordEncoder#encode} to set up
value in a config.
- Git Auth token encoded to access non-public GitHub repos. For public
GitHub repos token gives more velocity.
+ Created token can be specified as plain text.
PasswordEncoder-protected hex values are
+ auto-detected. Set authTokEncoded to true or false only to force a
mode.
+ Git Auth token is used to access non-public GitHub repos. For public
GitHub repos token gives more velocity.
*/
+ // "authTokEncoded": false,
"authTok": "",
//todo ^ specify token
diff --git
a/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/IJiraServerConfig.java
b/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/IJiraServerConfig.java
index bf1fd298..4c3a9177 100644
---
a/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/IJiraServerConfig.java
+++
b/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/IJiraServerConfig.java
@@ -60,11 +60,21 @@ public interface IJiraServerConfig {
*/
@Nullable String decodedHttpAuthToken();
+ /**
+ * @return Null or full HTTP Authorization header value for JIRA.
+ */
+ @Nullable
+ default String httpAuthorizationHeader() {
+ String tok = decodedHttpAuthToken();
+
+ return Strings.isNullOrEmpty(tok) ? null : "Basic " + tok;
+ }
+
/**
* @return {@code True} if JIRA authorization token is available.
*/
default boolean isJiraTokenAvailable() {
- return !Strings.isNullOrEmpty(decodedHttpAuthToken());
+ return !Strings.isNullOrEmpty(httpAuthorizationHeader());
}
default String restApiUrl() {
diff --git
a/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/PasswordEncoder.java
b/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/PasswordEncoder.java
index 092ccf5b..d03e53e3 100644
---
a/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/PasswordEncoder.java
+++
b/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/conf/PasswordEncoder.java
@@ -47,6 +47,51 @@ public class PasswordEncoder {
return new String(parseHexBinary(p), CryptUtil.CHARSET);
}
+ public static String decodeIfEncoded(String tok) {
+ if (!mayBeEncoded(tok))
+ return tok;
+
+ try {
+ return decode(tok);
+ }
+ catch (RuntimeException ignored) {
+ return tok;
+ }
+ }
+
+ public static boolean isEncoded(String tok) {
+ if (!mayBeEncoded(tok))
+ return false;
+
+ try {
+ decode(tok);
+
+ return true;
+ }
+ catch (RuntimeException ignored) {
+ return false;
+ }
+ }
+
+ private static boolean mayBeEncoded(String tok) {
+ if (Strings.isNullOrEmpty(tok))
+ return false;
+
+ String trimmed = tok.trim();
+
+ if (trimmed.length() % 2 != 0)
+ return false;
+
+ for (int i = 0; i < trimmed.length(); i++) {
+ char ch = trimmed.charAt(i);
+
+ if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch
>= 'A' && ch <= 'F')))
+ return false;
+ }
+
+ return true;
+ }
+
public static String encode(String pass) {
byte[] bytes = pass.getBytes(CryptUtil.CHARSET);
SecureRandom random = new SecureRandom();
diff --git
a/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/util/HttpUtil.java
b/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/util/HttpUtil.java
index 7261c59c..b62b4c17 100644
---
a/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/util/HttpUtil.java
+++
b/tcbot-common/src/main/java/org/apache/ignite/tcbot/common/util/HttpUtil.java
@@ -396,13 +396,14 @@ public class HttpUtil {
/**
* Send POST request to the JIRA url.
*
- * @param jiraAuthTok Authorization Base64 token.
+ * @param jiraAuthHeader Authorization header value.
* @param url URL.
* @param body Request POST params.
* @return Response body from given url.
* @throws IOException If failed.
*/
- public static String sendPostAsStringToJira(String jiraAuthTok, String
url, String body) throws IOException {
+ public static String sendPostAsStringToJira(@Nullable String
jiraAuthHeader, String url, String body)
+ throws IOException {
URL obj = new URL(url);
ensureIntegrationTestTarget(obj);
HttpURLConnection con = (HttpURLConnection)obj.openConnection();
@@ -411,7 +412,8 @@ public class HttpUtil {
Charset charset = StandardCharsets.UTF_8;
con.setRequestProperty("accept-charset", charset.toString());
- con.setRequestProperty("Authorization", "Basic " + jiraAuthTok);
+ if (jiraAuthHeader != null)
+ con.setRequestProperty("Authorization", jiraAuthHeader);
con.setRequestProperty("content-type", "application/json");
con.setInstanceFollowRedirects(false);
useKeepAlive(con);
@@ -434,10 +436,10 @@ public class HttpUtil {
/**
* Send GET request to the JIRA url.
*
- * @param jiraAuthTok Jira auth token.
+ * @param jiraAuthHeader Jira authorization header value.
* @param url Url.
*/
- public static String sendGetToJira(String jiraAuthTok, String url) throws
IOException {
+ public static String sendGetToJira(@Nullable String jiraAuthHeader, String
url) throws IOException {
Stopwatch started = Stopwatch.createStarted();
URL obj = new URL(url);
ensureIntegrationTestTarget(obj);
@@ -447,7 +449,8 @@ public class HttpUtil {
Charset charset = StandardCharsets.UTF_8;
con.setRequestProperty("accept-charset", charset.toString());
- con.setRequestProperty("Authorization", "Basic " + jiraAuthTok);
+ if (jiraAuthHeader != null)
+ con.setRequestProperty("Authorization", jiraAuthHeader);
con.setRequestProperty("content-type", "application/json");
con.setInstanceFollowRedirects(false);
useKeepAlive(con);
diff --git
a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/GitHubConfig.java
b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/GitHubConfig.java
index dae7647a..016ccfcb 100644
---
a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/GitHubConfig.java
+++
b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/GitHubConfig.java
@@ -33,6 +33,9 @@ public class GitHubConfig implements IGitHubConfig {
/** GitHub authorization token property name. */
public static final String GITHUB_AUTH_TOKEN = "github.auth_token";
+ /** GitHub authorization token PasswordEncoder flag property name. */
+ public static final String GITHUB_AUTH_TOKEN_ENCODED =
"github.auth_token.encoded";
+
/** Git branch naming prefix for PRLess contributions. */
public static final String GIT_BRANCH_PREFIX = "git.branch_prefix";
@@ -49,11 +52,17 @@ public class GitHubConfig implements IGitHubConfig {
private String branchPrefix;
/**
- * Git Auth token encoded to access non-public git repos, use {@link
PasswordEncoder#encodeJiraTok(String,
- * String)} to set up value in a config.
+ * Git Auth token to access non-public git repos. Plain and {@link
PasswordEncoder}-encoded tokens are
+ * auto-detected unless {@link #authTokEncoded} is set explicitly.
*/
private String authTok;
+ /**
+ * {@code True} if {@link #authTok} is encoded with {@link
PasswordEncoder}, {@code false} if it is plain, or
+ * {@code null} to auto-detect.
+ */
+ private Boolean authTokEncoded;
+
private Properties props;
/**
@@ -102,9 +111,17 @@ public class GitHubConfig implements IGitHubConfig {
/** {@inheritDoc} */
@Nullable
@Override public String gitAuthTok() {
- String encAuth = gitAuthTokenEncoded();
+ String authTok = gitAuthTokenConfigured();
+
+ if (isNullOrEmpty(authTok))
+ return null;
+
+ Boolean encoded = isAuthTokenEncoded();
- return isNullOrEmpty(encAuth) ? null : PasswordEncoder.decode(encAuth);
+ if (encoded == null)
+ return PasswordEncoder.decodeIfEncoded(authTok);
+
+ return encoded ? PasswordEncoder.decode(authTok) : authTok;
}
/** {@inheritDoc} */
@@ -145,7 +162,7 @@ public class GitHubConfig implements IGitHubConfig {
*
*/
@Nullable
- public String gitAuthTokenEncoded() {
+ public String gitAuthTokenConfigured() {
if (!Strings.isNullOrEmpty(authTok))
return authTok;
@@ -153,4 +170,31 @@ public class GitHubConfig implements IGitHubConfig {
? props.getProperty(GITHUB_AUTH_TOKEN)
: null;
}
+
+ /**
+ * @return Configured Github auth token.
+ */
+ @Deprecated
+ @Nullable
+ public String gitAuthTokenEncoded() {
+ return gitAuthTokenConfigured();
+ }
+
+ /**
+ * @return {@code True} if configured auth token is encoded with {@link
PasswordEncoder}, {@code false} if it is
+ * plain, or {@code null} if it should be auto-detected.
+ */
+ @Nullable
+ private Boolean isAuthTokenEncoded() {
+ if (authTokEncoded != null)
+ return authTokEncoded;
+
+ if (props != null && Strings.isNullOrEmpty(authTok)) {
+ String encoded = props.getProperty(GITHUB_AUTH_TOKEN_ENCODED);
+
+ return Strings.isNullOrEmpty(encoded) ? null :
Boolean.parseBoolean(encoded);
+ }
+
+ return null;
+ }
}
diff --git
a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/JiraServerConfig.java
b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/JiraServerConfig.java
index 72d94e32..c37affbc 100644
---
a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/JiraServerConfig.java
+++
b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/JiraServerConfig.java
@@ -33,6 +33,17 @@ public class JiraServerConfig implements IJiraServerConfig {
/** JIRA authorization token property name. */
public static final String JIRA_AUTH_TOKEN = "jira.auth_token";
+ /** JIRA authorization token PasswordEncoder flag property name. */
+ public static final String JIRA_AUTH_TOKEN_ENCODED =
"jira.auth_token.encoded";
+
+ /** JIRA authorization scheme property name. */
+ public static final String JIRA_AUTH_SCHEME = "jira.auth_scheme";
+
+ /** JIRA Basic authorization scheme. */
+ public static final String JIRA_AUTH_SCHEME_BASIC = "Basic";
+
+ /** JIRA Bearer authorization scheme. */
+ public static final String JIRA_AUTH_SCHEME_BEARER = "Bearer";
/** JIRA URL to build links to tickets. */
public static final String JIRA_URL = "jira.url";
@@ -63,11 +74,23 @@ public class JiraServerConfig implements IJiraServerConfig {
private Properties props;
/**
- * JIRA Auth token encoded to access JIRA, use {@link
PasswordEncoder#encodeJiraTok(String,
- * String)} to set up value in a config.
+ * JIRA Auth token to access JIRA. Plain and {@link
PasswordEncoder}-encoded tokens are auto-detected unless
+ * {@link #authTokEncoded} is set explicitly.
*/
private String authTok;
+ /**
+ * {@code True} if {@link #authTok} is encoded with {@link
PasswordEncoder}, {@code false} if it is plain, or
+ * {@code null} to auto-detect.
+ */
+ private Boolean authTokEncoded;
+
+ /**
+ * HTTP Authorization scheme. Use {@code Bearer} for JIRA personal access
tokens and {@code Basic} for legacy
+ * base64 username/password tokens. Encoded tokens default to Basic when
the scheme is not set.
+ */
+ private String authScheme;
+
/**
* JIRA Server URL. HTTPs is highly recommended.
*/
@@ -143,16 +166,102 @@ public class JiraServerConfig implements
IJiraServerConfig {
@Nullable
@Override
public String decodedHttpAuthToken() {
- String tok;
-
- if (Strings.isNullOrEmpty(authTok) && props != null)
- tok = props.getProperty(JIRA_AUTH_TOKEN);
- else
- tok = authTok;
+ String tok = authTokenConfigured();
if (isNullOrEmpty(tok))
return null;
- return PasswordEncoder.decode(tok);
+ Boolean encoded = isAuthTokenEncoded();
+
+ if (encoded == null)
+ return PasswordEncoder.decodeIfEncoded(tok);
+
+ return encoded ? PasswordEncoder.decode(tok) : tok;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable
+ @Override
+ public String httpAuthorizationHeader() {
+ String tok = decodedHttpAuthToken();
+
+ return isNullOrEmpty(tok) ? null : authScheme() + " " + tok;
+ }
+
+ /**
+ * @return Configured auth token.
+ */
+ @Nullable
+ private String authTokenConfigured() {
+ if (!Strings.isNullOrEmpty(authTok))
+ return authTok;
+
+ return props != null ? props.getProperty(JIRA_AUTH_TOKEN) : null;
+ }
+
+ /**
+ * @return {@code True} if configured auth token is encoded with {@link
PasswordEncoder}, {@code false} if it is
+ * plain, or {@code null} if it should be auto-detected.
+ */
+ @Nullable
+ private Boolean isAuthTokenEncoded() {
+ if (authTokEncoded != null)
+ return authTokEncoded;
+
+ if (props != null && Strings.isNullOrEmpty(authTok)) {
+ String encoded = props.getProperty(JIRA_AUTH_TOKEN_ENCODED);
+
+ return Strings.isNullOrEmpty(encoded) ? null :
Boolean.parseBoolean(encoded);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return HTTP Authorization scheme.
+ */
+ private String authScheme() {
+ String scheme = authSchemeConfigured();
+
+ if (Strings.isNullOrEmpty(scheme))
+ scheme = defaultAuthScheme();
+
+ if (JIRA_AUTH_SCHEME_BASIC.equalsIgnoreCase(scheme))
+ return JIRA_AUTH_SCHEME_BASIC;
+
+ if (JIRA_AUTH_SCHEME_BEARER.equalsIgnoreCase(scheme))
+ return JIRA_AUTH_SCHEME_BEARER;
+
+ throw new IllegalStateException("Unsupported JIRA auth scheme: " +
scheme);
+ }
+
+ /**
+ * @return Configured HTTP Authorization scheme.
+ */
+ @Nullable
+ private String authSchemeConfigured() {
+ if (!Strings.isNullOrEmpty(authScheme))
+ return authScheme;
+
+ return props != null && Strings.isNullOrEmpty(authTok) ?
props.getProperty(JIRA_AUTH_SCHEME) : null;
+ }
+
+ /**
+ * @return Default HTTP Authorization scheme.
+ */
+ private String defaultAuthScheme() {
+ if (props != null && Strings.isNullOrEmpty(authTok))
+ return JIRA_AUTH_SCHEME_BASIC;
+
+ return isAuthTokenEncodedOrAutoDetected() ? JIRA_AUTH_SCHEME_BASIC :
JIRA_AUTH_SCHEME_BEARER;
+ }
+
+ /**
+ * @return {@code True} if configured token is known or detected as
encoded.
+ */
+ private boolean isAuthTokenEncodedOrAutoDetected() {
+ Boolean encoded = isAuthTokenEncoded();
+
+ return encoded != null ? encoded :
PasswordEncoder.isEncoded(authTokenConfigured());
}
}
diff --git
a/tcbot-engine/src/test/java/org/apache/ignite/tcbot/engine/conf/AuthTokenConfigTest.java
b/tcbot-engine/src/test/java/org/apache/ignite/tcbot/engine/conf/AuthTokenConfigTest.java
new file mode 100644
index 00000000..51cc689a
--- /dev/null
+++
b/tcbot-engine/src/test/java/org/apache/ignite/tcbot/engine/conf/AuthTokenConfigTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.ignite.tcbot.engine.conf;
+
+import java.lang.reflect.Field;
+import java.util.Properties;
+import org.apache.ignite.tcbot.common.conf.PasswordEncoder;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Checks auth token configuration compatibility.
+ */
+public class AuthTokenConfigTest {
+ /** */
+ @Test
+ public void jiraJsonTokenIsPlainBearerByDefault() throws Exception {
+ JiraServerConfig cfg = withField(new JiraServerConfig(), "authTok",
"jira-pat");
+
+ assertEquals("jira-pat", cfg.decodedHttpAuthToken());
+ assertEquals("Bearer jira-pat", cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraJsonTokenCanBeEncodedBasic() throws Exception {
+ String basicTok = PasswordEncoder.userPwdToToken("user", "password");
+ JiraServerConfig cfg = withField(new JiraServerConfig(), "authTok",
PasswordEncoder.encode(basicTok));
+
+ withField(cfg, "authTokEncoded", true);
+ withField(cfg, "authScheme", "Basic");
+
+ assertEquals(basicTok, cfg.decodedHttpAuthToken());
+ assertEquals("Basic " + basicTok, cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraJsonTokenCanBeAutoDetectedAsEncoded() throws Exception {
+ String basicTok = PasswordEncoder.userPwdToToken("user", "password");
+ JiraServerConfig cfg = withField(new JiraServerConfig(), "authTok",
PasswordEncoder.encode(basicTok));
+
+ assertEquals(basicTok, cfg.decodedHttpAuthToken());
+ assertEquals("Basic " + basicTok, cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraJsonTokenExplicitlyEncodedDefaultsToBasic() throws
Exception {
+ String basicTok = PasswordEncoder.userPwdToToken("user", "password");
+ JiraServerConfig cfg = withField(new JiraServerConfig(), "authTok",
PasswordEncoder.encode(basicTok));
+
+ withField(cfg, "authTokEncoded", true);
+
+ assertEquals(basicTok, cfg.decodedHttpAuthToken());
+ assertEquals("Basic " + basicTok, cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraLegacyPropertyTokenIsEncodedBasicByDefault() {
+ String basicTok = PasswordEncoder.userPwdToToken("user", "password");
+ Properties props = new Properties();
+
+ props.setProperty(JiraServerConfig.JIRA_AUTH_TOKEN,
PasswordEncoder.encode(basicTok));
+
+ JiraServerConfig cfg = new JiraServerConfig("apache", props);
+
+ assertEquals(basicTok, cfg.decodedHttpAuthToken());
+ assertEquals("Basic " + basicTok, cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraLegacyPropertyTokenCanBePlainBearer() {
+ Properties props = new Properties();
+
+ props.setProperty(JiraServerConfig.JIRA_AUTH_TOKEN, "jira-pat");
+ props.setProperty(JiraServerConfig.JIRA_AUTH_SCHEME, "Bearer");
+
+ JiraServerConfig cfg = new JiraServerConfig("apache", props);
+
+ assertEquals("jira-pat", cfg.decodedHttpAuthToken());
+ assertEquals("Bearer jira-pat", cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraHexLikePlainPropertyTokenStaysPlainWhenDecodeFails() {
+ Properties props = new Properties();
+
+ props.setProperty(JiraServerConfig.JIRA_AUTH_TOKEN, "abcdef1234");
+ props.setProperty(JiraServerConfig.JIRA_AUTH_SCHEME, "Bearer");
+
+ JiraServerConfig cfg = new JiraServerConfig("apache", props);
+
+ assertEquals("abcdef1234", cfg.decodedHttpAuthToken());
+ assertEquals("Bearer abcdef1234", cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void jiraMissingTokenHasNoAuthorizationHeader() {
+ JiraServerConfig cfg = new JiraServerConfig();
+
+ assertNull(cfg.decodedHttpAuthToken());
+ assertNull(cfg.httpAuthorizationHeader());
+ }
+
+ /** */
+ @Test
+ public void githubJsonTokenIsPlainByDefault() throws Exception {
+ GitHubConfig cfg = withField(new GitHubConfig(), "authTok",
"github-pat");
+
+ assertEquals("github-pat", cfg.gitAuthTok());
+ }
+
+ /** */
+ @Test
+ public void githubJsonTokenCanBeEncoded() throws Exception {
+ GitHubConfig cfg = withField(new GitHubConfig(), "authTok",
PasswordEncoder.encode("github-pat"));
+
+ withField(cfg, "authTokEncoded", true);
+
+ assertEquals("github-pat", cfg.gitAuthTok());
+ }
+
+ /** */
+ @Test
+ public void githubJsonTokenCanBeAutoDetectedAsEncoded() throws Exception {
+ GitHubConfig cfg = withField(new GitHubConfig(), "authTok",
PasswordEncoder.encode("github-pat"));
+
+ assertEquals("github-pat", cfg.gitAuthTok());
+ }
+
+ /** */
+ @Test
+ public void githubLegacyPropertyTokenIsEncodedByDefault() {
+ Properties props = new Properties();
+
+ props.setProperty(GitHubConfig.GITHUB_AUTH_TOKEN,
PasswordEncoder.encode("github-pat"));
+
+ GitHubConfig cfg = new GitHubConfig().properties(props);
+
+ assertEquals("github-pat", cfg.gitAuthTok());
+ }
+
+ /** */
+ @Test
+ public void githubLegacyPropertyTokenCanBePlain() {
+ Properties props = new Properties();
+
+ props.setProperty(GitHubConfig.GITHUB_AUTH_TOKEN, "github-pat");
+
+ GitHubConfig cfg = new GitHubConfig().properties(props);
+
+ assertEquals("github-pat", cfg.gitAuthTok());
+ }
+
+ /** */
+ @Test
+ public void githubHexLikePlainPropertyTokenStaysPlainWhenDecodeFails() {
+ Properties props = new Properties();
+
+ props.setProperty(GitHubConfig.GITHUB_AUTH_TOKEN, "abcdef1234");
+
+ GitHubConfig cfg = new GitHubConfig().properties(props);
+
+ assertEquals("abcdef1234", cfg.gitAuthTok());
+ }
+
+ /**
+ * @param target Target object.
+ * @param fieldName Field name.
+ * @param val Field value.
+ */
+ private static <T> T withField(T target, String fieldName, Object val)
throws Exception {
+ Field field = target.getClass().getDeclaredField(fieldName);
+
+ field.setAccessible(true);
+ field.set(target, val);
+
+ return target;
+ }
+}
diff --git a/tcbot-github/README.md b/tcbot-github/README.md
index c1322e46..da65d8d3 100644
--- a/tcbot-github/README.md
+++ b/tcbot-github/README.md
@@ -1,3 +1,7 @@
TC Bot GitHub (service) module
--------------------------------
-Module for pure (non cached) requests to GitHub service
\ No newline at end of file
+Module for pure (non cached) requests to GitHub service
+
+GitHub authentication is configured in `branches.json` under `gitHubConfigs`.
+Personal access tokens can be stored directly in `authTok`.
+PasswordEncoder-protected hex values are auto-detected; set `authTokEncoded`
only to force a mode.
diff --git
a/tcbot-integration-tests/src/integrationTest/resources/branches.json
b/tcbot-integration-tests/src/integrationTest/resources/branches.json
index 93ea9aaf..96370349 100644
--- a/tcbot-integration-tests/src/integrationTest/resources/branches.json
+++ b/tcbot-integration-tests/src/integrationTest/resources/branches.json
@@ -20,7 +20,7 @@
"code": "apache",
"projectCode": "IGNITE",
"url": "http://127.0.0.1:8012/",
- "authTok":
"745AA699283F105E69FD8CDE51DDA66229D7D21E890C80004BBDABA4184C3CE09F5BF48E1924286A77B33B905EE84EA559ED65D22C3D0538C9B76580B8016D653154A66E880292923461E2846FFB3D10"
+ "authTok": "jira-test-token"
}
],
"gitHubConfigs": [
diff --git a/tcbot-jira/README.md b/tcbot-jira/README.md
index 241f9706..92eafdac 100644
--- a/tcbot-jira/README.md
+++ b/tcbot-jira/README.md
@@ -1,3 +1,9 @@
TC Bot JIRA (service) module
--------------------------------
-Module for pure (non cached) requests to JIRA service
\ No newline at end of file
+Module for pure (non cached) requests to JIRA service
+
+JIRA authentication is configured in `branches.json` under `jiraServers`.
+Personal Access Tokens use `authScheme: "Bearer"` and can be stored directly
in `authTok`.
+PasswordEncoder-protected hex values are auto-detected; set `authTokEncoded`
only to force a mode.
+Legacy Basic auth remains available with `authScheme: "Basic"` and a base64
`user:password` token.
+If `authScheme` is omitted, encoded tokens default to Basic for compatibility
and plain tokens default to Bearer.
diff --git a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
index 33f4f068..455fc65e 100644
--- a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
+++ b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
@@ -106,7 +106,7 @@ class Jira implements IJiraIntegration {
String url = jiraApiUrl + "issue/" + ticket + "/comment";
- return
HttpUtil.sendPostAsStringToJira(config().decodedHttpAuthToken(), url,
"{\"body\": " + comment + "}");
+ return
HttpUtil.sendPostAsStringToJira(config().httpAuthorizationHeader(), url,
"{\"body\": " + comment + "}");
}
/** {@inheritDoc} */
@@ -120,6 +120,6 @@ class Jira implements IJiraIntegration {
* @return Response as gson string.
*/
public String sendGetToJira(String url) throws IOException {
- return HttpUtil.sendGetToJira(config().decodedHttpAuthToken(),
config().restApiUrl() + url);
+ return HttpUtil.sendGetToJira(config().httpAuthorizationHeader(),
config().restApiUrl() + url);
}
}