added profile page

This commit is contained in:
Michael Zhang 2016-01-06 18:23:43 -06:00
parent 80ca1377de
commit df5d146e08
15 changed files with 288 additions and 139 deletions

View file

@ -17,6 +17,10 @@ server {
default_type text/html;
try_files /index.html /index.html;
}
location ~^/admin/(problems)$ {
default_type text/html;
try_files /index.html /index.html;
}
location ~ /api {
proxy_set_header Host $host;

4
deploy
View file

@ -5,7 +5,9 @@ pkill gunicorn
sudo service nginx stop
tmux kill-session -t ctf 2> /dev/null
sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
echo "Starting the server..."
cd /home/vagrant/server
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 'gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'

31
server/api.html Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: package api</title>
<meta charset="utf-8">
</head><body bgcolor="#f0f0f8">
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong>api</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/vagrant/server/api/__init__.py">/home/vagrant/server/api/__init__.py</a></font></td></tr></table>
<p></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="api.admin.html">admin</a><br>
<a href="api.api.html">api</a><br>
<a href="api.decorators.html">decorators</a><br>
</td><td width="25%" valign=top><a href="api.logger.html">logger</a><br>
<a href="api.models.html">models</a><br>
<a href="api.problem.html">problem</a><br>
</td><td width="25%" valign=top><a href="api.schemas.html">schemas</a><br>
<a href="api.user.html">user</a><br>
<a href="api.utils.html">utils</a><br>
</td><td width="25%" valign=top></td></tr></table></td></tr></table>
</body></html>

View file

@ -1,19 +1,26 @@
from flask import Blueprint, jsonify
from decorators import admins_only, api_wrapper, login_required
from decorators import admins_only, api_wrapper
from models import db, Problems, Files
import json
blueprint = Blueprint("admin", __name__)
@blueprint.route("/problem/data", methods=["POST"])
#@api_wrapper # Disable atm due to json serialization issues: will fix
@blueprint.route("/problems/list", methods=["POST"])
@api_wrapper
@admins_only
@login_required
def problem_data():
problems = Problems.query.add_columns("pid", "name", "category", "description", "hint", "value", "solves", "disabled", "flag").order_by(Problems.value).all()
jason = []
for problem in problems:
problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ]
jason.append({"pid": problem[1], "name": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "disabled": problem[8], "flag": problem[9], "files": problem_files})
return jsonify(data=jason)
problems = Problems.query.order_by(Problems.value).all()
problems_return = [ ]
for problem in problems:
problems_return.append({
"pid": problem.pid,
"name": problem.name,
"category": problem.category,
"description": problem.description,
"hint": problem.hint,
"value": problem.value,
"threshold": problem.threshold,
"weightmap": json.loads(problem.weightmap)
})
return { "success": 1, "problems": problems_return }

View file

@ -1,5 +1,5 @@
from flask.ext.sqlalchemy import SQLAlchemy
import datetime
import time
import utils
db = SQLAlchemy()
@ -14,6 +14,8 @@ class Users(db.Model):
password = db.Column(db.String(128))
admin = db.Column(db.Boolean)
utype = db.Column(db.Integer)
tid = db.Column(db.Integer)
registertime = db.Column(db.Integer)
def __init__(self, name, username, email, password, utype=1):
self.name = name
@ -23,6 +25,7 @@ class Users(db.Model):
self.password = utils.hash_password(password)
self.utype = utype
self.admin = False
self.registertime = int(time.time())
class Teams(db.Model):
tid = db.Column(db.Integer, primary_key=True)
@ -90,11 +93,11 @@ class LoginTokens(db.Model):
ua = db.Column(db.String(128))
ip = db.Column(db.String(16))
def __init__(self, uid, username, expiry=datetime.datetime.utcnow(), active=True, ua=None, ip=None):
def __init__(self, uid, username, expiry=int(time.time()), active=True, ua=None, ip=None):
self.sid = utils.generate_string()
self.uid = uid
self.username = username
self.issued = datetime.datetime.utcnow()
self.issued = int(time.time())
self.expiry = expiry
self.active = active
self.ua = ua

View file

