diff --git a/lib/modules/manager/git-submodules/extract.spec.ts b/lib/modules/manager/git-submodules/extract.spec.ts index f4687a3092f81fee526081e475c9dc5be92e81e8..6e756d0c0a20efb1d6fad685396b90c85bb6cc70 100644 --- a/lib/modules/manager/git-submodules/extract.spec.ts +++ b/lib/modules/manager/git-submodules/extract.spec.ts @@ -136,6 +136,35 @@ describe('modules/manager/git-submodules/extract', () => { ); }); + it('combined username+pwd from host rule is used to detect branch for gitlab', async () => { + gitMock.listRemote.mockResolvedValueOnce( + 'ref: refs/heads/main HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964 HEAD' + ); + hostRules.add({ + hostType: 'gitlab', + matchHost: 'gitlab.com', + username: 'username', + password: 'password', + }); + const res = await extractPackageFile('', '.gitmodules.2', {}); + expect(res?.deps).toHaveLength(1); + expect(res?.deps[0].currentValue).toBe('main'); + expect(gitMock.env).toHaveBeenCalledWith({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: 'url.https://username:password@gitlab.com/.insteadOf', + GIT_CONFIG_KEY_1: 'url.https://username:password@gitlab.com/.insteadOf', + GIT_CONFIG_KEY_2: 'url.https://username:password@gitlab.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@gitlab.com/', + GIT_CONFIG_VALUE_1: 'git@gitlab.com:', + GIT_CONFIG_VALUE_2: 'https://gitlab.com/', + }); + expect(gitMock.listRemote).toHaveBeenCalledWith([ + '--symref', + 'https://github.com/PowerShell/PowerShell-Docs', + 'HEAD', + ]); + }); + it('extracts multiple submodules', async () => { hostRules.add({ matchHost: 'github.com', token: '123test' }); hostRules.add({ diff --git a/lib/modules/manager/git-submodules/readme.md b/lib/modules/manager/git-submodules/readme.md index a6194f456ca6dd87a058e44118b0c01d01449700..6f90a52e8b220706c5e9759dd0b1f3a1b084e04a 100644 --- a/lib/modules/manager/git-submodules/readme.md +++ b/lib/modules/manager/git-submodules/readme.md @@ -20,7 +20,7 @@ The following logic is executed prior to "submodules" updating: The token from the `hostRules` entry matching `hostType=github` and `matchHost=api.github.com` is added as the default authentication for `github.com`. For those running against `github.com`, this token will be the default platform token. -Next, all `hostRules` with both a token and `matchHost` will be fetched, except for any github.com one from above. +Next, all `hostRules` with both a token or username/password and `matchHost` will be fetched, except for any github.com one from above. Rules from this list are converted to environment variable directives if they match _any_ of the following characteristics: diff --git a/lib/modules/manager/gomod/readme.md b/lib/modules/manager/gomod/readme.md index b84dbb933cb68374d04c9e042a0c33bc9f60442b..c1500021a9bb2a9f637160c7568cb35607f3a77e 100644 --- a/lib/modules/manager/gomod/readme.md +++ b/lib/modules/manager/gomod/readme.md @@ -35,7 +35,7 @@ The following logic is executed prior to "artifacts" updating: The token from the `hostRules` entry matching `hostType=github` and `matchHost=api.github.com` is added as the default authentication for `github.com`. For those running against `github.com`, this token will be the default platform token. -Next, all `hostRules` with both a token and `matchHost` will be fetched, except for any github.com one from above. +Next, all `hostRules` with both a token or username/password and `matchHost` will be fetched, except for any github.com one from above. Rules from this list are converted to environment variable directives if they match _any_ of the following characteristics: diff --git a/lib/util/git/auth.spec.ts b/lib/util/git/auth.spec.ts index 4c7ec1fdfcce186e664a79f71113396b774ff2a5..2b74fd1fdf3d4b804d3e64907f7f4d8893513762 100644 --- a/lib/util/git/auth.spec.ts +++ b/lib/util/git/auth.spec.ts @@ -28,6 +28,48 @@ describe('util/git/auth', () => { }); }); + it('returns url with username and password', () => { + expect( + getGitAuthenticatedEnvironmentVariables('https://example.com/', { + username: 'username', + password: 'password', + hostType: 'github', + matchHost: 'example.com', + }) + ).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: + 'url.https://username:password@example.com/.insteadOf', + GIT_CONFIG_KEY_1: + 'url.https://username:password@example.com/.insteadOf', + GIT_CONFIG_KEY_2: + 'url.https://username:password@example.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@example.com/', + GIT_CONFIG_VALUE_1: 'git@example.com:', + GIT_CONFIG_VALUE_2: 'https://example.com/', + }); + }); + + it('prefers token over username and password', () => { + expect( + getGitAuthenticatedEnvironmentVariables('https://github.com/', { + username: 'username', + password: 'password', + token: 'token1234', + hostType: 'github', + matchHost: 'github.com', + }) + ).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: 'url.https://ssh:token1234@github.com/.insteadOf', + GIT_CONFIG_KEY_1: 'url.https://git:token1234@github.com/.insteadOf', + GIT_CONFIG_KEY_2: 'url.https://token1234@github.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@github.com/', + GIT_CONFIG_VALUE_1: 'git@github.com:', + GIT_CONFIG_VALUE_2: 'https://github.com/', + }); + }); + it('returns url with token for different protocols', () => { expect( getGitAuthenticatedEnvironmentVariables('foobar://github.com/', { @@ -219,10 +261,8 @@ describe('util/git/auth', () => { getGitAuthenticatedEnvironmentVariables( 'https://gitlab.com/', { - username: 'testing', - password: '1234', hostType: 'gitlab', - matchHost: 'github.com', + matchHost: 'gitlab.com', }, { env: 'value' } ) @@ -402,6 +442,48 @@ describe('util/git/auth', () => { }); }); + it('returns environment variables with username and password', () => { + add({ + hostType: 'gitlab', + matchHost: 'https://gitlab.example.com', + username: 'user1234', + password: 'pass1234', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: + 'url.https://user1234:pass1234@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_1: + 'url.https://user1234:pass1234@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_2: + 'url.https://user1234:pass1234@gitlab.example.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@gitlab.example.com/', + GIT_CONFIG_VALUE_1: 'git@gitlab.example.com:', + GIT_CONFIG_VALUE_2: 'https://gitlab.example.com/', + }); + }); + + it('returns environment variables with URL encoded username and password', () => { + add({ + hostType: 'gitlab', + matchHost: 'https://gitlab.example.com', + username: 'user @ :$ abc', + password: 'abc @ blub pass0:', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({ + GIT_CONFIG_COUNT: '3', + GIT_CONFIG_KEY_0: + 'url.https://user%20%40%20%3A%24%20abc:abc%20%40%20blub%20pass0%3A@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_1: + 'url.https://user%20%40%20%3A%24%20abc:abc%20%40%20blub%20pass0%3A@gitlab.example.com/.insteadOf', + GIT_CONFIG_KEY_2: + 'url.https://user%20%40%20%3A%24%20abc:abc%20%40%20blub%20pass0%3A@gitlab.example.com/.insteadOf', + GIT_CONFIG_VALUE_0: 'ssh://git@gitlab.example.com/', + GIT_CONFIG_VALUE_1: 'git@gitlab.example.com:', + GIT_CONFIG_VALUE_2: 'https://gitlab.example.com/', + }); + }); + it('returns no environment variables when hostType is not supported', () => { add({ hostType: 'custom', @@ -411,6 +493,24 @@ describe('util/git/auth', () => { expect(getGitEnvironmentVariables()).toStrictEqual({}); }); + it('returns no environment variables when only username is set', () => { + add({ + hostType: 'custom', + matchHost: 'https://custom.example.com', + username: 'user123', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({}); + }); + + it('returns no environment variables when only password is set', () => { + add({ + hostType: 'custom', + matchHost: 'https://custom.example.com', + password: 'pass123', + }); + expect(getGitEnvironmentVariables()).toStrictEqual({}); + }); + it('returns environment variables when hostType is explicitly set', () => { add({ hostType: 'custom', diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts index 97017ab4ee5f9e2138dbf67cc2cfe1a4b491ff3e..a01613cc4d6d36b883c665bec839f885e4216e17 100644 --- a/lib/util/git/auth.ts +++ b/lib/util/git/auth.ts @@ -31,13 +31,13 @@ const standardGitAllowedHostTypes = [ */ export function getGitAuthenticatedEnvironmentVariables( originalGitUrl: string, - { token, hostType, matchHost }: HostRule, + { token, username, password, hostType, matchHost }: HostRule, environmentVariables?: NodeJS.ProcessEnv ): NodeJS.ProcessEnv { - if (!token) { + if (!token && !(username && password)) { logger.warn( // TODO: types (#22198) - `Could not create environment variable for ${matchHost!} as token was empty` + `Could not create environment variable for ${matchHost!} as neither token or username and password was set` ); return { ...environmentVariables }; } @@ -58,12 +58,22 @@ export function getGitAuthenticatedEnvironmentVariables( gitConfigCount = 0; } } + let authenticationRules: AuthenticationRule[]; + if (token) { + authenticationRules = getAuthenticationRulesWithToken( + originalGitUrl, + hostType, + token + ); + } else { + const encodedUsername = encodeURIComponent(username!); + const encodedPassword = encodeURIComponent(password!); - const authenticationRules = getAuthenticationRulesWithToken( - originalGitUrl, - hostType, - token - ); + authenticationRules = getAuthenticationRules( + originalGitUrl, + `${encodedUsername}:${encodedPassword}` + ); + } // create a shallow copy of the environmentVariables as base so we don't modify the input parameter object // add the two new config key and value to the returnEnvironmentVariables object @@ -151,15 +161,15 @@ export function getGitEnvironmentVariables( let environmentVariables: NodeJS.ProcessEnv = {}; // hard-coded logic to use authentication for github.com based on the githubToken for api.github.com - const githubToken = find({ + const gitHubHostRule = find({ hostType: 'github', url: 'https://api.github.com/', }); - if (githubToken?.token) { + if (gitHubHostRule?.token) { environmentVariables = getGitAuthenticatedEnvironmentVariables( 'https://github.com/', - githubToken + gitHubHostRule ); } @@ -170,10 +180,10 @@ export function getGitEnvironmentVariables( ...additionalHostTypes, ]); - // filter rules without `matchHost` and `token` and github api github rules + // filter rules without `matchHost` and `token` or username and password and github api github rules const hostRules = getAll() - .filter((r) => r.matchHost && r.token) - .filter((r) => !githubToken || !githubApiUrls.has(r.matchHost!)); + .filter((r) => r.matchHost && (r.token ?? (r.username && r.password))) + .filter((r) => !gitHubHostRule || !githubApiUrls.has(r.matchHost!)); // for each hostRule without hostType we add additional authentication variables to the environmentVariables // for each hostRule with hostType we add additional authentication variables to the environmentVariables