Skip to content

Commit 9264e96

Browse files
authored
Mark 3.1.0 (CTFd#1634)
# 3.1.0 / 2020-09-08 **General** - Loosen team password confirmation in team settings to also accept the team captain's password to make it easier to change the team password - Adds the ability to add custom user and team fields for registration/profile settings. - Improve Notifications pubsub events system to use a subscriber per server instead of a subscriber per browser. This should improve the reliability of CTFd at higher load and make it easier to deploy the Notifications system **Admin Panel** - Add a comments functionality for admins to discuss challenges, users, teams, pages - Adds a legal section in Configs where users can add a terms of service and privacy policy - Add a Custom Fields section in Configs where admins can add/edit custom user/team fields - Move user graphs into a modal for Admin Panel **API** - Add `/api/v1/comments` to manipulate and create comments **Themes** - Make scoreboard caching only cache the score table instead of the entire page. This is done by caching the specific template section. Refer to CTFd#1586, specifically the changes in `scoreboard.html`. - Add rel=noopener to external links to prevent tab napping attacks - Change the registration page to reference links to Terms of Service and Privacy Policy if specified in configuration **Miscellaneous** - Make team settings modal larger in the core theme - Update tests in Github Actions to properly test under MySQL and Postgres - Make gevent default in serve.py and add a `--disable-gevent` switch in serve.py - Add `tenacity` library for retrying logic - Add `pytest-sugar` for slightly prettier pytest output - Add a `listen()` method to `CTFd.utils.events.EventManager` and `CTFd.utils.events.RedisEventManager`. - This method should implement subscription for a CTFd worker to whatever underlying notification system there is. This should be implemented with gevent or a background thread. - The `subscribe()` method (which used to implement the functionality of the new `listen()` function) now only handles passing notifications from CTFd to the browser. This should also be implemented with gevent or a background thread.
1 parent c1d7910 commit 9264e96

File tree

145 files changed

+4716
-366
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+4716
-366
lines changed

.github/workflows/lint.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ jobs:
1111
strategy:
1212
matrix:
1313
python-version: ['3.6']
14-
TESTING_DATABASE_URL: ['sqlite://']
1514

1615
name: Linting
1716
steps:
@@ -30,6 +29,8 @@ jobs:
3029
3130
- name: Lint
3231
run: make lint
32+
env:
33+
TESTING_DATABASE_URL: 'sqlite://'
3334

3435
- name: Lint Dockerfile
3536
uses: brpaz/hadolint-action@master

.github/workflows/mysql.yml

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ jobs:
99
runs-on: ubuntu-latest
1010
services:
1111
mysql:
12-
image: mysql
12+
image: mysql:5.7
13+
env:
14+
MYSQL_ROOT_PASSWORD: password
1315
ports:
14-
- 3306:3306
16+
- 3306
17+
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
1518
redis:
1619
image: redis
1720
ports:
@@ -20,7 +23,6 @@ jobs:
2023
strategy:
2124
matrix:
2225
python-version: ['3.6']
23-
TESTING_DATABASE_URL: ['mysql+pymysql://root@localhost/ctfd']
2426

2527
name: Python ${{ matrix.python-version }}
2628
steps:
@@ -43,6 +45,7 @@ jobs:
4345
env:
4446
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
4547
AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
48+
TESTING_DATABASE_URL: mysql+pymysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/ctfd
4649

4750
- name: Codecov
4851
uses: codecov/[email protected]

.github/workflows/postgres.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
env:
1616
POSTGRES_HOST_AUTH_METHOD: trust
1717
POSTGRES_DB: ctfd
18+
POSTGRES_PASSWORD: password
1819
# Set health checks to wait until postgres has started
1920
options: >-
2021
--health-cmd pg_isready
@@ -29,7 +30,6 @@ jobs:
2930
strategy:
3031
matrix:
3132
python-version: ['3.6']
32-
TESTING_DATABASE_URL: ['postgres://postgres@localhost/ctfd']
3333

3434
name: Python ${{ matrix.python-version }}
3535
steps:
@@ -52,6 +52,7 @@ jobs:
5252
env:
5353
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
5454
AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
55+
TESTING_DATABASE_URL: postgres://postgres:password@localhost:${{ job.services.postgres.ports[5432] }}/ctfd
5556

5657
- name: Codecov
5758
uses: codecov/[email protected]

.github/workflows/sqlite.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ jobs:
1111
strategy:
1212
matrix:
1313
python-version: ['3.6']
14-
TESTING_DATABASE_URL: ['sqlite://']
1514

1615
name: Python ${{ matrix.python-version }}
1716
steps:
@@ -35,6 +34,7 @@ jobs:
3534
env:
3635
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
3736
AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
37+
TESTING_DATABASE_URL: 'sqlite://'
3838

3939
- name: Codecov
4040
uses: codecov/[email protected]

CHANGELOG.md

+36
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
# 3.1.0 / 2020-09-08
2+
3+
**General**
4+
5+
- Loosen team password confirmation in team settings to also accept the team captain's password to make it easier to change the team password
6+
- Adds the ability to add custom user and team fields for registration/profile settings.
7+
- Improve Notifications pubsub events system to use a subscriber per server instead of a subscriber per browser. This should improve the reliability of CTFd at higher load and make it easier to deploy the Notifications system
8+
9+
**Admin Panel**
10+
11+
- Add a comments functionality for admins to discuss challenges, users, teams, pages
12+
- Adds a legal section in Configs where users can add a terms of service and privacy policy
13+
- Add a Custom Fields section in Configs where admins can add/edit custom user/team fields
14+
- Move user graphs into a modal for Admin Panel
15+
16+
**API**
17+
18+
- Add `/api/v1/comments` to manipulate and create comments
19+
20+
**Themes**
21+
22+
- Make scoreboard caching only cache the score table instead of the entire page. This is done by caching the specific template section. Refer to #1586, specifically the changes in `scoreboard.html`.
23+
- Add rel=noopener to external links to prevent tab napping attacks
24+
- Change the registration page to reference links to Terms of Service and Privacy Policy if specified in configuration
25+
26+
**Miscellaneous**
27+
28+
- Make team settings modal larger in the core theme
29+
- Update tests in Github Actions to properly test under MySQL and Postgres
30+
- Make gevent default in serve.py and add a `--disable-gevent` switch in serve.py
31+
- Add `tenacity` library for retrying logic
32+
- Add `pytest-sugar` for slightly prettier pytest output
33+
- Add a `listen()` method to `CTFd.utils.events.EventManager` and `CTFd.utils.events.RedisEventManager`.
34+
- This method should implement subscription for a CTFd worker to whatever underlying notification system there is. This should be implemented with gevent or a background thread.
35+
- The `subscribe()` method (which used to implement the functionality of the new `listen()` function) now only handles passing notifications from CTFd to the browser. This should also be implemented with gevent or a background thread.
36+
137
# 3.0.2 / 2020-08-23
238

339
**Admin Panel**

CTFd/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from CTFd.utils.sessions import CachingSessionInterface
2727
from CTFd.utils.updates import update_check
2828

29-
__version__ = "3.0.2"
29+
__version__ = "3.1.0"
3030
__channel__ = "oss"
3131

3232

CTFd/api/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from CTFd.api.v1.awards import awards_namespace
55
from CTFd.api.v1.challenges import challenges_namespace
6+
from CTFd.api.v1.comments import comments_namespace
67
from CTFd.api.v1.config import configs_namespace
78
from CTFd.api.v1.files import files_namespace
89
from CTFd.api.v1.flags import flags_namespace
@@ -48,3 +49,4 @@
4849
CTFd_API_v1.add_namespace(pages_namespace, "/pages")
4950
CTFd_API_v1.add_namespace(unlocks_namespace, "/unlocks")
5051
CTFd_API_v1.add_namespace(tokens_namespace, "/tokens")
52+
CTFd_API_v1.add_namespace(comments_namespace, "/comments")

CTFd/api/v1/comments.py

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
from typing import List
2+
3+
from flask import request, session
4+
from flask_restx import Namespace, Resource
5+
6+
from CTFd.api.v1.helpers.request import validate_args
7+
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
8+
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
9+
from CTFd.constants import RawEnum
10+
from CTFd.models import (
11+
ChallengeComments,
12+
Comments,
13+
PageComments,
14+
TeamComments,
15+
UserComments,
16+
db,
17+
)
18+
from CTFd.schemas.comments import CommentSchema
19+
from CTFd.utils.decorators import admins_only
20+
from CTFd.utils.helpers.models import build_model_filters
21+
22+
comments_namespace = Namespace("comments", description="Endpoint to retrieve Comments")
23+
24+
25+
CommentModel = sqlalchemy_to_pydantic(Comments)
26+
27+
28+
class CommentDetailedSuccessResponse(APIDetailedSuccessResponse):
29+
data: CommentModel
30+
31+
32+
class CommentListSuccessResponse(APIListSuccessResponse):
33+
data: List[CommentModel]
34+
35+
36+
comments_namespace.schema_model(
37+
"CommentDetailedSuccessResponse", CommentDetailedSuccessResponse.apidoc()
38+
)
39+
40+
comments_namespace.schema_model(
41+
"CommentListSuccessResponse", CommentListSuccessResponse.apidoc()
42+
)
43+
44+
45+
def get_comment_model(data):
46+
model = Comments
47+
if "challenge_id" in data:
48+
model = ChallengeComments
49+
elif "user_id" in data:
50+
model = UserComments
51+
elif "team_id" in data:
52+
model = TeamComments
53+
elif "page_id" in data:
54+
model = PageComments
55+
else:
56+
model = Comments
57+
return model
58+
59+
60+
@comments_namespace.route("")
61+
class CommentList(Resource):
62+
@admins_only
63+
@comments_namespace.doc(
64+
description="Endpoint to list Comment objects in bulk",
65+
responses={
66+
200: ("Success", "CommentListSuccessResponse"),
67+
400: (
68+
"An error occured processing the provided or stored data",
69+
"APISimpleErrorResponse",
70+
),
71+
},
72+
)
73+
@validate_args(
74+
{
75+
"challenge_id": (int, None),
76+
"user_id": (int, None),
77+
"team_id": (int, None),
78+
"page_id": (int, None),
79+
"q": (str, None),
80+
"field": (RawEnum("CommentFields", {"content": "content"}), None),
81+
},
82+
location="query",
83+
)
84+
def get(self, query_args):
85+
q = query_args.pop("q", None)
86+
field = str(query_args.pop("field", None))
87+
CommentModel = get_comment_model(data=query_args)
88+
filters = build_model_filters(model=CommentModel, query=q, field=field)
89+
90+
comments = (
91+
CommentModel.query.filter_by(**query_args)
92+
.filter(*filters)
93+
.order_by(CommentModel.id.desc())
94+
.paginate(max_per_page=100)
95+
)
96+
schema = CommentSchema(many=True)
97+
response = schema.dump(comments.items)
98+
99+
if response.errors:
100+
return {"success": False, "errors": response.errors}, 400
101+
102+
return {
103+
"meta": {
104+
"pagination": {
105+
"page": comments.page,
106+
"next": comments.next_num,
107+
"prev": comments.prev_num,
108+
"pages": comments.pages,
109+
"per_page": comments.per_page,
110+
"total": comments.total,
111+
}
112+
},
113+
"success": True,
114+
"data": response.data,
115+
}
116+
117+
@admins_only
118+
@comments_namespace.doc(
119+
description="Endpoint to create a Comment object",
120+
responses={
121+
200: ("Success", "CommentDetailedSuccessResponse"),
122+
400: (
123+
"An error occured processing the provided or stored data",
124+
"APISimpleErrorResponse",
125+
),
126+
},
127+
)
128+
def post(self):
129+
req = request.get_json()
130+
# Always force author IDs to be the actual user
131+
req["author_id"] = session["id"]
132+
CommentModel = get_comment_model(data=req)
133+
134+
m = CommentModel(**req)
135+
db.session.add(m)
136+
db.session.commit()
137+
138+
schema = CommentSchema()
139+
140+
response = schema.dump(m)
141+
db.session.close()
142+
143+
return {"success": True, "data": response.data}
144+
145+
146+
@comments_namespace.route("/<comment_id>")
147+
class Comment(Resource):
148+
@admins_only
149+
@comments_namespace.doc(
150+
description="Endpoint to delete a specific Comment object",
151+
responses={200: ("Success", "APISimpleSuccessResponse")},
152+
)
153+
def delete(self, comment_id):
154+
comment = Comments.query.filter_by(id=comment_id).first_or_404()
155+
db.session.delete(comment)
156+
db.session.commit()
157+
db.session.close()
158+
159+
return {"success": True}

0 commit comments

Comments
 (0)