team creation, admin panel, etc.

This commit is contained in:
Michael Zhang 2016-01-11 21:54:26 -06:00
parent b7f4e75350
commit dea6de5cf3
15 changed files with 289 additions and 41 deletions

View file

@ -7,6 +7,7 @@ server {
index index.html index.htm; index index.html index.htm;
server_name localhost; server_name localhost;
error_page 404 /404;
# location / { # location / {
# try_files $uri $uri/ =404; # try_files $uri $uri/ =404;
@ -21,7 +22,7 @@ server {
default_type text/html; default_type text/html;
try_files /index.html /index.html; try_files /index.html /index.html;
} }
location ~^/profile/(.*)$ { location ~^/(profile|team)/(.*)$ {
default_type text/html; default_type text/html;
try_files /index.html /index.html; try_files /index.html /index.html;
} }

View file

@ -3,4 +3,5 @@ import logger
import models import models
import problem import problem
import user import user
import team
import utils import utils

View file

@ -6,7 +6,7 @@ import json
blueprint = Blueprint("admin", __name__) blueprint = Blueprint("admin", __name__)
@blueprint.route("/problems/list", methods=["POST"]) @blueprint.route("/problems/list", methods=["GET"])
@api_wrapper @api_wrapper
@admins_only @admins_only
def problem_data(): def problem_data():

View file

@ -13,9 +13,17 @@ def api_wrapper(f):
@wraps(f) @wraps(f)
def wrapper(*args, **kwds): def wrapper(*args, **kwds):
if request.method == "POST": if request.method == "POST":
token = session.pop("csrf_token") try:
if not token or token != request.form.get("csrf_token"): token = str(session.pop("csrf_token"))
return make_response(json.dumps({ "success": 0, "message": "Token has been tampered with." }), 403, response_header) provided_token = str(request.form.get("csrf_token"))
if not token or token != provided_token:
raise Exception
except Exception, e:
response = make_response(json.dumps({ "success": 0, "message": "Token has been tampered with." }), 403, response_header)
token = utils.generate_string()
response.set_cookie("csrf_token", token)
session["csrf_token"] = token
return response
web_result = {} web_result = {}
response = 200 response = 200
@ -29,11 +37,11 @@ def api_wrapper(f):
traceback.print_exc() traceback.print_exc()
web_result = { "success": 0, "message": "Something went wrong! Please notify us about this immediately.", "error": [ str(error), traceback.format_exc() ] } web_result = { "success": 0, "message": "Something went wrong! Please notify us about this immediately.", "error": [ str(error), traceback.format_exc() ] }
result = (json.dumps(web_result), response, response_header) result = (json.dumps(web_result), response, response_header)
response = make_response(result)
# Setting CSRF token # Setting CSRF token
if "token" not in session: if "token" not in session:
token = utils.generate_string() token = utils.generate_string()
response = make_response(result)
response.set_cookie("csrf_token", token) response.set_cookie("csrf_token", token)
session["csrf_token"] = token session["csrf_token"] = token

View file

@ -25,7 +25,9 @@ def initialize_logs():
if not os.path.exists(log_path): if not os.path.exists(log_path):
os.mkdir(log_path) os.mkdir(log_path)
logs = [os.path.join(log_path, "registrations.log"), os.path.join(log_path, "logins.log"), os.path.join(log_path, "submissions.log")] # logs = [os.path.join(log_path, "registrations.log"), os.path.join(log_path, "logins.log"), os.path.join(log_path, "submissions.log")]
logs = map(lambda x: os.path.join(log_path, x + ".log"), \
[ "registrations", "logins", "submissions", "create_team" ])
registration_log = logging.handlers.RotatingFileHandler(logs[0], maxBytes=10000) registration_log = logging.handlers.RotatingFileHandler(logs[0], maxBytes=10000)
login_log = logging.handlers.RotatingFileHandler(logs[1], maxBytes=10000) login_log = logging.handlers.RotatingFileHandler(logs[1], maxBytes=10000)

