mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 05:54:19 +01:00
Merge branch 'master' into 1691-challenge-preview-improvements
This commit is contained in:
@@ -85,11 +85,14 @@ class TokenList(Resource):
|
||||
def post(self):
|
||||
req = request.get_json()
|
||||
expiration = req.get("expiration")
|
||||
description = req.get("description")
|
||||
if expiration:
|
||||
expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d")
|
||||
|
||||
user = get_current_user()
|
||||
token = generate_user_token(user, expiration=expiration)
|
||||
token = generate_user_token(
|
||||
user, expiration=expiration, description=description
|
||||
)
|
||||
|
||||
# Explicitly use admin view so that user's can see the value of their token
|
||||
schema = TokenSchema(view="admin")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from flask import session
|
||||
from flask_babel import lazy_gettext as _l
|
||||
from wtforms import PasswordField, SelectField, StringField
|
||||
from wtforms import PasswordField, SelectField, StringField, TextAreaField
|
||||
from wtforms.fields.html5 import DateField, URLField
|
||||
|
||||
from CTFd.constants.languages import SELECT_LANGUAGE_LIST
|
||||
@@ -50,4 +50,5 @@ def SettingsForm(*args, **kwargs):
|
||||
|
||||
class TokensForm(BaseForm):
|
||||
expiration = DateField(_l("Expiration"))
|
||||
description = TextAreaField("Usage Description")
|
||||
submit = SubmitField(_l("Generate"))
|
||||
|
||||
@@ -916,6 +916,7 @@ class Tokens(db.Model):
|
||||
db.DateTime,
|
||||
default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30),
|
||||
)
|
||||
description = db.Column(db.Text)
|
||||
value = db.Column(db.String(128), unique=True)
|
||||
|
||||
user = db.relationship("Users", foreign_keys="Tokens.user_id", lazy="select")
|
||||
|
||||
@@ -9,8 +9,16 @@ class TokenSchema(ma.ModelSchema):
|
||||
dump_only = ("id", "expiration", "type")
|
||||
|
||||
views = {
|
||||
"admin": ["id", "type", "user_id", "created", "expiration", "value"],
|
||||
"user": ["id", "type", "created", "expiration"],
|
||||
"admin": [
|
||||
"id",
|
||||
"type",
|
||||
"user_id",
|
||||
"created",
|
||||
"expiration",
|
||||
"description",
|
||||
"value",
|
||||
],
|
||||
"user": ["id", "type", "created", "expiration", "description"],
|
||||
}
|
||||
|
||||
def __init__(self, view=None, *args, **kwargs):
|
||||
|
||||
@@ -61,7 +61,55 @@ function deleteSelectedSubmissions(_event) {
|
||||
});
|
||||
}
|
||||
|
||||
function showFlagsToggle(_event) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has("full")) {
|
||||
urlParams.delete("full");
|
||||
} else {
|
||||
urlParams.set("full", "true");
|
||||
}
|
||||
window.location.href = `${window.location.pathname}?${urlParams.toString()}`;
|
||||
}
|
||||
|
||||
function showFlag(event) {
|
||||
let target = $(event.currentTarget);
|
||||
let eye = target.find("i");
|
||||
let flag = target.parent().find("pre");
|
||||
if (!flag.hasClass("full-flag")) {
|
||||
flag.text(flag.attr("title"));
|
||||
flag.addClass("full-flag");
|
||||
eye.addClass("fa-eye-slash");
|
||||
eye.removeClass("fa-eye");
|
||||
} else {
|
||||
flag.text(flag.attr("title").substring(0, 42) + "...");
|
||||
flag.removeClass("full-flag");
|
||||
eye.addClass("fa-eye");
|
||||
eye.removeClass("fa-eye-slash");
|
||||
}
|
||||
}
|
||||
|
||||
function copyFlag(event) {
|
||||
let target = $(event.currentTarget);
|
||||
let flag = target.parent().find("pre");
|
||||
let text = flag.attr("title");
|
||||
navigator.clipboard.writeText(text);
|
||||
|
||||
$(event.currentTarget).tooltip({
|
||||
title: "Copied!",
|
||||
trigger: "manual"
|
||||
});
|
||||
$(event.currentTarget).tooltip("show");
|
||||
|
||||
setTimeout(function() {
|
||||
$(event.currentTarget).tooltip("hide");
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
$(() => {
|
||||
$("#show-full-flags-button").click(showFlagsToggle);
|
||||
$("#show-short-flags-button").click(showFlagsToggle);
|
||||
$(".show-flag").click(showFlag);
|
||||
$(".copy-flag").click(copyFlag);
|
||||
$(".delete-correct-submission").click(deleteCorrectSubmission);
|
||||
$("#submission-delete-button").click(deleteSelectedSubmissions);
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -48,7 +48,16 @@
|
||||
<div class="col-md-12">
|
||||
<div class="float-right pb-3">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-danger" id="submission-delete-button">
|
||||
{% if request.args.get("full") %}
|
||||
<button type="button" class="btn btn-outline-primary" data-toggle="tooltip" title="Show truncated flags" id="show-short-flags-button">
|
||||
<i class="btn-fa far fa-flag"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-outline-primary" data-toggle="tooltip" title="Show full flags" id="show-full-flags-button">
|
||||
<i class="btn-fa fas fa-flag"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="btn btn-outline-danger" data-toggle="tooltip" title="Delete selected submissions" id="submission-delete-button">
|
||||
<i class="btn-fa fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -109,7 +118,19 @@
|
||||
{{ sub.type }}
|
||||
</td>
|
||||
<td class="flag" id="{{ sub.id }}">
|
||||
<pre class="mb-0">{{ sub.provided }}</pre>
|
||||
<button class="btn btn-link p-0 float-left copy-flag" type="button">
|
||||
<i class="fas fa-clipboard"></i>
|
||||
</button>
|
||||
{% if request.args.get('full') %}
|
||||
<pre class="mb-0 pl-2" title="{{ sub.provided }}">{{ sub.provided }}</pre>
|
||||
{% else %}
|
||||
<pre class="mb-0 pl-2 float-left" title="{{ sub.provided }}">{{ sub.provided | truncate(45, True) }}</pre>
|
||||
{% if sub.provided | length > 50 %}
|
||||
<button class="btn btn-link p-0 pl-1 float-left show-flag">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center solve-time">
|
||||
<span data-time="{{ sub.date | isoformat }}"></span>
|
||||
|
||||
@@ -15,6 +15,10 @@ git subtree add --prefix CTFd/themes/core-beta git@github.com:CTFd/core-beta.git
|
||||
git subtree pull --prefix CTFd/themes/core-beta git@github.com:CTFd/core-beta.git main --squash
|
||||
```
|
||||
|
||||
### Subtree Gotcha
|
||||
|
||||
Make sure to use Merge Commits when dealing with the subtree here. For some reason Github's squash and commit uses the wrong line ending which causes issues with the subtree script: https://stackoverflow.com/a/47190256.
|
||||
|
||||
## Todo
|
||||
|
||||
- Document how we are using Vite
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
{{ form.expiration(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<b>{{ form.description.label(class="form-label") }}</b>
|
||||
{{ form.description(class="form-control", rows="3") }}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{ form.submit(class="btn btn-block btn-primary float-end px-4") }}
|
||||
@@ -218,6 +223,7 @@
|
||||
<tr>
|
||||
<td class="text-center"><b>{% trans %}Created{% endtrans %}</b></td>
|
||||
<td><b>{% trans %}Expiration{% endtrans %}</b></td>
|
||||
<td><b>{% trans %}Description{% endtrans %}</b></td>
|
||||
<td><b>{% trans %}Delete{% endtrans %}</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -230,6 +236,9 @@
|
||||
<td>
|
||||
<span data-time="{{ token.expiration | isoformat }}"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ token.description | default('', true) }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span
|
||||
class="delete-token" role="button"
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><b>{% trans %}User Name{% trans %}</b></td>
|
||||
<td><b>{% trans %}User Name{% endtrans %}</b></td>
|
||||
<td><b>{% trans %}Score{% endtrans %}</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -169,15 +169,15 @@
|
||||
<div class="row" x-data="TeamGraphs">
|
||||
<div class="col-md-6 d-none d-md-block d-lg-block py-4">
|
||||
<div class="progress">
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
:style="{ width: `${getSolvePercentage()}%`, 'background-color': 'rgb(0, 209, 64)' }"
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
:style="{ width: `${getSolvePercentage()}%`, 'background-color': 'rgb(0, 209, 64)' }"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
:style="{ width: `${getFailPercentage()}%`, 'background-color': 'rgb(207, 38, 0)' }"
|
||||
>
|
||||
</div>
|
||||
@@ -198,9 +198,9 @@
|
||||
<div class="col-md-6 d-none d-md-block d-lg-block py-4">
|
||||
<div class="progress">
|
||||
<template x-for="category in getCategoryBreakdown()" :key="category.name">
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
<div
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
:style="{ width: `${category.percent}%`, 'background-color': category.color }"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,10 @@
|
||||
<b>{{ form.expiration.label }}</b>
|
||||
{{ form.expiration(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<b>{{ form.description.label }}</b>
|
||||
{{ form.description(class="form-control", rows="3") }}
|
||||
</div>
|
||||
<div class="form-group text-right">
|
||||
{{ form.submit(class="btn btn-md btn-primary btn-outlined") }}
|
||||
</div>
|
||||
@@ -96,6 +99,7 @@
|
||||
<tr>
|
||||
<td class="text-center"><b>Created</b></td>
|
||||
<td class="text-center"><b>Expiration</b></td>
|
||||
<td class="text-center"><b>Description</b></td>
|
||||
<td class="text-center"><b>Delete</b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -104,6 +108,7 @@
|
||||
<tr>
|
||||
<td><span data-time="{{ token.created | isoformat }}"></span></td>
|
||||
<td><span data-time="{{ token.expiration | isoformat }}"></span></td>
|
||||
<td><span>{{ token.description | default('', true) }}</span></td>
|
||||
<td class="text-center">
|
||||
<span class="delete-token" role="button" data-token-id="{{ token.id }}">
|
||||
<i class="btn-fa fas fa-times"></i>
|
||||
|
||||
@@ -34,13 +34,15 @@ def logout_user():
|
||||
session.clear()
|
||||
|
||||
|
||||
def generate_user_token(user, expiration=None):
|
||||
def generate_user_token(user, expiration=None, description=None):
|
||||
temp_token = True
|
||||
while temp_token is not None:
|
||||
value = hexencode(os.urandom(32))
|
||||
value = "ctfd_" + hexencode(os.urandom(32))
|
||||
temp_token = UserTokens.query.filter_by(value=value).first()
|
||||
|
||||
token = UserTokens(user_id=user.id, expiration=expiration, value=value)
|
||||
token = UserTokens(
|
||||
user_id=user.id, expiration=expiration, description=description, value=value
|
||||
)
|
||||
db.session.add(token)
|
||||
db.session.commit()
|
||||
return token
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Add description column to tokens table
|
||||
|
||||
Revision ID: 9e6f6578ca84
|
||||
Revises: 0def790057c1
|
||||
Create Date: 2023-06-21 23:22:34.179636
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "9e6f6578ca84"
|
||||
down_revision = "0def790057c1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("tokens", sa.Column("description", sa.Text(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("tokens", "description")
|
||||
@@ -11,7 +11,7 @@ passlib==1.7.4
|
||||
bcrypt==4.0.1
|
||||
itsdangerous==1.1.0
|
||||
requests==2.28.1
|
||||
PyMySQL==0.9.3
|
||||
PyMySQL[rsa]==0.9.3
|
||||
gunicorn==20.1.0
|
||||
dataset==1.3.1
|
||||
cmarkgfm==2022.10.27
|
||||
|
||||
@@ -29,6 +29,7 @@ certifi==2022.12.7
|
||||
cffi==1.15.0
|
||||
# via
|
||||
# cmarkgfm
|
||||
# cryptography
|
||||
# pybluemonday
|
||||
charset-normalizer==2.0.12
|
||||
# via requests
|
||||
@@ -36,6 +37,8 @@ click==7.1.2
|
||||
# via flask
|
||||
cmarkgfm==2022.10.27
|
||||
# via -r requirements.in
|
||||
cryptography==40.0.2
|
||||
# via pymysql
|
||||
dataset==1.3.1
|
||||
# via -r requirements.in
|
||||
docutils==0.15.2
|
||||
@@ -117,7 +120,7 @@ pycparser==2.20
|
||||
# via cffi
|
||||
pydantic==1.6.2
|
||||
# via -r requirements.in
|
||||
pymysql==0.9.3
|
||||
pymysql[rsa]==0.9.3
|
||||
# via -r requirements.in
|
||||
pyrsistent==0.17.3
|
||||
# via jsonschema
|
||||
|
||||
Reference in New Issue
Block a user