Feature/challenge 3 - Employee manager #1350
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Tests | |
| permissions: | |
| contents: read | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| jobs: | |
| validate-submission-security: | |
| outputs: | |
| validation_needed: ${{ steps.validate-dirs.outputs.validation_needed }} | |
| validation_passed: ${{ steps.validate-dirs.outputs.validation_passed }} | |
| manual_approval_needed: ${{ steps.validate-dirs.outputs.manual_approval_needed }} | |
| changed_challenges: ${{ steps.validate-dirs.outputs.changed_challenges }} | |
| runs-on: ubuntu-latest | |
| name: Validate Submission Security | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| fetch-depth: 0 | |
| - name: Get PR author username | |
| id: pr-info | |
| run: | | |
| USERNAME="${{ github.event.pull_request.user.login }}" | |
| USERNAME_LOWER=$(echo "$USERNAME" | tr '[:upper:]' '[:lower:]') | |
| echo "pr_username=$USERNAME_LOWER" >> $GITHUB_OUTPUT | |
| echo "PR submitted by: $USERNAME (normalized to: $USERNAME_LOWER)" | |
| - name: Check modified submission directories | |
| id: validate-dirs | |
| run: | | |
| USERNAME="${{ steps.pr-info.outputs.pr_username }}" | |
| echo "Checking submission directories for PR by: $USERNAME" | |
| # Get list of changed files in this PR | |
| git fetch origin main | |
| CHANGED_FILES=$(git diff --name-only origin/main...HEAD) | |
| echo "Changed files:" | |
| echo "$CHANGED_FILES" | |
| # Check if any files outside of submissions directories are modified | |
| # Allow both regular challenge submissions and package challenge submissions | |
| NON_SUBMISSION_FILES=$(echo "$CHANGED_FILES" | grep -v -E "(challenge-[0-9]+/submissions/|packages/[^/]+/challenge-[^/]+/submissions/)" || true) | |
| # Check for manual approval in PR labels or comments | |
| MANUAL_APPROVAL_GRANTED="false" | |
| # Check if PR has manual approval label | |
| if [[ "${{ contains(github.event.pull_request.labels.*.name, 'manual-approval-granted') }}" == "true" ]]; then | |
| MANUAL_APPROVAL_GRANTED="true" | |
| echo "✅ Manual approval granted via label" | |
| fi | |
| if [ -n "$NON_SUBMISSION_FILES" ] && [ "$MANUAL_APPROVAL_GRANTED" != "true" ]; then | |
| echo "⚠️ WARNING: User '$USERNAME' modified files outside of submission directories:" | |
| echo "$NON_SUBMISSION_FILES" | |
| echo "🔍 This PR requires manual approval from maintainers" | |
| echo "✅ Maintainers can approve by adding the 'manual-approval-granted' label" | |
| echo "manual_approval_needed=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "manual_approval_needed=false" >> $GITHUB_OUTPUT | |
| fi | |
| # Extract challenges that were modified (either submissions or other files) | |
| # Include both regular challenges and package challenges | |
| REGULAR_CHALLENGES=$(echo "$CHANGED_FILES" | grep -E "^challenge-[0-9]+/" | sed 's|/.*||' || true) | |
| PACKAGE_CHALLENGES=$(echo "$CHANGED_FILES" | grep -E "^packages/[^/]+/challenge-[^/]+/" | sed -E 's|^(packages/[^/]+/challenge-[^/]+)/.*|\1|' || true) | |
| CHANGED_CHALLENGES=$(echo -e "$REGULAR_CHALLENGES\n$PACKAGE_CHALLENGES" | grep -v '^$' | sort -u || true) | |
| # Extract submission directories that were modified | |
| # Include both regular challenge submissions and package challenge submissions | |
| REGULAR_SUBMISSION_DIRS=$(echo "$CHANGED_FILES" | grep -E "challenge-[0-9]+/submissions/" | cut -d'/' -f3 || true) | |
| PACKAGE_SUBMISSION_DIRS=$(echo "$CHANGED_FILES" | grep -E "packages/[^/]+/challenge-[^/]+/submissions/" | cut -d'/' -f5 || true) | |
| MODIFIED_SUBMISSION_DIRS=$(echo -e "$REGULAR_SUBMISSION_DIRS\n$PACKAGE_SUBMISSION_DIRS" | grep -v '^$' | sort -u || true) | |
| if [ -n "$CHANGED_CHALLENGES" ]; then | |
| echo "Changed challenges: $CHANGED_CHALLENGES" | |
| # Convert to JSON array format for matrix | |
| CHALLENGES_JSON=$(echo "$CHANGED_CHALLENGES" | tr ' ' '\n' | sed 's/^/"/;s/$/"/' | tr '\n' ',' | sed 's/,$//') | |
| echo "changed_challenges=[$CHALLENGES_JSON]" >> $GITHUB_OUTPUT | |
| else | |
| echo "No challenges modified in this PR" | |
| echo "changed_challenges=[]" >> $GITHUB_OUTPUT | |
| fi | |
| if [ -z "$MODIFIED_SUBMISSION_DIRS" ]; then | |
| echo "No submission directories modified in this PR" | |
| # Still need to check if manual approval is required for non-submission files | |
| if [ -n "$NON_SUBMISSION_FILES" ] && [ "$MANUAL_APPROVAL_GRANTED" != "true" ]; then | |
| echo "⚠️ PR contains non-submission file changes that require manual approval" | |
| echo "validation_needed=true" >> $GITHUB_OUTPUT | |
| echo "validation_passed=false" >> $GITHUB_OUTPUT | |
| echo "manual_approval_needed=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ No submission files modified and no manual approval needed" | |
| echo "validation_needed=false" >> $GITHUB_OUTPUT | |
| echo "validation_passed=true" >> $GITHUB_OUTPUT | |
| echo "manual_approval_needed=false" >> $GITHUB_OUTPUT | |
| fi | |
| exit 0 | |
| fi | |
| echo "Modified submission directories:" | |
| echo "$MODIFIED_SUBMISSION_DIRS" | |
| # Validate each modified submission directory (case-insensitive comparison) | |
| INVALID_DIRS="" | |
| for DIR in $MODIFIED_SUBMISSION_DIRS; do | |
| DIR_LOWER=$(echo "$DIR" | tr '[:upper:]' '[:lower:]') | |
| USERNAME_LOWER=$(echo "$USERNAME" | tr '[:upper:]' '[:lower:]') | |
| echo "Comparing: '$DIR' (normalized: '$DIR_LOWER') vs username '$USERNAME' (normalized: '$USERNAME_LOWER')" | |
| if [ "$DIR_LOWER" != "$USERNAME_LOWER" ]; then | |
| INVALID_DIRS="$INVALID_DIRS $DIR" | |
| fi | |
| done | |
| # Validate that all changed files are within the user's own submission directories | |
| USERNAME_LOWER=$(echo "$USERNAME" | tr '[:upper:]' '[:lower:]') | |
| INVALID_FILES="" | |
| for FILE in $CHANGED_FILES; do | |
| # Check regular challenge submissions | |
| if [[ "$FILE" =~ ^challenge-[0-9]+/submissions/([^/]+)/ ]]; then | |
| SUBMISSION_USER="${BASH_REMATCH[1]}" | |
| SUBMISSION_USER_LOWER=$(echo "$SUBMISSION_USER" | tr '[:upper:]' '[:lower:]') | |
| if [ "$SUBMISSION_USER_LOWER" != "$USERNAME_LOWER" ]; then | |
| INVALID_FILES="$INVALID_FILES $FILE" | |
| fi | |
| fi | |
| # Check package challenge submissions | |
| if [[ "$FILE" =~ ^packages/[^/]+/challenge-[^/]+/submissions/([^/]+)/ ]]; then | |
| SUBMISSION_USER="${BASH_REMATCH[1]}" | |
| SUBMISSION_USER_LOWER=$(echo "$SUBMISSION_USER" | tr '[:upper:]' '[:lower:]') | |
| if [ "$SUBMISSION_USER_LOWER" != "$USERNAME_LOWER" ]; then | |
| INVALID_FILES="$INVALID_FILES $FILE" | |
| fi | |
| fi | |
| done | |
| # Check for strict security violations (modifying other users' submissions) | |
| # Skip this check if manual approval is granted | |
| if [ -n "$INVALID_DIRS" ] || [ -n "$INVALID_FILES" ]; then | |
| if [ "$MANUAL_APPROVAL_GRANTED" == "true" ]; then | |
| echo "✅ Manual approval granted - bypassing submission directory security checks" | |
| echo "⚠️ WARNING: User '$USERNAME' modified submission directories for other users:$INVALID_DIRS" | |
| if [ -n "$INVALID_FILES" ]; then | |
| echo "⚠️ WARNING: User '$USERNAME' modified files in other users' submission directories:" | |
| echo "$INVALID_FILES" | |
| fi | |
| echo "🔓 Manual approval allows bypassing normal security restrictions" | |
| else | |
| if [ -n "$INVALID_DIRS" ]; then | |
| echo "❌ STRICT SECURITY VIOLATION: User '$USERNAME' attempted to modify submission directories for other users:$INVALID_DIRS" | |
| fi | |
| if [ -n "$INVALID_FILES" ]; then | |
| echo "❌ STRICT SECURITY VIOLATION: User '$USERNAME' attempted to modify files in other users' submission directories:" | |
| echo "$INVALID_FILES" | |
| fi | |
| echo "✅ You can only modify submissions in directories named after your GitHub username: $USERNAME" | |
| echo "✅ Allowed directories: challenge-*/submissions/$USERNAME or packages/*/challenge-*/submissions/$USERNAME" | |
| echo "validation_passed=false" >> $GITHUB_OUTPUT | |
| echo "validation_needed=true" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| fi | |
| # Check if manual approval is needed and not granted | |
| if [ -n "$NON_SUBMISSION_FILES" ] && [ "$MANUAL_APPROVAL_GRANTED" != "true" ]; then | |
| echo "⚠️ PR contains non-submission file changes that require manual approval" | |
| echo "validation_passed=false" >> $GITHUB_OUTPUT | |
| echo "validation_needed=true" >> $GITHUB_OUTPUT | |
| else | |
| if [ "$MANUAL_APPROVAL_GRANTED" == "true" ]; then | |
| echo "✅ Manual approval granted - security validation bypassed" | |
| echo "🔓 All security checks bypassed due to manual approval" | |
| else | |
| echo "✅ Security validation passed" | |
| fi | |
| echo "validation_passed=true" >> $GITHUB_OUTPUT | |
| echo "validation_needed=false" >> $GITHUB_OUTPUT | |
| fi | |
| manual-approval-status: | |
| runs-on: ubuntu-latest | |
| needs: validate-submission-security | |
| if: needs.validate-submission-security.outputs.manual_approval_needed == 'true' | |
| steps: | |
| - name: Manual Approval Required | |
| run: | | |
| echo "🚨 MANUAL APPROVAL REQUIRED 🚨" | |
| echo "" | |
| echo "🔍 This PR modifies files outside of submission directories" | |
| echo "📝 Files modified outside challenge-*/submissions/ or packages/*/challenge-*/submissions/ need admin review" | |
| echo "" | |
| echo "📢 @RezaSi - Please review and approve this PR by:" | |
| echo " 1. ✅ Reviewing the changes carefully" | |
| echo " 2. 🏷️ Adding the 'manual-approval-granted' label" | |
| echo " 3. 🔄 Re-running this workflow (close/reopen PR)" | |
| echo "" | |
| echo "⚠️ SECURITY: Only approve changes you trust!" | |
| echo "❌ This workflow will block until manual approval is granted" | |
| no-challenges-changed: | |
| runs-on: ubuntu-latest | |
| needs: validate-submission-security | |
| if: needs.validate-submission-security.outputs.validation_passed == 'true' && needs.validate-submission-security.outputs.changed_challenges == '[]' | |
| steps: | |
| - name: No Tests Needed | |
| run: | | |
| echo "✅ PR validation passed but no challenge directories were modified" | |
| echo "🎯 This PR doesn't contain any challenge submissions to test" | |
| echo "✨ All security checks completed successfully" | |
| test-submissions: | |
| runs-on: ubuntu-latest | |
| needs: validate-submission-security | |
| if: needs.validate-submission-security.outputs.validation_passed == 'true' && needs.validate-submission-security.outputs.changed_challenges != '[]' | |
| strategy: | |
| matrix: | |
| challenge: ${{ fromJson(needs.validate-submission-security.outputs.changed_challenges) }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v3 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v4 | |
| with: | |
| go-version: '1.25.0' | |
| - name: Run Tests for ${{ matrix.challenge }} | |
| working-directory: ${{ matrix.challenge }} | |
| run: | | |
| USERNAME="${{ github.event.pull_request.user.login }}" | |
| # Check if this is a package challenge or regular challenge | |
| if [[ "${{ matrix.challenge }}" =~ ^packages/ ]]; then | |
| # Package challenge | |
| SUBMISSION_DIR="submissions/$USERNAME" | |
| if [ -d "$SUBMISSION_DIR" ]; then | |
| echo "Testing package challenge submission from $USERNAME" | |
| # Create a temporary directory for testing | |
| TEMP_DIR=$(mktemp -d) | |
| # Copy the user's solution file and test file to temp directory | |
| cp "$SUBMISSION_DIR/solution.go" "$TEMP_DIR/" | |
| cp "solution-template_test.go" "$TEMP_DIR/" | |
| # Copy go.mod if it exists | |
| if [ -f "go.mod" ]; then | |
| cp "go.mod" "$TEMP_DIR/" | |
| fi | |
| # Rename solution.go to solution-template.go for the test | |
| mv "$TEMP_DIR/solution.go" "$TEMP_DIR/solution-template.go" | |
| # Navigate to temp directory | |
| pushd "$TEMP_DIR" > /dev/null | |
| # Handle dependencies if go.mod exists | |
| if [ -f "go.mod" ]; then | |
| echo "Found go.mod file, downloading dependencies..." | |
| # Update module name to avoid conflicts | |
| sed -i 's/^module .*/module challenge/' go.mod | |
| go mod tidy | |
| else | |
| go mod init challenge | |
| fi | |
| # Run tests | |
| go test -v | |
| TEST_EXIT_CODE=$? | |
| # Return to original directory and cleanup | |
| popd > /dev/null | |
| rm -rf "$TEMP_DIR" | |
| exit $TEST_EXIT_CODE | |
| else | |
| echo "No submission found for $USERNAME in package challenge ${{ matrix.challenge }}" | |
| fi | |
| else | |
| # Regular challenge | |
| SUBMISSION_DIR="submissions/$USERNAME" | |
| if [ -d "$SUBMISSION_DIR" ]; then | |
| echo "Testing submission from $USERNAME" | |
| cp "$SUBMISSION_DIR"/*.go . | |
| # Handle dependencies if go.mod exists | |
| if [ -f "go.mod" ]; then | |
| echo "Found go.mod file, downloading dependencies..." | |
| go mod tidy | |
| fi | |
| go test -v | |
| else | |
| echo "No submission found for $USERNAME in ${{ matrix.challenge }}" | |
| fi | |
| fi |