diff --git a/lib/datasource/git-refs/index.ts b/lib/datasource/git-refs/index.ts index e814bb59e5d078e2a21e57fd03e6f124c00e12f4..0a5abffa94bf18c3146621bae7316f67c3cb318e 100644 --- a/lib/datasource/git-refs/index.ts +++ b/lib/datasource/git-refs/index.ts @@ -13,9 +13,10 @@ const cacheMinutes = 10; // git will prompt for known hosts or passwords, unless we activate BatchMode process.env.GIT_SSH_COMMAND = 'ssh -o BatchMode=yes'; -export async function getRawRefs({ - lookupName, -}: GetReleasesConfig): Promise<RawRefs[] | null> { +export async function getRawRefs( + { lookupName }: GetReleasesConfig, + hostType: string +): Promise<RawRefs[] | null> { const git = simpleGit(); const cacheNamespace = 'git-raw-refs'; @@ -29,7 +30,9 @@ export async function getRawRefs({ } // fetch remote tags - const lsRemote = await git.listRemote([getRemoteUrlWithToken(lookupName)]); + const lsRemote = await git.listRemote([ + getRemoteUrlWithToken(lookupName, hostType), + ]); if (!lsRemote) { return null; } @@ -70,7 +73,7 @@ export async function getRawRefs({ export async function getReleases({ lookupName, }: GetReleasesConfig): Promise<ReleaseResult | null> { - const rawRefs: RawRefs[] = await getRawRefs({ lookupName }); + const rawRefs: RawRefs[] = await getRawRefs({ lookupName }, id); const refs = rawRefs .filter((ref) => ref.type === 'tags' || ref.type === 'heads') @@ -97,7 +100,7 @@ export async function getDigest( { lookupName }: Partial<DigestConfig>, newValue?: string ): Promise<string | null> { - const rawRefs: RawRefs[] = await getRawRefs({ lookupName }); + const rawRefs: RawRefs[] = await getRawRefs({ lookupName }, id); const findValue = newValue || 'HEAD'; const ref = rawRefs.find((rawRef) => rawRef.value === findValue); if (ref) { diff --git a/lib/datasource/git-tags/index.ts b/lib/datasource/git-tags/index.ts index b0a3956de2f7e6b06509e4ee11e4d7c931890941..36ed8dee8fd031fbdc975abb03be2c653f877308 100644 --- a/lib/datasource/git-tags/index.ts +++ b/lib/datasource/git-tags/index.ts @@ -8,7 +8,7 @@ export const customRegistrySupport = false; export async function getReleases({ lookupName, }: GetReleasesConfig): Promise<ReleaseResult | null> { - const rawRefs = await gitRefs.getRawRefs({ lookupName }); + const rawRefs = await gitRefs.getRawRefs({ lookupName }, id); if (rawRefs === null) { return null; @@ -36,7 +36,7 @@ export async function getDigest( { lookupName }: Partial<DigestConfig>, newValue?: string ): Promise<string | null> { - const rawRefs = await gitRefs.getRawRefs({ lookupName }); + const rawRefs = await gitRefs.getRawRefs({ lookupName }, id); const findValue = newValue || 'HEAD'; const ref = rawRefs.find((rawRef) => rawRef.value === findValue); if (ref) { diff --git a/lib/util/git/url.spec.ts b/lib/util/git/url.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc8dc6e8556099cc355801c8a7955ff290f53ffe --- /dev/null +++ b/lib/util/git/url.spec.ts @@ -0,0 +1,96 @@ +import { getName, hostRules } from '../../../test/util'; +import { getHttpUrl, getRemoteUrlWithToken } from './url'; + +jest.mock('../host-rules'); + +describe(getName(), () => { + describe('getHttpUrl()', () => { + it('returns https url for git url', () => { + expect(getHttpUrl('git://foo.bar/')).toBe('https://foo.bar/'); + }); + + it('returns https url for https url', () => { + expect(getHttpUrl('https://foo.bar/')).toBe('https://foo.bar/'); + }); + + it('returns http url for http url', () => { + expect(getHttpUrl('http://foo.bar/')).toBe('http://foo.bar/'); + }); + }); + + describe('getRemoteUrlWithToken()', () => { + it('returns original url if no host rule is found', () => { + expect(getRemoteUrlWithToken('https://foo.bar/')).toBe( + 'https://foo.bar/' + ); + }); + + it('returns http url with token', () => { + hostRules.find.mockReturnValueOnce({ token: 'token' }); + expect(getRemoteUrlWithToken('http://foo.bar/')).toBe( + 'http://token@foo.bar/' + ); + }); + + it('returns https url with token', () => { + hostRules.find.mockReturnValueOnce({ token: 'token' }); + expect(getRemoteUrlWithToken('https://foo.bar/')).toBe( + 'https://token@foo.bar/' + ); + }); + + it('returns https url with token for non-http protocols', () => { + hostRules.find.mockReturnValueOnce({ token: 'token' }); + expect(getRemoteUrlWithToken('ssh://foo.bar/')).toBe( + 'https://token@foo.bar/' + ); + }); + + it('returns https url with encoded token', () => { + hostRules.find.mockReturnValueOnce({ token: 't#ken' }); + expect(getRemoteUrlWithToken('https://foo.bar/')).toBe( + 'https://t%23ken@foo.bar/' + ); + }); + + it('returns http url with username and password', () => { + hostRules.find.mockReturnValueOnce({ + username: 'user', + password: 'pass', + }); + expect(getRemoteUrlWithToken('http://foo.bar/')).toBe( + 'http://user:pass@foo.bar/' + ); + }); + + it('returns https url with username and password', () => { + hostRules.find.mockReturnValueOnce({ + username: 'user', + password: 'pass', + }); + expect(getRemoteUrlWithToken('https://foo.bar/')).toBe( + 'https://user:pass@foo.bar/' + ); + }); + + it('returns https url with username and password for non-http protocols', () => { + hostRules.find.mockReturnValueOnce({ + username: 'user', + password: 'pass', + }); + expect(getRemoteUrlWithToken('ssh://foo.bar/')).toBe( + 'https://user:pass@foo.bar/' + ); + }); + + it('returns https url with encoded username and password', () => { + hostRules.find.mockReturnValueOnce({ + username: 'u$er', + password: 'p@ss', + }); + expect(getRemoteUrlWithToken('https://foo.bar/')).toBe( + 'https://u%24er:p%40ss@foo.bar/' + ); + }); + }); +}); diff --git a/lib/util/git/url.ts b/lib/util/git/url.ts index fb671a3441cb09209d6432fcadd94511bf9813ec..881de8722b3c6fc1aec98a485e6e58a96c83f02e 100644 --- a/lib/util/git/url.ts +++ b/lib/util/git/url.ts @@ -4,18 +4,31 @@ import * as hostRules from '../host-rules'; export function getHttpUrl(url: string, token?: string): string { const parsedUrl = GitUrlParse(url); + parsedUrl.token = token; - return parsedUrl.toString('https'); + + const protocol = /^https?$/.exec(parsedUrl.protocol) + ? parsedUrl.protocol + : 'https'; + return parsedUrl.toString(protocol); } -export function getRemoteUrlWithToken(url: string): string { - let remote = url; +export function getRemoteUrlWithToken(url: string, hostType?: string): string { + const hostRule = hostRules.find({ url, hostType }); - const hostRule = hostRules.find({ url }); if (hostRule?.token) { logger.debug(`Found hostRules token for url ${url}`); - remote = getHttpUrl(url, hostRule.token); + + return getHttpUrl(url, encodeURIComponent(hostRule.token)); + } + + if (hostRule?.username && hostRule?.password) { + logger.debug(`Found hostRules username and password for url ${url}`); + const encodedUsername = encodeURIComponent(hostRule.username); + const encodedPassword = encodeURIComponent(hostRule.password); + + return getHttpUrl(url, `${encodedUsername}:${encodedPassword}`); } - return remote; + return url; }