team creation, admin panel, etc.
This commit is contained in:
parent
b7f4e75350
commit
dea6de5cf3
15 changed files with 289 additions and 41 deletions
|
@ -7,6 +7,7 @@ server {
|
|||
index index.html index.htm;
|
||||
|
||||
server_name localhost;
|
||||
error_page 404 /404;
|
||||
|
||||
# location / {
|
||||
# try_files $uri $uri/ =404;
|
||||
|
@ -21,7 +22,7 @@ server {
|
|||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
location ~^/profile/(.*)$ {
|
||||
location ~^/(profile|team)/(.*)$ {
|
||||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@ import logger
|
|||
import models
|
||||
import problem
|
||||
import user
|
||||
import team
|
||||
import utils
|
||||
|
|
|
@ -6,7 +6,7 @@ import json
|
|||
|
||||
blueprint = Blueprint("admin", __name__)
|
||||
|
||||
@blueprint.route("/problems/list", methods=["POST"])
|
||||
@blueprint.route("/problems/list", methods=["GET"])
|
||||
@api_wrapper
|
||||
@admins_only
|
||||
def problem_data():
|
||||
|
|
|
@ -13,9 +13,17 @@ def api_wrapper(f):
|
|||
@wraps(f)
|
||||
def wrapper(*args, **kwds):
|
||||
if request.method == "POST":
|
||||
token = session.pop("csrf_token")
|
||||
if not token or token != request.form.get("csrf_token"):
|
||||
return make_response(json.dumps({ "success": 0, "message": "Token has been tampered with." }), 403, response_header)
|
||||
try:
|
||||
token = str(session.pop("csrf_token"))
|
||||
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 = {}
|
||||
response = 200
|
||||
|
@ -29,11 +37,11 @@ def api_wrapper(f):
|
|||
traceback.print_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)
|
||||
response = make_response(result)
|
||||
|
||||
# Setting CSRF token
|
||||
if "token" not in session:
|
||||
token = utils.generate_string()
|
||||
response = make_response(result)
|
||||
response.set_cookie("csrf_token", token)
|
||||
session["csrf_token"] = token
|
||||
|
||||
|
|
|
@ -25,7 +25,9 @@ def initialize_logs():
|
|||
if not os.path.exists(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)
|
||||
login_log = logging.handlers.RotatingFileHandler(logs[1], maxBytes=10000)
|
||||
|
|
|
@ -29,17 +29,23 @@ class Users(db.Model):
|
|||
|
||||
class Teams(db.Model):
|
||||
tid = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(64), unique=True)
|
||||
join_code = db.Column(db.String(128), unique=True)
|
||||
teamname = db.Column(db.String(64), unique=True)
|
||||
teamname_lower = db.Column(db.String(64), unique=True)
|
||||
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)
|
||||
|
||||
def __init__(self, name, school):
|
||||
self.name = name
|
||||
self.school = school
|
||||
def __init__(self, teamname, owner):
|
||||
self.teamname = teamname
|
||||
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):
|
||||
pid = db.Column(db.Integer, primary_key=True)
|
||||
|
|
95
server/api/team.py
Normal file
95
server/api/team.py
Normal 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
|
|
@ -79,6 +79,8 @@ def user_status():
|
|||
"admin": is_admin(),
|
||||
"username": session["username"] if logged_in else "",
|
||||
}
|
||||
if logged_in:
|
||||
result["has_team"] = in_team(get_user().first())
|
||||
|
||||
return result
|
||||
|
||||
|
@ -86,7 +88,7 @@ def user_status():
|
|||
@api_wrapper
|
||||
def user_info():
|
||||
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 logged_in:
|
||||
username = session["username"]
|
||||
|
@ -116,8 +118,6 @@ def user_info():
|
|||
# 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_email = lambda email: get_user(email=email.lower()).first() is None
|
||||
|
||||
|
@ -125,19 +125,19 @@ UserSchema = Schema({
|
|||
Required("email"): check(
|
||||
([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_format], "Please enter a legit email.")
|
||||
([utils.__check_email_format], "Please enter a legit email.")
|
||||
),
|
||||
Required("name"): check(
|
||||
([str, Length(min=4, max=128)], "Your name should be between 4 and 128 characters long.")
|
||||
),
|
||||
Required("username"): check(
|
||||
([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?")
|
||||
),
|
||||
Required("password"): check(
|
||||
([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(
|
||||
([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 })
|
||||
elif email != None:
|
||||
match.update({ "email": email })
|
||||
# elif api.auth.is_logged_in():
|
||||
# match.update({ "uid": api.auth.get_uid() })
|
||||
elif is_logged_in():
|
||||
match.update({ "username": session["username"] })
|
||||
with app.app_context():
|
||||
result = Users.query.filter_by(**match)
|
||||
return result
|
||||
|
@ -184,6 +184,9 @@ def login_user(username, password):
|
|||
|
||||
return True
|
||||
|
||||
def in_team(user):
|
||||
return user.tid is not None and user.tid >= 0
|
||||
|
||||
def is_logged_in():
|
||||
if not("sid" in session and "username" in session): return False
|
||||
sid = session["sid"]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
@ -8,6 +9,9 @@ import unicodedata
|
|||
from functools import wraps
|
||||
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):
|
||||
return generate_password_hash(s)
|
||||
|
||||
|
@ -28,5 +32,5 @@ def flat_multi(multidict):
|
|||
flat = {}
|
||||
for key, values in multidict.items():
|
||||
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
|
|
@ -25,8 +25,9 @@ with app.app_context():
|
|||
app.secret_key = config.SECRET_KEY
|
||||
|
||||
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.team.blueprint, url_prefix="/api/team")
|
||||
app.register_blueprint(api.user.blueprint, url_prefix="/api/user")
|
||||
api.logger.initialize_logs()
|
||||
|
||||
@app.route("/api")
|
||||
|
|
|
@ -44,6 +44,14 @@ app.config(function($routeProvider, $locationProvider) {
|
|||
templateUrl: "pages/settings.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/team", {
|
||||
templateUrl: "pages/team.html",
|
||||
controller: "teamController"
|
||||
})
|
||||
.when("/team/:teamname", {
|
||||
templateUrl: "pages/team.html",
|
||||
controller: "teamController"
|
||||
})
|
||||
.when("/admin/problems", {
|
||||
templateUrl: "pages/admin/problems.html",
|
||||
controller: "adminProblemsController"
|
||||
|
@ -59,10 +67,9 @@ app.controller("mainController", ["$scope", "$http", function($scope, $http) {
|
|||
$scope.config = { navbar: { } };
|
||||
$.get("/api/user/status", function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.config.navbar.logged_in = result["logged_in"];
|
||||
$scope.config.navbar.username = result["username"];
|
||||
$scope.config.navbar.admin = result["admin"];
|
||||
$scope.$emit("adminStatus");
|
||||
delete result["success"];
|
||||
$scope.config.navbar = result;
|
||||
$scope.$emit("loginStatus");
|
||||
} else {
|
||||
$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) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
$scope.$on("adminStatus", function() {
|
||||
$scope.$on("loginStatus", function() {
|
||||
if ($scope.config["navbar"].logged_in != true) {
|
||||
location.href = "/login";
|
||||
return;
|
||||
|
@ -177,3 +210,21 @@ var login_form = function() {
|
|||
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
0
web/pages/learn.html
Normal file
|
@ -39,7 +39,12 @@
|
|||
<h4 class="panel-title">Team Information</h4>
|
||||
</div>
|
||||
<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 »</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
|
@ -73,7 +78,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="user['user_found']==false">
|
||||
<div ng-show="user['user_found']!=true">
|
||||
<div class="page-header">
|
||||
<h1>User Not Found</h1>
|
||||
</div>
|
||||
|
|
71
web/pages/team.html
Normal file
71
web/pages/team.html
Normal 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>
|
Loading…
Reference in a new issue