mirror of
https://github.com/crazy-max/ghaction-import-gpg.git
synced 2024-11-29 16:30:55 -05:00
Configure and check committer email against GPG user address
This commit is contained in:
parent
9c02eb15d9
commit
aca1ab6f61
12 changed files with 415 additions and 18 deletions
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.1.0 (2020/05/05)
|
||||||
|
|
||||||
|
* Configure and check committer email against GPG user address
|
||||||
|
|
||||||
## 1.0.0 (2020/05/04)
|
## 1.0.0 (2020/05/04)
|
||||||
|
|
||||||
* Enable signing for Git commits and tags (#4)
|
* Enable signing for Git commits and tags (#4)
|
||||||
|
|
11
README.md
11
README.md
|
@ -16,8 +16,9 @@ If you are interested, [check out](https://git.io/Je09Y) my other :octocat: GitH
|
||||||
|
|
||||||
* Works on Linux and MacOS [virtual environments](https://help.github.com/en/articles/virtual-environments-for-github-actions#supported-virtual-environments-and-hardware-resources)
|
* Works on Linux and MacOS [virtual environments](https://help.github.com/en/articles/virtual-environments-for-github-actions#supported-virtual-environments-and-hardware-resources)
|
||||||
* Allow to seed the internal cache of `gpg-agent` with provided passphrase
|
* Allow to seed the internal cache of `gpg-agent` with provided passphrase
|
||||||
* Purge imported GPG key and cache information from runner (security)
|
|
||||||
* Enable signing for Git commits and tags
|
* Enable signing for Git commits and tags
|
||||||
|
* Configure and check committer info against GPG key
|
||||||
|
* Purge imported GPG key and cache information from runner
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -51,9 +52,11 @@ jobs:
|
||||||
|
|
||||||
Following inputs can be used as `step.with` keys
|
Following inputs can be used as `step.with` keys
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|----------------------|---------|----------------------------------------------------------|
|
|------------------------|---------|----------------------------------------------------------|
|
||||||
| `git_gpgsign` | Bool | Enable signing for this Git repository (default `false`) |
|
| `git_gpgsign` | Bool | Enable signing for this Git repository (default `false`) |
|
||||||
|
| `git_committer_name` | String | Commit author's name (default [GITHUB_ACTOR](https://help.github.com/en/github/automating-your-workflow-with-github-actions/using-environment-variables#default-environment-variables) or `github-actions`) |
|
||||||
|
| `git_committer_email` | String | Commit author's email (default `<committer_name>@users.noreply.github.com`) |
|
||||||
|
|
||||||
### environment variables
|
### environment variables
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ const userInfo = {
|
||||||
email: 'joe@foo.bar',
|
email: 'joe@foo.bar',
|
||||||
passphrase: 'with stupid passphrase',
|
passphrase: 'with stupid passphrase',
|
||||||
keyID: 'D523BD50DD70B0BA',
|
keyID: 'D523BD50DD70B0BA',
|
||||||
userID: 'Joe Tester <joe@foo.bar>',
|
|
||||||
fingerprint: '27571A53B86AF0C799B38BA77D851EB72D73BDA0',
|
fingerprint: '27571A53B86AF0C799B38BA77D851EB72D73BDA0',
|
||||||
keygrip: 'BA83FC8947213477F28ADC019F6564A956456163',
|
keygrip: 'BA83FC8947213477F28ADC019F6564A956456163',
|
||||||
pgp: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
pgp: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
|
@ -5,7 +5,6 @@ const userInfo = {
|
||||||
email: 'joe@foo.bar',
|
email: 'joe@foo.bar',
|
||||||
passphrase: 'with stupid passphrase',
|
passphrase: 'with stupid passphrase',
|
||||||
keyID: 'D523BD50DD70B0BA',
|
keyID: 'D523BD50DD70B0BA',
|
||||||
userID: 'Joe Tester <joe@foo.bar>',
|
|
||||||
fingerprint: '27571A53B86AF0C799B38BA77D851EB72D73BDA0',
|
fingerprint: '27571A53B86AF0C799B38BA77D851EB72D73BDA0',
|
||||||
pgp: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
pgp: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
@ -120,7 +119,8 @@ describe('openpgp', () => {
|
||||||
it('returns a PGP private key', async () => {
|
it('returns a PGP private key', async () => {
|
||||||
await openpgp.readPrivateKey(userInfo.pgp).then(privateKey => {
|
await openpgp.readPrivateKey(userInfo.pgp).then(privateKey => {
|
||||||
expect(privateKey.keyID).toEqual(userInfo.keyID);
|
expect(privateKey.keyID).toEqual(userInfo.keyID);
|
||||||
expect(privateKey.userID).toEqual(userInfo.userID);
|
expect(privateKey.name).toEqual(userInfo.name);
|
||||||
|
expect(privateKey.email).toEqual(userInfo.email);
|
||||||
expect(privateKey.fingerprint).toEqual(userInfo.fingerprint);
|
expect(privateKey.fingerprint).toEqual(userInfo.fingerprint);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,10 @@ inputs:
|
||||||
git_gpgsign:
|
git_gpgsign:
|
||||||
description: 'Enable signing for this Git repository'
|
description: 'Enable signing for this Git repository'
|
||||||
default: 'false'
|
default: 'false'
|
||||||
|
git_committer_name:
|
||||||
|
description: 'Commit author''s name'
|
||||||
|
git_committer_email:
|
||||||
|
description: 'Commit author''s email'
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
|
|
338
dist/index.js
generated
vendored
338
dist/index.js
generated
vendored
|
@ -1031,6 +1031,9 @@ function run() {
|
||||||
core.setFailed('Signing key required');
|
core.setFailed('Signing key required');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const git_gpgsign = /true/i.test(core.getInput('git_gpgsign'));
|
||||||
|
const git_committer_name = core.getInput('git_committer_name') || process.env['GITHUB_ACTOR'] || 'github-actions';
|
||||||
|
const git_committer_email = core.getInput('git_committer_email') || `${git_committer_name}@users.noreply.github.com`;
|
||||||
core.info('📣 GnuPG info');
|
core.info('📣 GnuPG info');
|
||||||
const version = yield gpg.getVersion();
|
const version = yield gpg.getVersion();
|
||||||
const dirs = yield gpg.getDirs();
|
const dirs = yield gpg.getDirs();
|
||||||
|
@ -1043,7 +1046,8 @@ function run() {
|
||||||
const privateKey = yield openpgp.readPrivateKey(process.env.SIGNING_KEY);
|
const privateKey = yield openpgp.readPrivateKey(process.env.SIGNING_KEY);
|
||||||
core.debug(`Fingerprint : ${privateKey.fingerprint}`);
|
core.debug(`Fingerprint : ${privateKey.fingerprint}`);
|
||||||
core.debug(`KeyID : ${privateKey.keyID}`);
|
core.debug(`KeyID : ${privateKey.keyID}`);
|
||||||
core.debug(`UserID : ${privateKey.userID}`);
|
core.debug(`Name : ${privateKey.name}`);
|
||||||
|
core.debug(`Email : ${privateKey.email}`);
|
||||||
core.debug(`CreationTime : ${privateKey.creationTime}`);
|
core.debug(`CreationTime : ${privateKey.creationTime}`);
|
||||||
core.info('🔑 Importing secret key');
|
core.info('🔑 Importing secret key');
|
||||||
yield gpg.importKey(process.env.SIGNING_KEY).then(stdout => {
|
yield gpg.importKey(process.env.SIGNING_KEY).then(stdout => {
|
||||||
|
@ -1060,7 +1064,14 @@ function run() {
|
||||||
core.debug(stdout);
|
core.debug(stdout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (/true/i.test(core.getInput('git_gpgsign'))) {
|
if (git_gpgsign) {
|
||||||
|
core.info(`🔨 Configuring git committer to be ${git_committer_name} <${git_committer_email}>`);
|
||||||
|
if (git_committer_email != privateKey.email) {
|
||||||
|
core.setFailed('Committer email does not match GPG key user address');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield git.setConfig('user.name', git_committer_name);
|
||||||
|
yield git.setConfig('user.email', git_committer_email);
|
||||||
core.info('💎 Enable signing for this Git repository');
|
core.info('💎 Enable signing for this Git repository');
|
||||||
yield git.enableCommitGpgsign();
|
yield git.enableCommitGpgsign();
|
||||||
yield git.setUserSigningkey(privateKey.keyID);
|
yield git.setUserSigningkey(privateKey.keyID);
|
||||||
|
@ -1429,6 +1440,18 @@ function setUserSigningkey(keyid) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exports.setUserSigningkey = setUserSigningkey;
|
exports.setUserSigningkey = setUserSigningkey;
|
||||||
|
function getConfig(key) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return yield git(['config', key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.getConfig = getConfig;
|
||||||
|
function setConfig(key, value) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield git(['config', key, value]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.setConfig = setConfig;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
@ -45642,22 +45665,28 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
result["default"] = mod;
|
result["default"] = mod;
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const openpgp = __importStar(__webpack_require__(724));
|
const openpgp = __importStar(__webpack_require__(724));
|
||||||
|
const addressparser_1 = __importDefault(__webpack_require__(977));
|
||||||
exports.readPrivateKey = (armoredText) => __awaiter(void 0, void 0, void 0, function* () {
|
exports.readPrivateKey = (armoredText) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
const { keys: [privateKey], err: err } = yield openpgp.key.readArmored(armoredText);
|
const { keys: [privateKey], err: err } = yield openpgp.key.readArmored(armoredText);
|
||||||
if (err === null || err === void 0 ? void 0 : err.length) {
|
if (err === null || err === void 0 ? void 0 : err.length) {
|
||||||
throw err[0];
|
throw err[0];
|
||||||
}
|
}
|
||||||
|
const address = yield privateKey.getPrimaryUser().then(primaryUser => {
|
||||||
|
return addressparser_1.default(primaryUser.user.userId.userid)[0];
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
fingerprint: privateKey.getFingerprint().toUpperCase(),
|
fingerprint: privateKey.getFingerprint().toUpperCase(),
|
||||||
keyID: yield privateKey.getEncryptionKey().then(encKey => {
|
keyID: yield privateKey.getEncryptionKey().then(encKey => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return encKey === null || encKey === void 0 ? void 0 : encKey.getKeyId().toHex().toUpperCase();
|
return encKey === null || encKey === void 0 ? void 0 : encKey.getKeyId().toHex().toUpperCase();
|
||||||
}),
|
}),
|
||||||
userID: yield privateKey.getPrimaryUser().then(primaryUser => {
|
name: address.name,
|
||||||
return primaryUser.user.userId.userid;
|
email: address.address,
|
||||||
}),
|
|
||||||
creationTime: privateKey.getCreationTime()
|
creationTime: privateKey.getCreationTime()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -45723,6 +45752,305 @@ exports.exec = (command, args = [], silent) => __awaiter(void 0, void 0, void 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 977:
|
||||||
|
/***/ (function(module) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
// expose to the world
|
||||||
|
module.exports = addressparser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses structured e-mail addresses from an address field
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* 'Name <address@domain>'
|
||||||
|
*
|
||||||
|
* will be converted to
|
||||||
|
*
|
||||||
|
* [{name: 'Name', address: 'address@domain'}]
|
||||||
|
*
|
||||||
|
* @param {String} str Address field
|
||||||
|
* @return {Array} An array of address objects
|
||||||
|
*/
|
||||||
|
function addressparser(str) {
|
||||||
|
var tokenizer = new Tokenizer(str);
|
||||||
|
var tokens = tokenizer.tokenize();
|
||||||
|
|
||||||
|
var addresses = [];
|
||||||
|
var address = [];
|
||||||
|
var parsedAddresses = [];
|
||||||
|
|
||||||
|
tokens.forEach(function (token) {
|
||||||
|
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
||||||
|
if (address.length) {
|
||||||
|
addresses.push(address);
|
||||||
|
}
|
||||||
|
address = [];
|
||||||
|
} else {
|
||||||
|
address.push(token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (address.length) {
|
||||||
|
addresses.push(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses.forEach(function (address) {
|
||||||
|
address = _handleAddress(address);
|
||||||
|
if (address.length) {
|
||||||
|
parsedAddresses = parsedAddresses.concat(address);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return parsedAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts tokens for a single address into an address object
|
||||||
|
*
|
||||||
|
* @param {Array} tokens Tokens object
|
||||||
|
* @return {Object} Address object
|
||||||
|
*/
|
||||||
|
function _handleAddress(tokens) {
|
||||||
|
var token;
|
||||||
|
var isGroup = false;
|
||||||
|
var state = 'text';
|
||||||
|
var address;
|
||||||
|
var addresses = [];
|
||||||
|
var data = {
|
||||||
|
address: [],
|
||||||
|
comment: [],
|
||||||
|
group: [],
|
||||||
|
text: []
|
||||||
|
};
|
||||||
|
var i;
|
||||||
|
var len;
|
||||||
|
|
||||||
|
// Filter out <addresses>, (comments) and regular text
|
||||||
|
for (i = 0, len = tokens.length; i < len; i++) {
|
||||||
|
token = tokens[i];
|
||||||
|
if (token.type === 'operator') {
|
||||||
|
switch (token.value) {
|
||||||
|
case '<':
|
||||||
|
state = 'address';
|
||||||
|
break;
|
||||||
|
case '(':
|
||||||
|
state = 'comment';
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
state = 'group';
|
||||||
|
isGroup = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state = 'text';
|
||||||
|
}
|
||||||
|
} else if (token.value) {
|
||||||
|
if (state === 'address') {
|
||||||
|
// handle use case where unquoted name includes a "<"
|
||||||
|
// Apple Mail truncates everything between an unexpected < and an address
|
||||||
|
// and so will we
|
||||||
|
token.value = token.value.replace(/^[^<]*<\s*/, '');
|
||||||
|
}
|
||||||
|
data[state].push(token.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no text but a comment, replace the two
|
||||||
|
if (!data.text.length && data.comment.length) {
|
||||||
|
data.text = data.comment;
|
||||||
|
data.comment = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGroup) {
|
||||||
|
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
|
||||||
|
data.text = data.text.join(' ');
|
||||||
|
addresses.push({
|
||||||
|
name: data.text || (address && address.name),
|
||||||
|
group: data.group.length ? addressparser(data.group.join(',')) : []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If no address was found, try to detect one from regular text
|
||||||
|
if (!data.address.length && data.text.length) {
|
||||||
|
for (i = data.text.length - 1; i >= 0; i--) {
|
||||||
|
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
||||||
|
data.address = data.text.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _regexHandler = function (address) {
|
||||||
|
if (!data.address.length) {
|
||||||
|
data.address = [address.trim()];
|
||||||
|
return ' ';
|
||||||
|
} else {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// still no address
|
||||||
|
if (!data.address.length) {
|
||||||
|
for (i = data.text.length - 1; i >= 0; i--) {
|
||||||
|
// fixed the regex to parse email address correctly when email address has more than one @
|
||||||
|
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
|
||||||
|
if (data.address.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's still is no text but a comment exixts, replace the two
|
||||||
|
if (!data.text.length && data.comment.length) {
|
||||||
|
data.text = data.comment;
|
||||||
|
data.comment = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep only the first address occurence, push others to regular text
|
||||||
|
if (data.address.length > 1) {
|
||||||
|
data.text = data.text.concat(data.address.splice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join values with spaces
|
||||||
|
data.text = data.text.join(' ');
|
||||||
|
data.address = data.address.join(' ');
|
||||||
|
|
||||||
|
if (!data.address && isGroup) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
address = {
|
||||||
|
address: data.address || data.text || '',
|
||||||
|
name: data.text || data.address || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
if (address.address === address.name) {
|
||||||
|
if ((address.address || '').match(/@/)) {
|
||||||
|
address.name = '';
|
||||||
|
} else {
|
||||||
|
address.address = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses.push(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Tokenizer object for tokenizing address field strings
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {String} str Address field string
|
||||||
|
*/
|
||||||
|
function Tokenizer(str) {
|
||||||
|
this.str = (str || '').toString();
|
||||||
|
this.operatorCurrent = '';
|
||||||
|
this.operatorExpecting = '';
|
||||||
|
this.node = null;
|
||||||
|
this.escaped = false;
|
||||||
|
|
||||||
|
this.list = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator tokens and which tokens are expected to end the sequence
|
||||||
|
*/
|
||||||
|
Tokenizer.prototype.operators = {
|
||||||
|
'"': '"',
|
||||||
|
'(': ')',
|
||||||
|
'<': '>',
|
||||||
|
',': '',
|
||||||
|
':': ';',
|
||||||
|
// Semicolons are not a legal delimiter per the RFC2822 grammar other
|
||||||
|
// than for terminating a group, but they are also not valid for any
|
||||||
|
// other use in this context. Given that some mail clients have
|
||||||
|
// historically allowed the semicolon as a delimiter equivalent to the
|
||||||
|
// comma in their UI, it makes sense to treat them the same as a comma
|
||||||
|
// when used outside of a group.
|
||||||
|
';': ''
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenizes the original input string
|
||||||
|
*
|
||||||
|
* @return {Array} An array of operator|text tokens
|
||||||
|
*/
|
||||||
|
Tokenizer.prototype.tokenize = function () {
|
||||||
|
var chr, list = [];
|
||||||
|
for (var i = 0, len = this.str.length; i < len; i++) {
|
||||||
|
chr = this.str.charAt(i);
|
||||||
|
this.checkChar(chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.list.forEach(function (node) {
|
||||||
|
node.value = (node.value || '').toString().trim();
|
||||||
|
if (node.value) {
|
||||||
|
list.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a character is an operator or text and acts accordingly
|
||||||
|
*
|
||||||
|
* @param {String} chr Character from the address field
|
||||||
|
*/
|
||||||
|
Tokenizer.prototype.checkChar = function (chr) {
|
||||||
|
if ((chr in this.operators || chr === '\\') && this.escaped) {
|
||||||
|
this.escaped = false;
|
||||||
|
} else if (this.operatorExpecting && chr === this.operatorExpecting) {
|
||||||
|
this.node = {
|
||||||
|
type: 'operator',
|
||||||
|
value: chr
|
||||||
|
};
|
||||||
|
this.list.push(this.node);
|
||||||
|
this.node = null;
|
||||||
|
this.operatorExpecting = '';
|
||||||
|
this.escaped = false;
|
||||||
|
return;
|
||||||
|
} else if (!this.operatorExpecting && chr in this.operators) {
|
||||||
|
this.node = {
|
||||||
|
type: 'operator',
|
||||||
|
value: chr
|
||||||
|
};
|
||||||
|
this.list.push(this.node);
|
||||||
|
this.node = null;
|
||||||
|
this.operatorExpecting = this.operators[chr];
|
||||||
|
this.escaped = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.escaped && chr === '\\') {
|
||||||
|
this.escaped = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.node) {
|
||||||
|
this.node = {
|
||||||
|
type: 'text',
|
||||||
|
value: ''
|
||||||
|
};
|
||||||
|
this.list.push(this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.escaped && chr !== '\\') {
|
||||||
|
this.node.value += '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.node.value += chr;
|
||||||
|
this.escaped = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 986:
|
/***/ 986:
|
||||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -973,6 +973,11 @@
|
||||||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
|
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"addressparser": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y="
|
||||||
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.2",
|
"version": "6.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"@actions/core": "^1.2.3",
|
"@actions/core": "^1.2.3",
|
||||||
"@actions/exec": "^1.0.4",
|
"@actions/exec": "^1.0.4",
|
||||||
"@actions/github": "^2.1.1",
|
"@actions/github": "^2.1.1",
|
||||||
|
"addressparser": "^1.0.1",
|
||||||
"openpgp": "^4.10.4"
|
"openpgp": "^4.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
24
src/addressparser.d.ts
vendored
Normal file
24
src/addressparser.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
declare namespace addressparser {
|
||||||
|
interface Address {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses structured e-mail addresses from an address field
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* 'Name <address@domain>'
|
||||||
|
*
|
||||||
|
* will be converted to
|
||||||
|
*
|
||||||
|
* [{name: 'Name', address: 'address@domain'}]
|
||||||
|
*
|
||||||
|
* @param str Address field
|
||||||
|
* @return An array of address objects
|
||||||
|
*/
|
||||||
|
declare function addressparser(address: string): addressparser.Address[];
|
||||||
|
|
||||||
|
export = addressparser;
|
|
@ -16,3 +16,11 @@ export async function enableCommitGpgsign(): Promise<void> {
|
||||||
export async function setUserSigningkey(keyid: string): Promise<void> {
|
export async function setUserSigningkey(keyid: string): Promise<void> {
|
||||||
await git(['config', 'user.signingkey', keyid]);
|
await git(['config', 'user.signingkey', keyid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getConfig(key: string): Promise<string> {
|
||||||
|
return await git(['config', key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setConfig(key: string, value: string): Promise<void> {
|
||||||
|
await git(['config', key, value]);
|
||||||
|
}
|
||||||
|
|
20
src/main.ts
20
src/main.ts
|
@ -17,6 +17,12 @@ async function run(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const git_gpgsign = /true/i.test(core.getInput('git_gpgsign'));
|
||||||
|
const git_committer_name: string =
|
||||||
|
core.getInput('git_committer_name') || process.env['GITHUB_ACTOR'] || 'github-actions';
|
||||||
|
const git_committer_email: string =
|
||||||
|
core.getInput('git_committer_email') || `${git_committer_name}@users.noreply.github.com`;
|
||||||
|
|
||||||
core.info('📣 GnuPG info');
|
core.info('📣 GnuPG info');
|
||||||
const version = await gpg.getVersion();
|
const version = await gpg.getVersion();
|
||||||
const dirs = await gpg.getDirs();
|
const dirs = await gpg.getDirs();
|
||||||
|
@ -30,7 +36,8 @@ async function run(): Promise<void> {
|
||||||
const privateKey = await openpgp.readPrivateKey(process.env.SIGNING_KEY);
|
const privateKey = await openpgp.readPrivateKey(process.env.SIGNING_KEY);
|
||||||
core.debug(`Fingerprint : ${privateKey.fingerprint}`);
|
core.debug(`Fingerprint : ${privateKey.fingerprint}`);
|
||||||
core.debug(`KeyID : ${privateKey.keyID}`);
|
core.debug(`KeyID : ${privateKey.keyID}`);
|
||||||
core.debug(`UserID : ${privateKey.userID}`);
|
core.debug(`Name : ${privateKey.name}`);
|
||||||
|
core.debug(`Email : ${privateKey.email}`);
|
||||||
core.debug(`CreationTime : ${privateKey.creationTime}`);
|
core.debug(`CreationTime : ${privateKey.creationTime}`);
|
||||||
|
|
||||||
core.info('🔑 Importing secret key');
|
core.info('🔑 Importing secret key');
|
||||||
|
@ -52,7 +59,16 @@ async function run(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/true/i.test(core.getInput('git_gpgsign'))) {
|
if (git_gpgsign) {
|
||||||
|
core.info(`🔨 Configuring git committer to be ${git_committer_name} <${git_committer_email}>`);
|
||||||
|
if (git_committer_email != privateKey.email) {
|
||||||
|
core.setFailed('Committer email does not match GPG key user address');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await git.setConfig('user.name', git_committer_name);
|
||||||
|
await git.setConfig('user.email', git_committer_email);
|
||||||
|
|
||||||
core.info('💎 Enable signing for this Git repository');
|
core.info('💎 Enable signing for this Git repository');
|
||||||
await git.enableCommitGpgsign();
|
await git.enableCommitGpgsign();
|
||||||
await git.setUserSigningkey(privateKey.keyID);
|
await git.setUserSigningkey(privateKey.keyID);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import * as openpgp from 'openpgp';
|
import * as openpgp from 'openpgp';
|
||||||
|
import addressparser from 'addressparser';
|
||||||
|
|
||||||
export interface PrivateKey {
|
export interface PrivateKey {
|
||||||
fingerprint: string;
|
fingerprint: string;
|
||||||
keyID: string;
|
keyID: string;
|
||||||
userID: string;
|
name: string;
|
||||||
|
email: string;
|
||||||
creationTime: Date;
|
creationTime: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,15 +23,18 @@ export const readPrivateKey = async (armoredText: string): Promise<PrivateKey> =
|
||||||
throw err[0];
|
throw err[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const address = await privateKey.getPrimaryUser().then(primaryUser => {
|
||||||
|
return addressparser(primaryUser.user.userId.userid)[0];
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fingerprint: privateKey.getFingerprint().toUpperCase(),
|
fingerprint: privateKey.getFingerprint().toUpperCase(),
|
||||||
keyID: await privateKey.getEncryptionKey().then(encKey => {
|
keyID: await privateKey.getEncryptionKey().then(encKey => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return encKey?.getKeyId().toHex().toUpperCase();
|
return encKey?.getKeyId().toHex().toUpperCase();
|
||||||
}),
|
}),
|
||||||
userID: await privateKey.getPrimaryUser().then(primaryUser => {
|
name: address.name,
|
||||||
return primaryUser.user.userId.userid;
|
email: address.address,
|
||||||
}),
|
|
||||||
creationTime: privateKey.getCreationTime()
|
creationTime: privateKey.getCreationTime()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue