diff --git a/.gitignore b/.gitignore index ba0430d..5e6d690 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__/ \ No newline at end of file +__pycache__/ +testing_configuration.cfg \ No newline at end of file diff --git a/bridge/__init__.py b/bridge/__init__.py index 12987b0..3a79b75 100644 --- a/bridge/__init__.py +++ b/bridge/__init__.py @@ -3,9 +3,184 @@ from flask import request from flask import redirect from flask import abort +import requests + app = Flask(__name__) app.config.from_envvar('GIT_BRIDGE_SETTINGS') @app.route("/bridge") def index(): - return "you've reached the main page for an internal service. congrats!" \ No newline at end of file + return "you've reached the main page for an internal service. congrats!" + +@app.route("/bridge/endpoints/gitea/repo", methods=["POST"]) +def gitea_handle_repo_action(): + data = request.json + + try: + repository = data["repository"] + + repo_action = data["action"] + repo_id = repository["id"] + repo_owner = repository["owner"]["login"] + repo_name = repository["name"] + repo_description = repository["description"] + + except KeyError: + abort(400) # the data isn't formatted correctly + + """ + Our plan of action for handling these events: + - ignore deleting repositories -- this is a potentially destructive operation + that *only* *the* *user* *should* *do* + - create a cooresponding repo on github + - create a push mirror to github so pushes to gitea get to github + - create webhooks for issues (and in the future, pull requests) on both + Gitea and Github ends + """ + + if not repo_action == "created": + return '' + + github_created_repo_result = requests.post( + "https://api.github.com/user/repos", + json={ + "name": repo_name, + "description": repo_description, + "homepage": "https://{}/{}/{}".format( + app.config["GITEA_INSTANCE_DOMAIN"], + repo_owner, + repo_name, + ), + }, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": "token " + app.config["GITHUB_ACCESS_TOKEN"], + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + + try: + github_created_repo_result.raise_for_status() + except requests.exceptions.HTTPError: + abort(500) + + new_github_repo_url = github_created_repo_result.json()["html_url"] + + gitea_add_github_repo_url_result = requests.patch( + "https://{}/api/v1/repos/{}/{}".format( + app.config["GITEA_INSTANCE_DOMAIN"], + repo_owner, + repo_name, + ), + json={ + "website": new_github_repo_url, + }, + headers={ + "Authorization": "token " + app.config["GITEA_ACCESS_TOKEN"], + }, + ) + + try: + gitea_add_github_repo_url_result.raise_for_status() + except requests.exceptions.HTTPError: + abort(500) + + gitea_push_target_result = requests.post( + "https://{}/api/v1/repos/{}/{}/push_mirrors".format( + app.config["GITEA_INSTANCE_DOMAIN"], + repo_owner, + repo_name + ), + json={ + "interval": "8h0m0s", + "remote_address": new_github_repo_url, + "remote_password": app.config["GITHUB_ACCESS_TOKEN"], + "remote_username": repo_owner, + "sync_on_commit": True, + }, + headers={ + "Authorization": "token " + app.config["GITEA_ACCESS_TOKEN"], + }, + ) + + try: + gitea_push_target_result.raise_for_status() + except requests.exceptions.HTTPError: + abort(500) + + gitea_force_target_push = requests.post( + "https://{}/api/v1/repos/{}/{}/push_mirrors-sync".format( + app.config["GITEA_INSTANCE_DOMAIN"], + repo_owner, + repo_name + ), + headers={ + "Authorization": "token " + app.config["GITEA_ACCESS_TOKEN"], + }, + ) + + try: + gitea_force_target_push.raise_for_status() + except requests.exceptions.HTTPError: + abort(500) + + github_create_webhook_result = requests.post( + "https://api.github.com/repos/{}/{}/hooks".format( + repo_owner, + repo_name, + ), + json={ + "name": "web", + "config": { + "url": "https://{}/bridge/endpoints/github/issue".format( + app.config["GITEA_INSTANCE_DOMAIN"] + ), + "content_type": "json", + }, + "events": [ + "issues", "issue_comment", + ], + }, + headers={ + "Accept": "application/vnd.github+json", + "Authorization": "token " + app.config["GITHUB_ACCESS_TOKEN"], + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + + try: + github_create_webhook_result.raise_for_status() + except requests.exceptions.HTTPError: + abort(500) + + gitea_create_webhook_result = requests.post( + "https://{}/api/v1/repos/{}/{}/hooks".format( + app.config["GITEA_INSTANCE_DOMAIN"], + repo_owner, + repo_name, + ), + json={ + "active": True, + "type": "gitea", + "config": { + "content_type": "json", + "url": "https://{}/bridge/endpoints/gitea/issue".format( + app.config["GITEA_INSTANCE_DOMAIN"], + ), + "http_method": "post", + }, + "events": [ + "issues", "issue_comment", + ], + }, + headers={ + "Authorization": "token " + app.config["GITEA_ACCESS_TOKEN"], + }, + ) + + try: + gitea_create_webhook_result.raise_for_status() + except requests.exceptions.HTTPError: + abort(500) + + return '' \ No newline at end of file diff --git a/contrib/sample.cfg b/contrib/sample.cfg index 84951fb..f27d1ba 100644 --- a/contrib/sample.cfg +++ b/contrib/sample.cfg @@ -1 +1,4 @@ -SECRET_KEY="replace_me" \ No newline at end of file +SECRET_KEY="replace_me" +GITHUB_ACCESS_TOKEN="replace_me" +GITEA_ACCESS_TOKEN="replace_me" +GITEA_INSTANCE_DOMAIN="git.beepboop.systems" \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..9093e01 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,3 @@ +# a ""test"" ""suite"", if it could be called that + +This directory contains a bunch of programs for auditing the behavior of the Flask app under different requests from different services. It contains some captured verbatim websockets callbacks so I can develop on them. \ No newline at end of file diff --git a/tests/data/gitea-repo-created.json b/tests/data/gitea-repo-created.json new file mode 100644 index 0000000..83cd0ef --- /dev/null +++ b/tests/data/gitea-repo-created.json @@ -0,0 +1,136 @@ +{ + "action": "created", + "repository": { + "id": 73, + "owner": { + "id": 1, + "login": "stupidcomputer", + "login_name": "", + "full_name": "stupidcomputer", + "email": "stupidcomputer@noreply.git.beepboop.systems", + "avatar_url": "https://secure.gravatar.com/avatar/b642b4217b34b1e8d3bd915fc65c4452?d=identicon", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-23T22:23:01-05:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "https://beepboop.systems", + "description": "High schooler interested in computers, security, and reproducible systems.\r\n", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "stupidcomputer" + }, + "name": "sadkfjasdf", + "full_name": "stupidcomputer/sadkfjasdf", + "description": "", + "empty": true, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 0, + "language": "", + "languages_url": "https://git.beepboop.systems/api/v1/repos/stupidcomputer/sadkfjasdf/languages", + "html_url": "https://git.beepboop.systems/stupidcomputer/sadkfjasdf", + "url": "https://git.beepboop.systems/api/v1/repos/stupidcomputer/sadkfjasdf", + "link": "", + "ssh_url": "gitea@git.beepboop.systems:stupidcomputer/sadkfjasdf.git", + "clone_url": "https://git.beepboop.systems/stupidcomputer/sadkfjasdf.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 0, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2024-10-08T22:47:49-05:00", + "updated_at": "2024-10-08T22:47:49-05:00", + "archived_at": "1969-12-31T18:00:00-06:00", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null + }, + "organization": { + "id": 1, + "login": "stupidcomputer", + "login_name": "", + "full_name": "stupidcomputer", + "email": "stupidcomputer@noreply.git.beepboop.systems", + "avatar_url": "https://secure.gravatar.com/avatar/b642b4217b34b1e8d3bd915fc65c4452?d=identicon", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-23T22:23:01-05:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "https://beepboop.systems", + "description": "High schooler interested in computers, security, and reproducible systems.\r\n", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "stupidcomputer" + }, + "sender": { + "id": 1, + "login": "stupidcomputer", + "login_name": "", + "full_name": "stupidcomputer", + "email": "stupidcomputer@noreply.git.beepboop.systems", + "avatar_url": "https://secure.gravatar.com/avatar/b642b4217b34b1e8d3bd915fc65c4452?d=identicon", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-06-23T22:23:01-05:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "https://beepboop.systems", + "description": "High schooler interested in computers, security, and reproducible systems.\r\n", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "stupidcomputer" + } +} diff --git a/tests/gitea-repo-created.py b/tests/gitea-repo-created.py new file mode 100644 index 0000000..f23bc1d --- /dev/null +++ b/tests/gitea-repo-created.py @@ -0,0 +1,8 @@ +import requests +import json + +with open("data/gitea-repo-created.json", "r") as file: + data = json.loads(file.read()) + +r = requests.post("http://127.0.0.1:5000/bridge/endpoints/gitea/repo", json=data) +print(r.text) \ No newline at end of file