diff --git a/.github/workflows/browserstack-prepare-artifacts.yaml b/.github/workflows/browserstack-prepare-artifacts.yaml index 0a6c47bc..21be8218 100644 --- a/.github/workflows/browserstack-prepare-artifacts.yaml +++ b/.github/workflows/browserstack-prepare-artifacts.yaml @@ -8,6 +8,13 @@ name: Prepare Device Farm Artifacts on: workflow_call: + inputs: + head_sha: + description: Head commit SHA to check out + type: string + head_repo: + description: Head repository to check out + type: string secrets: SIGNING_KEYSTORE: description: 'Needed for signing the apk artifacts' @@ -39,8 +46,9 @@ jobs: - name: Clone the repository uses: actions/checkout@v5 with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{github.event.pull_request.head.repo.full_name}} + ref: ${{ inputs.head_sha || github.event.pull_request.head.sha }} + repository: ${{ inputs.head_repo || github.event.pull_request.head.repo.full_name }} + persist-credentials: false # Set up the variables for the tests from the secrets - name: Setup config file for e2e tests diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 049fd520..35dac2f0 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -13,8 +13,11 @@ on: description: Platform to execute on type: string default: ubuntu-latest - testrail-run-id: - description: The TestRail run ID + head_sha: + description: Head commit SHA to check out + type: string + head_repo: + description: Head repository to check out type: string secrets: SLACK_WEBHOOK: @@ -23,12 +26,6 @@ on: CODECOV_TOKEN: description: Codecov token required: true - TESTRAIL_USERNAME: - description: TestRail username - required: true - TESTRAIL_API_KEY: - description: TestRail API key - required: true jobs: build-and-test: runs-on: ubuntu-latest @@ -38,8 +35,9 @@ jobs: - name: Clone the repository uses: actions/checkout@v5 with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{github.event.pull_request.head.repo.full_name}} + ref: ${{ inputs.head_sha || github.event.pull_request.head.sha }} + repository: ${{ inputs.head_repo || github.event.pull_request.head.repo.full_name }} + persist-credentials: false # Setup JDK and cache and restore dependencies. - name: Setup JDK 17 @@ -51,20 +49,13 @@ jobs: # Execute all unit tests from all modules - name: Execute unit tests - env: - TESTRAIL_ENABLE: ${{vars.TESTRAIL_ENABLE}} - TESTRAIL_DEBUG: ${{vars.TESTRAIL_DEBUG}} - TESTRAIL_URL: ${{vars.TESTRAIL_URL}} - TESTRAIL_PROJECT_ID: ${{vars.TESTRAIL_PROJECT_ID}} - TESTRAIL_RUN_ID: ${{inputs.testrail-run-id}} - TESTRAIL_USERNAME: ${{secrets.TESTRAIL_USERNAME}} - TESTRAIL_API_KEY: ${{secrets.TESTRAIL_API_KEY}} run: | ./gradlew --rerun-tasks testDebugUnitTestCoverage --stacktrace --no-daemon # Publish test reports for the unit tests + # dorny/test-reporter needs checks:write which is unavailable for fork PRs - name: Publish test results - if: success() || failure() + if: (success() || failure()) && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) uses: dorny/test-reporter@v2 with: name: Unit tests results @@ -103,4 +94,4 @@ jobs: status: ${{ job.status }} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} - if: always() \ No newline at end of file + if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4a6dd7ba..8f9aadf3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,6 @@ name: CI -# Trigger on push or pull request on: pull_request: types: [opened, reopened, synchronize, edited] @@ -17,52 +16,114 @@ on: - master - develop -permissions: write-all + # Allows maintainers to trigger the full CI against a fork PR by commenting /run-ci + issue_comment: + types: [created] + +permissions: + contents: read + checks: write + issues: write + pull-requests: write + pages: write + id-token: write + jobs: - # Create TestRail run - create-testrail-run: - name: Create TestRail Run - uses: ./.github/workflows/create-testrail-run.yaml - secrets: - TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} - TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }} + # Resolves head SHA and repo for all event types. + # For issue_comment, also validates the /run-ci command and commenter permissions, + # and reacts with 👀 to acknowledge the trigger. + resolve-context: + name: Resolve PR context + runs-on: ubuntu-latest + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' || + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/run-ci') && + (github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR')) + outputs: + head_sha: ${{ steps.ctx.outputs.head_sha }} + head_repo: ${{ steps.ctx.outputs.head_repo }} + steps: + - name: React to /run-ci comment with 👀 + if: github.event_name == 'issue_comment' + uses: actions/github-script@v7 + with: + script: | + if (context.payload.comment.body.trim() !== '/run-ci') { + core.setFailed('Comment must be exactly "/run-ci" to trigger CI.'); + return; + } + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + + - name: Resolve head SHA and repo + id: ctx + uses: actions/github-script@v7 + with: + script: | + if (context.eventName === 'push') { + core.setOutput('head_sha', context.sha); + core.setOutput('head_repo', `${context.repo.owner}/${context.repo.repo}`); + } else if (context.eventName === 'pull_request') { + core.setOutput('head_sha', context.payload.pull_request.head.sha); + core.setOutput('head_repo', context.payload.pull_request.head.repo.full_name); + } else { + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + core.setOutput('head_sha', pr.data.head.sha); + core.setOutput('head_repo', pr.data.head.repo.full_name); + } # Build and run unit tests build-and-test: name: Build and test + needs: resolve-context uses: ./.github/workflows/build-and-test.yaml - needs: create-testrail-run + with: + head_sha: ${{ needs.resolve-context.outputs.head_sha }} + head_repo: ${{ needs.resolve-context.outputs.head_repo }} secrets: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} - TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }} - with: - testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}} # Run integration tests on emulators integration-tests: name: Integration tests - uses: ./.github/workflows/integration-tests.yaml if: ${{ vars.ENABLE_INTEGRATION_TESTS == 'true' }} - needs: create-testrail-run + needs: resolve-context + uses: ./.github/workflows/integration-tests.yaml + with: + head_sha: ${{ needs.resolve-context.outputs.head_sha }} + head_repo: ${{ needs.resolve-context.outputs.head_repo }} secrets: E2E_CONFIG: ${{ secrets.E2E_CONFIG }} DAVINCI_E2E_CONFIG: ${{ secrets.DAVINCI_E2E_CONFIG }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} - TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }} - with: - testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}} # Build and sign BrowserStack test artifacts - # Skip this step for PRs created by dependabot + # Skip for dependabot and fork PRs (signing secrets unavailable for forks) browserstack-prepare-artifacts: name: Prepare device farm artifacts + if: github.actor != 'dependabot[bot]' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + needs: + - build-and-test + - resolve-context uses: ./.github/workflows/browserstack-prepare-artifacts.yaml - if: ${{ github.actor != 'dependabot[bot]' }} - needs: build-and-test + with: + head_sha: ${{ needs.resolve-context.outputs.head_sha }} + head_repo: ${{ needs.resolve-context.outputs.head_repo }} secrets: E2E_CONFIG: ${{ secrets.E2E_CONFIG }} DAVINCI_E2E_CONFIG: ${{ secrets.DAVINCI_E2E_CONFIG }} @@ -164,30 +225,85 @@ jobs: with: browserstack-push-build-id: ${{ needs.browserstack-push-run.outputs.browserstack-push-build-id }} - # Run Mend SCA Scan + # Run Mend SCA Scan — skip for fork PRs (Mend credentials unavailable) mend-sca-scan: name: Mend SCA Scan + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + needs: resolve-context uses: ./.github/workflows/mend-sca-scan.yaml + with: + head_sha: ${{ needs.resolve-context.outputs.head_sha }} + head_repo: ${{ needs.resolve-context.outputs.head_repo }} secrets: MEND_EMAIL: ${{ secrets.MEND_EMAIL }} MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - # Run Mend SAST Scan + # Run Mend SAST Scan — skip for fork PRs (Mend credentials unavailable) mend-sast-scan: name: Mend SAST Scan + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + needs: resolve-context uses: ./.github/workflows/mend-sast-scan.yaml + with: + head_sha: ${{ needs.resolve-context.outputs.head_sha }} + head_repo: ${{ needs.resolve-context.outputs.head_repo }} secrets: MEND_EMAIL: ${{ secrets.MEND_EMAIL }} MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - # Generate and publish API docs + # Generate and publish API docs — only on pushes to main branches, not on PRs docs: name: Docs + if: github.event_name == 'push' uses: ./.github/workflows/docs.yaml - # Calculate package sizes + # Calculate package sizes — not applicable for issue_comment (no PR merge ref available) package-sizes: name: Package Sizes - uses: ./.github/workflows/package-sizes.yml \ No newline at end of file + if: github.event_name != 'issue_comment' + uses: ./.github/workflows/package-sizes.yml + + # Post /run-ci result back to the PR as a comment and update the reaction + notify: + name: Notify /run-ci result + if: always() && github.event_name == 'issue_comment' && needs.resolve-context.result != 'skipped' + needs: + - resolve-context + - build-and-test + - integration-tests + - browserstack-oath-results + - browserstack-push-results + - mend-sca-scan + - mend-sast-scan + runs-on: ubuntu-latest + steps: + - name: Post result comment and update reaction + uses: actions/github-script@v7 + with: + script: | + const results = [ + '${{ needs.build-and-test.result }}', + '${{ needs.integration-tests.result }}', + '${{ needs.browserstack-oath-results.result }}', + '${{ needs.browserstack-push-results.result }}', + '${{ needs.mend-sca-scan.result }}', + '${{ needs.mend-sast-scan.result }}', + ]; + const allPassed = results.every(r => r === 'success' || r === 'skipped'); + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: allPassed + ? `✅ \`/run-ci\` — CI passed. [View run](${runUrl})` + : `❌ \`/run-ci\` — CI failed. [View run](${runUrl})` + }); + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: allPassed ? 'rocket' : '-1' + }); diff --git a/.github/workflows/create-testrail-run.yaml b/.github/workflows/create-testrail-run.yaml deleted file mode 100644 index d21e4248..00000000 --- a/.github/workflows/create-testrail-run.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# -# Copyright (c) 2024 - 2025 Ping Identity Corporation. All rights reserved. -# -# This software may be modified and distributed under the terms -# of the MIT license. See the LICENSE file for details. -# - -name: Create TestRail Run -on: - workflow_call: - secrets: - TESTRAIL_USERNAME: - description: TestRail username - required: true - TESTRAIL_API_KEY: - description: TestRail API key - required: true - inputs: - testrail-run-name: - description: The name of the TestRail run - type: string - default: "PR #${{ github.event.number }} :: ${{ github.event.pull_request.title }} (${{github.event.pull_request.head.ref}})" - outputs: - testrail-run-id: - description: The newly created run id in TestRail - value: ${{jobs.create-testrail-run.outputs.testrail_run_id}} -jobs: - create-testrail-run: - runs-on: ubuntu-latest - outputs: - testrail_run_id: ${{steps.testrail-run-id.outputs.testrail_run_id}} - - steps: - # Crate a new TestRail run - - name: Create a new TestRail run - env: - TESTRAIL_URL: ${{vars.TESTRAIL_URL}} - TESTRAIL_PROJECT_ID: ${{vars.TESTRAIL_PROJECT_ID}} - TESTRAIL_RUN_NAME: ${{inputs.testrail-run-name}} - TESTRAIL_USERNAME: ${{secrets.TESTRAIL_USERNAME}} - TESTRAIL_API_KEY: ${{secrets.TESTRAIL_API_KEY}} - run: | - echo "TESTRAIL_RUN_ID=$(curl -H 'Content-Type: application/json' -u $TESTRAIL_USERNAME:$TESTRAIL_API_KEY $TESTRAIL_URL/index.php\?/api/v2/add_run/$TESTRAIL_PROJECT_ID --data "{\"name\":\"$TESTRAIL_RUN_NAME\", \"include_all\": true}" | jq '.id')" >> $GITHUB_ENV - - - name: Set the newly created testrail run id as output - id: testrail-run-id - run: | - echo "testrail_run_id=${{env.TESTRAIL_RUN_ID}}" - echo "testrail_run_id=${{env.TESTRAIL_RUN_ID}}" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 78bbb965..394e4998 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -14,8 +14,11 @@ on: description: Platform to execute on type: string default: ubuntu-latest - testrail-run-id: - description: TestRail Run ID + head_sha: + description: Head commit SHA to check out + type: string + head_repo: + description: Head repository to check out type: string secrets: SLACK_WEBHOOK: @@ -24,12 +27,6 @@ on: CODECOV_TOKEN: description: Codecov token required: true - TESTRAIL_USERNAME: - description: TestRail username - required: true - TESTRAIL_API_KEY: - description: TestRail API key - required: true E2E_CONFIG: description: Variables for the e2e tests required: true @@ -59,8 +56,9 @@ jobs: - name: Clone the repository uses: actions/checkout@v5 with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ inputs.head_sha || github.event.pull_request.head.sha }} + repository: ${{ inputs.head_repo || github.event.pull_request.head.repo.full_name }} + persist-credentials: false - name: Setup config file for e2e tests run: | @@ -106,27 +104,9 @@ jobs: disable-animations: true script: echo "Generated AVD snapshot for caching." - - name: Update the testrail integration configuration - run: | - cat foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - sed -i -e "s/\"TESTRAIL_ENABLE\", \".*\"/\"TESTRAIL_ENABLE\", \"${{ vars.TESTRAIL_ENABLE }}\"/g" foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - sed -i -e "s/\"TESTRAIL_DEBUG\", \".*\"/\"TESTRAIL_DEBUG\", \"${{ vars.TESTRAIL_DEBUG }}\"/g" foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - sed -i -e "s/\"TESTRAIL_PROJECT_ID\", \".*\"/\"TESTRAIL_PROJECT_ID\", \"${{ vars.TESTRAIL_PROJECT_ID }}\"/g" foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - sed -i -e "s/\"TESTRAIL_RUN_ID\", \".*\"/\"TESTRAIL_RUN_ID\", \"${{ inputs.testrail-run-id }}\"/g" foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - sed -i -e "s/\"TESTRAIL_USERNAME\", \".*\"/\"TESTRAIL_USERNAME\", \"${{ secrets.TESTRAIL_USERNAME }}\"/g" foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - sed -i -e "s/\"TESTRAIL_API_KEY\", \".*\"/\"TESTRAIL_API_KEY\", \"${{ secrets.TESTRAIL_API_KEY }}\"/g" foundation/testrail/src/main/kotlin/com/pingidentity/testrail/TestRailClientConfig.kt - - name: Execute integration tests timeout-minutes: 30 continue-on-error: true - env: - TESTRAIL_ENABLE: ${{ vars.TESTRAIL_ENABLE }} - TESTRAIL_DEBUG: ${{ vars.TESTRAIL_DEBUG }} - TESTRAIL_URL: ${{ vars.TESTRAIL_URL }} - TESTRAIL_PROJECT_ID: ${{ vars.TESTRAIL_PROJECT_ID }} - TESTRAIL_RUN_ID: ${{ inputs.testrail-run-id }} - TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }} - TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }} uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} diff --git a/.github/workflows/mend-sast-scan.yaml b/.github/workflows/mend-sast-scan.yaml index fefb1d2c..41c5e135 100644 --- a/.github/workflows/mend-sast-scan.yaml +++ b/.github/workflows/mend-sast-scan.yaml @@ -8,6 +8,13 @@ name: Run Mend SAST Scan on: workflow_call: + inputs: + head_sha: + description: Head commit SHA to check out + type: string + head_repo: + description: Head repository to check out + type: string secrets: MEND_EMAIL: description: Mend email @@ -26,8 +33,9 @@ jobs: - name: Clone the repository uses: actions/checkout@v5 with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{github.event.pull_request.head.repo.full_name}} + ref: ${{ inputs.head_sha || github.event.pull_request.head.sha }} + repository: ${{ inputs.head_repo || github.event.pull_request.head.repo.full_name }} + persist-credentials: false - name: Download and cache the Mend CLI executable id: cache-mend @@ -143,7 +151,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, name: "Mend SAST Report", - head_sha: context.sha, + head_sha: '${{ inputs.head_sha || github.event.pull_request.head.sha || github.sha }}', status: "completed", conclusion: "${{ steps.sast_conclusion.outputs.conclusion }}", output: { diff --git a/.github/workflows/mend-sca-scan.yaml b/.github/workflows/mend-sca-scan.yaml index f676bf7a..54ed4bb5 100644 --- a/.github/workflows/mend-sca-scan.yaml +++ b/.github/workflows/mend-sca-scan.yaml @@ -8,6 +8,13 @@ name: Run Mend SCA Scan on: workflow_call: + inputs: + head_sha: + description: Head commit SHA to check out + type: string + head_repo: + description: Head repository to check out + type: string secrets: MEND_EMAIL: description: Mend email @@ -26,8 +33,9 @@ jobs: - name: Clone the repository uses: actions/checkout@v5 with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{github.event.pull_request.head.repo.full_name}} + ref: ${{ inputs.head_sha || github.event.pull_request.head.sha }} + repository: ${{ inputs.head_repo || github.event.pull_request.head.repo.full_name }} + persist-credentials: false - name: Download and cache the Mend CLI executable id: cache-mend @@ -166,7 +174,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, name: "Mend SCA Report", - head_sha: context.sha, + head_sha: '${{ inputs.head_sha || github.event.pull_request.head.sha || github.sha }}', status: "completed", conclusion: "${{ steps.sca_conclusion.outputs.conclusion }}", output: { diff --git a/mfa/fido/src/main/kotlin/com/pingidentity/fido/davinci/AbstractFidoCollector.kt b/mfa/fido/src/main/kotlin/com/pingidentity/fido/davinci/AbstractFidoCollector.kt index 9b9583fc..58c5b184 100644 --- a/mfa/fido/src/main/kotlin/com/pingidentity/fido/davinci/AbstractFidoCollector.kt +++ b/mfa/fido/src/main/kotlin/com/pingidentity/fido/davinci/AbstractFidoCollector.kt @@ -79,7 +79,7 @@ abstract class AbstractFidoCollector : Collector, DaVinciAware, Subm /** * Returns an empty [JsonObject] when a FIDO error has occurred — non-null sentinel so - * [Collectors.eventType] picks up this collector and the `actionKey` error path fires. + * `Collectors.eventType` picks up this collector and the `actionKey` error path fires. * Subclasses override to provide the success payload. */ override fun payload(): JsonObject? {