From f649763cc04adb390973bf224b64d2a801687015 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Thu, 25 Jun 2026 16:01:57 -0400 Subject: [PATCH 01/11] setup sharepoint lookup --- .github/actions/check-for-CLA/index.js | 133 ++++++++++++++++----- .github/actions/check-for-CLA/package.json | 2 - .github/workflows/cla.yml | 4 +- 3 files changed, 105 insertions(+), 34 deletions(-) diff --git a/.github/actions/check-for-CLA/index.js b/.github/actions/check-for-CLA/index.js index bb39551976a1..479065c37898 100644 --- a/.github/actions/check-for-CLA/index.js +++ b/.github/actions/check-for-CLA/index.js @@ -1,7 +1,6 @@ import { Octokit } from "@octokit/core"; -import { google } from "googleapis"; import Handlebars from "handlebars"; -import fs from "fs-extra"; +import fs from "node:fs"; import { dirname, join } from "path"; import { fileURLToPath } from "url"; @@ -13,50 +12,112 @@ const PULL_REQUST_INFO = { gitHubToken: process.env.GITHUB_TOKEN, }; -const GOOGLE_SHEETS_INFO = { - APIKeys: process.env.GOOGLE_KEYS, - individualCLASheetId: process.env.INDIVIDUAL_CLA_SHEET_ID, - corporateCLASheetId: process.env.CORPORATE_CLA_SHEET_ID, +const parseMicrosoftGraphInfo = () => { + const configJson = process.env.MICROSOFT_GRAPH_INFO_JSON; + if (!configJson) { + return {}; + } + + let parsedConfig; + try { + parsedConfig = JSON.parse(configJson); + } catch { + throw new Error("MICROSOFT_GRAPH_INFO_JSON is not valid JSON."); + } + + return { + tenantId: parsedConfig.tenantId, + clientId: parsedConfig.clientId, + clientSecret: parsedConfig.clientSecret, + siteId: parsedConfig.siteId, + driveId: parsedConfig.driveId, + individualWorkbookItemId: parsedConfig.individualWorkbookItemId, + individualTableName: parsedConfig.individualTableName ?? "CLA_Individual", + individualColumnName: + parsedConfig.individualColumnName ?? "GitHub Username", + corporateWorkbookItemId: parsedConfig.corporateWorkbookItemId, + corporateTableName: parsedConfig.corporateTableName ?? "CLA_Corporate", + corporateColumnName: parsedConfig.corporateColumnName ?? "Schedule A", + }; }; +const MICROSOFT_GRAPH_INFO = parseMicrosoftGraphInfo(); + const CONTRIBUTORS_URL = "https://github.com/CesiumGS/cesium/blob/main/CONTRIBUTORS.md"; -const getGoogleSheetsApiClient = async () => { - const googleConfigFilePath = "GoogleConfig.json"; - fs.writeFileSync(googleConfigFilePath, GOOGLE_SHEETS_INFO.APIKeys); +const getGraphAccessToken = async () => { + const tokenUrl = `https://login.microsoftonline.com/${MICROSOFT_GRAPH_INFO.tenantId}/oauth2/v2.0/token`; - const auth = new google.auth.GoogleAuth({ - keyFile: googleConfigFilePath, - scopes: ["https://www.googleapis.com/auth/spreadsheets"], + const body = new URLSearchParams({ + grant_type: "client_credentials", + client_id: MICROSOFT_GRAPH_INFO.clientId, + client_secret: MICROSOFT_GRAPH_INFO.clientSecret, + scope: "https://graph.microsoft.com/.default", }); - const googleAuthClient = await auth.getClient(); - return google.sheets({ version: "v4", auth: googleAuthClient }); -}; + const response = await fetch(tokenUrl, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body, + }); -const getValuesFromGoogleSheet = async (sheetId, cellRanges) => { - const googleSheetsApi = await getGoogleSheetsApiClient(); + if (!response.ok) { + throw new Error( + `Failed to obtain Microsoft Graph access token (${response.status}).`, + ); + } + + const tokenResponse = await response.json(); + return tokenResponse.access_token; +}; - return googleSheetsApi.spreadsheets.values.get({ - spreadsheetId: sheetId, - range: cellRanges, +const getValuesFromTableColumnValues = async ( + workbookItemId, + tableName, + columnName, +) => { + const accessToken = await getGraphAccessToken(); + + const table = encodeURIComponent(tableName); + const column = encodeURIComponent(columnName); + + const url = + `https://graph.microsoft.com/v1.0/sites/${MICROSOFT_GRAPH_INFO.siteId}` + + `/drives/${MICROSOFT_GRAPH_INFO.driveId}` + + `/items/${workbookItemId}` + + `/workbook/tables/${table}/columns/${column}/range`; + + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json", + }, }); + + if (!response.ok) { + throw new Error(`Failed to read Excel workbook range (${response.status})`); + } + + const workbookResponse = await response.json(); + return workbookResponse.values ?? []; }; const checkIfIndividualCLAFound = async () => { - const response = await getValuesFromGoogleSheet( - GOOGLE_SHEETS_INFO.individualCLASheetId, - "D2:D", + const rows = await getValuesFromTableColumnValues( + MICROSOFT_GRAPH_INFO.individualWorkbookItemId, + MICROSOFT_GRAPH_INFO.individualTableName, + MICROSOFT_GRAPH_INFO.individualColumnName, ); - const rows = response.data.values; for (let i = 0; i < rows.length; i++) { if (rows[i].length === 0) { continue; } - const rowUsername = rows[i][0].toLowerCase(); + const rowUsername = rows[i][0].toLowerCase() || undefined; if (PULL_REQUST_INFO.username.toLowerCase() === rowUsername) { return true; } @@ -66,12 +127,12 @@ const checkIfIndividualCLAFound = async () => { }; const checkIfCorporateCLAFound = async () => { - const response = await getValuesFromGoogleSheet( - GOOGLE_SHEETS_INFO.corporateCLASheetId, - "H2:H", + const rows = await getValuesFromTableColumnValues( + MICROSOFT_GRAPH_INFO.corporateWorkbookItemId, + MICROSOFT_GRAPH_INFO.corporateTableName, + MICROSOFT_GRAPH_INFO.corporateColumnName, ); - const rows = response.data.values; for (let i = 0; i < rows.length; i++) { if (rows[i].length === 0) { continue; @@ -162,6 +223,20 @@ const main = async () => { let hasSignedCLA; let errorFoundOnCLACheck; + if ( + !MICROSOFT_GRAPH_INFO.tenantId || + !MICROSOFT_GRAPH_INFO.clientId || + !MICROSOFT_GRAPH_INFO.clientSecret || + !MICROSOFT_GRAPH_INFO.siteId || + !MICROSOFT_GRAPH_INFO.driveId || + !MICROSOFT_GRAPH_INFO.individualWorkbookItemId || + !MICROSOFT_GRAPH_INFO.corporateWorkbookItemId + ) { + throw new Error( + "Missing required Microsoft Graph environment variables for CLA lookup.", + ); + } + try { hasSignedCLA = await checkIfUserHasSignedAnyCLA(); } catch (error) { diff --git a/.github/actions/check-for-CLA/package.json b/.github/actions/check-for-CLA/package.json index d04d9c10a6d3..ebec5e6cadd6 100644 --- a/.github/actions/check-for-CLA/package.json +++ b/.github/actions/check-for-CLA/package.json @@ -4,8 +4,6 @@ "main": "index.js", "dependencies": { "@octokit/core": "^6.1.2", - "fs-extra": "^11.2.0", - "googleapis": "^137.1.0", "handlebars": "^4.7.8" }, "type": "module", diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index a3d152a86538..023cdcfe3262 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -24,6 +24,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PULL_REQUEST_ID: ${{ github.event.number }} - GOOGLE_KEYS: ${{ secrets.GOOGLE_KEYS }} - INDIVIDUAL_CLA_SHEET_ID: ${{ secrets.INDIVIDUAL_CLA_SHEET_ID }} - CORPORATE_CLA_SHEET_ID: ${{ secrets.CORPORATE_CLA_SHEET_ID }} + MICROSOFT_GRAPH_INFO_JSON: ${{ secrets.MICROSOFT_GRAPH_INFO_JSON }} From ff89bc175056d1565d5c80de1b073ee6855167bc Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Tue, 28 Jul 2026 16:29:11 -0400 Subject: [PATCH 02/11] add microsoft cla forms --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 944352e97f9b..77c3520ead2b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,8 +55,8 @@ We love pull requests. We strive to promptly review them, provide feedback, and Before we can review a pull request, we require a signed Contributor License Agreement. There is a CLA for: -- [individuals](https://docs.google.com/forms/d/e/1FAIpQLScU-yvQdcdjCFHkNXwdNeEXx5Qhu45QXuWX_uF5qiLGFSEwlA/viewform) and -- [corporations](https://docs.google.com/forms/d/e/1FAIpQLSeYEaWlBl1tQEiegfHMuqnH9VxyfgXGyIw13C2sN7Fj3J3GVA/viewform). +- [individuals](https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=MpZ-Bkzq2U6ebeKUlW4oS78iG8J34U1LhszEWj2jT9hUMzlEQkw0MkdWR1NMOVlEWkFFRDEzRzdMQiQlQCN0PWcu) and +- [corporations](https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=MpZ-Bkzq2U6ebeKUlW4oS78iG8J34U1LhszEWj2jT9hURDVaWTQ5MUdSN0haVzhYTFRFMVNQVU85OCQlQCN0PWcu). This only needs to be completed once, and enables contributions to all of the projects under the [CesiumGS](https://github.com/CesiumGS) organization, including CesiumJS. The CLA ensures you retain copyright to your contributions, and provides us the right to use, modify, and redistribute your contributions using the [Apache 2.0 License](LICENSE.md). From 896192f474d8f955856229564d04d43cab5d2890 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 29 Jul 2026 16:01:37 -0400 Subject: [PATCH 03/11] add credential rotation reminder cron job --- .github/workflows/cla-rotation-reminder.yml | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/cla-rotation-reminder.yml diff --git a/.github/workflows/cla-rotation-reminder.yml b/.github/workflows/cla-rotation-reminder.yml new file mode 100644 index 000000000000..98ba0b57c87c --- /dev/null +++ b/.github/workflows/cla-rotation-reminder.yml @@ -0,0 +1,57 @@ +name: "CLA credential rotation reminder" + +on: + schedule: + # 12:00 UTC on July 1 every year + - cron: "0 12 1 7 *" + workflow_dispatch: + +jobs: + create-rotation-issue: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Create annual CLA rotation issue + uses: actions/github-script@v8 + with: + script: | + const year = new Date().getUTCFullYear(); + const title = `Rotate CLA workflow credentials for ${year}`; + const label = "priority - next release"; + + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + per_page: 100, + }); + + const alreadyExists = issues.some((issue) => issue.title === title); + if (alreadyExists) { + core.info(`Issue already exists: ${title}`); + return; + } + + const body = [ + "## Annual CLA workflow credential rotation", + "", + "Rotate the Microsoft Graph credentials used by the CLA check workflow (`.github/workflows/cla.yml`).", + "", + "### Checklist", + "- [ ] Rotate Azure app client secret used by `MICROSOFT_GRAPH_INFO_JSON`", + "- [ ] Update `MICROSOFT_GRAPH_INFO_JSON` GitHub secret with new credential values", + "- [ ] Validate `.github/actions/check-for-CLA/index.js` runs successfully in CI", + "- [ ] Confirm new PRs receive CLA check comment/label behavior", + "", + "_Created automatically on July 1._", + ].join("\n"); + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + labels: [label], + }); + core.info(`Created issue with label: ${label}`); From 3d370f9ca228f945d0cadffbc06d7b52bbfcc847 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 12 Aug 2026 12:16:45 -0400 Subject: [PATCH 04/11] update cla form links --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77c3520ead2b..6af26084ace6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,8 +55,8 @@ We love pull requests. We strive to promptly review them, provide feedback, and Before we can review a pull request, we require a signed Contributor License Agreement. There is a CLA for: -- [individuals](https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=MpZ-Bkzq2U6ebeKUlW4oS78iG8J34U1LhszEWj2jT9hUMzlEQkw0MkdWR1NMOVlEWkFFRDEzRzdMQiQlQCN0PWcu) and -- [corporations](https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=MpZ-Bkzq2U6ebeKUlW4oS78iG8J34U1LhszEWj2jT9hURDVaWTQ5MUdSN0haVzhYTFRFMVNQVU85OCQlQCN0PWcu). +- [individuals](https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=MpZ-Bkzq2U6ebeKUlW4oSwLq-QNnfjxPgQhu1KdM4KxUQk43SlM5Q1NWQ1dGTVoyWjMzT1dCSk5GSy4u) and +- [corporations](https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=MpZ-Bkzq2U6ebeKUlW4oSwLq-QNnfjxPgQhu1KdM4KxURVVPRFNBRkFEV0JSR1hQR0Q4SkxDM01ROC4u). This only needs to be completed once, and enables contributions to all of the projects under the [CesiumGS](https://github.com/CesiumGS) organization, including CesiumJS. The CLA ensures you retain copyright to your contributions, and provides us the right to use, modify, and redistribute your contributions using the [Apache 2.0 License](LICENSE.md). From 9691f202d76113c630848606f00cc07d8b466bc6 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 12 Aug 2026 12:37:42 -0400 Subject: [PATCH 05/11] update git ignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index e454997d884a..e4124c25e1ec 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,3 @@ yarn.lock .idea/workspace.xml .idea/tasks.xml .idea/shelf - -# Used in the CLA checking GitHub workflow -GoogleConfig.json From 60b739d5350b3e55d6bb38967c22424834c95766 Mon Sep 17 00:00:00 2001 From: Luke McKinstry Date: Wed, 19 Aug 2026 10:55:33 -0400 Subject: [PATCH 06/11] Update .github/actions/check-for-CLA/index.js Co-authored-by: Gabby Getz --- .github/actions/check-for-CLA/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/check-for-CLA/index.js b/.github/actions/check-for-CLA/index.js index 479065c37898..1cc6616170eb 100644 --- a/.github/actions/check-for-CLA/index.js +++ b/.github/actions/check-for-CLA/index.js @@ -117,7 +117,10 @@ const checkIfIndividualCLAFound = async () => { continue; } - const rowUsername = rows[i][0].toLowerCase() || undefined; + let rowUsername; + if (rows[i][0] && rows[i][0].length > 0) { + rowUsername = rows[i][0].toLowerCase(); + } if (PULL_REQUST_INFO.username.toLowerCase() === rowUsername) { return true; } From 53dbea4af140c7abe78ec31b8d070c298c8edcda Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 19 Aug 2026 10:56:47 -0400 Subject: [PATCH 07/11] throw error when env var is missing --- .github/actions/check-for-CLA/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check-for-CLA/index.js b/.github/actions/check-for-CLA/index.js index 1cc6616170eb..b3e0a9809381 100644 --- a/.github/actions/check-for-CLA/index.js +++ b/.github/actions/check-for-CLA/index.js @@ -15,7 +15,7 @@ const PULL_REQUST_INFO = { const parseMicrosoftGraphInfo = () => { const configJson = process.env.MICROSOFT_GRAPH_INFO_JSON; if (!configJson) { - return {}; + throw new Error("MICROSOFT_GRAPH_INFO_JSON not found."); } let parsedConfig; From b2a6bd0a02c9b9f40f80450b822542d0ef86ca7f Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 19 Aug 2026 11:56:04 -0400 Subject: [PATCH 08/11] add readme --- .github/actions/check-for-CLA/README.md | 130 ++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 .github/actions/check-for-CLA/README.md diff --git a/.github/actions/check-for-CLA/README.md b/.github/actions/check-for-CLA/README.md new file mode 100644 index 000000000000..10bbb3289889 --- /dev/null +++ b/.github/actions/check-for-CLA/README.md @@ -0,0 +1,130 @@ +# CLA Check Automation + +This document explains the Contributor License Agreement (CLA) automation process used by CesiumJS CI. + +It covers: + +- the general CLA architecture, +- when to rotate/update credentials, +- and where maintainers can access the required credentials. + +See also: https://github.com/CesiumGS/alkali/issues/30 + +## CLA Process Overview + +When a pull request is opened, the CLA workflow runs and checks whether the PR author has a signed CLA on file. + +- Workflow: `.github/workflows/cla.yml` +- Script: `.github/actions/check-for-CLA/index.js` +- Trigger: `pull_request_target` on `opened` + +The script looks up the GitHub username in Microsoft-hosted CLA data and then: + +- posts a confirmation comment when a CLA is found, or +- posts CLA instructions and applies `PR - Needs Signed CLA` when a CLA is not found. + +## Architecture Summary + +### SharePoint Resources + +```text +┌──────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐ +│ Microsoft Forms │ ───> │ Power Automate │ ───> │ Excel Workbook │ +│ Survey │ │ Flow │ │ (SharePoint) │ +└──────────────────┘ └──────────────────┘ └─────────────────────────┘ +``` + +- CLA data is stored in Excel workbooks in OneDrive/SharePoint. +- Workbooks are accessible from the file tree in OneDrive/SharePoint: + - https://bentley-my.sharepoint.com/ + - https://bentley.sharepoint.com/:f:/r/sites/Platform/Shared%20Documents/CLAs +- Permissions: + - Platform members and visitors can view. + - Limited teams/users (eg. CesiumJS and Cesium Native maintainers) can edit. + +#### Forms and Flow ownership + +- Microsoft Forms survey is managed at https://forms.office.com/ +- Power Automate flow is managed at https://make.powerautomate.com/ +- We use a **dedicated service account** as the owner for both: + 1. Microsoft Forms survey + 2. Power Automate flow + +#### Flow behavior + +1. Trigger: _When a new response is submitted_ +2. Action: _Get response details_ +3. Action: _Add a row into a table (Excel Online Business)_ + +### GitHub Actions Script Access to SharePoint Resources + +```text +┌──────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐ +│ GitHub Actions │ ───> │ Microsoft Graph │ ───> │ Excel Workbook │ +│ │ │ API │ │ (Microsoft SharePoint) │ +└──────────────────┘ └──────────────────┘ └─────────────────────────┘ +``` + +- The CI script accesses SharePoint via the Microsoft Graph REST API: + - https://learn.microsoft.com/en-us/graph/ +- This is done using a Microsoft Entra app registration. +- The runtime auth/config data (including app credentials and workbook metadata) is stored in a single GitHub Actions secret: + - `MICROSOFT_GRAPH_INFO_JSON` +- The script exchanges these values for an OAuth access token at runtime before reading workbook tables. + +## Credential and Metadata Contents + +`MICROSOFT_GRAPH_INFO_JSON` contains the values required for Graph auth and workbook lookups, including: + +- `tenantId` +- `clientId` +- `clientSecret` +- `siteId` +- `driveId` +- `individualWorkbookItemId` +- `individualTableName` +- `individualColumnName` +- `corporateWorkbookItemId` +- `corporateTableName` +- `corporateColumnName` + +## When to Update Credentials + +Update credentials and/or metadata whenever any of the following occurs: + +1. Client secret rotation or expiration for the Entra app + - File a request with Bentley IT to rotate secret in the Entra app +2. Service account ownership changes +3. SharePoint site/drive/workbook item changes +4. Workbook table or column name changes +5. CI authentication failures or CLA lookup failures + +Additionally, a yearly reminder workflow creates a tracking issue each July 1: + +- Workflow: `.github/workflows/cla-rotation-reminder.yml` +- Label: `priority - next release` +- Follow process outlined above to rotate secret in the Entra app +- Request token expiration be set to after the July 1 the following + +## How to Access Credentials + +Credential access is limited to maintainers with appropriate permissions. + +- GitHub Actions secrets +- Repository settings: https://github.com/CesiumGS/cesium/settings/secrets/actions +- Secret name: `MICROSOFT_GRAPH_INFO_JSON` + +- Backup storage + - Backup copies are stored in Bitwarden. + - Dedicated service account for Forms/Flow ownership: `cesium-cla-automation@bentley.com` + - Graph credential entry: `CLA Automation - Microsoft Graph Credentials for GitHub CI` + +## Rotation / Validation Checklist + +After any credential update: + +1. Update `MICROSOFT_GRAPH_INFO_JSON` in GitHub Actions secrets. +2. Open a test PR (or re-run the CLA workflow) to validate behavior. +3. Verify signed users are recognized. +4. Verify unsigned users receive CLA instructions and label. +5. Confirm backup credentials in Bitwarden are current. From 347cb79961ff8d7f37d9da14099e9d0753cd7453 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 19 Aug 2026 12:00:09 -0400 Subject: [PATCH 09/11] link cla ref docs in rotation reminder workflow --- .github/workflows/cla-rotation-reminder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cla-rotation-reminder.yml b/.github/workflows/cla-rotation-reminder.yml index 98ba0b57c87c..c3ec5c7fb157 100644 --- a/.github/workflows/cla-rotation-reminder.yml +++ b/.github/workflows/cla-rotation-reminder.yml @@ -37,6 +37,8 @@ jobs: "## Annual CLA workflow credential rotation", "", "Rotate the Microsoft Graph credentials used by the CLA check workflow (`.github/workflows/cla.yml`).", + "Reference documentation: `.github/actions/check-for-CLA/README.md`", + "https://github.com/CesiumGS/cesium/blob/main/.github/actions/check-for-CLA/README.md", "", "### Checklist", "- [ ] Rotate Azure app client secret used by `MICROSOFT_GRAPH_INFO_JSON`", From 3a1f7aa24e5cd90f6673e774cade46d7d9e48d50 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 19 Aug 2026 12:06:17 -0400 Subject: [PATCH 10/11] prettier --- .github/actions/check-for-CLA/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check-for-CLA/index.js b/.github/actions/check-for-CLA/index.js index b3e0a9809381..425bdcfaff0b 100644 --- a/.github/actions/check-for-CLA/index.js +++ b/.github/actions/check-for-CLA/index.js @@ -119,7 +119,7 @@ const checkIfIndividualCLAFound = async () => { let rowUsername; if (rows[i][0] && rows[i][0].length > 0) { - rowUsername = rows[i][0].toLowerCase(); + rowUsername = rows[i][0].toLowerCase(); } if (PULL_REQUST_INFO.username.toLowerCase() === rowUsername) { return true; From 7ac32c91ca6f8b516cc91ff3f32608568045ccb9 Mon Sep 17 00:00:00 2001 From: ggetz Date: Tue, 25 Aug 2026 10:54:02 -0400 Subject: [PATCH 11/11] Edits for brevity, clarity --- .github/actions/check-for-CLA/README.md | 87 ++++++++----------------- 1 file changed, 27 insertions(+), 60 deletions(-) diff --git a/.github/actions/check-for-CLA/README.md b/.github/actions/check-for-CLA/README.md index 10bbb3289889..9a156ab3d0b2 100644 --- a/.github/actions/check-for-CLA/README.md +++ b/.github/actions/check-for-CLA/README.md @@ -1,22 +1,19 @@ # CLA Check Automation -This document explains the Contributor License Agreement (CLA) automation process used by CesiumJS CI. +This document explains the [Contributor License Agreement (CLA)](../../../CONTRIBUTING.md#contributor-license-agreement-cla) automation process used by CesiumJS CI. It covers: - the general CLA architecture, -- when to rotate/update credentials, +- when to update credentials, - and where maintainers can access the required credentials. -See also: https://github.com/CesiumGS/alkali/issues/30 - ## CLA Process Overview When a pull request is opened, the CLA workflow runs and checks whether the PR author has a signed CLA on file. -- Workflow: `.github/workflows/cla.yml` -- Script: `.github/actions/check-for-CLA/index.js` -- Trigger: `pull_request_target` on `opened` +- **Workflow**: [`.github/workflows/cla.yml`](../../workflows/cla.yml) +- **Script**: [`.github/actions/check-for-CLA/index.js`](./index.js) The script looks up the GitHub username in Microsoft-hosted CLA data and then: @@ -34,23 +31,17 @@ The script looks up the GitHub username in Microsoft-hosted CLA data and then: └──────────────────┘ └──────────────────┘ └─────────────────────────┘ ``` -- CLA data is stored in Excel workbooks in OneDrive/SharePoint. -- Workbooks are accessible from the file tree in OneDrive/SharePoint: - - https://bentley-my.sharepoint.com/ - - https://bentley.sharepoint.com/:f:/r/sites/Platform/Shared%20Documents/CLAs -- Permissions: - - Platform members and visitors can view. - - Limited teams/users (eg. CesiumJS and Cesium Native maintainers) can edit. +We use a **dedicated service account**, `cesium-cla-automation@bentley.com`, as the owner for both: + +1. [Microsoft Forms surveys](https://forms.office.com/) +2. [Power Automate flow](https://make.powerautomate.com/) -#### Forms and Flow ownership +CLA data is stored in [Excel workbooks in OneDrive/SharePoint](https://bentley.sharepoint.com/:f:/r/sites/Platform/Shared%20Documents/CLAs) -- Microsoft Forms survey is managed at https://forms.office.com/ -- Power Automate flow is managed at https://make.powerautomate.com/ -- We use a **dedicated service account** as the owner for both: - 1. Microsoft Forms survey - 2. Power Automate flow +- Platform members and visitors can **view** +- Limited teams/users (eg. CesiumJS and Cesium Native maintainers) can **edit** -#### Flow behavior +#### Power Automate Flow behavior 1. Trigger: _When a new response is submitted_ 2. Action: _Get response details_ @@ -65,14 +56,7 @@ The script looks up the GitHub username in Microsoft-hosted CLA data and then: └──────────────────┘ └──────────────────┘ └─────────────────────────┘ ``` -- The CI script accesses SharePoint via the Microsoft Graph REST API: - - https://learn.microsoft.com/en-us/graph/ -- This is done using a Microsoft Entra app registration. -- The runtime auth/config data (including app credentials and workbook metadata) is stored in a single GitHub Actions secret: - - `MICROSOFT_GRAPH_INFO_JSON` -- The script exchanges these values for an OAuth access token at runtime before reading workbook tables. - -## Credential and Metadata Contents +The CI script accesses SharePoint data via the [Microsoft Graph REST API](https://learn.microsoft.com/en-us/graph/) using a Microsoft Entra app registration. The app credentials and workbook metadata are configured in a JSON string stored in a [GitHub Actions secret](https://github.com/CesiumGS/cesium/settings/secrets/actions). `MICROSOFT_GRAPH_INFO_JSON` contains the values required for Graph auth and workbook lookups, including: @@ -88,43 +72,26 @@ The script looks up the GitHub username in Microsoft-hosted CLA data and then: - `corporateTableName` - `corporateColumnName` -## When to Update Credentials - -Update credentials and/or metadata whenever any of the following occurs: - -1. Client secret rotation or expiration for the Entra app - - File a request with Bentley IT to rotate secret in the Entra app -2. Service account ownership changes -3. SharePoint site/drive/workbook item changes -4. Workbook table or column name changes -5. CI authentication failures or CLA lookup failures +If a Sharepoint resource is migrated to a new location, or a workbook table or column name changes, `MICROSOFT_GRAPH_INFO_JSON` and [`.github/actions/check-for-CLA/index.js`](./index.js) must be updated to reflect changes. -Additionally, a yearly reminder workflow creates a tracking issue each July 1: - -- Workflow: `.github/workflows/cla-rotation-reminder.yml` -- Label: `priority - next release` -- Follow process outlined above to rotate secret in the Entra app -- Request token expiration be set to after the July 1 the following - -## How to Access Credentials +## Accessing Credentials Credential access is limited to maintainers with appropriate permissions. -- GitHub Actions secrets -- Repository settings: https://github.com/CesiumGS/cesium/settings/secrets/actions -- Secret name: `MICROSOFT_GRAPH_INFO_JSON` - -- Backup storage - - Backup copies are stored in Bitwarden. +- **GitHub Actions secrets**: Accessable by users with the CesiumJS **maintainer** role in [**Repository settings / Actions secrets and variables**](https://github.com/CesiumGS/cesium/settings/secrets/actions). Backup copies are stored in Bitwarden. +- **Sharepoint access**: Credentials are shared in Bitwarden. - Dedicated service account for Forms/Flow ownership: `cesium-cla-automation@bentley.com` - Graph credential entry: `CLA Automation - Microsoft Graph Credentials for GitHub CI` -## Rotation / Validation Checklist +## Updating Credentials + +Entra app access credentials are configured to expire after one year. A yearly reminder workflow, [`.github/workflows/cla-rotation-reminder.yml`](../../workflows/cla-rotation-reminder.yml) creates a tracking issue on July 1. -After any credential update: +Follow process outlined in the issue to rotate the client secret in the Entra app: -1. Update `MICROSOFT_GRAPH_INFO_JSON` in GitHub Actions secrets. -2. Open a test PR (or re-run the CLA workflow) to validate behavior. -3. Verify signed users are recognized. -4. Verify unsigned users receive CLA instructions and label. -5. Confirm backup credentials in Bitwarden are current. +1. File a request with Bentley IT to rotate the client secret in the Entra app. Request token expiration be set to **August 1 the following year**. +2. Update the value of `MICROSOFT_GRAPH_INFO_JSON` in GitHub Actions secrets. +3. (Optional) Open a test PR or re-run the CLA workflow to validate behavior. + - Verify signed users are recognized. + - Verify unsigned users receive CLA instructions and label. +4. Confirm backup credentials in Bitwarden are current.