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
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ ARG PORT=8080
ENV PORT=${PORT}
EXPOSE ${PORT}

CMD ["sh", "-c", "gunicorn app:application --bind=0.0.0.0:${PORT} --access-logfile=- --timeout=600"]
# --access-logfile - prints access log to stdout
# --error-log - prints errors to stdout
# --capture-output logging and print go to error log (stdout)
CMD ["sh", "-c", "gunicorn app:application --bind=0.0.0.0:${PORT} --access-logfile - --error-log - --capture-output --timeout=600"]
3 changes: 2 additions & 1 deletion config.env.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
# GitHub secrets
GITHUB_CLIENT_ID = os.environ.get('GITHUB_ID', '')
GITHUB_SECRET = os.environ.get('GITHUB_SECRET', '')
ORG_TOKEN = os.environ.get('GITHUB_ORG_TOKEN', '')
GITHUB_APP_ID = os.environ.get('GITHUB_APP_ID', '')
GITHUB_APP_PRIVATE_KEY = os.environ.get('GITHUB_APP_PRIVATE_KEY', '')

# Twitch secrets
TWITCH_CLIENT_ID = os.environ.get('TWITCH_CLIENT_ID', '')
Expand Down
130 changes: 113 additions & 17 deletions eac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import base64
from typing import Any

import jwt
from requests.models import HTTPError

import flask
import werkzeug
from flask import Flask, request, redirect, session, render_template, send_from_directory, jsonify
Expand Down Expand Up @@ -51,7 +54,7 @@
+ '&code=%s'

_GITHUB_AUTH_URI = 'https://github.com/login/oauth/authorize' \
+ '?client_id=%s'\
+ '?client_id=%s' \
+ '&state=%s'
_GITHUB_TOKEN_URI = 'https://github.com/login/oauth/access_token' \
+ '?client_id=%s' \
Expand Down Expand Up @@ -168,19 +171,29 @@ def _github_landing() -> tuple[str, int]:
(APP.config['GITHUB_CLIENT_ID'], APP.config['GITHUB_SECRET'],
request.args.get('code')),
headers={'Accept': 'application/json'},
timeout=APP.config['REQUEST_TIMEOUT'],
)
token = resp.json()['access_token']
timeout=APP.config['REQUEST_TIMEOUT'])
try:
resp.raise_for_status()
except HTTPError as e:
print('response:', resp.json())
raise e

resp_json = resp.json()
token = resp_json['access_token']
header = {
'Authorization': 'token ' + token,
'Accept': 'application/vnd.github.v3+json'
}

user_resp = requests.get(
'https://api.github.com/user',
headers=header,
timeout=APP.config['REQUEST_TIMEOUT'],
)
user_resp = requests.get('https://api.github.com/user',
headers=header,
timeout=APP.config['REQUEST_TIMEOUT'])
try:
user_resp.raise_for_status()
except HTTPError as e:
print('response:', user_resp.json())
raise e

user_resp_json = user_resp.json()

github_username = user_resp_json['login']
Expand All @@ -194,20 +207,88 @@ def _github_landing() -> tuple[str, int]:
return render_template('callback.html'), 200


def _get_github_jwt() -> str:
signing_key = APP.config["GITHUB_APP_PRIVATE_KEY"]

payload = {
'iat': int(time.time()),
'exp': int(time.time() + 600),
'iss': APP.config['GITHUB_APP_ID'],
}

encoded_jwt = jwt.encode(payload, signing_key, algorithm='RS256')

return encoded_jwt


def _auth_github_org() -> str:
jwt_auth = _get_github_jwt()

headers = {
'Accept': 'application/vnd.github.v3+json',
'Authorization': f'Bearer {jwt_auth}',
}

org_installation_resp = requests.get(
'https://api.github.com/orgs/ComputerScienceHouse/installation',
headers=headers,
timeout=APP.config['REQUEST_TIMEOUT'])
try:
org_installation_resp.raise_for_status()
except HTTPError as e:
print('response:', org_installation_resp.json())
raise e

org_installation_json = org_installation_resp.json()
org_installation_id = org_installation_json['id']

org_token_resp = requests.post(
f'https://api.github.com/app/installations/{org_installation_id}/access_tokens',
headers=headers,
timeout=APP.config['REQUEST_TIMEOUT'])
try:
org_token_resp.raise_for_status()
except HTTPError as e:
print('response:', org_token_resp.json())
raise e

org_token_json = org_token_resp.json()
org_token = org_token_json['token']

return org_token


def _link_github(github_username: str, github_id: str, member: Any) -> None:
"""
Puts a member's github into LDAP and adds them to the org.
:param github_username: the user's github username
:param github_id: the user's github id
:param member: the member's LDAP object
"""
payload = {'invitee_id': github_id}
requests.post(
org_token = _auth_github_org()

payload = {
'org': 'ComputerScienceHouse',
'invitee_id': github_id,
'role': 'direct_member'
}

github_org_headers = {
'Accept': 'application/vnd.github.v3+json',
'Authorization': f'Token {org_token}',
}

resp = requests.post(
'https://api.github.com/orgs/ComputerScienceHouse/invitations',
headers=_ORG_HEADER,
data=payload,
timeout=APP.config['REQUEST_TIMEOUT'],
)
headers=github_org_headers,
json=payload,
timeout=APP.config['REQUEST_TIMEOUT'])
try:
resp.raise_for_status()
except HTTPError as e:
print('response:', resp.json())
raise e

member.github = github_username


Expand All @@ -217,12 +298,27 @@ def _revoke_github() -> werkzeug.Response:
""" Clear's a member's github in LDAP and removes them from the org. """
uid = str(session['userinfo'].get('preferred_username', ''))
member = _LDAP.get_member(uid, uid=True)
requests.delete(

org_token = _auth_github_org()

headers = {
'Accept': 'application/vnd.github.v3+json',
'Authorization': f'Token {org_token}',
}

resp = requests.delete(
'https://api.github.com/orgs/ComputerScienceHouse/members/' +
member.github,
headers=_ORG_HEADER,
headers=headers,
timeout=APP.config['REQUEST_TIMEOUT'],
)

try:
resp.raise_for_status()
except HTTPError as e:
print('response:', resp.json())
raise e

member.github = None
return jsonify(success=True)

Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
csh-ldap==2.4.0
csh-ldap @ git+https://github.com/ComputerScienceHouse/[email protected]
Flask==3.1.2
Flask-pyoidc==3.14.3
gunicorn==23.0.0
Expand All @@ -7,4 +7,6 @@ pylint==3.3.8
pylint-quotes==0.2.3
requests==2.32.5
sentry-sdk[flask]==2.37.1
PyJWT==2.10.1
cryptography==46.0.2
yapf==0.43.0