View file

@ -29,17 +29,23 @@ class Users(db.Model):
class Teams(db.Model): class Teams(db.Model):
tid = db.Column(db.Integer, primary_key=True) tid = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True) teamname = db.Column(db.String(64), unique=True)
join_code = db.Column(db.String(128), unique=True) teamname_lower = db.Column(db.String(64), unique=True)
school = db.Column(db.Text) school = db.Column(db.Text)
size = db.Column(db.Integer)
score = db.Column(db.Integer)
observer = db.Column(db.Boolean)
owner = db.Column(db.Integer) owner = db.Column(db.Integer)
def __init__(self, name, school): def __init__(self, teamname, owner):
self.name = name self.teamname = teamname
self.school = school self.teamname_lower = teamname.lower()
self.owner = owner
def get_members(self):
members = [ ]
for member in Users.query.filter_by(tid=self.tid).all():
members.append({
"username": member.username
})
return members
class Problems(db.Model): class Problems(db.Model):
pid = db.Column(db.Integer, primary_key=True) pid = db.Column(db.Integer, primary_key=True)

95
server/api/team.py Normal file
View file

@ -0,0 +1,95 @@
from flask import Blueprint, request
from flask import current_app as app
from voluptuous import Schema, Length, Required
from models import db, Teams, Users
from decorators import api_wrapper, login_required, WebException
from user import in_team, get_user, is_logged_in
from schemas import verify_to_schema, check
import utils
blueprint = Blueprint("team", __name__)
###############
# TEAM ROUTES #
###############
@blueprint.route("/create", methods=["POST"])
@api_wrapper
@login_required
def team_create():
params = utils.flat_multi(request.form)
user = get_user().first()
if user.tid is not None or user.tid >= 0 or get_team(owner=user.uid).first() is not None:
raise WebException("You're already in a team!")
verify_to_schema(TeamSchema, params)
teamname = params.get("teamname")
team = Teams(teamname, user.uid)
with app.app_context():
db.session.add(team)
db.session.commit()
Users.query.filter_by(uid=user.uid).update({ "tid": team.tid })
db.session.commit()
return { "success": 1, "message": "Success!" }
@blueprint.route("/info", methods=["GET"])
@api_wrapper
def team_info():
logged_in = is_logged_in()
me = False
teamname = utils.flat_multi(request.args).get("teamname")
if logged_in:
my_team = get_team().first()
if my_team is not None:
if teamname is None:
teamname = my_team.teamname
me = True
elif teamname.lower() == my_team.teamname.lower():
me = True
if teamname is None:
raise WebException("No team specified.")
team = get_team(teamname_lower=teamname.lower()).first()
if team is None:
raise WebException("Team not found.")
teamdata = {
"teamname": team.teamname
}
teamdata["in_team"] = me
return { "success": 1, "team": teamdata }
##################
# TEAM FUNCTIONS #
##################
__check_teamname = lambda teamname: get_team(teamname_lower=teamname.lower()).first() is None
TeamSchema = Schema({
Required("teamname"): check(
([str, Length(min=4, max=32)], "Your teamname should be between 4 and 32 characters long."),
([utils.__check_ascii], "Please only use ASCII characters in your teamname."),
([__check_teamname], "This teamname is taken, did you forget your password?")
),
}, extra=True)
def get_team(tid=None, teamname=None, teamname_lower=None, owner=None):
match = {}
if teamname != None:
match.update({ "teamname": teamname })
elif teamname_lower != None:
match.update({ "teamname_lower": teamname_lower })
elif tid != None:
match.update({ "tid": tid })
elif owner != None:
match.update({ "owner": owner })
elif is_logged_in():
user = get_user().first()
if user.tid is not None:
match.update({ "tid": user.tid })
with app.app_context():
result = Teams.query.filter_by(**match)
return result

View file

