mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 22:14:25 +01:00
TOS and Privacy Policy Pages (#1632)
* Adds a legal section where users can add a terms of service and privacy policy * Optionally show links to the TOS and Privacy Policy on the registration page * Closes #1621
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
from flask import url_for
|
||||||
|
|
||||||
from CTFd.constants import JinjaEnum, RawEnum
|
from CTFd.constants import JinjaEnum, RawEnum
|
||||||
from CTFd.utils import get_config
|
from CTFd.utils import get_config
|
||||||
|
|
||||||
@@ -63,5 +65,19 @@ class _ConfigsWrapper:
|
|||||||
def theme_settings(self):
|
def theme_settings(self):
|
||||||
return json.loads(get_config("theme_settings", default="null"))
|
return json.loads(get_config("theme_settings", default="null"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tos_or_privacy(self):
|
||||||
|
tos = bool(get_config("tos_url") or get_config("tos_text"))
|
||||||
|
privacy = bool(get_config("privacy_url") or get_config("privacy_text"))
|
||||||
|
return tos or privacy
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tos_link(self):
|
||||||
|
return get_config("tos_url", default=url_for("views.tos"))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def privacy_link(self):
|
||||||
|
return get_config("privacy_url", default=url_for("views.privacy"))
|
||||||
|
|
||||||
|
|
||||||
Configs = _ConfigsWrapper()
|
Configs = _ConfigsWrapper()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from wtforms import BooleanField, SelectField, StringField
|
from wtforms import BooleanField, SelectField, StringField, TextAreaField
|
||||||
from wtforms.fields.html5 import IntegerField
|
from wtforms.fields.html5 import IntegerField, URLField
|
||||||
from wtforms.widgets.html5 import NumberInput
|
from wtforms.widgets.html5 import NumberInput
|
||||||
|
|
||||||
from CTFd.forms import BaseForm
|
from CTFd.forms import BaseForm
|
||||||
@@ -60,3 +60,21 @@ class ExportCSVForm(BaseForm):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
submit = SubmitField("Download CSV")
|
submit = SubmitField("Download CSV")
|
||||||
|
|
||||||
|
|
||||||
|
class LegalSettingsForm(BaseForm):
|
||||||
|
tos_url = URLField(
|
||||||
|
"Terms of Service URL",
|
||||||
|
description="External URL to a Terms of Service document hosted elsewhere",
|
||||||
|
)
|
||||||
|
tos_text = TextAreaField(
|
||||||
|
"Terms of Service", description="Text shown on the Terms of Service page",
|
||||||
|
)
|
||||||
|
privacy_url = URLField(
|
||||||
|
"Privacy Policy URL",
|
||||||
|
description="External URL to a Privacy Policy document hosted elsewhere",
|
||||||
|
)
|
||||||
|
privacy_text = TextAreaField(
|
||||||
|
"Privacy Policy", description="Text shown on the Privacy Policy page",
|
||||||
|
)
|
||||||
|
submit = SubmitField("Update")
|
||||||
|
|||||||
@@ -268,6 +268,17 @@ $(() => {
|
|||||||
theme_settings_editor.refresh();
|
theme_settings_editor.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(
|
||||||
|
"a[href='#legal'], a[href='#tos-config'], a[href='#privacy-policy-config']"
|
||||||
|
).on("shown.bs.tab", function(_e) {
|
||||||
|
$("#tos-config .CodeMirror").each(function(i, el) {
|
||||||
|
el.CodeMirror.refresh();
|
||||||
|
});
|
||||||
|
$("#privacy-policy-config .CodeMirror").each(function(i, el) {
|
||||||
|
el.CodeMirror.refresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$("#theme-settings-modal form").submit(function(e) {
|
$("#theme-settings-modal form").submit(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
theme_settings_editor
|
theme_settings_editor
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -38,6 +38,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link rounded-0" href="#ctftime" role="tab" data-toggle="tab">Time</a>
|
<a class="nav-link rounded-0" href="#ctftime" role="tab" data-toggle="tab">Time</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link rounded-0" href="#legal" role="tab" data-toggle="tab">Legal</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link rounded-0" href="#backup" role="tab" data-toggle="tab">Backup</a>
|
<a class="nav-link rounded-0" href="#backup" role="tab" data-toggle="tab">Backup</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -74,6 +77,8 @@
|
|||||||
|
|
||||||
{% include "admin/configs/time.html" %}
|
{% include "admin/configs/time.html" %}
|
||||||
|
|
||||||
|
{% include "admin/configs/legal.html" %}
|
||||||
|
|
||||||
{% include "admin/configs/backup.html" %}
|
{% include "admin/configs/backup.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
56
CTFd/themes/admin/templates/configs/legal.html
Normal file
56
CTFd/themes/admin/templates/configs/legal.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<div role="tabpanel" class="tab-pane config-section" id="legal">
|
||||||
|
|
||||||
|
{% with form = Forms.config.LegalSettingsForm(tos_url=tos_url, tos_text=tos_text, privacy_url=privacy_url, privacy_text=privacy_text) %}
|
||||||
|
<form method="POST" autocomplete="off" class="w-100">
|
||||||
|
<ul class="nav nav-tabs mb-3">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="#tos-config" role="tab" data-toggle="tab">
|
||||||
|
Terms of Service
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#privacy-policy-config" role="tab" data-toggle="tab">
|
||||||
|
Privacy Policy
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="tos-config">
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.tos_url.label }}
|
||||||
|
{{ form.tos_url(class="form-control") }}
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
{{ form.tos_url.description }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.tos_text.label }}
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
{{ form.tos_text.description }}
|
||||||
|
</small>
|
||||||
|
{{ form.tos_text(class="form-control markdown", rows=15) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="privacy-policy-config">
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.privacy_url.label }}
|
||||||
|
{{ form.privacy_url(class="form-control") }}
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
{{ form.privacy_url.description }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.privacy_text.label }}
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
{{ form.privacy_text.description }}
|
||||||
|
</small>
|
||||||
|
{{ form.privacy_text(class="form-control markdown", rows=15) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form.submit(class="btn btn-md btn-primary float-right") }}
|
||||||
|
</form>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
@@ -55,6 +55,18 @@
|
|||||||
{{ form.submit(class="btn btn-md btn-primary btn-outlined float-right") }}
|
{{ form.submit(class="btn btn-md btn-primary btn-outlined float-right") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if Configs.tos_or_privacy %}
|
||||||
|
<div class="row pt-3">
|
||||||
|
<div class="col-md-12 text-center">
|
||||||
|
<small class="text-muted text-center">
|
||||||
|
By registering, you agree to the
|
||||||
|
<a href="{{ Configs.privacy_link }}" rel="noopener" target="_blank">privacy policy</a>
|
||||||
|
and <a href="{{ Configs.tos_link }}" rel="noopener" target="_blank">terms of service</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from CTFd.utils import config, get_config, set_config
|
|||||||
from CTFd.utils import user as current_user
|
from CTFd.utils import user as current_user
|
||||||
from CTFd.utils import validators
|
from CTFd.utils import validators
|
||||||
from CTFd.utils.config import is_setup
|
from CTFd.utils.config import is_setup
|
||||||
from CTFd.utils.config.pages import get_page
|
from CTFd.utils.config.pages import build_html, get_page
|
||||||
from CTFd.utils.config.visibility import challenges_visible
|
from CTFd.utils.config.visibility import challenges_visible
|
||||||
from CTFd.utils.dates import ctf_ended, ctftime, view_after_ctf
|
from CTFd.utils.dates import ctf_ended, ctftime, view_after_ctf
|
||||||
from CTFd.utils.decorators import authed_only
|
from CTFd.utils.decorators import authed_only
|
||||||
@@ -332,6 +332,30 @@ def static_html(route):
|
|||||||
return render_template("page.html", content=page.content)
|
return render_template("page.html", content=page.content)
|
||||||
|
|
||||||
|
|
||||||
|
@views.route("/tos")
|
||||||
|
def tos():
|
||||||
|
tos_url = get_config("tos_url")
|
||||||
|
tos_text = get_config("tos_text")
|
||||||
|
if tos_url:
|
||||||
|
return redirect(tos_url)
|
||||||
|
elif tos_text:
|
||||||
|
return render_template("page.html", content=build_html(tos_text))
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@views.route("/privacy")
|
||||||
|
def privacy():
|
||||||
|
privacy_url = get_config("privacy_url")
|
||||||
|
privacy_text = get_config("privacy_text")
|
||||||
|
if privacy_url:
|
||||||
|
return redirect(privacy_url)
|
||||||
|
elif privacy_text:
|
||||||
|
return render_template("page.html", content=build_html(privacy_text))
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
@views.route("/files", defaults={"path": ""})
|
@views.route("/files", defaults={"path": ""})
|
||||||
@views.route("/files/<path:path>")
|
@views.route("/files/<path:path>")
|
||||||
def files(path):
|
def files(path):
|
||||||
|
|||||||
27
tests/test_legal.py
Normal file
27
tests/test_legal.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from CTFd.utils import set_config
|
||||||
|
from tests.helpers import create_ctfd, destroy_ctfd
|
||||||
|
|
||||||
|
|
||||||
|
def test_legal_settings():
|
||||||
|
app = create_ctfd()
|
||||||
|
with app.app_context():
|
||||||
|
set_config("tos_text", "Terms of Service")
|
||||||
|
set_config("privacy_text", "Privacy Policy")
|
||||||
|
|
||||||
|
with app.test_client() as client:
|
||||||
|
r = client.get("/register")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "privacy policy" in r.get_data(as_text=True)
|
||||||
|
assert "terms of service" in r.get_data(as_text=True)
|
||||||
|
|
||||||
|
r = client.get("/tos")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "Terms of Service" in r.get_data(as_text=True)
|
||||||
|
|
||||||
|
r = client.get("/privacy")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "Privacy Policy" in r.get_data(as_text=True)
|
||||||
|
destroy_ctfd(app)
|
||||||
Reference in New Issue
Block a user