2 # Copyright (C) 2018 rubenwardy
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 from flask import Blueprint
19 bp = Blueprint("github", __name__)
21 from flask import redirect, url_for, request, flash, abort
22 from flask_user import current_user
23 from sqlalchemy import func
24 from flask_github import GitHub
25 from app import github, csrf
26 from app.models import db, User, APIToken, Package
27 from app.utils import loginUser
28 from app.blueprints.api.support import error, handleCreateRelease
31 @bp.route("/github/start/")
33 return github.authorize("")
35 @bp.route("/github/callback/")
36 @github.authorized_handler
37 def callback(oauth_token):
38 next_url = request.args.get("next")
39 if oauth_token is None:
40 flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
41 return redirect(url_for("user.login"))
46 url = "https://api.github.com/user"
47 r = requests.get(url, headers={"Authorization": "token " + oauth_token})
48 username = r.json()["login"]
50 # Get user by github username
51 userByGithub = User.query.filter(func.lower(User.github_username) == func.lower(username)).first()
53 # If logged in, connect
54 if current_user and current_user.is_authenticated:
55 if userByGithub is None:
56 current_user.github_username = username
58 flash("Linked github to account", "success")
59 return redirect(url_for("homepage.home"))
61 flash("Github account is already associated with another user", "danger")
62 return redirect(url_for("homepage.home"))
64 # If not logged in, log in
66 if userByGithub is None:
67 flash("Unable to find an account for that Github user", "danger")
68 return redirect(url_for("users.claim"))
69 elif loginUser(userByGithub):
70 if not current_user.hasPassword():
71 return redirect(next_url or url_for("users.set_password", optional=True))
73 return redirect(next_url or url_for("homepage.home"))
75 flash("Authorization failed [err=gh-login-failed]", "danger")
76 return redirect(url_for("user.login"))
79 @bp.route("/github/webhook/", methods=["POST"])
85 github_url = "github.com/" + json["repository"]["full_name"]
86 package = Package.query.filter(Package.repo.like("%{}%".format(github_url))).first()
88 return error(400, "Unknown package")
90 # Get all tokens for package
91 possible_tokens = APIToken.query.filter_by(package=package).all()
98 header_signature = request.headers.get('X-Hub-Signature')
99 if header_signature is None:
100 return error(403, "Expected payload signature")
102 sha_name, signature = header_signature.split('=')
103 if sha_name != 'sha1':
104 return error(403, "Expected SHA1 payload signature")
106 for token in possible_tokens:
107 mac = hmac.new(token.access_token.encode("utf-8"), msg=request.data, digestmod='sha1')
109 if hmac.compare_digest(str(mac.hexdigest()), signature):
113 if actual_token is None:
114 return error(403, "Invalid authentication")
120 event = request.headers.get("X-GitHub-Event")
122 title = json["head_commit"]["message"].partition("\n")[0]
125 return error(400, "Unknown event, expected 'push'")
131 return handleCreateRelease(actual_token, package, title, ref)