Skip to content
Merged
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
33 changes: 33 additions & 0 deletions ansible/roles/dev-desktop/files/git-credential-dev-desktop-inner
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@
from sys import argv, stdin, stderr
import jwt
import os
import re
import requests
import time


# GitHub user and organization names may contain alphanumerics and hyphens.
GITHUB_USER_NAME = re.compile(r"^[A-Za-z0-9-]+$")
# GitHub repository names may also contain underscores and dots.
GITHUB_REPOSITORY_NAME = re.compile(r"^[A-Za-z0-9_.-]+$")


def generate_jwt():
base = os.path.dirname(os.path.realpath(__file__))
with open('/etc/github-app-credentials/app_id.txt', 'r') as fh:
Expand Down Expand Up @@ -38,22 +45,26 @@ def generate_token(user, repo):
"Authorization": f"Bearer {token}",
})

# GitHub Docs: https://docs.github.com/en/rest/apps/apps#get-a-repository-installation-for-the-authenticated-app
installation = req("GET", f"repos/{user}/{repo}/installation")
if installation.status_code == 404:
# 404 could be caused either by the app not being installed at all on the account,
# or by the repository not being authorized with the app. Try to retrieve the
# installation in the user's account to check which case we're handling.
# GitHub Docs: https://docs.github.com/en/rest/apps/apps#get-a-user-installation-for-the-authenticated-app
user_installation = req("GET", f"users/{user}/installation")
if user_installation.status_code == 200:
raise RepositoryNotAuthorizedError(user_installation.json()["html_url"])
else:
# Requesting the app is required to get the app URL.
# GitHub Docs: https://docs.github.com/en/rest/apps/apps#get-the-authenticated-app
app = req("GET", "app")
app.raise_for_status()
raise ApplicationNotInstalledError(app.json()["html_url"])
installation.raise_for_status()
installation = installation.json()

# GitHub Docs: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app
auth = req("POST", f"app/installations/{installation['id']}/access_tokens")
if auth.status_code == 403:
raise SuspendedInstallationError(installation["html_url"])
Expand All @@ -75,6 +86,10 @@ def credential_provider():
if repo.endswith(".git"):
repo = repo[:-len(".git")]

if not is_valid_github_user_name(git_user) or not is_valid_github_repository_name(repo):
invalid_github_repository(config["path"])
return

# Get the username of the user who executed sudo. This is safe because
# unprivileged users can't execute this script directly, and even if they
# somehow manage to execute the script they won't have permissions to
Expand Down Expand Up @@ -132,6 +147,24 @@ def log(message=""):
print(message, file=stderr)


def invalid_github_repository(path):
log("error: failed to obtain git credentials")
log()
log(f"The GitHub repository path is not valid: {path}")
log("Expected a repository URL like https://github.com/<owner>/<repository>.git")
log()
print("quit=true")


def is_valid_github_repository_name(repo):
# Reject path separators and dot segments before interpolating into API URLs.
return repo not in (".", "..") and GITHUB_REPOSITORY_NAME.fullmatch(repo) is not None


def is_valid_github_user_name(user):
return GITHUB_USER_NAME.fullmatch(user) is not None


class ApplicationNotInstalledError(RuntimeError):
def __init__(self, app_url):
self.app_url = app_url
Expand Down
Loading