371 |
372 |
373 | ## License
374 |
375 | Contributor License Agreement assistant
376 |
377 | Licensed under the Apache License, Version 2.0 (the "License");
378 | you may not use this file except in compliance with the License.
379 | You may obtain a copy of the License at
380 |
381 | http://www.apache.org/licenses/LICENSE-2.0
382 |
383 | Unless required by applicable law or agreed to in writing, software
384 | distributed under the License is distributed on an "AS IS" BASIS,
385 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
386 | See the License for the specific language governing permissions and
387 | limitations under the License.
388 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Vulnerabilities
2 |
3 | The CLA Assistant is built with security and data privacy in mind to ensure your data is safe.
4 |
5 | ## Reporting
6 |
7 | We are grateful for security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline.
8 |
9 | **Please do not report security vulnerabilities directly on GitHub. GitHub Issues can be publicly seen and therefore would result in a direct disclosure.**
10 |
11 | For reporting a vulnerability, please use the Vulnerability Report Form for Security Researchers on [SAP Trust Center](https://www.sap.com/about/trust-center/security/incident-management.html).
12 | Please address questions about data privacy, security concepts, and other media requests using the Vulnerability Report Form for Security Researchers on SAP Trust Center.
13 |
14 |
15 |
16 | ## Disclosure Handling
17 |
18 | SAP is committed to timely review and respond to your request. The resolution of code defects will be handled by a dedicated group of security experts and prepared in a private GitHub repository. The project will inform the public about resolved security vulnerabilities via GitHub Security Advisories.
19 |
--------------------------------------------------------------------------------
/__tests__/main.test.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core'
2 | import * as github from '@actions/github'
3 | import { context } from '@actions/github'
4 | import { getclas } from '../src/checkcla'
5 | import { lockPullRequest } from '../src/pullRequestLock'
6 | import { run } from '../src/main'
7 | import { mocked } from 'ts-jest/utils'
8 |
9 | jest.mock('@actions/core')
10 | jest.mock('@actions/github')
11 | jest.mock('../src/pullRequestLock')
12 | jest.mock('../src/checkcla')
13 | const mockedGetClas = mocked(getclas)
14 | const mockedLockPullRequest = mocked(lockPullRequest)
15 |
16 |
17 | describe('Pull request event', () => {
18 |
19 | beforeEach(async () => {
20 | // @ts-ignore
21 | github.context = {
22 | eventName: 'pull_request',
23 | ref: 'refs/pull/232/merge',
24 | workflow: 'CLA Assistant',
25 | action: 'ibakshaygithub-action-1',
26 | actor: 'ibakshay',
27 | payload: {
28 | action: 'closed',
29 | number: '1',
30 | pull_request: {
31 | number: 1,
32 | title: 'test',
33 | user: {
34 | login: 'ibakshay',
35 | },
36 | },
37 | repository: {
38 | name: 'auto-assign',
39 | owner: {
40 | login: 'ibakshay',
41 | },
42 | },
43 | },
44 | repo: {
45 | owner: 'ibakshay',
46 | repo: 'auto-assign',
47 | },
48 | issue: {
49 | owner: 'kentaro-m',
50 | repo: 'auto-assign',
51 | number: 1,
52 | },
53 | sha: ''
54 | }
55 |
56 | }
57 | )
58 |
59 | test('the lockPullRequest method should be called if there is a pull request merge/closed', async () => {
60 |
61 | await run()
62 | expect(mockedLockPullRequest).toHaveBeenCalled()
63 |
64 |
65 | })
66 |
67 | test('the checkcla method should not called if there is a pull request merge/closed', async () => {
68 |
69 | await run()
70 | expect(mockedGetClas).not.toHaveBeenCalled()
71 | })
72 |
73 | test('the lockPullRequest method should not be called if there is a pull request opened', async () => {
74 |
75 | github.context.payload.action = 'opened'
76 | await run()
77 |
78 | expect(mockedLockPullRequest).not.toHaveBeenCalled()
79 |
80 | })
81 |
82 | test('the checkcla method should be called if there is a pull request opened', async () => {
83 |
84 | github.context.payload.action = 'opened'
85 | await run()
86 | expect(mockedGetClas).toHaveBeenCalled()
87 |
88 | })
89 |
90 | test('the lockPullRequest method should not be called if there is a pull request sync', async () => {
91 |
92 | github.context.payload.action = 'synchronize'
93 |
94 | await run()
95 |
96 | expect(mockedLockPullRequest).not.toHaveBeenCalled()
97 |
98 | })
99 |
100 | test('the checkcla method should be called if there is a pull request sync', async () => {
101 | github.context.payload.action = 'synchronize'
102 | await run()
103 | expect(mockedGetClas).toHaveBeenCalled()
104 |
105 | })
106 |
107 |
108 | })
--------------------------------------------------------------------------------
/__tests__/pullRequestLock.test.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core'
2 | import * as github from '@actions/github'
3 | import { context } from '@actions/github'
4 | import { getclas } from '../src/checkcla'
5 | import { lockPullRequest } from '../src/pullRequestLock'
6 | import { run } from '../src/main'
7 | import { mocked } from 'ts-jest/utils'
8 |
9 | jest.mock('@actions/core')
10 | jest.mock('@actions/github')
11 |
12 | //const mockedLockPullRequest = mocked(lockPullRequest)
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: "CLA assistant lite"
2 | description: "An action to handle the Contributor License Agreement (CLA) and Developer Certificate of Orgin (DCO)"
3 | author: "SAP"
4 | branding:
5 | icon: "award"
6 | color: blue
7 | inputs:
8 | path-to-signatures:
9 | description: "Give a path for storing CLAs in a json file "
10 | default: "./signatures/cla.json"
11 | branch:
12 | description: "provide a branch where all the CLAs are stored"
13 | default: "master"
14 | allowlist:
15 | description: "users in the allow list don't have to sign the CLA document"
16 | default: ""
17 | remote-repository-name:
18 | description: "provide the remote repository name where all the signatures should be stored"
19 | remote-organization-name:
20 | description: "provide the remote organization name where all the signatures should be stored"
21 | path-to-document:
22 | description: "Fully qualified web link to the document - example: https://github.com/cla-assistant/github-action/blob/master/SAPCLA.md"
23 | signed-commit-message:
24 | description: "Commit message when a new contributor signs the CLA in a PR"
25 | signed-empty-commit-message:
26 | description: "Commit message when a new contributor signs the CLA (empty)"
27 | create-file-commit-message:
28 | description: "Commit message when a new file is created"
29 | custom-notsigned-prcomment:
30 | description: "Introductory message to ask new contributors to sign"
31 | custom-pr-sign-comment:
32 | description: "The signature to be committed in order to sign the CLA."
33 | custom-allsigned-prcomment:
34 | description: "pull request comment when everyone has signed, defaults to **CLA Assistant Lite** All Contributors have signed the CLA."
35 | use-dco-flag:
36 | description: "Set this to true if you want to use a dco instead of a cla"
37 | default: "false"
38 | lock-pullrequest-aftermerge:
39 | description: "Will lock the pull request after merge so that the signature the contributors cannot revoke their signature comments after merge"
40 | default: "true"
41 | suggest-recheck:
42 | description: "Controls whether or not the action's comment should suggest that users comment `recheck`."
43 | default: "true"
44 | runs:
45 | using: "node20"
46 | main: 'dist/index.js'
47 |
--------------------------------------------------------------------------------
/docs/contributors.md:
--------------------------------------------------------------------------------
1 | # Contributors
2 |
3 | ### Checkin
4 |
5 | - Do checkin source (src)
6 | - Do checkin build output (lib)
7 | - Do checkin runtime node_modules
8 | - Do not checkin devDependency node_modules (husky can help see below)
9 |
10 | ### devDependencies
11 |
12 | In order to handle correctly checking in node_modules without devDependencies, we run [Husky](https://github.com/typicode/husky) before each commit.
13 | This step ensures that formatting and checkin rules are followed and that devDependencies are excluded. To make sure Husky runs correctly, please use the following workflow:
14 |
15 | ```
16 | npm install # installs all devDependencies including Husky
17 | git add abc.ext # Add the files you've changed. This should include files in src, lib, and node_modules (see above)
18 | git commit -m "Informative commit message" # Commit. This will run Husky
19 | ```
20 |
21 | During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier) as well as pruning out devDependencies using `npm prune --production`.
22 | It will also make sure these changes are appropriately included in your commit (no further work is needed)
--------------------------------------------------------------------------------
/images/adding-clafile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contributor-assistant/github-action/ca4a40a7d1004f18d9960b404b97e5f30a505a08/images/adding-clafile.gif
--------------------------------------------------------------------------------
/images/allowlist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contributor-assistant/github-action/ca4a40a7d1004f18d9960b404b97e5f30a505a08/images/allowlist.gif
--------------------------------------------------------------------------------
/images/personal-access-token.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contributor-assistant/github-action/ca4a40a7d1004f18d9960b404b97e5f30a505a08/images/personal-access-token.gif
--------------------------------------------------------------------------------
/images/personalaccesstoken.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contributor-assistant/github-action/ca4a40a7d1004f18d9960b404b97e5f30a505a08/images/personalaccesstoken.gif
--------------------------------------------------------------------------------
/images/signature-process.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contributor-assistant/github-action/ca4a40a7d1004f18d9960b404b97e5f30a505a08/images/signature-process.gif
--------------------------------------------------------------------------------
/images/signature-storage-file.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contributor-assistant/github-action/ca4a40a7d1004f18d9960b404b97e5f30a505a08/images/signature-storage-file.gif
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | moduleFileExtensions: ['js', 'ts'],
4 | testEnvironment: 'node',
5 | testMatch: ['**/*.test.ts'],
6 | testRunner: 'jest-circus/runner',
7 | transform: {
8 | '^.+\\.ts$': 'ts-jest'
9 | },
10 | verbose: true
11 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-action",
3 | "version": "0.0.1",
4 | "description": "GitHub Action for storing CLA signatures",
5 | "main": "lib/main.js",
6 | "scripts": {
7 | "test": "jest",
8 | "build": "tsc && ncc build",
9 | "buildAndAdd": "npm run build && git add ."
10 | },
11 | "husky": {
12 | "hooks": {
13 | "pre-commit": "npm run buildAndAdd"
14 | }
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/cla-assistant/github-action.git"
19 | },
20 | "keywords": [
21 | "actions",
22 | "node",
23 | "setup"
24 | ],
25 | "dependencies": {
26 | "@actions/core": "^1.10.0",
27 | "@actions/github": "^4.0.0",
28 | "@octokit/rest": "^16.43.2",
29 | "actions-toolkit": "^2.1.0",
30 | "husky": "^4.3.8",
31 | "lodash": "^4.17.21",
32 | "node-fetch": "^3.3.0"
33 | },
34 | "devDependencies": {
35 | "@octokit/types": "8.1.1",
36 | "@types/node": "^18.11.18",
37 | "@vercel/ncc": "^0.38.0",
38 | "ts-jest": "^29.0.5",
39 | "typescript": "^4.9.4"
40 | },
41 | "author": "ibakshay",
42 | "license": "Apache-2.0",
43 | "bugs": {
44 | "url": "https://github.com/cla-assistant/github-action/issues"
45 | },
46 | "homepage": "https://github.com/cla-assistant/github-action#readme"
47 | }
48 |
--------------------------------------------------------------------------------
/src/addEmptyCommit.ts:
--------------------------------------------------------------------------------
1 | import { octokit } from './octokit'
2 | import { context } from '@actions/github'
3 |
4 | import * as core from '@actions/core'
5 | import * as input from './shared/getInputs'
6 | import { getPrSignComment } from './shared/pr-sign-comment'
7 |
8 |
9 | export async function addEmptyCommit() {
10 | const contributorName: string = context?.payload?.comment?.user?.login
11 | core.info(`Adding empty commit for ${contributorName} who has signed the CLA `)
12 |
13 | if (context.payload.comment) {
14 |
15 | //Do empty commit only when the contributor signs the CLA with the PR comment
16 | if (context.payload.comment.body.toLowerCase().trim() === getPrSignComment().toLowerCase().trim()) {
17 | try {
18 | const message = input.getSignedCommitMessage() ?
19 | input.getSignedCommitMessage().replace('$contributorName', contributorName) :
20 | ` @${contributorName} has signed the CLA `
21 | const pullRequestResponse = await octokit.pulls.get({
22 | owner: context.repo.owner,
23 | repo: context.repo.repo,
24 | pull_number: context.payload.issue!.number
25 | })
26 |
27 | const baseCommit = await octokit.git.getCommit({
28 | owner: context.repo.owner,
29 | repo: context.repo.repo,
30 | commit_sha: pullRequestResponse.data.head.sha
31 | })
32 |
33 | const tree = await octokit.git.getTree({
34 | owner: context.repo.owner,
35 | repo: context.repo.repo,
36 | tree_sha: baseCommit.data.tree.sha
37 | })
38 | const newCommit = await octokit.git.createCommit(
39 | {
40 | owner: context.repo.owner,
41 | repo: context.repo.repo,
42 | message: message,
43 | tree: tree.data.sha,
44 | parents: [pullRequestResponse.data.head.sha]
45 | }
46 | )
47 | return octokit.git.updateRef({
48 | owner: context.repo.owner,
49 | repo: context.repo.repo,
50 | ref: `heads/${pullRequestResponse.data.head.ref}`,
51 | sha: newCommit.data.sha
52 | })
53 |
54 | } catch (error) {
55 | core.error(`failed when adding empty commit with the contributor's signature name: ${error} `)
56 |
57 | }
58 | }
59 | }
60 | return
61 | }
62 |
--------------------------------------------------------------------------------
/src/checkAllowList.ts:
--------------------------------------------------------------------------------
1 | import { CommittersDetails } from './interfaces'
2 |
3 | import * as _ from 'lodash'
4 | import * as input from './shared/getInputs'
5 |
6 |
7 |
8 | function isUserNotInAllowList(committer) {
9 |
10 | const allowListPatterns: string[] = input.getAllowListItem().split(',')
11 |
12 | return allowListPatterns.filter(function (pattern) {
13 | pattern = pattern.trim()
14 | if (pattern.includes('*')) {
15 | const regex = _.escapeRegExp(pattern).split('\\*').join('.*')
16 |
17 | return new RegExp(regex).test(committer)
18 | }
19 | return pattern === committer
20 | }).length > 0
21 | }
22 |
23 | export function checkAllowList(committers: CommittersDetails[]): CommittersDetails[] {
24 | const committersAfterAllowListCheck: CommittersDetails[] = committers.filter(committer => committer && !(isUserNotInAllowList !== undefined && isUserNotInAllowList(committer.name)))
25 | return committersAfterAllowListCheck
26 | }
--------------------------------------------------------------------------------
/src/graphql.ts:
--------------------------------------------------------------------------------
1 | import { octokit } from './octokit'
2 | import { context } from '@actions/github'
3 | import { CommittersDetails } from './interfaces'
4 |
5 |
6 |
7 | export default async function getCommitters(): Promise