|
1 | | -name: Test PyPI yank capability |
| 1 | +name: Test PyPI token capabilities |
2 | 2 |
|
3 | 3 | on: |
4 | 4 | workflow_dispatch: |
|
7 | 7 | - test-pypi-yank |
8 | 8 |
|
9 | 9 | jobs: |
10 | | - test-yank: |
| 10 | + test-capabilities: |
11 | 11 | runs-on: ubuntu-latest |
| 12 | + env: |
| 13 | + PYPI_TOKEN: ${{ secrets.PYPI }} |
12 | 14 | steps: |
13 | | - - name: Install twine |
14 | | - run: pip install twine |
| 15 | + - name: Install dependencies |
| 16 | + run: pip install twine requests |
15 | 17 |
|
16 | | - - name: Attempt to yank policyengine-uk 0.35.0 |
17 | | - env: |
18 | | - PYPI_TOKEN: ${{ secrets.PYPI }} |
| 18 | + - name: "Test 1: Verify token is valid (check auth via upload endpoint)" |
19 | 19 | run: | |
20 | | - STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ |
21 | | - -X POST "https://pypi.org/pypi/policyengine-uk/0.35.0/yank" \ |
22 | | - -H "Authorization: Bearer $PYPI_TOKEN" \ |
23 | | - -d "reason=Test%3A+verifying+org+secret+has+yank+permissions") |
24 | | - echo "HTTP status: $STATUS" |
| 20 | + # POST with no files - a valid token gets 400 (bad request, missing file) |
| 21 | + # an invalid token gets 403 |
| 22 | + STATUS=$(curl -s -o /tmp/auth_test.txt -w "%{http_code}" \ |
| 23 | + -X POST "https://upload.pypi.org/legacy/" \ |
| 24 | + -u "__token__:$PYPI_TOKEN") |
| 25 | + echo "Auth check status: $STATUS" |
| 26 | + cat /tmp/auth_test.txt |
| 27 | + if [ "$STATUS" = "400" ]; then |
| 28 | + echo "RESULT: Token authenticated OK (400 = auth passed, bad request)" |
| 29 | + elif [ "$STATUS" = "403" ]; then |
| 30 | + echo "RESULT: Token REJECTED (403 = wrong token or no permission)" |
| 31 | + else |
| 32 | + echo "RESULT: Unexpected status $STATUS" |
| 33 | + fi |
| 34 | +
|
| 35 | + - name: "Test 2: Attempt upload of a dummy/existing version (tests publish scope)" |
| 36 | + run: | |
| 37 | + # We'll try uploading a fake dist — if token has upload scope we get 400 (bad file) |
| 38 | + # If token is read-only we get 403 |
| 39 | + echo "dummy" > /tmp/fake.tar.gz |
| 40 | + STATUS=$(curl -s -o /tmp/upload_test.txt -w "%{http_code}" \ |
| 41 | + -X POST "https://upload.pypi.org/legacy/" \ |
| 42 | + -u "__token__:$PYPI_TOKEN" \ |
| 43 | + -F ":action=file_upload" \ |
| 44 | + -F "protocol_version=1" \ |
| 45 | + -F "name=policyengine-uk" \ |
| 46 | + -F "version=0.35.0" \ |
| 47 | + -F "filetype=sdist" \ |
| 48 | + -F "pyversion=source" \ |
| 49 | + -F "content=@/tmp/fake.tar.gz;type=application/octet-stream") |
| 50 | + echo "Upload test status: $STATUS" |
| 51 | + cat /tmp/upload_test.txt |
| 52 | + if [ "$STATUS" = "400" ]; then |
| 53 | + echo "RESULT: Token has upload permission (400 = auth OK, file invalid)" |
| 54 | + elif [ "$STATUS" = "403" ]; then |
| 55 | + echo "RESULT: Token LACKS upload permission (403)" |
| 56 | + fi |
| 57 | +
|
| 58 | + - name: "Test 3: Attempt yank via warehouse internal route" |
| 59 | + run: | |
| 60 | + # PyPI warehouse exposes a CSRF-protected manage route; tokens don't work here |
| 61 | + # but we test to confirm the response |
| 62 | + STATUS=$(curl -s -o /tmp/yank_test.txt -w "%{http_code}" \ |
| 63 | + -X POST "https://pypi.org/manage/project/policyengine-uk/release/0.35.0/yank/" \ |
| 64 | + -H "Authorization: token $PYPI_TOKEN" \ |
| 65 | + -H "Content-Type: application/x-www-form-urlencoded" \ |
| 66 | + -d "yanked_reason=test") |
| 67 | + echo "Yank attempt status: $STATUS" |
| 68 | + cat /tmp/yank_test.txt | python3 -c "import sys; content=sys.stdin.read(); print(content[:300])" |
25 | 69 | if [ "$STATUS" = "200" ]; then |
26 | | - echo "YANK SUCCEEDED - token has yank permissions" |
| 70 | + echo "RESULT: YANK SUCCEEDED" |
| 71 | + elif [ "$STATUS" = "302" ]; then |
| 72 | + echo "RESULT: Redirect (may have worked or redirected to login)" |
27 | 73 | elif [ "$STATUS" = "403" ]; then |
28 | | - echo "YANK FAILED 403 - token lacks permission or wrong scope" |
| 74 | + echo "RESULT: Forbidden - token not accepted for yank" |
29 | 75 | elif [ "$STATUS" = "401" ]; then |
30 | | - echo "YANK FAILED 401 - token authentication failed" |
| 76 | + echo "RESULT: Unauthorized - token rejected" |
31 | 77 | else |
32 | | - echo "YANK FAILED with unexpected status $STATUS" |
| 78 | + echo "RESULT: Status $STATUS - token likely not accepted for web management routes" |
33 | 79 | fi |
| 80 | +
|
| 81 | + - name: "Test 4: Attempt yank via upload.pypi.org with :action=yank" |
| 82 | + run: | |
| 83 | + STATUS=$(curl -s -o /tmp/yank2_test.txt -w "%{http_code}" \ |
| 84 | + -X POST "https://upload.pypi.org/legacy/" \ |
| 85 | + -u "__token__:$PYPI_TOKEN" \ |
| 86 | + -F ":action=yank" \ |
| 87 | + -F "name=policyengine-uk" \ |
| 88 | + -F "version=0.35.0" \ |
| 89 | + -F "yanked_reason=test+yank+capability") |
| 90 | + echo "Yank via upload endpoint status: $STATUS" |
| 91 | + cat /tmp/yank2_test.txt | python3 -c "import sys; content=sys.stdin.read(); print(content[:300])" |
| 92 | + if [ "$STATUS" = "200" ]; then |
| 93 | + echo "RESULT: YANK SUCCEEDED via upload endpoint" |
| 94 | + elif [ "$STATUS" = "400" ]; then |
| 95 | + echo "RESULT: Auth OK but action not supported (400)" |
| 96 | + elif [ "$STATUS" = "403" ]; then |
| 97 | + echo "RESULT: Forbidden" |
| 98 | + else |
| 99 | + echo "RESULT: Status $STATUS" |
| 100 | + fi |
| 101 | +
|
| 102 | + - name: Summary |
| 103 | + run: | |
| 104 | + echo "==============================" |
| 105 | + echo "Test summary:" |
| 106 | + echo "See individual steps above for RESULT lines." |
| 107 | + echo "Token prefix (first 10 chars):" |
| 108 | + echo "$PYPI_TOKEN" | cut -c1-15 |
0 commit comments