Skip to content

Commit dd05f57

Browse files
authoredJun 26, 2021
Limit total number of teams (CTFd#1867)
* Adds support for a total teams limit
1 parent df27d0e commit dd05f57

File tree

9 files changed

+110
-1
lines changed

9 files changed

+110
-1
lines changed
 

‎.dockerignore

+2
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ CTFd/uploads/**/*
1515
**/node_modules
1616
**/*.pyc
1717
**/__pycache__
18+
.venv*
19+
venv*

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ __pycache__/
88
# Distribution / packaging
99
.Python
1010
env/
11+
venv*
12+
.venv*
1113
build/
1214
develop-eggs/
1315
dist/

‎.prettierignore

+3
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ CTFd/themes/**/vendor/
88
*.svg
99
*.mp3
1010
*.webm
11+
.pytest_cache
12+
venv*
13+
.venv*

‎CTFd/auth.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import base64
22

33
import requests
4-
from flask import Blueprint
4+
from flask import Blueprint, abort
55
from flask import current_app as app
66
from flask import redirect, render_template, request, session, url_for
77
from itsdangerous.exc import BadSignature, BadTimeSignature, SignatureExpired
@@ -510,6 +510,16 @@ def oauth_redirect():
510510

511511
team = Teams.query.filter_by(oauth_id=team_id).first()
512512
if team is None:
513+
num_teams_limit = int(get_config("num_teams", default=0))
514+
num_teams = Teams.query.filter_by(
515+
banned=False, hidden=False
516+
).count()
517+
if num_teams_limit and num_teams >= num_teams_limit:
518+
abort(
519+
403,
520+
description=f"Reached the maximum number of teams ({num_teams_limit}). Please join an existing team.",
521+
)
522+
513523
team = Teams(name=team_name, oauth_id=team_id, captain_id=user.id)
514524
db.session.add(team)
515525
db.session.commit()

‎CTFd/forms/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class AccountSettingsForm(BaseForm):
4343
widget=NumberInput(min=0),
4444
description="Amount of users per team (Teams mode only)",
4545
)
46+
num_teams = IntegerField(
47+
widget=NumberInput(min=0), description="Max number of teams (Teams mode only)",
48+
)
4649
verify_emails = SelectField(
4750
"Verify Emails",
4851
description="Control whether users must confirm their email addresses before playing",

‎CTFd/teams.py

+8
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ def new():
197197
description="Team creation is currently disabled. Please join an existing team.",
198198
)
199199

200+
num_teams_limit = int(get_config("num_teams", default=0))
201+
num_teams = Teams.query.filter_by(banned=False, hidden=False).count()
202+
if num_teams_limit and num_teams >= num_teams_limit:
203+
abort(
204+
403,
205+
description=f"Reached the maximum number of teams ({num_teams_limit}). Please join an existing team.",
206+
)
207+
200208
user = get_current_user_attrs()
201209
if user.team_id:
202210
errors.append("You are already in a team. You cannot join another.")

‎CTFd/themes/admin/templates/configs/accounts.html

+8
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@
3838
</small>
3939
</div>
4040

41+
<div class="form-group">
42+
{{ form.num_teams.label }}
43+
{{ form.num_teams(class="form-control", value=num_teams) }}
44+
<small class="form-text text-muted">
45+
{{ form.num_teams.description }}
46+
</small>
47+
</div>
48+
4149
<div class="form-group">
4250
{{ form.team_disbanding.label }}
4351
{{ form.team_disbanding(class="form-control", value=team_disbanding) }}

‎tests/oauth/test_teams.py

+39
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,42 @@ def test_team_size_limit():
3030
login_with_mlc(app, team_name="team_name", team_oauth_id=1234)
3131
assert len(Teams.query.filter_by(id=team_id).first().members) == 2
3232
destroy_ctfd(app)
33+
34+
35+
def test_num_teams_limit():
36+
"""Only num_teams teams can be created even via MLC"""
37+
app = create_ctfd(user_mode="teams")
38+
app.config.update(
39+
{
40+
"OAUTH_CLIENT_ID": "ctfd_testing_client_id",
41+
"OAUTH_CLIENT_SECRET": "ctfd_testing_client_secret",
42+
"OAUTH_AUTHORIZATION_ENDPOINT": "http://auth.localhost/oauth/authorize",
43+
"OAUTH_TOKEN_ENDPOINT": "http://auth.localhost/oauth/token",
44+
"OAUTH_API_ENDPOINT": "http://api.localhost/user",
45+
}
46+
)
47+
with app.app_context():
48+
set_config("num_teams", 1)
49+
gen_team(app.db, member_count=1, oauth_id=1234)
50+
login_with_mlc(
51+
app,
52+
name="foobar",
53+
email="foobar@a.com",
54+
oauth_id=111,
55+
team_name="foobar",
56+
team_oauth_id=1111,
57+
raise_for_error=False,
58+
)
59+
assert Teams.query.count() == 1
60+
61+
set_config("num_teams", 2)
62+
login_with_mlc(
63+
app,
64+
name="foobarbaz",
65+
email="foobarbaz@a.com",
66+
oauth_id=222,
67+
team_name="foobarbaz",
68+
team_oauth_id=2222,
69+
)
70+
assert Teams.query.count() == 2
71+
destroy_ctfd(app)

‎tests/teams/test_teams.py

+34
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,40 @@ def test_team_size_limit():
181181
destroy_ctfd(app)
182182

183183

184+
def test_num_teams_limit():
185+
"""Only num_teams teams can be created"""
186+
app = create_ctfd(user_mode="teams")
187+
with app.app_context():
188+
set_config("num_teams", 1)
189+
190+
# Create a team
191+
gen_team(app.db, member_count=1)
192+
193+
register_user(app)
194+
with login_as_user(app) as client:
195+
r = client.get("/teams/new")
196+
assert r.status_code == 403
197+
198+
# team should be blocked from creation
199+
with client.session_transaction() as sess:
200+
data = {
201+
"name": "team1",
202+
"password": "password",
203+
"nonce": sess.get("nonce"),
204+
}
205+
r = client.post("/teams/new", data=data)
206+
resp = r.get_data(as_text=True)
207+
assert Teams.query.count() == 1
208+
assert "Reached the maximum number of teams" in resp
209+
210+
# Can the team be created after the num has been bumped
211+
set_config("num_teams", 2)
212+
r = client.post("/teams/new", data=data)
213+
resp = r.get_data(as_text=True)
214+
assert Teams.query.count() == 2
215+
destroy_ctfd(app)
216+
217+
184218
def test_team_creation_disable():
185219
app = create_ctfd(user_mode="teams")
186220
with app.app_context():

0 commit comments

Comments
 (0)