@ -79,6 +79,8 @@ def user_status():
"admin": is_admin(), "admin": is_admin(),
"username": session["username"] if logged_in else "", "username": session["username"] if logged_in else "",
} }
if logged_in:
result["has_team"] = in_team(get_user().first())
return result return result
@ -86,7 +88,7 @@ def user_status():
@api_wrapper @api_wrapper
def user_info(): def user_info():
logged_in = is_logged_in() logged_in = is_logged_in()
username = utils.flat_multi(request.form).get("username") username = utils.flat_multi(request.args).get("username")
if username is None: if username is None:
if logged_in: if logged_in:
username = session["username"] username = session["username"]
@ -116,8 +118,6 @@ def user_info():
# USER FUNCTIONS # # USER FUNCTIONS #
################## ##################
__check_email_format = lambda email: re.match(".+@.+\..{2,}", email) is not None
__check_ascii = lambda s: all(ord(c) < 128 for c in s)
__check_username = lambda username: get_user(username_lower=username.lower()).first() is None __check_username = lambda username: get_user(username_lower=username.lower()).first() is None
__check_email = lambda email: get_user(email=email.lower()).first() is None __check_email = lambda email: get_user(email=email.lower()).first() is None
@ -125,19 +125,19 @@ UserSchema = Schema({
Required("email"): check( Required("email"): check(
([str, Length(min=4, max=128)], "Your email should be between 4 and 128 characters long."), ([str, Length(min=4, max=128)], "Your email should be between 4 and 128 characters long."),
([__check_email], "Someone already registered this email."), ([__check_email], "Someone already registered this email."),
([__check_email_format], "Please enter a legit email.") ([utils.__check_email_format], "Please enter a legit email.")
), ),
Required("name"): check( Required("name"): check(
([str, Length(min=4, max=128)], "Your name should be between 4 and 128 characters long.") ([str, Length(min=4, max=128)], "Your name should be between 4 and 128 characters long.")
), ),
Required("username"): check( Required("username"): check(
([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."), ([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."),
([__check_ascii], "Please only use ASCII characters in your username."), ([utils.__check_ascii], "Please only use ASCII characters in your username."),
([__check_username], "This username is taken, did you forget your password?") ([__check_username], "This username is taken, did you forget your password?")
), ),
Required("password"): check( Required("password"): check(
([str, Length(min=4, max=64)], "Your password should be between 4 and 64 characters long."), ([str, Length(min=4, max=64)], "Your password should be between 4 and 64 characters long."),
([__check_ascii], "Please only use ASCII characters in your password."), ([utils.__check_ascii], "Please only use ASCII characters in your password."),
), ),
Required("type"): check( Required("type"): check(
([str, lambda x: x.isdigit()], "Please use the online form.") ([str, lambda x: x.isdigit()], "Please use the online form.")
@ -155,8 +155,8 @@ def get_user(username=None, username_lower=None, email=None, uid=None):
match.update({ "uid": uid }) match.update({ "uid": uid })
elif email != None: elif email != None:
match.update({ "email": email }) match.update({ "email": email })
# elif api.auth.is_logged_in(): elif is_logged_in():
# match.update({ "uid": api.auth.get_uid() }) match.update({ "username": session["username"] })
with app.app_context(): with app.app_context():
result = Users.query.filter_by(**match) result = Users.query.filter_by(**match)
return result return result
@ -184,6 +184,9 @@ def login_user(username, password):
return True return True
def in_team(user):
return user.tid is not None and user.tid >= 0
def is_logged_in(): def is_logged_in():
if not("sid" in session and "username" in session): return False if not("sid" in session and "username" in session): return False
sid = session["sid"] sid = session["sid"]

View file

@ -1,6 +1,7 @@
import datetime import datetime
import json import json
import random import random
import re
import string import string
import traceback import traceback
import unicodedata import unicodedata
@ -8,6 +9,9 @@ import unicodedata
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
__check_email_format = lambda email: re.match(".+@.+\..{2,}", email) is not None
__check_ascii = lambda s: all(c in string.printable for c in s)
def hash_password(s): def hash_password(s):
return generate_password_hash(s) return generate_password_hash(s)
@ -28,5 +32,5 @@ def flat_multi(multidict):
flat = {} flat = {}
for key, values in multidict.items(): for key, values in multidict.items():
value = values[0] if type(values) == list and len(values) == 1 else values value = values[0] if type(values) == list and len(values) == 1 else values
flat[key] = unicodedata.normalize("NFKD", value).encode("ascii", "ignore") flat[key] = value.encode("utf-8")
return flat return flat

View file

@ -25,8 +25,9 @@ with app.app_context():
app.secret_key = config.SECRET_KEY app.secret_key = config.SECRET_KEY
app.register_blueprint(api.admin.blueprint, url_prefix="/api/admin") app.register_blueprint(api.admin.blueprint, url_prefix="/api/admin")
app.register_blueprint(api.user.blueprint, url_prefix="/api/user")
app.register_blueprint(api.problem.blueprint, url_prefix="/api/problem") app.register_blueprint(api.problem.blueprint, url_prefix="/api/problem")
app.register_blueprint(api.team.blueprint, url_prefix="/api/team")
app.register_blueprint(api.user.blueprint, url_prefix="/api/user")
api.logger.initialize_logs() api.logger.initialize_logs()
@app.route("/api") @app.route("/api")

View file

@ -44,6 +44,14 @@ app.config(function($routeProvider, $locationProvider) {
templateUrl: "pages/settings.html", templateUrl: "pages/settings.html",
controller: "mainController" controller: "mainController"
}) })
.when("/team", {
templateUrl: "pages/team.html",
controller: "teamController"
})
.when("/team/:teamname", {
templateUrl: "pages/team.html",
controller: "teamController"
})
.when("/admin/problems", { .when("/admin/problems", {
templateUrl: "pages/admin/problems.html", templateUrl: "pages/admin/problems.html",
controller: "adminProblemsController" controller: "adminProblemsController"
@ -59,10 +67,9 @@ app.controller("mainController", ["$scope", "$http", function($scope, $http) {
$scope.config = { navbar: { } }; $scope.config = { navbar: { } };
$.get("/api/user/status", function(result) { $.get("/api/user/status", function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
$scope.config.navbar.logged_in = result["logged_in"]; delete result["success"];
$scope.config.navbar.username = result["username"]; $scope.config.navbar = result;
$scope.config.navbar.admin = result["admin"]; $scope.$emit("loginStatus");
$scope.$emit("adminStatus");
} else { } else {
$scope.config.navbar.logged_in = false; $scope.config.navbar.logged_in = false;
} }
@ -92,9 +99,35 @@ app.controller("profileController", ["$controller", "$scope", "$http", "$routePa
}); });
}]); }]);
app.controller("loginController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope });
$scope.$on("loginStatus", function() {
if ($scope.config["navbar"].logged_in != true) {
location.href = "/login";
return;
}
});
}]);
app.controller("teamController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
var data = { };
if ("teamname" in $routeParams) {
data["teamname"] = $routeParams["teamname"];
} else {
$controller("loginController", { $scope: $scope });
}
$.get("/api/team/info", data, function(result) {
if (result["success"] == 1) {
$scope.team = result["team"];
}
$scope.$apply();
$(".timeago").timeago();
});
}]);
app.controller("adminController", ["$controller", "$scope", "$http", function($controller, $scope, $http) { app.controller("adminController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope }); $controller("mainController", { $scope: $scope });
$scope.$on("adminStatus", function() { $scope.$on("loginStatus", function() {
if ($scope.config["navbar"].logged_in != true) { if ($scope.config["navbar"].logged_in != true) {
location.href = "/login"; location.href = "/login";
return; return;
@ -176,4 +209,22 @@ var login_form = function() {
var result = JSON.parse(jqXHR["responseText"]); var result = JSON.parse(jqXHR["responseText"]);
display_message("login_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]); display_message("login_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]);
}); });
};
// team page
var create_team = function() {
var input = "#create_team input";
var data = $("#create_team").serializeObject();
data["csrf_token"] = $.cookie("csrf_token");
$.post("/api/team/create", data, function(result) {
if (result["success"] == 1) {
location.reload(true);
} else {
display_message("create_team_msg", "danger", result["message"]);
}
}).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]);
});
}; };

0
web/pages/learn.html Normal file
View file

View file

@ -39,7 +39,12 @@
<h4 class="panel-title">Team Information</h4> <h4 class="panel-title">Team Information</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
Hi. <div ng-show="user['in_team']==true">
</div>
<div ng-show="user['in_team']!=true">
<p>{{ user['me']==true ? "You're" : "This user is" }} not a part of a team.</p>
<a href="/team" class="btn btn-primary" ng-show="user['me']==true">Join or create one now &raquo;</a>
</div>
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -73,7 +78,7 @@
</div> </div>
</div> </div>
</div> </div>
<div ng-show="user['user_found']==false"> <div ng-show="user['user_found']!=true">
<div class="page-header"> <div class="page-header">
<h1>User Not Found</h1> <h1>User Not Found</h1>
</div> </div>

View file

@ -1,11 +1,11 @@
<div class="fade_in"> <div class="fade_in">
<h1 class="heading1">Shell</h1> <h1 class="heading1">Shell</h1>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">Username: (insert username) | Password: (insert password)</h3> <h3 class="panel-title">Username: (insert username) | Password: (insert password)</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<h3>Insert Shell Here</h3> <h3>Insert Shell Here</h3>
</div> </div>
</div> </div>
</div> </div>

71
web/pages/team.html Normal file
View file

