Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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].0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd bump to 2.5.1 for this fix ComputerScienceHouse/csh_ldap#39

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