mirror of https://github.com/JohnHammond/CTFd.git
Resolve issues with pages and caching (#771)
* Update base.html to move custom_css precedence * Fix Page creation & caching * Add Page loading test * Fix creating Page with an invalid route * Don't call cache.clear() unless it's absolutely needed * Fix showing uploaded files after uploading to media library * Fix previewing challenges from the admin panel if it has requirements * Hardcode CACHE_THRESHOLD to 0 in FileSystemCache to prevent random sessions getting deleted (Closes #772)selenium-screenshot-testing
parent
fb0d8877cb
commit
e2ff705494
|
@ -86,7 +86,7 @@ def plugin(plugin):
|
|||
continue
|
||||
set_config(k, v)
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
clear_config()
|
||||
return '1'
|
||||
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ class Challenge(Resource):
|
|||
|
||||
prereqs = set(requirements.get('prerequisites', []))
|
||||
anonymize = requirements.get('anonymize')
|
||||
if solve_ids >= prereqs:
|
||||
if solve_ids >= prereqs or is_admin():
|
||||
pass
|
||||
else:
|
||||
if anonymize:
|
||||
|
|
|
@ -2,7 +2,7 @@ from flask import session, request
|
|||
from flask_restplus import Namespace, Resource
|
||||
from CTFd.models import db, Pages
|
||||
from CTFd.schemas.pages import PageSchema
|
||||
from CTFd.utils.events import socketio
|
||||
from CTFd.cache import clear_pages
|
||||
|
||||
from CTFd.utils.decorators import (
|
||||
admins_only
|
||||
|
@ -47,6 +47,8 @@ class PageList(Resource):
|
|||
response = schema.dump(response.data)
|
||||
db.session.close()
|
||||
|
||||
clear_pages()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'data': response.data
|
||||
|
@ -91,6 +93,8 @@ class PageDetail(Resource):
|
|||
response = schema.dump(response.data)
|
||||
db.session.close()
|
||||
|
||||
clear_pages()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'data': response.data
|
||||
|
@ -103,6 +107,8 @@ class PageDetail(Resource):
|
|||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
clear_pages()
|
||||
|
||||
return {
|
||||
'success': True
|
||||
}
|
||||
|
|
|
@ -12,3 +12,9 @@ def clear_config():
|
|||
def clear_standings():
|
||||
from CTFd.utils.scores import get_standings
|
||||
cache.delete_memoized(get_standings)
|
||||
|
||||
|
||||
def clear_pages():
|
||||
from CTFd.utils.config.pages import get_page, get_pages
|
||||
cache.delete_memoized(get_pages)
|
||||
cache.delete_memoized(get_page)
|
||||
|
|
|
@ -73,6 +73,7 @@ class Config(object):
|
|||
else:
|
||||
CACHE_TYPE = 'filesystem'
|
||||
CACHE_DIR = os.path.join(os.path.dirname(__file__), os.pardir, '.data', 'filesystem_cache')
|
||||
CACHE_THRESHOLD = 0 # Override the threshold of cached values on the filesystem. The default is 500. Don't change unless you know what you're doing.
|
||||
|
||||
'''
|
||||
=== SECURITY ===
|
||||
|
@ -210,4 +211,5 @@ class TestingConfig(Config):
|
|||
UPDATE_CHECK = False
|
||||
REDIS_URL = None
|
||||
CACHE_TYPE = 'simple'
|
||||
CACHE_THRESHOLD = 500
|
||||
SAFE_MODE = True
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from sqlalchemy.sql.expression import union_all
|
||||
from marshmallow import fields, post_load
|
||||
from marshmallow import validate, ValidationError
|
||||
from marshmallow import validate, ValidationError, pre_load
|
||||
from marshmallow_sqlalchemy import field_for
|
||||
from CTFd.models import ma, Pages
|
||||
|
||||
|
@ -11,6 +11,12 @@ class PageSchema(ma.ModelSchema):
|
|||
include_fk = True
|
||||
dump_only = ('id', )
|
||||
|
||||
@pre_load
|
||||
def validate_route(self, data):
|
||||
route = data.get('route')
|
||||
if route and route.startswith('/'):
|
||||
data['route'] = route.strip('/')
|
||||
|
||||
def __init__(self, view=None, *args, **kwargs):
|
||||
if view:
|
||||
if type(view) == str:
|
||||
|
|
|
@ -9,6 +9,7 @@ var editor = CodeMirror.fromTextArea(
|
|||
|
||||
function show_files(data) {
|
||||
var list = $('#media-library-list');
|
||||
list.empty();
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var f = data[i];
|
||||
|
@ -107,8 +108,7 @@ function submit_form() {
|
|||
button: 'Okay'
|
||||
});
|
||||
} else {
|
||||
console.log(data);
|
||||
window.location = script_root + '/admin/pages/' + data.id;
|
||||
window.location = script_root + '/admin/pages/' + response.data.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ function preview_page() {
|
|||
|
||||
function upload_media() {
|
||||
upload_files($('#media-library-upload'), function (data) {
|
||||
console.log(data);
|
||||
refresh_files();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -146,12 +146,6 @@ $(document).ready(function () {
|
|||
insert_at_cursor(editor, entry);
|
||||
});
|
||||
|
||||
|
||||
$('#publish-page').click(function (e) {
|
||||
e.preventDefault();
|
||||
submit_form();
|
||||
});
|
||||
|
||||
$('#save-page').click(function (e) {
|
||||
e.preventDefault();
|
||||
submit_form();
|
||||
|
|
|
@ -93,8 +93,10 @@
|
|||
<form id="page-edit" method="POST">
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<h3>Title</h3>
|
||||
<p class="text-muted">This is the title shown on the navigation bar</p>
|
||||
<label>
|
||||
Title<br>
|
||||
<small class="text-muted">This is the title shown on the navigation bar</small>
|
||||
</label>
|
||||
<input class="form-control radius" id="route" type="text" name="title"
|
||||
value="{% if page is defined %}{{ page.title }}{% endif %}" placeholder="Title">
|
||||
</div>
|
||||
|
@ -102,20 +104,15 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div class="col-md-12">
|
||||
<h3>Route</h3>
|
||||
<p class="text-muted">This is the URL route that your page will be at (e.g. /page). You can
|
||||
also enter links to link to that page.</p>
|
||||
<label>
|
||||
Route<br>
|
||||
<small class="text-muted">
|
||||
This is the URL route that your page will be at (e.g. /page). You can
|
||||
also enter links to link to that page.
|
||||
</small>
|
||||
</label>
|
||||
<input class="form-control radius" id="route" type="text" name="route"
|
||||
value="{% if page is defined %}{{ page.route }}{% endif %}" placeholder="Route">
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label float-right">
|
||||
<input class="form-check-input" type="checkbox" name="auth_required"
|
||||
{% if page is defined and page.auth_required %}checked{% endif %}>
|
||||
Users must be logged in to see this page
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -123,7 +120,7 @@
|
|||
<div class="col-md-12">
|
||||
|
||||
<h3>Content</h3>
|
||||
<p class="text-muted">This is the HTML content of your page</p>
|
||||
<small class="text-muted">This is the HTML content of your page</small>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist" id="content-edit">
|
||||
<li class="nav-item" role="presentation" class="active">
|
||||
|
@ -148,6 +145,45 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="nav-link d-none d-md-block d-lg-block">|</span>
|
||||
|
||||
<div class="form-group pr-3">
|
||||
Draft:
|
||||
{% set draft = page is defined and page.draft == True %}
|
||||
<select name="draft">
|
||||
<option value="true" {% if draft == True %}selected{% endif %}>
|
||||
True
|
||||
</option>
|
||||
<option value="false" {% if draft == False %}selected{% endif %}>
|
||||
False
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group pr-3">
|
||||
Hidden:
|
||||
{% set hidden = page is defined and page.hidden == True %}
|
||||
<select name="hidden">
|
||||
<option value="true" {% if hidden == True %}selected{% endif %}>True
|
||||
</option>
|
||||
<option value="false" {% if hidden == False %}selected{% endif %}>False
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group pr-3">
|
||||
Authentication Required:
|
||||
{% set auth_required = page is defined and page.auth_required == True %}
|
||||
<select name="auth_required">
|
||||
<option value="true" {% if auth_required == True %}selected{% endif %}>
|
||||
True
|
||||
</option>
|
||||
<option value="false" {% if auth_required == False %}selected{% endif %}>
|
||||
False
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
@ -167,9 +203,6 @@
|
|||
|
||||
<div class="form-group float-right">
|
||||
<input name='nonce' type='hidden' value="{{ nonce }}">
|
||||
<button class="btn btn-success" id="publish-page">
|
||||
Publish
|
||||
</button>
|
||||
<button class="btn btn-primary" id="save-page">
|
||||
{% if page is defined %}
|
||||
Update
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
<tr>
|
||||
<td><b>Title</b></td>
|
||||
<td><b>Route</b></td>
|
||||
<td><b>Authentication</b></td>
|
||||
<td class="text-center"><b>Authentication</b></td>
|
||||
<td class="text-center"><b>Hidden</b></td>
|
||||
<td class="text-center"><b>Published</b></td>
|
||||
<td class="text-center" width="10px"><b>Settings</b></td>
|
||||
</tr>
|
||||
|
@ -40,12 +41,18 @@
|
|||
{{ page.route }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="page-authentication">
|
||||
<td class="text-center">
|
||||
{% if page.auth_required %}
|
||||
Required
|
||||
{% else %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if page.hidden %}
|
||||
Hidden
|
||||
{% else %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if page.draft %}
|
||||
Draft
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
<link rel="stylesheet" href="{{ url_for('views.themes', theme=get_ctf_theme(), path='css/jumbotron.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('views.themes', theme=get_ctf_theme(), path='css/sticky-footer.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('views.themes', theme=get_ctf_theme(), path='css/base.css') }}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('views.custom_css') }}">
|
||||
{% block stylesheets %}{% endblock %}
|
||||
{% for stylesheet in get_registered_stylesheets() %}
|
||||
{% if stylesheet.startswith('http') %}
|
||||
|
@ -24,6 +23,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('views.custom_css') }}">
|
||||
<script src="{{ url_for('views.themes', theme=get_ctf_theme(), path='js/vendor/promise-polyfill.min.js') }}"></script>
|
||||
<script src="{{ url_for('views.themes', theme=get_ctf_theme(), path='js/vendor/fetch.min.js') }}"></script>
|
||||
<script src="{{ url_for('views.themes', theme=get_ctf_theme(), path='js/CTFd.js') }}"></script>
|
||||
|
|
|
@ -7,39 +7,28 @@ import time
|
|||
import os
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def ctf_name():
|
||||
name = get_config('ctf_name')
|
||||
return name if name else 'CTFd'
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def user_mode():
|
||||
return get_config('user_mode')
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def ctf_logo():
|
||||
return get_config('ctf_logo')
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def ctf_theme():
|
||||
theme = get_config('ctf_theme')
|
||||
return theme if theme else ''
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def user_mode():
|
||||
return get_config('user_mode')
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def is_setup():
|
||||
return get_config('setup')
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def is_scoreboard_frozen():
|
||||
freeze = get_config('freeze')
|
||||
|
||||
|
@ -51,12 +40,10 @@ def is_scoreboard_frozen():
|
|||
return False
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def can_send_mail():
|
||||
return mailserver() or mailgun()
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def get_mail_provider():
|
||||
if app.config.get('MAIL_SERVER') and app.config.get('MAIL_PORT'):
|
||||
return 'smtp'
|
||||
|
@ -68,7 +55,6 @@ def get_mail_provider():
|
|||
return 'mailgun'
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def mailgun():
|
||||
if app.config.get('MAILGUN_API_KEY') and app.config.get('MAILGUN_BASE_URL'):
|
||||
return True
|
||||
|
@ -77,7 +63,6 @@ def mailgun():
|
|||
return False
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def mailserver():
|
||||
if app.config.get('MAIL_SERVER') and app.config.get('MAIL_PORT'):
|
||||
return True
|
||||
|
@ -86,7 +71,6 @@ def mailserver():
|
|||
return False
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def get_themes():
|
||||
dir = os.path.join(app.root_path, 'themes')
|
||||
return [name for name in os.listdir(dir) if os.path.isdir(os.path.join(dir, name)) and name != 'admin']
|
||||
|
|
|
@ -2,12 +2,12 @@ from CTFd.cache import cache
|
|||
from CTFd.models import Pages
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
# @cache.memoize()
|
||||
def get_pages():
|
||||
db_pages = Pages.query.filter(Pages.route != "index", Pages.draft != True).all()
|
||||
db_pages = Pages.query.filter(Pages.route != "index", Pages.draft.isnot(True), Pages.hidden.isnot(True)).all()
|
||||
return db_pages
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def get_page(route):
|
||||
return Pages.query.filter(Pages.route == route, Pages.draft != True).first()
|
||||
return Pages.query.filter(Pages.route == route, Pages.draft.isnot(True), Pages.hidden.isnot(True)).first()
|
||||
|
|
|
@ -42,14 +42,23 @@ def test_api_pages_post_admin():
|
|||
with login_as_user(app, name="admin") as client:
|
||||
with client.session_transaction() as sess:
|
||||
nonce = sess.get('nonce')
|
||||
r = client.post('/api/v1/pages', json={
|
||||
"title": "Title",
|
||||
r = client.post(
|
||||
'/api/v1/pages',
|
||||
json={
|
||||
"title": "testing_page_title",
|
||||
"route": "/route",
|
||||
"content": "content",
|
||||
"id": "",
|
||||
"content": "testing_page_content",
|
||||
"nonce": nonce,
|
||||
"auth_required": False})
|
||||
"auth_required": False
|
||||
}
|
||||
)
|
||||
r = client.get('/')
|
||||
assert r.status_code == 200
|
||||
assert "testing_page_title" in r.get_data(as_text=True)
|
||||
|
||||
r = client.get('/route')
|
||||
assert r.status_code == 200
|
||||
assert "testing_page_content" in r.get_data(as_text=True)
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue