diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index ddee20dc23..558e3be93c 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -2,10 +2,13 @@ name: Nightly Tests on: schedule: - - cron: "0 5 * * 3,5" # cron is UTC, this translates to 10 PM PST Tues and Thur. + - cron: "0 5 * * 2-6" # cron is UTC; 9 PM PT every weekday (Mon-Fri). # This lets us trigger the workflow from a browser. workflow_dispatch: +permissions: + contents: read + jobs: ios-nightly: strategy: @@ -19,11 +22,16 @@ jobs: - ios: ^18 xcode: ^16 uses: ./.github/workflows/reusable-test-workflow.yaml + permissions: + contents: read + pull-requests: write with: lib: ${{ matrix.lib }} ios: ${{ matrix.ios }} xcode: ${{ matrix.xcode }} - secrets: inherit + secrets: + TEST_CREDENTIALS: ${{ secrets.TEST_CREDENTIALS }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} native-samples-nightly: strategy: @@ -37,8 +45,9 @@ jobs: - ios: ^18 xcode: ^16 uses: ./.github/workflows/reusable-build-workflow.yaml + permissions: + contents: read with: app: ${{ matrix.app }} ios: ${{ matrix.ios }} xcode: ${{ matrix.xcode }} - secrets: inherit diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index abd06184d0..44b2adf2bf 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -1,36 +1,49 @@ name: Pull Request on: - pull_request_target: + # pull_request_target is used so secrets are available to fork PRs. + # Mitigated by per-job Member Check (see "Check Write Permission" / "Validate Write Permission"). + pull_request_target: # zizmor: ignore[dangerous-triggers] branches: [dev, master] +permissions: + contents: read + jobs: static-analysis: runs-on: macos-latest + permissions: + contents: read + pull-requests: write env: BUNDLE_GEMFILE: ${{ github.workspace }}/.github/DangerFiles/Gemfile steps: - name: Check Write Permission - uses: octokit/request-action@v2.x + uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0 id: check_permissions with: route: GET /repos/${{ github.repository }}/collaborators/${{ github.triggering_actor }}/permission env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Debug Permission Response + env: + PERMISSION_DATA: ${{ steps.check_permissions.outputs.data }} run: | - echo "Permission raw response: ${{ steps.check_permissions.outputs.data }}" + echo "Permission raw response: $PERMISSION_DATA" - name: Validate Write Permission + env: + PERMISSION: ${{ fromJson(steps.check_permissions.outputs.data).permission }} + ACTOR: ${{ github.triggering_actor }} run: | - permission=$(echo "${{ fromJson(steps.check_permissions.outputs.data).permission }}") - echo "User ${{ github.triggering_actor }} has permission: $permission" - if [[ "$permission" != "write" && "$permission" != "admin" ]]; then - echo "User ${{ github.triggering_actor }} does not have sufficient permission (write or admin) to proceed. Someone from the team needs to rerun this workflow AFTER it has been deemed safe." + echo "User $ACTOR has permission: $PERMISSION" + if [[ "$PERMISSION" != "write" && "$PERMISSION" != "admin" ]]; then + echo "User $ACTOR does not have sufficient permission (write or admin) to proceed. Someone from the team needs to rerun this workflow AFTER it has been deemed safe." exit 1 fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: + persist-credentials: false # We need a sufficient depth or Danger will occasionally run into issues checking which files were modified. fetch-depth: 100 # This is dangerous without the member check @@ -45,7 +58,7 @@ jobs: run: xcodebuild analyze -workspace SalesforceMobileSDK.xcworkspace -scheme MobileSync -sdk 'iphonesimulator' \ CLANG_ANALYZER_OUTPUT=plist-html CLANG_ANALYZER_OUTPUT_DIR=./clangReport RUN_CLANG_STATIC_ANALYZER=YES - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: ruby-version: '3.3' bundler-cache: true @@ -56,37 +69,45 @@ jobs: test-orchestrator: runs-on: macos-latest + permissions: + contents: read + pull-requests: write env: BUNDLE_GEMFILE: ${{ github.workspace }}/.github/DangerFiles/Gemfile outputs: libs: ${{ steps.test-orchestrator.outputs.libs }} steps: - name: Check Write Permission - uses: octokit/request-action@v2.x + uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0 id: check_permissions with: route: GET /repos/${{ github.repository }}/collaborators/${{ github.triggering_actor }}/permission env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Debug Permission Response + env: + PERMISSION_DATA: ${{ steps.check_permissions.outputs.data }} run: | - echo "Permission raw response: ${{ steps.check_permissions.outputs.data }}" + echo "Permission raw response: $PERMISSION_DATA" - name: Validate Write Permission + env: + PERMISSION: ${{ fromJson(steps.check_permissions.outputs.data).permission }} + ACTOR: ${{ github.triggering_actor }} run: | - permission=$(echo "${{ fromJson(steps.check_permissions.outputs.data).permission }}") - echo "User ${{ github.triggering_actor }} has permission: $permission" - if [[ "$permission" != "write" && "$permission" != "admin" ]]; then - echo "User ${{ github.triggering_actor }} does not have sufficient permission (write or admin) to proceed. Someone from the team needs to rerun this workflow AFTER it has been deemed safe." + echo "User $ACTOR has permission: $PERMISSION" + if [[ "$PERMISSION" != "write" && "$PERMISSION" != "admin" ]]; then + echo "User $ACTOR does not have sufficient permission (write or admin) to proceed. Someone from the team needs to rerun this workflow AFTER it has been deemed safe." exit 1 fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: + persist-credentials: false # We need a sufficient depth or Danger will occasionally run into issues checking which files were modified. fetch-depth: 100 # This is dangerous without the member check ref: ${{ github.event.pull_request.head.sha }} - - uses: ruby/setup-ruby@v1 + - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: ruby-version: '3.3' bundler-cache: true @@ -109,12 +130,17 @@ jobs: - ios: ^18 xcode: ^16 uses: ./.github/workflows/reusable-test-workflow.yaml + permissions: + contents: read + pull-requests: write with: lib: ${{ matrix.lib }} ios: ${{ matrix.ios }} xcode: ${{ matrix.xcode }} is_pr: true - secrets: inherit + secrets: + TEST_CREDENTIALS: ${{ secrets.TEST_CREDENTIALS }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} native-samples-pr: needs: [test-orchestrator] @@ -127,14 +153,15 @@ jobs: - ios: ^26 xcode: ^26 - ios: ^18 - xcode: ^16 + xcode: ^16 uses: ./.github/workflows/reusable-build-workflow.yaml + permissions: + contents: read with: app: ${{ matrix.app }} ios: ${{ matrix.ios }} xcode: ${{ matrix.xcode }} is_pr: true - secrets: inherit ui-tests-pr: needs: [test-orchestrator] @@ -148,6 +175,9 @@ jobs: - ios: ^18 xcode: ^16 uses: ./.github/workflows/reusable-ui-test-workflow.yaml + permissions: + contents: read + pull-requests: write with: is_pr: true ios: ${{ matrix.ios }} @@ -155,4 +185,6 @@ jobs: pr_test: "AuthFlowTesterUITests/LegacyLoginTests/testCAOpaque_DefaultScopes_WebServerFlow" short_timeout: "2" long_timeout: "7" - secrets: inherit \ No newline at end of file + secrets: + UI_TEST_CONFIG: ${{ secrets.UI_TEST_CONFIG }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/reusable-build-workflow.yaml b/.github/workflows/reusable-build-workflow.yaml index 4c97fa7462..f5d36f6da2 100644 --- a/.github/workflows/reusable-build-workflow.yaml +++ b/.github/workflows/reusable-build-workflow.yaml @@ -20,21 +20,27 @@ on: type: boolean default: false +permissions: + contents: read + jobs: build-sample-app: runs-on: ${{ inputs.macos }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 if: ${{ inputs.is_pr }} with: + persist-credentials: false ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 if: ${{ !inputs.is_pr }} with: + persist-credentials: false ref: ${{ github.head_ref }} - name: Install Dependencies run: ./install.sh - - uses: mxcl/xcodebuild@v3 + - uses: mxcl/xcodebuild@d3ee9b419c1be9a988086c58fe0988f32d99cfc5 # v3.6.0 + id: build with: xcode: ${{ inputs.xcode }} platform: iOS @@ -43,3 +49,13 @@ jobs: scheme: ${{ inputs.app }} action: 'build' verbosity: xcbeautify + - name: Archive build logs on failure + if: failure() && steps.build.outcome == 'failure' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: xcode-build-logs-${{ inputs.app }}-ios${{ inputs.ios }} + path: | + ~/Library/Developer/Xcode/DerivedData/**/Logs/Build/*.xcactivitylog + ~/Library/Logs/DiagnosticReports/** + if-no-files-found: ignore + retention-days: 14 diff --git a/.github/workflows/reusable-test-workflow.yaml b/.github/workflows/reusable-test-workflow.yaml index 3844c89738..ad9293a20d 100644 --- a/.github/workflows/reusable-test-workflow.yaml +++ b/.github/workflows/reusable-test-workflow.yaml @@ -19,18 +19,29 @@ on: is_pr: type: boolean default: false + secrets: + TEST_CREDENTIALS: + required: true + CODECOV_TOKEN: + required: true + +permissions: + contents: read + pull-requests: write jobs: test-ios: runs-on: ${{ inputs.macos }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 if: ${{ inputs.is_pr }} with: + persist-credentials: false ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 if: ${{ !inputs.is_pr }} with: + persist-credentials: false ref: ${{ github.head_ref }} - name: Install Dependencies env: @@ -38,7 +49,7 @@ jobs: run: | ./install.sh echo $TEST_CREDENTIALS > ./shared/test/test_credentials.json - - uses: mxcl/xcodebuild@v3 + - uses: mxcl/xcodebuild@d3ee9b419c1be9a988086c58fe0988f32d99cfc5 # v3.6.0 id: xcodebuild with: xcode: ${{ inputs.xcode }} @@ -50,11 +61,23 @@ jobs: verbosity: xcbeautify - name: Parse test results if: success() || failure() + env: + LIB: ${{ inputs.lib }} + IOS: ${{ inputs.ios }} run: | brew install xcresultparser - xcresultparser -o junit test.xcresult > test-results-${{ inputs.lib }}-ios${{ inputs.ios }}.xml + xcresultparser -o junit test.xcresult > "test-results-${LIB}-ios${IOS}.xml" + - name: Archive xcodebuild logs on build failure + if: failure() && steps.xcodebuild.outcome == 'failure' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: xcodebuild-logs-${{ inputs.lib }}-ios${{ inputs.ios }} + path: | + ~/Library/Developer/Xcode/DerivedData/**/Logs/Build/*.xcactivitylog + if-no-files-found: ignore + retention-days: 14 - name: Test Report - uses: mikepenz/action-junit-report@v5 + uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: success() || failure() with: check_name: ${{ inputs.lib }} iOS ${{ inputs.ios }} Test Results @@ -66,7 +89,7 @@ jobs: comment: true job_summary: ${{ steps.xcodebuild.outcome == 'failure' }} report_paths: 'test-results-${{ inputs.lib }}-ios${{ inputs.ios }}.xml' - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 if: success() || failure() with: flags: ${{ inputs.lib }} @@ -74,7 +97,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Upload test results artifact if: success() || failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: test-results-${{ inputs.lib }}-ios${{ inputs.ios }} path: test-results-${{ inputs.lib }}-ios${{ inputs.ios }}.xml \ No newline at end of file diff --git a/.github/workflows/reusable-ui-test-workflow.yaml b/.github/workflows/reusable-ui-test-workflow.yaml index 03bd896ed3..df604c7ac7 100644 --- a/.github/workflows/reusable-ui-test-workflow.yaml +++ b/.github/workflows/reusable-ui-test-workflow.yaml @@ -41,6 +41,15 @@ on: default: "" required: false type: string + secrets: + UI_TEST_CONFIG: + required: true + CODECOV_TOKEN: + required: true + +permissions: + contents: read + pull-requests: write jobs: test-ui: @@ -48,20 +57,24 @@ jobs: steps: - name: Set result suffix for matrix runs id: result_suffix + env: + TEST_SUITE: ${{ inputs.test_suite }} run: | - if [ -n "${{ inputs.test_suite }}" ]; then - echo "suffix=$(echo '${{ inputs.test_suite }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" + if [ -n "$TEST_SUITE" ]; then + printf 'suffix=%s\n' "${TEST_SUITE//\//-}" >> "$GITHUB_OUTPUT" else - echo "suffix=all" >> "$GITHUB_OUTPUT" + printf 'suffix=%s\n' "all" >> "$GITHUB_OUTPUT" fi - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 if: ${{ inputs.is_pr }} with: + persist-credentials: false fetch-depth: 100 ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 if: ${{ !inputs.is_pr }} with: + persist-credentials: false ref: ${{ github.head_ref }} - name: Install Dependencies env: @@ -70,7 +83,7 @@ jobs: ./install.sh echo "$UI_TEST_CONFIG" > ./shared/test/ui_test_config.json # Select Xcode only (mxcl action does not support -retry-tests-on-failure) - - uses: mxcl/xcodebuild@v3 + - uses: mxcl/xcodebuild@d3ee9b419c1be9a988086c58fe0988f32d99cfc5 # v3.6.0 with: xcode: ${{ inputs.xcode }} platform: iOS @@ -78,12 +91,15 @@ jobs: action: none - name: Resolve simulator destination id: resolve_destination + env: + DESTINATION_INPUT: ${{ inputs.destination }} + IOS: ${{ inputs.ios }} run: | - if [ -n "${{ inputs.destination }}" ]; then - echo "destination=${{ inputs.destination }}" >> "$GITHUB_OUTPUT" + if [ -n "$DESTINATION_INPUT" ]; then + printf 'destination=%s\n' "$DESTINATION_INPUT" >> "$GITHUB_OUTPUT" else # Extract major iOS version from input (e.g. ^26 -> 26, 26.0 -> 26) - IOS_VERSION="${{ inputs.ios }}" + IOS_VERSION="$IOS" IOS_MAJOR=$(echo "$IOS_VERSION" | sed 's/^\^//' | cut -d. -f1) SIM_UDID=$(xcrun simctl list devices available -j | python3 -c ' import sys, json @@ -103,17 +119,24 @@ jobs: echo "::error::No available iPhone simulator found for iOS $IOS_MAJOR" exit 1 fi - echo "destination=platform=iOS Simulator,id=$SIM_UDID" >> "$GITHUB_OUTPUT" + printf 'destination=platform=iOS Simulator,id=%s\n' "$SIM_UDID" >> "$GITHUB_OUTPUT" fi - name: Run UI tests id: xcodebuild + env: + CODE_COVERAGE: YES + DESTINATION: ${{ steps.resolve_destination.outputs.destination }} + PR_TEST: ${{ inputs.pr_test }} + TEST_SUITE: ${{ inputs.test_suite }} + # Xcode 15.3+ only forwards env vars prefixed with TEST_RUNNER_ to the test runner (prefix is stripped). + TEST_RUNNER_UI_TEST_SHORT_TIMEOUT: ${{ inputs.short_timeout }} + TEST_RUNNER_UI_TEST_LONG_TIMEOUT: ${{ inputs.long_timeout }} run: | - DESTINATION="${{ steps.resolve_destination.outputs.destination }}" ONLY_TESTING_ARGS=() - if [ -n "${{ inputs.pr_test }}" ]; then - ONLY_TESTING_ARGS+=(-only-testing "${{ inputs.pr_test }}") - elif [ -n "${{ inputs.test_suite }}" ]; then - ONLY_TESTING_ARGS+=(-only-testing "${{ inputs.test_suite }}") + if [ -n "$PR_TEST" ]; then + ONLY_TESTING_ARGS+=(-only-testing "$PR_TEST") + elif [ -n "$TEST_SUITE" ]; then + ONLY_TESTING_ARGS+=(-only-testing "$TEST_SUITE") fi set -o pipefail xcodebuild test \ @@ -125,11 +148,15 @@ jobs: -resultBundlePath test \ -enableCodeCoverage YES \ | xcbeautify - env: - CODE_COVERAGE: YES - # Xcode 15.3+ only forwards env vars prefixed with TEST_RUNNER_ to the test runner (prefix is stripped). - TEST_RUNNER_UI_TEST_SHORT_TIMEOUT: ${{ inputs.short_timeout }} - TEST_RUNNER_UI_TEST_LONG_TIMEOUT: ${{ inputs.long_timeout }} + - name: Archive xcodebuild logs on build failure + if: failure() && steps.xcodebuild.outcome == 'failure' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: xcodebuild-logs-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }} + path: | + ~/Library/Developer/Xcode/DerivedData/**/Logs/Build/*.xcactivitylog + if-no-files-found: ignore + retention-days: 14 - name: Verify xcresult bundle exists if: success() || failure() run: | @@ -141,11 +168,14 @@ jobs: ls -lh test.xcresult/ - name: Parse test results if: success() || failure() + env: + IOS: ${{ inputs.ios }} + SUFFIX: ${{ steps.result_suffix.outputs.suffix }} run: | brew install xcresultparser - xcresultparser -o junit test.xcresult > test-results-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }}.xml + xcresultparser -o junit test.xcresult > "test-results-authflowtester-ui-ios${IOS}-${SUFFIX}.xml" - name: Test Report - uses: mikepenz/action-junit-report@v5 + uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: success() || failure() with: check_name: AuthFlowTester UI Test Results ${{ steps.result_suffix.outputs.suffix }} @@ -159,7 +189,7 @@ jobs: report_paths: 'test-results-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }}.xml' check_retries: false group_suite: true - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 if: success() || failure() with: flags: AuthFlowTesterUI @@ -167,7 +197,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Upload xcresult bundle if: success() || failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: xcresult-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }} path: test.xcresult/ diff --git a/.github/workflows/ui-test-nightly.yaml b/.github/workflows/ui-test-nightly.yaml index b2afe6c602..d126682d47 100644 --- a/.github/workflows/ui-test-nightly.yaml +++ b/.github/workflows/ui-test-nightly.yaml @@ -2,9 +2,12 @@ name: UI Nightly Tests on: schedule: - - cron: "0 5 * * 2,4" # cron is UTC, this translates to 10 PM PST Mon and Wed. + - cron: "0 7 * * 2-6" # cron is UTC; 11 PM PT every weekday (Mon-Fri). workflow_dispatch: +permissions: + contents: read + jobs: ios-ui-test-nightly: strategy: @@ -17,9 +20,14 @@ jobs: - ios: ^18 xcode: ^16 uses: ./.github/workflows/reusable-ui-test-workflow.yaml + permissions: + contents: read + pull-requests: write with: ios: ${{ matrix.ios }} xcode: ${{ matrix.xcode }} short_timeout: "2" long_timeout: "7" - secrets: inherit + secrets: + UI_TEST_CONFIG: ${{ secrets.UI_TEST_CONFIG }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}