diff --git a/server/api/models.py b/server/api/models.py index 8fa9982..62a537f 100644 --- a/server/api/models.py +++ b/server/api/models.py @@ -48,7 +48,9 @@ class Teams(db.Model): members = [ ] for member in Users.query.filter_by(tid=self.tid).all(): members.append({ - "username": member.username + "username": member.username, + "name": member.name, + "captain": member.uid == self.owner }) return members @@ -71,6 +73,25 @@ class Teams(db.Model): except ValueError: return (-1, "--") + def get_pending_invitations(self, toid=None): + if toid is not None: + invitation = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid, toid=toid).first() + if invitation is None: + return None + else: + user = db.session.query(Users).filter_by(uid=invitation.toid).first() + return { "username": user.username, "name": user.name, "uid": user.uid } + result = [ ] + invitations = db.session.query(TeamInvitations).filter_by(rtype=0, frid=self.tid).all() + for invitation in invitations: + user = db.session.query(Users).filter_by(uid=invitation.toid).first() + result.append({ + "username": user.username, + "name": user.name, + "uid": user.uid + }) + return result + class Problems(db.Model): pid = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(128)) @@ -115,10 +136,6 @@ class Solves(db.Model): self.flag = flag self.correct = correct -########## -# TOKENS # -########## - class LoginTokens(db.Model): sid = db.Column(db.String(64), unique=True, primary_key=True) uid = db.Column(db.Integer) @@ -137,4 +154,16 @@ class LoginTokens(db.Model): self.expiry = expiry self.active = active self.ua = ua - self.ip = ip \ No newline at end of file + self.ip = ip + +class TeamInvitations(db.Model): + rid = db.Column(db.Integer, primary_key=True) + rtype = db.Column(db.Integer) + frid = db.Column(db.Integer) + toid = db.Column(db.Integer) + date = db.Column(db.Integer, default=utils.get_time_since_epoch()) + + def __init__(self, rtype, frid, toid): + self.rtype = rtype + self.frid = frid + self.toid = toid \ No newline at end of file diff --git a/server/api/team.py b/server/api/team.py index 0e59427..ffcfdab 100644 --- a/server/api/team.py +++ b/server/api/team.py @@ -2,7 +2,7 @@ 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 models import db, Teams, Users, TeamInvitations from decorators import api_wrapper, login_required, WebException from schemas import verify_to_schema, check @@ -37,31 +37,64 @@ def team_create(): return { "success": 1, "message": "Success!" } +@blueprint.route("/invite", methods=["POST"]) +@api_wrapper +@login_required +def team_invite(): + 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() + if _user.uid != _team.owner: + raise WebException("You must be the captain of your team to invite members!") + + new_member = params.get("new_member") + if new_member is None: + raise WebException("Please provide a username!") + _user2 = user.get_user(username_lower=new_member.lower()).first() + if _user2 is None: + raise WebException("User doesn't exist!") + if _user2.tid > 0: + raise WebException("This user is already a part of a team!") + + if _team.get_pending_invitations(toid=_user2.uid) is not None: + raise WebException("You've already invited this member!") + + req = TeamInvitations(0, _team.tid, _user2.uid) + with app.app_context(): + db.session.add(req) + db.session.commit() + + return { "success": 1, "message": "Success!" } + @blueprint.route("/info", methods=["GET"]) @api_wrapper def team_info(): logged_in = user.is_logged_in() - me = False + in_team = False + owner = False + _user = None + search = { } teamname = utils.flat_multi(request.args).get("teamname") + if teamname: + search.update({ "teamname_lower": teamname.lower() }) 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, - "school": team.school - } - teamdata["in_team"] = me + _user = user.get_user().first() + if user.in_team(_user): + if "teamname_lower" not in search: + search.update({ "tid": _user.tid }) + in_team = True + team = get_team(**search).first() + teamdata = get_team_info(**search) + if logged_in: + in_team = teamdata["tid"] == _user.tid + owner = teamdata["captain"] == _user.uid + teamdata["in_team"] = in_team + if in_team: + teamdata["is_owner"] = owner + if owner: + teamdata["pending_invitations"] = team.get_pending_invitations() return { "success": 1, "team": teamdata } ################## @@ -84,6 +117,9 @@ TeamSchema = Schema({ def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None): team = get_team(tid=tid, teamname=teamname, teamname_lower=teamname_lower, owner=owner).first() + if team is None: + raise WebException("Team not found.") + place_number, place = team.place() result = { "tid": team.tid, @@ -91,7 +127,9 @@ def get_team_info(tid=None, teamname=None, teamname_lower=None, owner=None): "school": team.school, "place": place, "place_number": place_number, - "points": team.points() + "points": team.points(), + "members": team.get_members(), + "captain": team.owner, } return result diff --git a/server/api/user.py b/server/api/user.py index d91e8d2..119cf35 100644 --- a/server/api/user.py +++ b/server/api/user.py @@ -137,7 +137,7 @@ UserSchema = Schema({ ), Required("username"): check( ([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."), - ([utils.__check_ascii], "Please only use ASCII characters in your username."), + ([utils.__check_alphanumeric], "Please only use alphanumeric characters in your username."), ([__check_username], "This username is taken, did you forget your password?") ), Required("password"): check( diff --git a/server/api/utils.py b/server/api/utils.py index 652382c..92a117f 100644 --- a/server/api/utils.py +++ b/server/api/utils.py @@ -13,6 +13,7 @@ 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) +__check_alphanumeric = lambda s: all(c in string.digits + string.ascii_uppercase + string.ascii_lowercase for c in s) def hash_password(s): return generate_password_hash(s) diff --git a/web/js/easyctf.js b/web/js/easyctf.js index 65310f8..89190d3 100644 --- a/web/js/easyctf.js +++ b/web/js/easyctf.js @@ -1,4 +1,8 @@ var app = angular.module("easyctf", [ "ngRoute" ]); + +app.config(["$compileProvider", function($compileProvider) { + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/); +}]); app.config(function($routeProvider, $locationProvider) { $routeProvider.when("/", { templateUrl: "pages/home.html", @@ -158,6 +162,18 @@ function display_message(containerId, alertType, message, callback) { }); }; +function api_call(method, url, data, callback) { + if (method.toLowerCase() == "post") { + data["csrf_token"] = $.cookie("csrf_token"); + } + $.ajax({ + "type": method, + "datatype": "json", + "data": data, + "url": url + }).done(callback); +} + $.fn.serializeObject = function() { var a, o; o = {}; @@ -180,8 +196,7 @@ $.fn.serializeObject = function() { var register_form = function() { var input = "#register_form input"; var data = $("#register_form").serializeObject(); - data["csrf_token"] = $.cookie("csrf_token"); - $.post("/api/user/register", data, function(result) { + api_call("POST", "/api/user/register", data, function(result) { if (result["success"] == 1) { location.href = "/profile"; } else { @@ -198,8 +213,7 @@ var register_form = function() { var login_form = function() { var input = "#login_form input"; var data = $("#login_form").serializeObject(); - data["csrf_token"] = $.cookie("csrf_token"); - $.post("/api/user/login", data, function(result) { + api_call("POST", "/api/user/login", data, function(result) { if (result["success"] == 1) { location.href = "/profile"; } else { @@ -216,8 +230,7 @@ var login_form = function() { 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) { + api_call("POST", "/api/team/create", data, function(result) { if (result["success"] == 1) { location.reload(true); } else { @@ -227,4 +240,14 @@ var create_team = function() { var result = JSON.parse(jqXHR["responseText"]); display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]); }); +}; + +var add_member = function() { + var input = "#add_member input"; + var data = $("#add_member").serializeObject(); + api_call("POST", "/api/team/invite", data, function(result) { + if (result["success"] == 1) { + location.reload(true); + } + }); }; \ No newline at end of file diff --git a/web/pages/team.html b/web/pages/team.html index c0fc96b..5f0b3d9 100644 --- a/web/pages/team.html +++ b/web/pages/team.html @@ -16,7 +16,7 @@ } -
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.
The team you were looking for doesn't exist. Check to make sure you've spelled the name right.
+