Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/actions/security/org-membership-check/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,7 @@ This composite action checks if a GitHub user is a member of the dotCMS organiza

## Implementation Details

The action uses the GitHub CLI (`gh`) with a fine-grained GitHub token to check organization membership via the GitHub API endpoint `GET /orgs/dotCMS/members/{username}`.

**Token Requirements:**
- Fine-grained token with organization membership read permissions
- Should be from a machine/service account for security
- Stored as repository secret: `MACHINE_USER_CORE_ORG_MEMBERSHIP_CHECK`
The action uses the GitHub CLI (`gh`) with the repository's `GITHUB_TOKEN` to check organization membership via the GitHub API endpoint `GET /orgs/dotCMS/members/{username}`.

**Key Design Decision: Status Code vs Response Body**

Expand Down
30 changes: 16 additions & 14 deletions .github/actions/security/org-membership-check/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,27 @@ runs:

set +e # Don't exit on error, we want to handle it gracefully

# Check organization membership using GitHub API with fine-grained token
# Check organization membership using GitHub API
#
# Uses a fine-grained token with organization membership read permissions
# to check both public and private organization membership reliably.
# IMPORTANT: We check the HTTP status code (via exit code) rather than parsing
# the response body because GitHub's membership API has specific behavior:
#
# API Behavior:
# - HTTP 200 + user object = Public member
# - HTTP 200 + empty response = Private member
# - HTTP 404 = Not a member
# - Other errors = API/token issues

echo "Checking organization membership for ${{ inputs.username }} in dotCMS..."
# HTTP 200 (exit code 0) = User IS a member (regardless of response content)
# - Public member: Returns user object with populated fields
# - Private member: Returns empty response body (but still 200 OK)
#
# HTTP 404 (exit code 1) = User is NOT a member
# - Returns {"message":"Not Found",...} error response
#
# This approach correctly handles both public and private organization members
# without needing to distinguish between their visibility settings.

# Use the provided fine-grained token instead of default GITHUB_TOKEN
response=$(gh api orgs/dotCMS/members/${{ inputs.username }} \
--header "Authorization: token ${{ inputs.github_token }}" 2>/dev/null)
response=$(gh api orgs/dotCMS/members/${{ inputs.username }} 2>/dev/null)
api_exit_code=$?

if [ $api_exit_code -eq 0 ]; then
# HTTP 200: User is a member (public or private)
# We check response content only for logging clarity, not authorization logic
if [ -n "$response" ]; then
echo "✅ User ${{ inputs.username }} is a member of dotCMS (public membership)"
else
Expand All @@ -59,7 +60,8 @@ runs:
echo "is_member=true" >> $GITHUB_OUTPUT
echo "membership_status=member" >> $GITHUB_OUTPUT
else
# HTTP 404 or other error: User is not a member or API issue
# HTTP 404 or other error: User is not a member

echo "❌ User ${{ inputs.username }} is not a member of dotCMS (API exit code: $api_exit_code)"
echo "is_member=false" >> $GITHUB_OUTPUT
echo "membership_status=non-member" >> $GITHUB_OUTPUT
Expand Down
Loading