From bc7af104c4913c17967bda7d22b51e8c688af57d Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Wed, 2 Mar 2016 23:36:51 -0600 Subject: [PATCH] avatars and invitation requests done. --- .gitignore | 1 + deploy | 2 +- scripts/requirements.txt | 3 +- scripts/setup.sh | 2 +- server/api/team.py | 40 ++++++++++++++++++++++- server/api/user.py | 18 +++++++++-- server/api/utils.py | 69 +++++++++++++++++++++++++++++++++++----- web/css/easyctf.css | 4 +++ web/js/easyctf.js | 22 ++++++++++++- web/pages/profile.html | 6 ++-- web/pages/settings.html | 9 +++++- web/pages/team.html | 31 ++++++++++++++---- 12 files changed, 182 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 60a090c..3052973 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .bundle/config logs/ files/ +pfp/ # Object files *.o diff --git a/deploy b/deploy index bb04d8e..04bce76 100755 --- a/deploy +++ b/deploy @@ -10,4 +10,4 @@ sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf echo "Starting the server..." cd /home/vagrant/server sudo service nginx start -tmux new-session -s ctf -d 'gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini' \ No newline at end of file +tmux new-session -s ctf -d 'tmux set remain-on-exit on && gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini' \ No newline at end of file diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 7d8974f..6fa252f 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -4,4 +4,5 @@ Flask-SQLAlchemy SQLAlchemy gunicorn requests -voluptuous \ No newline at end of file +voluptuous +PIL \ No newline at end of file diff --git a/scripts/setup.sh b/scripts/setup.sh index 95accdc..0d46ac4 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,7 +12,7 @@ sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again p echo "Installing dependencies..." apt-get -y install python -apt-get -y install python-pip +apt-get -y install python-pip libjpeg-dev apt-get -y install python-dev libmysqlclient-dev apt-get -y install nginx apt-get -y install mysql-server diff --git a/server/api/team.py b/server/api/team.py index 5eb946a..d0d48f1 100644 --- a/server/api/team.py +++ b/server/api/team.py @@ -36,7 +36,7 @@ def team_create(): Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid }) db.session.commit() - session["tid"] = tid + session["tid"] = team.tid return { "success": 1, "message": "Success!" } @blueprint.route("/delete", methods=["POST"]) @@ -175,6 +175,9 @@ def team_accept_invite(): if _team is None: raise WebException("Team not found.") + if len(_team.get_members()) >= 5: + raise WebException("This team is full.") + invitation = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user.uid).first() if invitation is None: raise WebException("Invitation doesn't exist.") @@ -190,6 +193,41 @@ def team_accept_invite(): return { "success": 1, "message": "Success!" } +@blueprint.route("/invite/request/accept", methods=["POST"]) +@api_wrapper +def team_accept_invite_request(): + params = utils.flat_multi(request.form) + _user = user.get_user().first() + if not user.in_team(_user): + raise WebException("You must be in a team!") + _team = get_team(tid=_user.tid).first() + tid = _team.tid + if _user.uid != _team.owner: + raise WebException("You must be the captain of your team to rescind invitations!") + + if len(_team.get_members()) >= 5: + raise WebException("Your team is full.") + + uid = params.get("uid") + _user2 = user.get_user(uid=uid).first() + if user.in_team(_user2): + raise WebException("This user is already in a team!") + + invitation = TeamInvitations.query.filter_by(rtype=1, frid=_user2.uid, toid=tid).first() + if invitation is None: + raise WebException("Invitation doesn't exist.") + + with app.app_context(): + _user2 = Users.query.filter_by(uid=_user2.uid).first() + _user2.tid = tid + db.session.delete(invitation) + invitation2 = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user2.uid).first() + if invitation2 is not None: + db.session.delete(invitation2) + db.session.commit() + + return { "success": 1, "message": "Success!" } + @blueprint.route("/info", methods=["GET"]) @api_wrapper def team_info(): diff --git a/server/api/user.py b/server/api/user.py index 9923c06..c9af129 100644 --- a/server/api/user.py +++ b/server/api/user.py @@ -1,4 +1,4 @@ -from flask import Blueprint, make_response, session, request, redirect, url_for +from flask import Blueprint, make_response, session, request, redirect, url_for, send_file from flask import current_app as app from voluptuous import Schema, Length, Required @@ -88,6 +88,7 @@ def user_register(): with app.app_context(): db.session.add(user) db.session.commit() + utils.generate_identicon(email, user.uid) logger.log(__name__, logger.INFO, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8"))) login_user(username, password) @@ -160,7 +161,8 @@ def user_info(): "registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z", "me": me, "show_email": show_email, - "in_team": user_in_team + "in_team": user_in_team, + "uid": user.uid } if show_email: userdata["email"] = user.email @@ -171,6 +173,18 @@ def user_info(): userdata["invitations"] = invitations return { "success": 1, "user": userdata } +@blueprint.route("/avatar/", methods=["GET"]) +def user_avatar(uid): + uid = int(uid) + try: + return send_file("pfp/%d.png" % uid, mimetype="image/png") + except: + user = get_user(uid=uid).first() + if user is not None: + utils.generate_identicon(user.email, user.uid) + return send_file("pfp/%d.png" % uid, mimetype="image/png") + return abort(404) + ################## # USER FUNCTIONS # ################## diff --git a/server/api/utils.py b/server/api/utils.py index 92a117f..c2adef0 100644 --- a/server/api/utils.py +++ b/server/api/utils.py @@ -1,4 +1,5 @@ import datetime +import hashlib import json import random import re @@ -7,6 +8,8 @@ import string import traceback import unicodedata +from PIL import Image, ImageDraw + from flask import current_app as app from functools import wraps from werkzeug.security import generate_password_hash, check_password_hash @@ -39,11 +42,61 @@ def flat_multi(multidict): return flat def send_email(recipient, subject, body): - api_key = app.config["MG_API_KEY"] - data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]), - "to": recipient, - "subject": subject, - "text": body - } - auth = ("api", api_key) - return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data) + api_key = app.config["MG_API_KEY"] + data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]), + "to": recipient, + "subject": subject, + "text": body + } + auth = ("api", api_key) + return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data) + +def generate_identicon(email, filename): + email = email.strip().lower() + h = hashlib.sha1(email).hexdigest() + size = 256 + margin = 0.08 + baseMargin = int(size * margin) + cell = int((size - baseMargin * 2.0) / 5) + margin = int((size - cell * 5.0) / 2) + image = Image.new("RGB", (size, size)) + draw = ImageDraw.Draw(image) + + def hsl2rgb(h, s, b): + h *= 6 + s1 = [] + s *= b if b < 0.5 else 1-b + b += s + s1.append(b) + s1.append(b - h % 1 * s * 2) + s *= 2 + b -= s + s1.append(b) + s1.append(b) + s1.append(b + h % 1 * s) + s1.append(b + s) + + return [ + s1[~~h % 6], s1[(h|16) % 6], s1[(h|8) % 6] + ] + + rgb = hsl2rgb(int(h[-7:], 16) & 0xfffffff, 0.5, 0.7) + bg = (255, 255, 255) + fg = (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255)) + + # print fg, bg + draw.rectangle([(0, 0), (size, size)], fill=bg) + + for i in range(15): + c = bg if int(h[i], 16) % 2 == 1 else fg + if i < 5: + draw.rectangle([(2*cell + margin, i*cell + margin), (3*cell + margin, (i+1)*cell + margin)], fill=c) + elif i < 10: + draw.rectangle([(1*cell + margin, (i-5)*cell + margin), (2*cell + margin, (i-4)*cell + margin)], fill=c) + draw.rectangle([(3*cell + margin, (i-5)*cell + margin), (4*cell + margin, (i-4)*cell + margin)], fill=c) + elif i < 15: + draw.rectangle([(0*cell + margin, (i-10)*cell + margin), (1*cell + margin, (i-9)*cell + margin)], fill=c) + draw.rectangle([(4*cell + margin, (i-10)*cell + margin), (5*cell + margin, (i-9)*cell + margin)], fill=c) + + image.save(open("pfp/%s.png" % filename, "w"), "PNG") + return \ No newline at end of file diff --git a/web/css/easyctf.css b/web/css/easyctf.css index 3e8b653..f1da53c 100644 --- a/web/css/easyctf.css +++ b/web/css/easyctf.css @@ -24,4 +24,8 @@ body { .NO_HOVER_UNDERLINE_DAMMIT:hover, .NO_HOVER_UNDERLINE_DAMMIT:focus, .NO_HOVER_UNDERLINE_DAMMIT:active { text-decoration:none; +} + +#avatar { + border: 1px solid rgb(221, 221, 221); } \ No newline at end of file diff --git a/web/js/easyctf.js b/web/js/easyctf.js index a58ee9d..f6dfda3 100644 --- a/web/js/easyctf.js +++ b/web/js/easyctf.js @@ -46,7 +46,7 @@ app.config(function($routeProvider, $locationProvider) { }) .when("/settings", { templateUrl: "pages/settings.html", - controller: "mainController" + controller: "settingsController" }) .when("/forgot", { templateUrl: "pages/forgot.html", @@ -188,6 +188,17 @@ app.controller("adminProblemsController", ["$controller", "$scope", "$http", fun }); }]); +app.controller("settingsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) { + $controller("mainController", { $scope: $scope }); + $.get("/api/user/info", {}, function(result) { + if (result["success"] == 1) { + console.log(result["user"]); + $scope.user = result["user"]; + } + $scope.$apply(); + }); +}]); + function display_message(containerId, alertType, message, callback) { $("#" + containerId).html("
" + message + "
"); $("#" + containerId).hide().slideDown("fast", "swing", function() { @@ -382,3 +393,12 @@ var accept_invitation = function(tid) { } }); }; + +var accept_invitation_request = function(uid) { + var data = { "uid": uid }; + api_call("POST", "/api/team/invite/request/accept", data, function(result) { + if (result["success"] == 1) { + location.reload(true); + } + }); +}; diff --git a/web/pages/profile.html b/web/pages/profile.html index 45aedee..5e0539a 100644 --- a/web/pages/profile.html +++ b/web/pages/profile.html @@ -3,8 +3,10 @@
- - Edit Picture +
+ + Edit Picture +

{{ user.name }}

@{{ user.username }}
diff --git a/web/pages/settings.html b/web/pages/settings.html index fc22a07..1026dce 100644 --- a/web/pages/settings.html +++ b/web/pages/settings.html @@ -18,7 +18,14 @@

Public Profile

-

Hi.

+ +
+
+
+
+
{{ user }}
+
+
diff --git a/web/pages/team.html b/web/pages/team.html index 960cb81..9dcbe4f 100644 --- a/web/pages/team.html +++ b/web/pages/team.html @@ -43,7 +43,7 @@

{{ member['name'] }}

Owner
-

@{{ member['username'] }}

+

@{{ member['username'] }}

-
+

{{ member['name'] }}

-

@{{ member['username'] }}

+

@{{ member['username'] }}

@@ -87,15 +87,26 @@

Invitation Requests

-
+

{{ member['name'] }}

-

@{{ member['username'] }}

+

@{{ member['username'] }}

+ +
+
+ +
+
+
+
@@ -185,5 +196,11 @@ e.preventDefault(); } }); - $("[data-toggle=tooltip]").tooltip(); - + $(document).ready(function() { + $("[data-toggle=tooltip]").tooltip(); + $("ul[role=tablist]").tab(); + $("a[role=tab]").click(function(e) { + e.preventDefault(); + }); + }); + \ No newline at end of file