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 @@ } -
+
@@ -33,8 +33,58 @@
+
+
+
+
+

Team Members

+
+
+
+

{{ member['name'] }}

+

@{{ member['username'] }}

+
+
+ + +
+
+
+

Pending Invitations

+
+
+
+ +

{{ member['name'] }}

+

@{{ member['username'] }}

+
+
+
+
+
+
+
-
+
@@ -75,7 +125,6 @@
-
@@ -93,6 +142,13 @@

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.

+