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
Kevin Chung 2018-12-01 16:25:39 -05:00 committed by GitHub
parent fb0d8877cb
commit e2ff705494
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 107 additions and 60 deletions

View File

@ -86,7 +86,7 @@ def plugin(plugin):
continue
set_config(k, v)
with app.app_context():
cache.clear()
clear_config()
return '1'

View File

@ -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:

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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']

View File

@ -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()

View File

@ -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",
"route": "/route",
"content": "content",
"id": "",
"nonce": nonce,
"auth_required": False})
r = client.post(
'/api/v1/pages',
json={
"title": "testing_page_title",
"route": "/route",
"content": "testing_page_content",
"nonce": nonce,
"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)