fix: use valid git credentials when multiple are provided (#1669)
This commit is contained in:
parent
77a75f072b
commit
2bf377194e
@ -4,6 +4,48 @@ const hostedGitInfo = require('hosted-git-info');
|
|||||||
const {verifyAuth} = require('./git');
|
const {verifyAuth} = require('./git');
|
||||||
const debug = require('debug')('semantic-release:get-git-auth-url');
|
const debug = require('debug')('semantic-release:get-git-auth-url');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Machinery to format a repository URL with the given credentials
|
||||||
|
*
|
||||||
|
* @param {String} protocol URL protocol (which should not be present in repositoryUrl)
|
||||||
|
* @param {String} repositoryUrl User-given repository URL
|
||||||
|
* @param {String} gitCredentials The basic auth part of the URL
|
||||||
|
*
|
||||||
|
* @return {String} The formatted Git repository URL.
|
||||||
|
*/
|
||||||
|
function formatAuthUrl(protocol, repositoryUrl, gitCredentials) {
|
||||||
|
const [match, auth, host, basePort, path] =
|
||||||
|
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<port>\d+)?:?\/?(?<path>.*)$/.exec(repositoryUrl) || [];
|
||||||
|
const {port, hostname, ...parsed} = parse(
|
||||||
|
match ? `ssh://${auth ? `${auth}@` : ''}${host}${basePort ? `:${basePort}` : ''}/${path}` : repositoryUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
return format({
|
||||||
|
...parsed,
|
||||||
|
auth: gitCredentials,
|
||||||
|
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
|
||||||
|
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify authUrl by calling git.verifyAuth, but don't throw on failure
|
||||||
|
*
|
||||||
|
* @param {Object} context semantic-release context.
|
||||||
|
* @param {String} authUrl Repository URL to verify
|
||||||
|
*
|
||||||
|
* @return {String} The authUrl as is if the connection was successfull, null otherwise
|
||||||
|
*/
|
||||||
|
async function ensureValidAuthUrl({cwd, env, branch}, authUrl) {
|
||||||
|
try {
|
||||||
|
await verifyAuth(authUrl, branch.name, {cwd, env});
|
||||||
|
return authUrl;
|
||||||
|
} catch (error) {
|
||||||
|
debug(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the the git repository URL to use to push, either:
|
* Determine the the git repository URL to use to push, either:
|
||||||
* - The `repositoryUrl` as is if allowed to push
|
* - The `repositoryUrl` as is if allowed to push
|
||||||
@ -15,7 +57,8 @@ const debug = require('debug')('semantic-release:get-git-auth-url');
|
|||||||
*
|
*
|
||||||
* @return {String} The formatted Git repository URL.
|
* @return {String} The formatted Git repository URL.
|
||||||
*/
|
*/
|
||||||
module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
|
module.exports = async (context) => {
|
||||||
|
const {cwd, env, branch} = context;
|
||||||
const GIT_TOKENS = {
|
const GIT_TOKENS = {
|
||||||
GIT_CREDENTIALS: undefined,
|
GIT_CREDENTIALS: undefined,
|
||||||
GH_TOKEN: undefined,
|
GH_TOKEN: undefined,
|
||||||
@ -30,6 +73,7 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
|
|||||||
BITBUCKET_TOKEN_BASIC_AUTH: '',
|
BITBUCKET_TOKEN_BASIC_AUTH: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let {repositoryUrl} = context.options;
|
||||||
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
|
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
|
||||||
const {protocol, ...parsed} = parse(repositoryUrl);
|
const {protocol, ...parsed} = parse(repositoryUrl);
|
||||||
|
|
||||||
@ -47,24 +91,30 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
|
|||||||
await verifyAuth(repositoryUrl, branch.name, {cwd, env});
|
await verifyAuth(repositoryUrl, branch.name, {cwd, env});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
debug('SSH key auth failed, falling back to https.');
|
debug('SSH key auth failed, falling back to https.');
|
||||||
|
const envVars = Object.keys(GIT_TOKENS).filter((envVar) => !isNil(env[envVar]));
|
||||||
|
|
||||||
const envVar = Object.keys(GIT_TOKENS).find((envVar) => !isNil(env[envVar]));
|
// Skip verification if there is no ambiguity on which env var to use for authentication
|
||||||
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
|
if (envVars.length === 1) {
|
||||||
|
const gitCredentials = `${GIT_TOKENS[envVars[0]] || ''}${env[envVars[0]]}`;
|
||||||
|
return formatAuthUrl(protocol, repositoryUrl, gitCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
if (gitCredentials) {
|
if (envVars.length > 1) {
|
||||||
// If credentials are set via environment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
|
debug(`Found ${envVars.length} credentials in environment, trying all of them`);
|
||||||
const [match, auth, host, basePort, path] =
|
|
||||||
/^(?!.+:\/\/)(?:(?<auth>.*)@)?(?<host>.*?):(?<port>\d+)?:?\/?(?<path>.*)$/.exec(repositoryUrl) || [];
|
|
||||||
const {port, hostname, ...parsed} = parse(
|
|
||||||
match ? `ssh://${auth ? `${auth}@` : ''}${host}${basePort ? `:${basePort}` : ''}/${path}` : repositoryUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
return format({
|
const candidateRepositoryUrls = [];
|
||||||
...parsed,
|
for (const envVar of envVars) {
|
||||||
auth: gitCredentials,
|
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar]}`;
|
||||||
host: `${hostname}${protocol === 'ssh:' ? '' : port ? `:${port}` : ''}`,
|
const authUrl = formatAuthUrl(protocol, repositoryUrl, gitCredentials);
|
||||||
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
|
candidateRepositoryUrls.push(ensureValidAuthUrl(context, authUrl));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const validRepositoryUrls = await Promise.all(candidateRepositoryUrls);
|
||||||
|
const chosenAuthUrlIndex = validRepositoryUrls.findIndex((url) => url !== null);
|
||||||
|
if (chosenAuthUrlIndex > -1) {
|
||||||
|
debug(`Using "${envVars[chosenAuthUrlIndex]}" to authenticate`);
|
||||||
|
return validRepositoryUrls[chosenAuthUrlIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ const {writeJson, readJson} = require('fs-extra');
|
|||||||
const execa = require('execa');
|
const execa = require('execa');
|
||||||
const {WritableStreamBuffer} = require('stream-buffers');
|
const {WritableStreamBuffer} = require('stream-buffers');
|
||||||
const delay = require('delay');
|
const delay = require('delay');
|
||||||
|
const getAuthUrl = require('../lib/get-git-auth-url');
|
||||||
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
|
const {SECRET_REPLACEMENT} = require('../lib/definitions/constants');
|
||||||
const {
|
const {
|
||||||
gitHead,
|
gitHead,
|
||||||
@ -656,3 +657,43 @@ test('Hide sensitive environment variable values from the logs', async (t) => {
|
|||||||
t.regex(stderr, new RegExp(`Error: Console token ${escapeRegExp(SECRET_REPLACEMENT)}`));
|
t.regex(stderr, new RegExp(`Error: Console token ${escapeRegExp(SECRET_REPLACEMENT)}`));
|
||||||
t.regex(stderr, new RegExp(`Throw error: Exposing ${escapeRegExp(SECRET_REPLACEMENT)}`));
|
t.regex(stderr, new RegExp(`Throw error: Exposing ${escapeRegExp(SECRET_REPLACEMENT)}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Use the valid git credentials when multiple are provided', async (t) => {
|
||||||
|
const {cwd, authUrl} = await gitbox.createRepo('test-auth');
|
||||||
|
|
||||||
|
t.is(
|
||||||
|
await getAuthUrl({
|
||||||
|
cwd,
|
||||||
|
env: {
|
||||||
|
GITHUB_TOKEN: 'dummy',
|
||||||
|
GITLAB_TOKEN: 'trash',
|
||||||
|
BB_TOKEN_BASIC_AUTH: gitbox.gitCredential,
|
||||||
|
GIT_ASKPASS: 'echo',
|
||||||
|
GIT_TERMINAL_PROMPT: 0,
|
||||||
|
},
|
||||||
|
branch: {name: 'master'},
|
||||||
|
options: {repositoryUrl: 'http://toto@localhost:2080/git/test-auth.git'},
|
||||||
|
}),
|
||||||
|
authUrl
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Use the repository URL as is if none of the given git credentials are valid', async (t) => {
|
||||||
|
const {cwd} = await gitbox.createRepo('test-invalid-auth');
|
||||||
|
const dummyUrl = 'http://toto@localhost:2080/git/test-auth.git';
|
||||||
|
|
||||||
|
t.is(
|
||||||
|
await getAuthUrl({
|
||||||
|
cwd,
|
||||||
|
env: {
|
||||||
|
GITHUB_TOKEN: 'dummy',
|
||||||
|
GITLAB_TOKEN: 'trash',
|
||||||
|
GIT_ASKPASS: 'echo',
|
||||||
|
GIT_TERMINAL_PROMPT: 0,
|
||||||
|
},
|
||||||
|
branch: {name: 'master'},
|
||||||
|
options: {repositoryUrl: dummyUrl},
|
||||||
|
}),
|
||||||
|
dummyUrl
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user