@ -6,6 +6,7 @@ from models import db, LoginTokens, Users
from decorators import api_wrapper, WebException
from schemas import verify_to_schema, check
import datetime
import logger
import re
import requests
@ -43,7 +44,7 @@ def user_register():
return { "success": 1, "message": "Success!" }
@blueprint.route("/logout", methods=["GET", "POST"])
@blueprint.route("/logout", methods=["POST"])
@api_wrapper
def user_logout():
sid = session["sid"]
@ -80,6 +81,36 @@ def user_status():
}
return result
@blueprint.route("/info", methods=["POST"])
@api_wrapper
def user_info():
logged_in = is_logged_in()
username = utils.flat_multi(request.form).get("username")
if username is None:
if logged_in:
username = session["username"]
if username is None:
raise WebException("No user specified.")
me = username.lower() == session["username"].lower()
user = get_user(username_lower=username.lower()).first()
if user is None:
raise WebException("User not found.")
show_email = me if logged_in else False
userdata = {
"user_found": True,
"name": user.name,
"username": user.username,
"type": ["Student", "Instructor", "Observer"][user.utype - 1],
"admin": user.admin,
"registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z",
"me": me,
"show_email": show_email
}
if show_email:
userdata["email"] = user.email
return { "success": 1, "user": userdata }
##################
# USER FUNCTIONS #
##################
@ -148,7 +179,7 @@ def login_user(username, password):
session["sid"] = token.sid
session["username"] = token.username
session["admin"] = user.utype == 0
session["admin"] = user.admin == True
return True

View file

@ -5,6 +5,8 @@ import api
import config
import json
from api.decorators import api_wrapper
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = config.SQLALCHEMY_DATABASE_URI
@ -24,8 +26,9 @@ app.register_blueprint(api.problem.blueprint, url_prefix="/api/problem")
api.logger.initialize_logs()
@app.route("/api")
@api_wrapper
def api_main():
return json.dumps({ "success": 1, "message": "The API is online." })
return { "success": 1, "message": "The API is online." }
if __name__ == "__main__":
with app.app_context():

View file

@ -10,4 +10,14 @@
* {
font-family: "Proxima Nova";
}
.tab-content {
padding: 10px;
> .tab-pane {
display: none;
}
> .active {
display: block;
}
}

View file

@ -39,6 +39,16 @@
<div ng-show="config.navbar['competition_started']==true">
</div>
<li><a href="/chat">Chat</a></li>
<li class="dropdown" ng-show="config.navbar['admin']==true">
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/admin/stats">Statistics</a></li>
<li><a href="/admin/problems">Problem Editor</a></li>
<li><a href="/admin/teams">Team Management</a></li>
<li role="separator" class="divider"></li>
<li><a href="/admin/settings">CTF Settings</a></li>
</ul>
</li>
<li class="dropdown">
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ config.navbar["username"] }} <span class="caret"></span></a>
<ul class="dropdown-menu">
@ -55,7 +65,7 @@
</div>
</nav>
<div id="mainContent" class="ui container">
<div id="mainContent" class="container">
<div ng-view></div>
</div>
@ -64,7 +74,8 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js" integrity="sha384-r1y8TJcloKTvouxnYsi4PJAx+nHNr90ibsEn3zznzDzWBN9X3o3kbHLSgcIPtzAp" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular-route.min.js" integrity="sha384-9MZDoFf10trgrfsQOs9GJhf/mP/sh5weVp3FDSi8h/4TEaV6dloEDkpxGTaOmAs6" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/js/bootstrap-select.min.js" integrity="sha384-1qZEXZBmj54fSiiWT8bZQGEpCumJWDrAoEqMdg6N5bTTLCkU5RXoNeUsKWekRYob" crossorigin="anonymous"></script>
<script src="js/easyctf.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js" integrity="sha384-Bap3DetwPgo4GEFvaIDVSIrz5G0mUAUsfCUcEsi+JrrNu7dyj3gBmuAG4hDIGg/4" crossorigin="anonymous"></script>
<script src="/js/easyctf.js"></script>
</body>
</html>

View file

