Allow using any user id of the key's user ids as committer identity. Fixes #156

This commit is contained in:
Florian Friedrich 2024-04-29 11:11:31 +02:00
parent 78fb6ec0e4
commit 5170336089
No known key found for this signature in database
GPG key ID: 5F2F8CADF15B7C89
5 changed files with 73 additions and 33 deletions

View file

@ -229,12 +229,13 @@ The following inputs can be used as `step.with` keys
Following outputs are available
| Name | Type | Description |
|---------------|--------|---------------------------------------------------------------------------------------------------------------------------------|
| `fingerprint` | String | Fingerprint of the GPG key (recommended as [user ID](https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html)) |
| `keyid` | String | Low 64 bits of the X.509 certificate SHA-1 fingerprint |
| `name` | String | Name associated with the GPG key |
| `email` | String | Email address associated with the GPG key |
| Name | Type | Description |
|---------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `fingerprint` | String | Fingerprint of the GPG key (recommended as [user ID](https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html)) |
| `keyid` | String | Low 64 bits of the X.509 certificate SHA-1 fingerprint |
| `name` | String | Primary name associated with the GPG key |
| `email` | String | Primary email address associated with the GPG key |
| `userids` | String (JSON) | All user ids (including primary) associated with the GPG Key.<br/>The output is a JSON array where each object has a `name` and `email` key. Use [fromJson](https://docs.github.com/en/actions/learn-github-actions/expressions#fromjson) to turn the String back into a JSON array |
## Contributing

View file

@ -17,8 +17,16 @@ const userInfos = [
encoding: 'utf8',
flag: 'r'
}),
name: 'Joe Tester',
email: 'joe@foo.bar',
primaryUserId: {
name: 'Joe Tester',
email: 'joe@foo.bar'
},
userIds: [
{
name: 'Joe Tester',
email: 'joe@foo.bar'
}
],
keyID: '7D851EB72D73BDA0',
fingerprint: '27571A53B86AF0C799B38BA77D851EB72D73BDA0',
keygrip: '3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627'
@ -37,8 +45,16 @@ const userInfos = [
encoding: 'utf8',
flag: 'r'
}),
name: 'Joe Bar',
email: 'joe@bar.foo',
primaryUserId: {
name: 'Joe Bar',
email: 'joe@bar.foo'
},
userIds: [
{
name: 'Joe Bar',
email: 'joe@bar.foo'
}
],
keyID: '6071D218380FDCC8',
fingerprint: '87F257B89CE462100BEC0FFE6071D218380FDCC8',
keygrips: ['F5C3ABFAAB36B427FD98C4EDD0387E08EA1E8092', 'DEE0FC98F441519CA5DE5D79773CB29009695FEB']
@ -52,16 +68,22 @@ for (const userInfo of userInfos) {
it('returns a PGP private key from an armored string', async () => {
await openpgp.readPrivateKey(userInfo.pgp).then(privateKey => {
expect(privateKey.keyID).toEqual(userInfo.keyID);
expect(privateKey.name).toEqual(userInfo.name);
expect(privateKey.email).toEqual(userInfo.email);
expect(privateKey.primaryUserId.name).toEqual(userInfo.primaryUserId.name);
expect(privateKey.primaryUserId.email).toEqual(userInfo.primaryUserId.email);
expect(privateKey.allUserIds).toHaveLength(userInfo.userIds.length);
expect(privateKey.allUserIds[0].name).toEqual(userInfo.userIds[0].name);
expect(privateKey.allUserIds[0].email).toEqual(userInfo.userIds[0].email);
expect(privateKey.fingerprint).toEqual(userInfo.fingerprint);
});
});
it('returns a PGP private key from a base64 armored string', async () => {
await openpgp.readPrivateKey(userInfo.pgp_base64).then(privateKey => {
expect(privateKey.keyID).toEqual(userInfo.keyID);
expect(privateKey.name).toEqual(userInfo.name);
expect(privateKey.email).toEqual(userInfo.email);
expect(privateKey.primaryUserId.name).toEqual(userInfo.primaryUserId.name);
expect(privateKey.primaryUserId.email).toEqual(userInfo.primaryUserId.email);
expect(privateKey.allUserIds).toHaveLength(userInfo.userIds.length);
expect(privateKey.allUserIds[0].name).toEqual(userInfo.userIds[0].name);
expect(privateKey.allUserIds[0].email).toEqual(userInfo.userIds[0].email);
expect(privateKey.fingerprint).toEqual(userInfo.fingerprint);
});
});
@ -69,7 +91,7 @@ for (const userInfo of userInfos) {
describe('generateKeyPair', () => {
it('generates a PGP key pair', async () => {
await openpgp.generateKeyPair(userInfo.name, userInfo.email, userInfo.passphrase).then(keyPair => {
await openpgp.generateKeyPair(userInfo.primaryUserId.name, userInfo.primaryUserId.email, userInfo.passphrase).then(keyPair => {
expect(keyPair).not.toBeUndefined();
expect(keyPair.publicKey).not.toBeUndefined();
expect(keyPair.privateKey).not.toBeUndefined();

View file

@ -56,9 +56,11 @@ outputs:
keyid:
description: 'Low 64 bits of the X.509 certificate SHA-1 fingerprint'
name:
description: 'Name associated with the GPG key'
description: 'Primary name associated with the GPG key'
email:
description: 'Email address associated with the GPG key'
description: 'Primary email address associated with the GPG key'
userids:
description: 'All user ids (including primary) associated with the GPG Key'
runs:
using: 'node20'

View file

@ -28,8 +28,10 @@ async function run(): Promise<void> {
await core.group(`GPG private key info`, async () => {
core.info(`Fingerprint : ${privateKey.fingerprint}`);
core.info(`KeyID : ${privateKey.keyID}`);
core.info(`Name : ${privateKey.name}`);
core.info(`Email : ${privateKey.email}`);
for (const userId of privateKey.allUserIds) {
const isPrimary = userId.email === privateKey.primaryUserId.email;
core.info(`User ID : ${userId.name} <${userId.email}>${isPrimary ? ' (primary)' : ''}`);
}
core.info(`CreationTime : ${privateKey.creationTime}`);
});
@ -91,21 +93,24 @@ async function run(): Promise<void> {
core.setOutput('fingerprint', fingerprint);
core.info(`keyid=${privateKey.keyID}`);
core.setOutput('keyid', privateKey.keyID);
core.info(`name=${privateKey.name}`);
core.setOutput('name', privateKey.name);
core.info(`email=${privateKey.email}`);
core.setOutput('email', privateKey.email);
core.info(`name=${privateKey.primaryUserId.name}`);
core.setOutput('name', privateKey.primaryUserId.name);
core.info(`email=${privateKey.primaryUserId.email}`);
core.setOutput('email', privateKey.primaryUserId.email);
core.info(`userids=${JSON.stringify(privateKey.allUserIds)}`);
core.setOutput('userids', privateKey.allUserIds);
});
if (inputs.gitUserSigningkey) {
core.info('Setting GPG signing keyID for this Git repository');
await git.setConfig('user.signingkey', privateKey.keyID, inputs.gitConfigGlobal);
const userEmail = inputs.gitCommitterEmail || privateKey.email;
const userName = inputs.gitCommitterName || privateKey.name;
const userName = inputs.gitCommitterName || privateKey.primaryUserId.name;
const userEmail = inputs.gitCommitterEmail || privateKey.primaryUserId.email;
if (userEmail != privateKey.email) {
core.setFailed(`Committer email "${inputs.gitCommitterEmail}" (name: "${inputs.gitCommitterName}") does not match GPG private key email "${privateKey.email}" (name: "${privateKey.name}")`);
if (!privateKey.allUserIds.some(id => id.email === userEmail)) {
const keyIdentities = privateKey.allUserIds.map(id => `"${id.email}" (name: "${id.name}")`).join(', ');
core.setFailed(`Committer email "${inputs.gitCommitterEmail}" (name: "${inputs.gitCommitterName}") does not match GPG any of the private key user id email addresses: ${keyIdentities}`);
return;
}

View file

@ -1,11 +1,16 @@
import * as openpgp from 'openpgp';
import addressparser from 'addressparser';
export interface UserId {
name: string;
email: string;
}
export interface PrivateKey {
fingerprint: string;
keyID: string;
name: string;
email: string;
primaryUserId: UserId;
allUserIds: UserId[];
creationTime: Date;
}
@ -19,15 +24,20 @@ export const readPrivateKey = async (key: string): Promise<PrivateKey> => {
armoredKey: (await isArmored(key)) ? key : Buffer.from(key, 'base64').toString()
});
const address = await privateKey.getPrimaryUser().then(primaryUser => {
return addressparser(primaryUser.user.userID?.userID)[0];
const primaryUserId: UserId = await privateKey.getPrimaryUser().then(primaryUser => {
const address = addressparser(primaryUser.user.userID?.userID)[0];
return {name: address.name, email: address.address};
});
const allUserIds: UserId[] = privateKey.getUserIDs().map(userId => {
const address = addressparser(userId)[0];
return {name: address.name, email: address.address};
});
return {
fingerprint: privateKey.getFingerprint().toUpperCase(),
keyID: privateKey.getKeyID().toHex().toUpperCase(),
name: address.name,
email: address.address,
primaryUserId: primaryUserId,
allUserIds: allUserIds,
creationTime: privateKey.getCreationTime()
};
};