Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ _Note: the private keys can and should be rotated periodically to limit the risk


### Install the GitHub Application
Once you have the GitHub Application defined, you will need to install the application on the target organization or repository/
Once you have the GitHub Application defined, you will need to install the application on the target organization, user, or repository/
repositories that you want it to have access to. These will be any repositories that you want to gather information
from or want the application to modify as per the scopes that were defined when the application was installed.

_Note: The GitHub Application will need to be installed on the organization and or repository that you are executing
_Note: The GitHub Application will need to be installed on the organization or user account, or repository that you are executing
the GitHub Actions workflow from, as the implementation requires this to be able to generate the access tokens_.


Expand All @@ -76,6 +76,7 @@ generates for you or Base64 encode it in the secret.
* `application_private_key`: A private key generated for the GitHub Application so that you can authenticate (PEM format or base64 encoded)
* `permissions`: The optional limited permissions to request, specifying this allows you to request a subset of the permissions for the underlying GitHub Application. Defaults to all permissions available to the GitHub Application when not specified. Must be provided in a comma separated list of token permissions e.g. `issues:read, secrets:write, packages:read`
* `organization`: An optional organization name if the GitHub Application is installed at the Organization level (instead of the repository).
* `user`: An optional user name if the GitHub Application is installed on the user's account (instead of the repository).
* `github_api_base_url`: An optional URL to the GitHub API, this will be read and loaded from the runner environment by default, but you might be bridging access to a secondary GHES instance or from GHES to GHEC, you can utilize this to make sure the Octokit library is talking to the right GitHub instance.
* `https_proxy`: An optional proxy to use for connecting with the GitHub instance. If the runner has `HTTP_PROXY` or `HTTPS_PROXY` specified as environment variables it will attempt to use those if this parameter is not specified.

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ inputs:
description: The GitHub Organization to get the application installation for, if not specified will use the current repository instead
required: false

user:
description: The GitHub User to get the application installation for, if not specified will use the current repository instead
required: false

github_api_base_url:
description: The GitHub API base URL to use, no needed it working within the same GitHub instance as the workflow as it will get picked up from the environment
required: false
Expand Down
11 changes: 11 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ async function run() {

try {
const userSpecifiedOrganization = core.getInput('organization')
, userSpecifiedUser = core.getInput('user')
, repository = process.env['GITHUB_REPOSITORY']
, repoParts = repository.split('/')
;
Expand All @@ -37,6 +38,16 @@ async function run() {
} else {
fail(null, `GitHub Application is not installed on the specified organization: ${userSpecifiedOrganization}`);
}
} else if (userSpecifiedUser) {
core.info(`Obtaining application installation for user: ${userSpecifiedUser}`);

// use the user specified to get the installation
const installation = await app.getUserInstallation(userSpecifiedUser);
if (installation && installation.id) {
installationId = installation.id;
} else {
fail(null, `GitHub Application is not installed on the specified user: ${userSpecifiedUser}`);
}
} else {
core.info(`Obtaining application installation for repository: ${repository}`);

Expand Down
13 changes: 13 additions & 0 deletions lib/github-application.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ class GitHubApplication {
});
}

getUserInstallation(username) {
return this.client.rest.apps.getUserInstallation({
username: username
}).catch(err => {
throw new Error(`Failed to resolve installation of application for user ${username}; ${err.message}`);
}).then(resp => {
if (resp.status === 200) {
return resp.data;
}
throw new Error(`Unexpected status code ${resp.status}; ${resp.data}`);
});
}

getInstallationAccessToken(installationId, permissions) {
if (!installationId) {
throw new Error('GitHub Application installation id must be provided');
Expand Down
18 changes: 13 additions & 5 deletions lib/github-application.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ describe('GitHubApplication', () => {
expect(data).to.have.property('permissions');
});

it('should be able to get installation for a user', async () => {
const data = await app.getUserInstallation(
testValues.getTestUser(TEST_APPLICATION_NAME)
);

expect(data).to.have.property('id');
expect(data).to.have.property('permissions');
});

it('should fetch the requested permissions (read)', async () => {
const data = await app.getOrganizationInstallation(
testValues.getTestOrganization(TEST_APPLICATION_NAME)
Expand Down Expand Up @@ -196,12 +205,11 @@ describe('GitHubApplication', () => {
});


it('should be able to get access token for a repository installation', async () => {
const repoInstall = await app.getRepositoryInstallation(
testValues.getTestRepositoryOwner(TEST_APPLICATION_NAME),
testValues.getTestRepository(TEST_APPLICATION_NAME)
it('should be able to get access token for a user installation', async () => {
const userInstall = await app.getUserInstallation(
testValues.getTestUser(TEST_APPLICATION_NAME)
)
, accessToken = await app.getInstallationAccessToken(repoInstall.id)
, accessToken = await app.getInstallationAccessToken(userInstall.id)
;
expect(accessToken).to.have.property('token');

Expand Down
4 changes: 4 additions & 0 deletions test/test-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ module.exports = {
getTestOrganization: function(appName) {
return getAppTestValue(appName, 'org');
},

getTestUser: function(appName) {
return getAppTestValue(appName, 'user');
}
}

function loadData() {
Expand Down