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
48 changes: 47 additions & 1 deletion gh-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,52 @@ octocat/octocat

Change a repository visibility to internal, for example

### check-enterprise-owner.sh

Checks if a user is an enterprise admin (owner) using the GitHub GraphQL API.

Usage:

```shell
./check-enterprise-owner.sh <ENTERPRISE_SLUG> <USERNAME>
```

### check-enterprise-team-membership.sh

Checks if a user is a member of an enterprise team using the GitHub API (private preview feature).

Usage:

```shell
./check-enterprise-team-membership.sh <enterprise> <team-slug> <username>
```

> [!NOTE]
> This script uses a private preview API for enterprise teams, which may change without notice.

### check-organization-team-membership.sh

Checks if a user is a member of a specific team in an organization using the GitHub API.

Usage:

```shell
./check-organization-team-membership.sh <organization> <team-slug> <username>
```

> [!NOTE]
> Your token must have the `read:org` scope to view team membership.

### check-repository-admin.sh

Checks if a user is a collaborator in a given repository and determines if they have admin access.

Usage:

```shell
./check-repository-admin.sh <OWNER> <REPOSITORY> <USERNAME>
```

### copy-organization-members.sh

Copy organization members from one organization to the other, the member will **retain** the source role (owner or member), member cannot be demoted, if they already exist at the target with an owner role they cannot be demoted to member.
Expand Down Expand Up @@ -1016,7 +1062,7 @@ Gets the list of organization secrets that are available by repository (all repo

Public repositories are ignored and not listed.

A repository can only use a max of [100 organization secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#limits-for-secrets) that are available to it. The purpose of this script is to get list of repositories and the number of organization secrets available to them mostly to figure out if you are hitting the limit and not all secrets are really available to the repository (only first 100 secrets sorted by secret name are available).
A repository can only use a max of [100 organization secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#limits-for-secrets) that are available to it. The purpose of this script is to get list of repositories and the number of organization secrets available to them mostly to figure out if you are hitting the limit and not all secrets are really available.

usage:

Expand Down
163 changes: 163 additions & 0 deletions gh-cli/check-enterprise-owner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/bin/bash

# Check if a user is an enterprise admin
# API: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/objects#enterpriseownerinfo
# Reference: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/queries#enterprise

# Usage: ./check-enterprise-owner.sh <ENTERPRISE_SLUG> <USERNAME>
# Example: ./check-enterprise-owner.sh octocat johndoe

set -e

# Check if required parameters are provided
if [ $# -lt 2 ]; then
echo "Usage: $0 <ENTERPRISE_SLUG> <USERNAME>"
echo "Example: $0 octocat johndoe"
echo ""
echo "Note: Requires 'gh' CLI to be installed and authenticated"
exit 1
fi

ENTERPRISE_SLUG="$1"
TARGET_USER="$2"

# Check if gh CLI is available
if ! command -v gh &> /dev/null; then
echo "Error: GitHub CLI (gh) is required but not installed."
echo "Install it from: https://cli.github.com/"
exit 1
fi

# Check if gh is authenticated
if ! gh auth status &> /dev/null; then
echo "Error: GitHub CLI is not authenticated."
echo "Run 'gh auth login' to authenticate."
exit 1
fi

# Check if jq is available for JSON parsing
if ! command -v jq &> /dev/null; then
echo "Error: jq is required for JSON parsing but not installed."
echo "Install it with: brew install jq (on macOS) or your package manager"
exit 1
fi

USER_FOUND=false
CURSOR=""
HAS_NEXT_PAGE=true

echo "Checking if user '$TARGET_USER' is an enterprise admin for '$ENTERPRISE_SLUG'..."

while [ "$HAS_NEXT_PAGE" = "true" ]; do
# Build the GraphQL query with optional cursor
if [ -z "$CURSOR" ]; then
QUERY='
query CheckEnterpriseAdmin($slug: String!) {
enterprise(slug: $slug) {
id
name
slug
ownerInfo {
admins(first: 100) {
totalCount
pageInfo {
hasNextPage
endCursor
}
nodes {
login
name
}
}
}
}
}'
RESPONSE=$(gh api graphql -f query="$QUERY" -f slug="$ENTERPRISE_SLUG" 2>/dev/null)
else
QUERY='
query CheckEnterpriseAdmin($slug: String!, $cursor: String!) {
enterprise(slug: $slug) {
id
name
slug
ownerInfo {
admins(first: 100, after: $cursor) {
totalCount
pageInfo {
hasNextPage
endCursor
}
nodes {
login
name
}
}
}
}
}'
RESPONSE=$(gh api graphql -f query="$QUERY" -f slug="$ENTERPRISE_SLUG" -f cursor="$CURSOR" 2>/dev/null)
fi

# Check for errors in the response
if [ $? -ne 0 ] || echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "API call failed")
echo "✗ Error accessing enterprise '$ENTERPRISE_SLUG': $ERROR_MSG"

# Check for common error types
if echo "$ERROR_MSG" | grep -q "Could not resolve to an Enterprise"; then
echo " Enterprise slug '$ENTERPRISE_SLUG' not found or not accessible"
elif echo "$ERROR_MSG" | grep -q "Must have admin access"; then
echo " Current user does not have admin access to view enterprise admins"
fi
exit 1
fi

# Check if enterprise data exists
if ! echo "$RESPONSE" | jq -e '.data.enterprise' > /dev/null 2>&1; then
echo "✗ Enterprise '$ENTERPRISE_SLUG' not found or not accessible"
exit 1
fi

# Extract data from response
TOTAL_COUNT=$(echo "$RESPONSE" | jq -r '.data.enterprise.ownerInfo.admins.totalCount')
HAS_NEXT_PAGE=$(echo "$RESPONSE" | jq -r '.data.enterprise.ownerInfo.admins.pageInfo.hasNextPage')
CURSOR=$(echo "$RESPONSE" | jq -r '.data.enterprise.ownerInfo.admins.pageInfo.endCursor')

# Check if target user is in current page
ADMINS=$(echo "$RESPONSE" | jq -r '.data.enterprise.ownerInfo.admins.nodes[].login')

PAGE_COUNT=$(echo "$RESPONSE" | jq -r '.data.enterprise.ownerInfo.admins.nodes | length')
echo "Checking page with $PAGE_COUNT admins..."

for admin in $ADMINS; do
# Case-insensitive comparison
if [ "$(echo "$admin" | tr '[:upper:]' '[:lower:]')" = "$(echo "$TARGET_USER" | tr '[:upper:]' '[:lower:]')" ]; then
USER_FOUND=true
echo "✓ User '$TARGET_USER' found as enterprise admin!"

# Get full admin details
ADMIN_NAME=$(echo "$RESPONSE" | jq -r --arg login "$admin" '.data.enterprise.ownerInfo.admins.nodes[] | select(.login == $login) | .name')
echo " Login: $admin"
echo " Name: $ADMIN_NAME"
break 2 # Break out of both loops
fi
done

# If no next page, break
if [ "$HAS_NEXT_PAGE" = "false" ]; then
break
fi
done

# Final result
echo ""
echo "=== SUMMARY ==="
echo "Enterprise: $ENTERPRISE_SLUG"
echo "Total admins checked: $TOTAL_COUNT"
if [ "$USER_FOUND" = "true" ]; then
echo "Result: ✓ '$TARGET_USER' IS an enterprise admin"
exit 0
else
echo "Result: ✗ '$TARGET_USER' is NOT an enterprise admin"
exit 1
fi
87 changes: 87 additions & 0 deletions gh-cli/check-enterprise-team-membership.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash

# API GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}
# NOTE: Enterprise Teams is a private preview feature, API may subject to change without notice
#
# Check if a user is a member of an enterprise team
# Response Code: 200 if user is a member, otherwise not

# Function to display usage
usage() {
echo "Usage: $0 <enterprise> <team-slug> <username>"
echo ""
echo "Parameters:"
echo " enterprise - The enterprise slug"
echo " team-slug - The enterprise team slug"
echo " username - The username to check"
echo ""
echo "Example:"
echo " $0 my-enterprise dev-team octocat"
exit 1
}

# Check if required parameters are provided
if [ $# -ne 3 ]; then
echo "Error: Missing required parameters"
usage
fi

ENTERPRISE="$1"
TEAM_SLUG="$2"
USERNAME="$3"

echo "Checking if user '$USERNAME' is a member of enterprise team '$TEAM_SLUG' in enterprise '$ENTERPRISE'..."

# Make the API call using gh api and capture both response and HTTP status code
TEMP_DIR=$(mktemp -d)
RESPONSE_FILE="$TEMP_DIR/response.json"
HEADERS_FILE="$TEMP_DIR/headers.txt"

# Use gh api with --include flag to get headers, then parse status code
gh api "/enterprises/$ENTERPRISE/teams/$TEAM_SLUG/memberships/$USERNAME" \
--include > "$TEMP_DIR/full_response.txt" 2>&1
API_EXIT_CODE=$?

# Extract HTTP status code from the response headers
if [ $API_EXIT_CODE -eq 0 ]; then
# Split headers and body
sed '/^$/q' "$TEMP_DIR/full_response.txt" > "$HEADERS_FILE"
sed '1,/^$/d' "$TEMP_DIR/full_response.txt" > "$RESPONSE_FILE"

# Extract status code from first line of headers
HTTP_STATUS=$(head -n1 "$HEADERS_FILE" | grep -o '[0-9]\{3\}' | head -n1)
RESPONSE=$(cat "$RESPONSE_FILE")
else
# If gh api failed, set status as non-200
RESPONSE=$(cat "$TEMP_DIR/full_response.txt")
HTTP_STATUS="non-200"
fi

echo "HTTP Status Code: $HTTP_STATUS"

# Check response based on HTTP status code - only 200 indicates membership
if [ "$HTTP_STATUS" = "200" ]; then
# 200 OK - User is a member
TYPE=$(echo "$RESPONSE" | jq -r '.type // "unknown"' 2>/dev/null)

echo "✅ User '$USERNAME' is a member of team '$TEAM_SLUG'"
if [ "$TYPE" != "null" ] && [ "$TYPE" != "unknown" ]; then
echo " Type: $TYPE"
fi

# Display full response if verbose
if [ "$VERBOSE" = "true" ]; then
echo ""
echo "Full response:"
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
fi

# Cleanup
rm -rf "$TEMP_DIR"
exit 0
else
# Any non-200 status code means user is not a member
echo "❌ User '$USERNAME' is not a member of team '$TEAM_SLUG' in enterprise '$ENTERPRISE'"
rm -rf "$TEMP_DIR"
exit 1
fi
54 changes: 54 additions & 0 deletions gh-cli/check-organization-team-membership.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash

# Checks if a user is a member of a specific team in an organization using the GitHub API.
#
# Usage:
# ./check-org-team-membership.sh <organization> <team-slug> <username>
#
# Example:
# ./check-org-team-membership.sh my-organization security johndoe
#
# Requirements:
# - GitHub CLI (`gh`) must be installed and authenticated
# - Token must have `read:org` scope to view team membership
# - Uses GitHub API endpoint: /orgs/{organization}/teams/{team-slug}/memberships/{username}
# - This script does not paginate as the endpoint is for a single user/team
#
# Notes:
# - Organization, team-slug, and username must be provided as input parameters

set -e

if [ $# -lt 3 ]; then
echo "Usage: $0 <organization> <team-slug> <username>"
echo "Example: $0 my-organization security johndoe"
echo ""
echo "Note: Requires 'gh' CLI to be installed and authenticated"
exit 1
fi

organization="$1"
team_slug="$2"
username="$3"

if ! command -v gh &> /dev/null; then
echo "Error: GitHub CLI (gh) is required but not installed."
echo "Install it from: https://cli.github.com/"
exit 1
fi

if ! gh auth status &> /dev/null; then
echo "Error: GitHub CLI is not authenticated."
echo "Run 'gh auth login' to authenticate."
exit 1
fi

echo "Checking if user '$username' is a member of team '$team_slug' in organization '$organization'..."

if gh api "orgs/$organization/teams/$team_slug/memberships/$username" --silent 2>/dev/null; then
echo "✓ User '$username' is a member of team '$team_slug' in organization '$organization'"
exit 0
else
echo "✗ User '$username' is NOT a member of team '$team_slug' in organization '$organization'"
exit 1
fi
Loading