@ -0,0 +1,71 @@
<style>
.editable {
display: inline-block;
padding: 15px;
outline: none;
border: 1px solid rgba(0, 0, 0, 0);
}
.editable:hover {
border: 1px solid #999;
}
.editable:focus {
border: 1px solid #999;
background-color: #FFF;
}
.padded {
display: inline-block;
padding: 15px;
}
</style>
<div ng-show="!(config.navbar['logged_in']==true && config.navbar['has_team']!=true)">
<div class="jumbotron">
<center>
<div ng-show="team['in_team']==true">
<h1><span data-toggle="tooltip" data-placement="top" title="Click to edit team name." id="teamname_edit" class="editable" contenteditable>{{ team['teamname'] }}</span></h1>
<h4><i class="fa fa-fw fa-university"></i> <span data-toggle="tooltip" data-placement="top" title="Click to edit school." id="school_edit" class="editable" contenteditable>{{ team['school'] || 'Add School' }}</span></h4>
</div>
<div ng-show="team['in_team']!=true">
<h1><span class="padded">{{ team['teamname'] }}</span></h1>
<h4><i class="fa fa-fw fa-university"></i> <span class="padded">{{ team['school'] || 'Unknown Affiliation' }}</span></h4>
</div>
</center>
</div>
</div>
<div ng-show="config.navbar['logged_in']==true && config.navbar['has_team']!=true">
<div class="page-header">
<h1>Team</h1>
</div>
<p>To participate in EasyCTF, you must be on a <b>team</b>. If you'd like to go solo, just create a team by yourself. Read about team eligibility in the <a href="/rules">rules</a>.</p>
<form class="form-horizontal" onsubmit="create_team(); return false;" id="create_team">
<fieldset>
<div id="create_team_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<div class="input-group">
<input class="form-control input-lg" type="text" required name="teamname" id="teamname" placeholder="Choose a team name..." autocomplete="off" />
<span class="input-group-btn">
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Create Team" />
</span>
</div>
</div>
</fieldset>
</form>
<div class="page-header">
<h3>Invitations</h3>
</div>
<p>You need an invitation to join another team. If you'd like to request to be a member of their team, go to their team page and click the Request button.</p>
</div>
<script type="text/javascript">
$("#teamname_edit").on("keypress", function(e) {
if (e.keyCode == 13) {
e.preventDefault();
}
});
$("[data-toggle=tooltip]").tooltip();
</script>