@ -16,6 +16,10 @@ app.config(function($routeProvider, $locationProvider) {
templateUrl: "pages/learn.html",
controller: "mainController"
})
.when("/chat", {
templateUrl: "pages/chat.html",
controller: "mainController"
})
.when("/register", {
templateUrl: "pages/register.html",
controller: "mainController"
@ -26,11 +30,19 @@ app.config(function($routeProvider, $locationProvider) {
})
.when("/profile", {
templateUrl: "pages/profile.html",
controller: "mainController"
controller: "profileController"
})
.when("/logout", {
templateUrl: "pages/blank.html",
controller: "logoutController"
})
.when("/admin/problems", {
templateUrl: "pages/admin/problems.html",
controller: "adminProblemsController"
})
.otherwise({
templateUrl: "pages/404.html",
controller: "mainController"
});
$locationProvider.html5Mode(true);
});
@ -41,11 +53,14 @@ app.controller("mainController", ["$scope", "$http", function($scope, $http) {
if (result["success"] == 1) {
$scope.config.navbar.logged_in = result["logged_in"];
$scope.config.navbar.username = result["username"];
$scope.config.navbar.admin = result["admin"];
} else {
$scope.config.navbar.logged_in = false;
}
$scope.$apply();
}).fail(function() {
$scope.config.navbar.logged_in = false;
$scope.$apply();
});
}]);
@ -55,6 +70,21 @@ app.controller("logoutController", function() {
});
});
app.controller("profileController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope });
$.post("/api/user/info", function(result) {
if (result["success"] == 1) {
$scope.user = result["user"];
}
$scope.$apply();
$(".timeago").timeago();
});
}]);
app.controller("adminProblemsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
$controller("mainController", { $scope: $scope });
}]);
function display_message(containerId, alertType, message, callback) {
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
$("#" + containerId).hide().slideDown("fast", "swing", function() {

View file

@ -1,16 +0,0 @@
function login_form(email, password) {
$("#login").attr("disabled", "disabled");
$.post("/api/user/login", {
email: email,
password: password
}, function(data) {
if (data.success == 1) {
display_message("status", "success", "Success!", function() {
$("#login").removeAttr("disabled");
window.location = "#/account";
});
} else {
display_message("status", "danger", data.message, function() {$("#login").removeAttr("disabled");});
}
});
}

View file

@ -1,27 +0,0 @@
$("#registration-form").on("submit", function(e) {
e.preventDefault();
register($("#name").val(), $("#username").val(), $("#password").val(), $("#password_confirm").val(), $("#email").val(), $("#g-recaptcha-response").val());
});
function register(name, username, password, password_confirm, email, captcha_response) {
$("#register").attr("disabled", "disabled");
$.post("/api/user/register", {
name: name,
username: username,
password: password,
password_confirm: password_confirm,
email: email,
captcha_response: captcha_response
}, function(data) {
$("#status").text(data.message);
if (data.success == 1) {
display_message("status", "success", "Success!", function() {
$("#register").removeAttr("disabled");
window.location = "#/login";
});
} else {
display_message("status", "danger", data.message, function() {$("#register").removeAttr("disabled")});
grecaptcha.reset();
}
});
}

5
web/pages/404.html Normal file
View file

@ -0,0 +1,5 @@
<div class="page-header">
<h1>404: Page Not Found</h1>
</div>
<p>Go away. Stop snooping around, you little creep.</p>

View file

@ -1,77 +1,77 @@
<center class="fade_in ng-scope">
<h1>Problems</h1>
<div id="status"></div>
<input type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#add-modal" value="Add Problem">
<div id="problems"></div>
<div class="modal fade" id="add-modal" tabindex="-2" role="dialog" aria-labelledby="add-modal-label" data-backdrop="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="add-modal-label">Add Problem</h4>
</div>
<div class="modal-body">
<form id="add-form" method="POST" action="/api/problem/add" enctype="multipart/form-data">
<div class="panel-heading">
<div class="row">
<div class="col-md-6">
<input type="text" name="name" id="name" autocomplete="on" placeholder="Name" class="form-control">
</div>
<div class="col-md-6">
<input type="text" name="category" id="category" autocomplete="on" placeholder="Category" class="form-control">
</div>
</div>
</div>
<div class="panel-body">
<textarea type="text" name="description" id="description" autocomplete="on" placeholder="Description" class="form-control"></textarea>
<br><br>
<div class="row">
<div class="col-md-6">
<input type="text" name="flag" id="flag" autocomplete="off" placeholder="EasyCTF{insert_correct_flag_here}" class="form-control">
</div>
<div class="col-md-6">
<input type="text" name="problem-hint" id="problem-hint" autocomplete="off" placeholder="Hint" class="form-control">
</div>
</div>
<br>
<div class="row">
<input type="number" name="value" id="value" autocomplete="off" placeholder="Value" class="form-control-number center-block">
</div>
</div>
<div class="panel-footer">
<h4>These are important files!</h4>
<hr>
<div class="row">
<input type="file" name="files[]" id="files" multiple="true">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<input form="add-form" id="add-problem" type="submit" class="btn btn-primary" value="Add Problem">
</div>
</div>
</div>
</div>
<div class="modal fade" id="delete-modal" tabindex="-1" role="dialog" aria-labelledby="delete-modal-label" data-backdrop="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="delete-modal-label">Delete Problem</h4>
</div>
<div class="modal-body">
Are you sure you want to delete this problem? You cannot undo this.
<div id="delete_status"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No</button>
<button type="button" id="delete" class="btn btn-primary">Yes</button>
</div>
</div>
</div>
</div>
<h1>Problems</h1>
<div id="status"></div>
<input type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#add-modal" value="Add Problem">
<div id="problems"></div>
<div class="modal fade" id="add-modal" tabindex="-2" role="dialog" aria-labelledby="add-modal-label" data-backdrop="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="add-modal-label">Add Problem</h4>
</div>
<div class="modal-body">
<form id="add-form" method="POST" action="/api/problem/add" enctype="multipart/form-data">
<div class="panel-heading">
<div class="row">
<div class="col-md-6">
<input type="text" name="name" id="name" autocomplete="on" placeholder="Name" class="form-control">
</div>
<div class="col-md-6">
<input type="text" name="category" id="category" autocomplete="on" placeholder="Category" class="form-control">
</div>
</div>
</div>
<div class="panel-body">
<textarea type="text" name="description" id="description" autocomplete="on" placeholder="Description" class="form-control"></textarea>
<br><br>
<div class="row">
<div class="col-md-6">
<input type="text" name="flag" id="flag" autocomplete="off" placeholder="EasyCTF{insert_correct_flag_here}" class="form-control">
</div>
<div class="col-md-6">
<input type="text" name="problem-hint" id="problem-hint" autocomplete="off" placeholder="Hint" class="form-control">
</div>
</div>
<br>
<div class="row">
<input type="number" name="value" id="value" autocomplete="off" placeholder="Value" class="form-control-number center-block">
</div>
</div>
<div class="panel-footer">
<h4>These are important files!</h4>
<hr>
<div class="row">
<input type="file" name="files[]" id="files" multiple="true">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<input form="add-form" id="add-problem" type="submit" class="btn btn-primary" value="Add Problem">
</div>
</div>
</div>
</div>
<div class="modal fade" id="delete-modal" tabindex="-1" role="dialog" aria-labelledby="delete-modal-label" data-backdrop="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="delete-modal-label">Delete Problem</h4>
</div>
<div class="modal-body">
Are you sure you want to delete this problem? You cannot undo this.
<div id="delete_status"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No</button>
<button type="button" id="delete" class="btn btn-primary">Yes</button>
</div>
</div>
</div>
</div>
<script src="js/admin/problems.js"></script>
<script src="js/admin/problems.js"></script>
</center>

View file

@ -0,0 +1,55 @@
<div ng-show="user['user_found']==true">
<div class="row">
<div class="col-sm-3 col-xs-12">
<div class="panel panel-default">
<div class="panel-body">
<img src="//www.gravatar.com/avatar/?size=512" id="avatar" style="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>
<h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2>
<small style="display:block;font-size:1.5em;">@{{ user.username }}</small>
<hr>
<div>
<i class="fa fa-fw fa-user"></i>
{{ user.type }}
<div class="label label-info" ng-show="user['admin']==true">ADMIN</div>
</div>
<div ng-show="user['show_email']==true">
<i class="fa fa-fw fa-envelope"></i>
<a style="color:#666;" href="mailto:{{ user.email }}">
<span id="email">{{ user.email }}</span>
</a>
</div>
<div>
<i class="fa fa-fw fa-clocK-o"></i>
Joined <time class="timeago" datetime="{{ user.registertime }}"></time>
</div>
</div>
</div>
</div>
<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 ng-show="user['user_found']==false">
<div class="page-header">
<h1>User Not Found</h1>
</div>
<p>The user you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
</div>
<script type="text/javascript">
$(document).ready(function() {
$("ul[role=tablist]").tab();
});
</script>