mirror of
https://forgejo.stefka.eu/jiriks74/create-pull-request.git
synced 2025-01-18 16:01:06 +01:00
Refactor extraheader auth handling
This commit is contained in:
parent
a6a1a418bf
commit
24012f5c84
9 changed files with 460 additions and 419 deletions
|
@ -2,13 +2,10 @@ import * as core from '@actions/core'
|
|||
import {createOrUpdateBranch} from './create-or-update-branch'
|
||||
import {GitHubHelper} from './github-helper'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {ConfigOption, GitConfigHelper} from './git-config-helper'
|
||||
import {GitAuthHelper} from './git-auth-helper'
|
||||
import {GitIdentityHelper} from './git-identity-helper'
|
||||
import * as utils from './utils'
|
||||
|
||||
const EXTRAHEADER_OPTION = 'http.https://github.com/.extraheader'
|
||||
const EXTRAHEADER_VALUE_REGEX = '^AUTHORIZATION:'
|
||||
|
||||
const DEFAULT_COMMIT_MESSAGE = '[create-pull-request] automated change'
|
||||
const DEFAULT_TITLE = 'Changes by create-pull-request action'
|
||||
const DEFAULT_BODY =
|
||||
|
@ -36,21 +33,17 @@ export interface Inputs {
|
|||
}
|
||||
|
||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
let gitConfigHelper
|
||||
let extraHeaderOption = new ConfigOption()
|
||||
let gitAuthHelper
|
||||
try {
|
||||
// Get the repository path
|
||||
const repoPath = utils.getRepoPath(inputs.path)
|
||||
// Create a git command manager
|
||||
const git = await GitCommandManager.create(repoPath)
|
||||
|
||||
// Unset and save the extraheader config option if it exists
|
||||
// Save and unset the extraheader auth config if it exists
|
||||
core.startGroup('Save persisted git credentials')
|
||||
gitConfigHelper = new GitConfigHelper(git)
|
||||
extraHeaderOption = await gitConfigHelper.getAndUnsetConfigOption(
|
||||
EXTRAHEADER_OPTION,
|
||||
EXTRAHEADER_VALUE_REGEX
|
||||
)
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
core.endGroup()
|
||||
|
||||
// Set defaults
|
||||
|
@ -64,10 +57,8 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||
// Determine the GitHub repository from git config
|
||||
// This will be the target repository for the pull request branch
|
||||
core.startGroup('Determining the checked out repository')
|
||||
const remoteOriginUrlConfig = await gitConfigHelper.getConfigOption(
|
||||
'remote.origin.url'
|
||||
)
|
||||
const remote = utils.getRemoteDetail(remoteOriginUrlConfig.value)
|
||||
const remoteUrl = await git.tryGetRemoteUrl()
|
||||
const remote = utils.getRemoteDetail(remoteUrl)
|
||||
core.endGroup()
|
||||
core.info(
|
||||
`Pull request branch target repository set to ${remote.repository}`
|
||||
|
@ -75,16 +66,7 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||
|
||||
if (remote.protocol == 'HTTPS') {
|
||||
core.startGroup('Configuring credential for HTTPS authentication')
|
||||
// Encode and configure the basic credential for HTTPS access
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${inputs.token}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
core.setSecret(basicCredential)
|
||||
git.setAuthGitOptions([
|
||||
'-c',
|
||||
`http.https://github.com/.extraheader=AUTHORIZATION: basic ${basicCredential}`
|
||||
])
|
||||
await gitAuthHelper.configureAuth(inputs.token)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
|
@ -216,17 +198,10 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
} finally {
|
||||
// Restore the extraheader config option
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
core.startGroup('Restore persisted git credentials')
|
||||
if (extraHeaderOption.value != '') {
|
||||
if (
|
||||
await gitConfigHelper.addConfigOption(
|
||||
EXTRAHEADER_OPTION,
|
||||
extraHeaderOption.value
|
||||
)
|
||||
)
|
||||
core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`)
|
||||
}
|
||||
await gitAuthHelper.removeAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
|
126
src/git-auth-helper.ts
Normal file
126
src/git-auth-helper.ts
Normal file
|
@ -0,0 +1,126 @@
|
|||
import * as core from '@actions/core'
|
||||
import * as fs from 'fs'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import * as path from 'path'
|
||||
import {URL} from 'url'
|
||||
|
||||
export class GitAuthHelper {
|
||||
private git: GitCommandManager
|
||||
private gitConfigPath: string
|
||||
private extraheaderConfigKey: string
|
||||
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
||||
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
||||
private persistedExtraheaderConfigValue = ''
|
||||
|
||||
constructor(git: GitCommandManager) {
|
||||
this.git = git
|
||||
this.gitConfigPath = path.join(
|
||||
this.git.getWorkingDirectory(),
|
||||
'.git',
|
||||
'config'
|
||||
)
|
||||
const serverUrl = this.getServerUrl()
|
||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||
}
|
||||
|
||||
async savePersistedAuth(): Promise<void> {
|
||||
// Save and unset persisted extraheader credential in git config if it exists
|
||||
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
||||
}
|
||||
|
||||
async restorePersistedAuth(): Promise<void> {
|
||||
if (this.persistedExtraheaderConfigValue) {
|
||||
try {
|
||||
await this.setExtraheaderConfig(this.persistedExtraheaderConfigValue)
|
||||
core.info('Persisted git credentials restored')
|
||||
} catch (e) {
|
||||
core.warning(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async configureToken(token: string): Promise<void> {
|
||||
// Encode and configure the basic credential for HTTPS access
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${token}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
core.setSecret(basicCredential)
|
||||
const extraheaderConfigValue = `AUTHORIZATION: basic ${basicCredential}`
|
||||
await this.setExtraheaderConfig(extraheaderConfigValue)
|
||||
}
|
||||
|
||||
async removeAuth(): Promise<void> {
|
||||
await this.getAndUnset()
|
||||
}
|
||||
|
||||
private async setExtraheaderConfig(
|
||||
extraheaderConfigValue: string
|
||||
): Promise<void> {
|
||||
// Configure a placeholder value. This approach avoids the credential being captured
|
||||
// by process creation audit events, which are commonly logged. For more information,
|
||||
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||
// See https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts#L267-L274
|
||||
await this.git.config(
|
||||
this.extraheaderConfigKey,
|
||||
this.extraheaderConfigPlaceholderValue
|
||||
)
|
||||
// Replace the placeholder
|
||||
await this.gitConfigStringReplace(
|
||||
this.extraheaderConfigPlaceholderValue,
|
||||
extraheaderConfigValue
|
||||
)
|
||||
}
|
||||
|
||||
private async getAndUnset(): Promise<string> {
|
||||
let configValue = ''
|
||||
// Save and unset persisted extraheader credential in git config if it exists
|
||||
if (
|
||||
await this.git.configExists(
|
||||
this.extraheaderConfigKey,
|
||||
this.extraheaderConfigValueRegex
|
||||
)
|
||||
) {
|
||||
configValue = await this.git.getConfigValue(
|
||||
this.extraheaderConfigKey,
|
||||
this.extraheaderConfigValueRegex
|
||||
)
|
||||
if (
|
||||
await this.git.tryConfigUnset(
|
||||
this.extraheaderConfigKey,
|
||||
this.extraheaderConfigValueRegex
|
||||
)
|
||||
) {
|
||||
core.info(`Unset config key '${this.extraheaderConfigKey}'`)
|
||||
} else {
|
||||
core.warning(
|
||||
`Failed to unset config key '${this.extraheaderConfigKey}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
return configValue
|
||||
}
|
||||
|
||||
private async gitConfigStringReplace(
|
||||
find: string,
|
||||
replace: string
|
||||
): Promise<void> {
|
||||
let content = (await fs.promises.readFile(this.gitConfigPath)).toString()
|
||||
const index = content.indexOf(find)
|
||||
if (index < 0 || index != content.lastIndexOf(find)) {
|
||||
throw new Error(`Unable to replace '${find}' in ${this.gitConfigPath}`)
|
||||
}
|
||||
content = content.replace(find, replace)
|
||||
await fs.promises.writeFile(this.gitConfigPath, content)
|
||||
}
|
||||
|
||||
private getServerUrl(): URL {
|
||||
// todo: remove GITHUB_URL after support for GHES Alpha is no longer needed
|
||||
// See https://github.com/actions/checkout/blob/main/src/url-helper.ts#L22-L29
|
||||
return new URL(
|
||||
process.env['GITHUB_SERVER_URL'] ||
|
||||
process.env['GITHUB_URL'] ||
|
||||
'https://github.com'
|
||||
)
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@ const tagsRefSpec = '+refs/tags/*:refs/tags/*'
|
|||
export class GitCommandManager {
|
||||
private gitPath: string
|
||||
private workingDirectory: string
|
||||
// Git options used when commands require auth
|
||||
private authGitOptions?: string[]
|
||||
// Git options used when commands require an identity
|
||||
private identityGitOptions?: string[]
|
||||
|
||||
|
@ -21,10 +19,6 @@ export class GitCommandManager {
|
|||
return new GitCommandManager(workingDirectory, gitPath)
|
||||
}
|
||||
|
||||
setAuthGitOptions(authGitOptions: string[]): void {
|
||||
this.authGitOptions = authGitOptions
|
||||
}
|
||||
|
||||
setIdentityGitOptions(identityGitOptions: string[]): void {
|
||||
this.identityGitOptions = identityGitOptions
|
||||
}
|
||||
|
@ -68,6 +62,38 @@ export class GitCommandManager {
|
|||
await this.exec(args)
|
||||
}
|
||||
|
||||
async config(
|
||||
configKey: string,
|
||||
configValue: string,
|
||||
globalConfig?: boolean
|
||||
): Promise<void> {
|
||||
await this.exec([
|
||||
'config',
|
||||
globalConfig ? '--global' : '--local',
|
||||
configKey,
|
||||
configValue
|
||||
])
|
||||
}
|
||||
|
||||
async configExists(
|
||||
configKey: string,
|
||||
configValue = '.',
|
||||
globalConfig?: boolean
|
||||
): Promise<boolean> {
|
||||
const output = await this.exec(
|
||||
[
|
||||
'config',
|
||||
globalConfig ? '--global' : '--local',
|
||||
'--name-only',
|
||||
'--get-regexp',
|
||||
configKey,
|
||||
configValue
|
||||
],
|
||||
true
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async diff(options?: string[]): Promise<string> {
|
||||
const args = ['-c', 'core.pager=cat', 'diff']
|
||||
if (options) {
|
||||
|
@ -82,12 +108,7 @@ export class GitCommandManager {
|
|||
remoteName?: string,
|
||||
options?: string[]
|
||||
): Promise<void> {
|
||||
const args = ['-c', 'protocol.version=2']
|
||||
if (this.authGitOptions) {
|
||||
args.push(...this.authGitOptions)
|
||||
}
|
||||
args.push('fetch')
|
||||
|
||||
const args = ['-c', 'protocol.version=2', 'fetch']
|
||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||
args.push('--no-tags')
|
||||
}
|
||||
|
@ -110,6 +131,17 @@ export class GitCommandManager {
|
|||
await this.exec(args)
|
||||
}
|
||||
|
||||
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||
const output = await this.exec([
|
||||
'config',
|
||||
'--local',
|
||||
'--get-regexp',
|
||||
configKey,
|
||||
configValue
|
||||
])
|
||||
return output.stdout.trim().split(`${configKey} `)[1]
|
||||
}
|
||||
|
||||
getWorkingDirectory(): string {
|
||||
return this.workingDirectory
|
||||
}
|
||||
|
@ -133,14 +165,9 @@ export class GitCommandManager {
|
|||
|
||||
async push(options?: string[]): Promise<void> {
|
||||
const args = ['push']
|
||||
if (this.authGitOptions) {
|
||||
args.unshift(...this.authGitOptions)
|
||||
}
|
||||
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
|
@ -187,21 +214,23 @@ export class GitCommandManager {
|
|||
|
||||
async tryConfigUnset(
|
||||
configKey: string,
|
||||
configValue = '.',
|
||||
globalConfig?: boolean
|
||||
): Promise<boolean> {
|
||||
const output = await this.exec(
|
||||
[
|
||||
'config',
|
||||
globalConfig ? '--global' : '--local',
|
||||
'--unset-all',
|
||||
configKey
|
||||
'--unset',
|
||||
configKey,
|
||||
configValue
|
||||
],
|
||||
true
|
||||
)
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async tryGetFetchUrl(): Promise<string> {
|
||||
async tryGetRemoteUrl(): Promise<string> {
|
||||
const output = await this.exec(
|
||||
['config', '--local', '--get', 'remote.origin.url'],
|
||||
true
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import * as core from '@actions/core'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
|
||||
export class ConfigOption {
|
||||
name = ''
|
||||
value = ''
|
||||
}
|
||||
|
||||
export class GitConfigHelper {
|
||||
private git: GitCommandManager
|
||||
|
||||
constructor(git: GitCommandManager) {
|
||||
this.git = git
|
||||
}
|
||||
|
||||
async addConfigOption(name: string, value: string): Promise<boolean> {
|
||||
const result = await this.git.exec(
|
||||
['config', '--local', '--add', name, value],
|
||||
true
|
||||
)
|
||||
return result.exitCode === 0
|
||||
}
|
||||
|
||||
async unsetConfigOption(name: string, valueRegex = '.'): Promise<boolean> {
|
||||
const result = await this.git.exec(
|
||||
['config', '--local', '--unset', name, valueRegex],
|
||||
true
|
||||
)
|
||||
return result.exitCode === 0
|
||||
}
|
||||
|
||||
async configOptionExists(name: string, valueRegex = '.'): Promise<boolean> {
|
||||
const result = await this.git.exec(
|
||||
['config', '--local', '--name-only', '--get-regexp', name, valueRegex],
|
||||
true
|
||||
)
|
||||
return result.exitCode === 0
|
||||
}
|
||||
|
||||
async getConfigOption(name: string, valueRegex = '.'): Promise<ConfigOption> {
|
||||
const option = new ConfigOption()
|
||||
const result = await this.git.exec(
|
||||
['config', '--local', '--get-regexp', name, valueRegex],
|
||||
true
|
||||
)
|
||||
option.name = name
|
||||
option.value = result.stdout.trim().split(`${name} `)[1]
|
||||
return option
|
||||
}
|
||||
|
||||
async getAndUnsetConfigOption(
|
||||
name: string,
|
||||
valueRegex = '.'
|
||||
): Promise<ConfigOption> {
|
||||
if (await this.configOptionExists(name, valueRegex)) {
|
||||
const option = await this.getConfigOption(name, valueRegex)
|
||||
if (await this.unsetConfigOption(name, valueRegex)) {
|
||||
core.debug(`Unset config option '${name}'`)
|
||||
return option
|
||||
}
|
||||
}
|
||||
return new ConfigOption()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import * as core from '@actions/core'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {GitConfigHelper} from './git-config-helper'
|
||||
import * as utils from './utils'
|
||||
|
||||
// Default the committer and author to the GitHub Actions bot
|
||||
|
@ -23,41 +22,35 @@ export class GitIdentityHelper {
|
|||
}
|
||||
|
||||
private async getGitIdentityFromConfig(): Promise<GitIdentity | undefined> {
|
||||
const gitConfigHelper = new GitConfigHelper(this.git)
|
||||
|
||||
if (
|
||||
(await gitConfigHelper.configOptionExists('user.name')) &&
|
||||
(await gitConfigHelper.configOptionExists('user.email'))
|
||||
(await this.git.configExists('user.name')) &&
|
||||
(await this.git.configExists('user.email'))
|
||||
) {
|
||||
const userName = await gitConfigHelper.getConfigOption('user.name')
|
||||
const userEmail = await gitConfigHelper.getConfigOption('user.email')
|
||||
const userName = await this.git.getConfigValue('user.name')
|
||||
const userEmail = await this.git.getConfigValue('user.email')
|
||||
return {
|
||||
authorName: userName.value,
|
||||
authorEmail: userEmail.value,
|
||||
committerName: userName.value,
|
||||
committerEmail: userEmail.value
|
||||
authorName: userName,
|
||||
authorEmail: userEmail,
|
||||
committerName: userName,
|
||||
committerEmail: userEmail
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(await gitConfigHelper.configOptionExists('committer.name')) &&
|
||||
(await gitConfigHelper.configOptionExists('committer.email')) &&
|
||||
(await gitConfigHelper.configOptionExists('author.name')) &&
|
||||
(await gitConfigHelper.configOptionExists('author.email'))
|
||||
(await this.git.configExists('committer.name')) &&
|
||||
(await this.git.configExists('committer.email')) &&
|
||||
(await this.git.configExists('author.name')) &&
|
||||
(await this.git.configExists('author.email'))
|
||||
) {
|
||||
const committerName = await gitConfigHelper.getConfigOption(
|
||||
'committer.name'
|
||||
)
|
||||
const committerEmail = await gitConfigHelper.getConfigOption(
|
||||
'committer.email'
|
||||
)
|
||||
const authorName = await gitConfigHelper.getConfigOption('author.name')
|
||||
const authorEmail = await gitConfigHelper.getConfigOption('author.email')
|
||||
const committerName = await this.git.getConfigValue('committer.name')
|
||||
const committerEmail = await this.git.getConfigValue('committer.email')
|
||||
const authorName = await this.git.getConfigValue('author.name')
|
||||
const authorEmail = await this.git.getConfigValue('author.email')
|
||||
return {
|
||||
authorName: authorName.value,
|
||||
authorEmail: authorEmail.value,
|
||||
committerName: committerName.value,
|
||||
committerEmail: committerEmail.value
|
||||
authorName: authorName,
|
||||
authorEmail: authorEmail,
|
||||
committerName: committerName,
|
||||
committerEmail: committerEmail
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue