avatars and invitation requests done.

This commit is contained in:
Michael Zhang 2016-03-02 23:36:51 -06:00
parent 95657606bc
commit bc7af104c4
12 changed files with 182 additions and 25 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
.bundle/config .bundle/config
logs/ logs/
files/ files/
pfp/
# Object files # Object files
*.o *.o

2
deploy
View file

@ -10,4 +10,4 @@ sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
echo "Starting the server..." echo "Starting the server..."
cd /home/vagrant/server cd /home/vagrant/server
sudo service nginx start sudo service nginx start
tmux new-session -s ctf -d 'gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini' tmux new-session -s ctf -d 'tmux set remain-on-exit on && gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'

View file

@ -4,4 +4,5 @@ Flask-SQLAlchemy
SQLAlchemy SQLAlchemy
gunicorn gunicorn
requests requests
voluptuous voluptuous
PIL

View file

@ -12,7 +12,7 @@ sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again p
echo "Installing dependencies..." echo "Installing dependencies..."
apt-get -y install python 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 python-dev libmysqlclient-dev
apt-get -y install nginx apt-get -y install nginx
apt-get -y install mysql-server apt-get -y install mysql-server

View file

@ -36,7 +36,7 @@ def team_create():
Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid }) Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid })
db.session.commit() db.session.commit()
session["tid"] = tid session["tid"] = team.tid
return { "success": 1, "message": "Success!" } return { "success": 1, "message": "Success!" }
@blueprint.route("/delete", methods=["POST"]) @blueprint.route("/delete", methods=["POST"])
@ -175,6 +175,9 @@ def team_accept_invite():
if _team is None: if _team is None:
raise WebException("Team not found.") 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() invitation = TeamInvitations.query.filter_by(rtype=0, frid=tid, toid=_user.uid).first()
if invitation is None: if invitation is None:
raise WebException("Invitation doesn't exist.") raise WebException("Invitation doesn't exist.")
@ -190,6 +193,41 @@ def team_accept_invite():
return { "success": 1, "message": "Success!" } 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"]) @blueprint.route("/info", methods=["GET"])
@api_wrapper @api_wrapper
def team_info(): def team_info():

View file

@ -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 flask import current_app as app
from voluptuous import Schema, Length, Required from voluptuous import Schema, Length, Required
@ -88,6 +88,7 @@ def user_register():
with app.app_context(): with app.app_context():
db.session.add(user) db.session.add(user)
db.session.commit() 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"))) logger.log(__name__, logger.INFO, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8")))
login_user(username, password) login_user(username, password)
@ -160,7 +161,8 @@ def user_info():
"registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z", "registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z",
"me": me, "me": me,
"show_email": show_email, "show_email": show_email,
"in_team": user_in_team "in_team": user_in_team,
"uid": user.uid
} }
if show_email: if show_email:
userdata["email"] = user.email userdata["email"] = user.email
@ -171,6 +173,18 @@ def user_info():
userdata["invitations"] = invitations userdata["invitations"] = invitations
return { "success": 1, "user": userdata } return { "success": 1, "user": userdata }
@blueprint.route("/avatar/<uid>", 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 # # USER FUNCTIONS #
################## ##################

View file

@ -1,4 +1,5 @@
import datetime import datetime
import hashlib
import json import json
import random import random
import re import re
@ -7,6 +8,8 @@ import string
import traceback import traceback
import unicodedata import unicodedata
from PIL import Image, ImageDraw
from flask import current_app as app from flask import current_app as app
from functools import wraps from functools import wraps
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
@ -39,11 +42,61 @@ def flat_multi(multidict):
return flat return flat
def send_email(recipient, subject, body): def send_email(recipient, subject, body):
api_key = app.config["MG_API_KEY"] api_key = app.config["MG_API_KEY"]
data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]), data = {"from": "EasyCTF Administrator <%s>" % (app.config["ADMIN_EMAIL"]),
"to": recipient, "to": recipient,
"subject": subject, "subject": subject,
"text": body "text": body
} }
auth = ("api", api_key) auth = ("api", api_key)
return requests.post("https://api.mailgun.net/v3/%s/messages" % (app.config["MG_HOST"]), auth=auth, data=data) 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

View file

@ -24,4 +24,8 @@ body {
.NO_HOVER_UNDERLINE_DAMMIT:hover, .NO_HOVER_UNDERLINE_DAMMIT:focus, .NO_HOVER_UNDERLINE_DAMMIT:active { .NO_HOVER_UNDERLINE_DAMMIT:hover, .NO_HOVER_UNDERLINE_DAMMIT:focus, .NO_HOVER_UNDERLINE_DAMMIT:active {
text-decoration:none; text-decoration:none;
}
#avatar {
border: 1px solid rgb(221, 221, 221);
} }

View file

@ -46,7 +46,7 @@ app.config(function($routeProvider, $locationProvider) {
}) })
.when("/settings", { .when("/settings", {
templateUrl: "pages/settings.html", templateUrl: "pages/settings.html",
controller: "mainController" controller: "settingsController"
}) })
.when("/forgot", { .when("/forgot", {
templateUrl: "pages/forgot.html", 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) { function display_message(containerId, alertType, message, callback) {
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>"); $("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
$("#" + containerId).hide().slideDown("fast", "swing", function() { $("#" + 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);
}
});
};

View file

@ -3,8 +3,10 @@
<div class="col-sm-3 col-xs-12"> <div class="col-sm-3 col-xs-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body"> <div class="panel-body">
<img src="//www.gravatar.com/avatar/?size=512" id="avatar" style="width:100%;max-width:100%;" /> <div style="width:100%;max-width:100%;">
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="http://en.gravatar.com/emails/" target="_blank">Edit Picture</a></small> <img src="/api/user/avatar/{{ user.uid }}" id="avatar" style="width:100%;" />
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="/settings#profile">Edit Picture</a></small>
</div>
<h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2> <h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2>
<small style="display:block;font-size:1.5em;color:#999;">@{{ user.username }}</small> <small style="display:block;font-size:1.5em;color:#999;">@{{ user.username }}</small>
<hr> <hr>

View file

@ -18,7 +18,14 @@
<h4 class="panel-title">Public Profile</h4> <h4 class="panel-title">Public Profile</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p>Hi.</p> <label>Profile Picture</label>
<div class="row">
<div class="col-sm-2">
</div>
<div class="col-sm-10">
<pre>{{ user }}</pre>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>

View file

@ -43,7 +43,7 @@
<div class="list-group-item" ng-repeat="member in team['members']"> <div class="list-group-item" ng-repeat="member in team['members']">
<h4 class="list-group-item-heading" style="display:inline-block;">{{ member['name'] }}</h4> <h4 class="list-group-item-heading" style="display:inline-block;">{{ member['name'] }}</h4>
<div class="label label-info" ng-show="member['captain']==true">Owner</div> <div class="label label-info" ng-show="member['captain']==true">Owner</div>
<p class="list-group-item-text">@{{ member['username'] }}</p> <p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
</div> </div>
</div> </div>
<div class="panel-footer" ng-show="team['is_owner']==true"> <div class="panel-footer" ng-show="team['is_owner']==true">
@ -75,10 +75,10 @@
<h4 class="panel-title">Pending Invitations</h4> <h4 class="panel-title">Pending Invitations</h4>
</div> </div>
<div class="list-group"> <div class="list-group">
<div class="list-group-item" ng-repeat="member in team['pending_invitations']"> <div class="list-group-item" ng-repeat="member in team['pending_invitations']" href="/profile/{{ member['username'] }}">
<a class="badge" ng-href="javascript:rescind_invitation({{ member['uid'] }});"><i class="fa fa-fw fa-times"></i></a> <a class="badge" ng-href="javascript:rescind_invitation({{ member['uid'] }});"><i class="fa fa-fw fa-times"></i></a>
<h4 class="list-group-item-heading">{{ member['name'] }}</h4> <h4 class="list-group-item-heading">{{ member['name'] }}</h4>
<p class="list-group-item-text">@{{ member['username'] }}</p> <p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
</div> </div>
</div> </div>
</div> </div>
@ -87,15 +87,26 @@
<h4 class="panel-title">Invitation Requests</h4> <h4 class="panel-title">Invitation Requests</h4>
</div> </div>
<div class="list-group"> <div class="list-group">
<div class="list-group-item" ng-repeat="member in team['invitation_requests']"> <div class="list-group-item" ng-repeat="member in team['invitation_requests']" href="/profile/{{ member['username'] }}">
<a class="badge" ng-href="javascript:accept_invitation_request({{ member['uid'] }});"><i class="fa fa-fw fa-check"></i></a> <a class="badge" ng-href="javascript:accept_invitation_request({{ member['uid'] }});"><i class="fa fa-fw fa-check"></i></a>
<h4 class="list-group-item-heading">{{ member['name'] }}</h4> <h4 class="list-group-item-heading">{{ member['name'] }}</h4>
<p class="list-group-item-text">@{{ member['username'] }}</p> <p class="list-group-item-text"><a href="/profile/{{ member['username'] }}">@{{ member['username'] }}</a></p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-9 col-xs-12"> <div class="col-sm-9 col-xs-12">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="profile">
</div>
<div role="tabpanel" class="tab-pane" id="activity">
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -185,5 +196,11 @@
e.preventDefault(); e.preventDefault();
} }
}); });
$("[data-toggle=tooltip]").tooltip(); $(document).ready(function() {
</script> $("[data-toggle=tooltip]").tooltip();
$("ul[role=tablist]").tab();
$("a[role=tab]").click(function(e) {
e.preventDefault();
});
});
</script>