forked from picoCTF/picoCTF
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.py
337 lines (288 loc) · 9.6 KB
/
config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
"""Stores and retrieves runtime settings from the database."""
import datetime
from copy import deepcopy
from functools import wraps
import api
from api import PicoException
"""
Default Settings
These are the default settings that will be loaded
into the database if no settings are already loaded.
"""
default_settings = {
"enable_feedback": True,
# TIME WINDOW
"start_time": datetime.datetime.utcnow(),
"end_time": datetime.datetime.utcnow(),
# COMPETITION INFORMATION
"competition_name": "CTF Placeholder",
"competition_url": "http://192.168.2.2",
"admin_email": "[email protected]", # Contact given to parents
# EMAIL WHITELIST
"email_filter": [],
# TEAMS
"max_team_size": 5,
# BATCH REGISTRATION
"max_batch_registrations": 250, # Maximum batch registrations / teacher
# ACHIEVEMENTS
"achievements": {
"enable_achievements": True,
"processor_base_path": "./achievements",
},
"username_blacklist": [
"adm",
"admin",
"audio",
"backup",
"bin",
"cdrom",
"competitors",
"crontab",
"daemon",
"dialout",
"dip",
"disk",
"dnsmasq",
"fax",
"floppy",
"games",
"gnats",
"hacksports",
"input",
"irc",
"kmem",
"list",
"lp",
"lxd",
"mail",
"man",
"messagebus",
"mlocate",
"netdev",
"news",
"nobody",
"nogroup",
"operator",
"plugdev",
"pollinate",
"proxy",
"root",
"sasl",
"shadow",
"shellinabox",
"src",
"ssh",
"sshd",
"staff",
"sudo",
"sync",
"sys",
"syslog",
"tape",
"tty",
"ubuntu",
"users",
"utmp",
"uucp",
"uuidd",
"vagrant",
"vboxadd",
"vboxsf",
"video",
"voice",
"wetty",
],
# EMAIL (SMTP)
"email": {
"enable_email": False,
"email_verification": False,
"parent_verification_email": True,
"smtp_url": "",
"smtp_port": 587,
"email_username": "",
"email_password": "",
"from_addr": "",
"from_name": "",
"max_verification_emails": 3,
"smtp_security": "TLS",
"reset_password_body": """
We recently received a request to reset the password for the following {competition_name} account:
\t{username}
Our records show that this is the email address used to register the above account.
If you did not request to reset the password for the above account then you need not take any further steps.
If you did request the password reset please follow the link below to set your new password.
{competition_url}/reset#{token_value}
Best of luck!
The {competition_name} Team""", # noqa (79char)
############ Readablity spacing ############
"verification_body": """
Welcome to {competition_name}!
Your account has been registered with username {user_name}. You will need to
visit the verification link below and then login to finalize your account's
creation.
If you believe this to be a mistake, and you haven't recently created an account
for {competition_name} then you can safely ignore this email.
Verification link: {verification_link}
Good luck and have fun!
The {competition_name} Team""", # noqa (79char)
############ Readablity spacing ############
"verification_parent_body": """
Welcome to {competition_name}!
Your child or your child's teacher registered to participate in picoCTF, an
online cyber security capture-the-flag competition created for educational
purposes. Please see picoCTF.com for details about the competition. picoCTF
is developed by Carnegie Mellon University faculty, staff and students.
A user account has just been created on our platform and your email address was
submitted by the user as the email address of the user's parent.
Thank you for authorizing the participation of your child age 13-17 in
{competition_name} and providing your email address as part of the account registration process
for your child. As a reminder, the Terms of Service, Privacy Statement and
Competition Rules for {competition_name} can be found at {competition_url}.
If you received this email in error because you did not authorize your child's
registration for {competition_name}, you are not the child's parent or legal guardian,
or your child is under age 13, please email us immediately at {admin_email}.
The {competition_name} Team""", # noqa (79char)
############ Readablity spacing ############
"invite_body": """
You have been invited by the teacher of classroom {group_name} to compete in {competition_name}.
You will need to follow the registration link below to finish the account creation process.
If you believe this to be a mistake you can safely ignore this email.
Registration link: {registration_link}
Good luck!
The {competition_name} Team""", # noqa (79char)
############ Readablity spacing ############
"deletion_notification_body": """
This is a notification that the following {competition_name} account associated with this
email address has been deleted:
\t{username}
Due to the following reason:
\t{delete_reason}
The {competition_name} Team""", # noqa (79char)
},
# CAPTCHA
"captcha": {
"enable_captcha": False,
"captcha_url": "https://www.google.com/recaptcha/api/siteverify",
"reCAPTCHA_public_key": "",
"reCAPTCHA_private_key": "",
},
# SHELL SERVERS
"shell_servers": {
"enable_sharding": False,
"default_stepping": 5000,
"steps": [7500, 12500, 17500],
"limit_added_range": False,
},
# MINIGAME TOKEN VALUES
"minigame": {
"secret": "foo",
"token_values": {
"a1": 10,
"a2": 20,
"a3": 30,
"b1": 15,
"b2": 30,
"b3": 45,
"c3": 20,
},
},
# RATE LIMITING
"enable_rate_limiting": True,
# GROUP LIMIT
"group_limit": 20,
}
def get_settings():
"""Retrieve settings from the database."""
db = api.db.get_conn()
settings = db.settings.find_one({}, {"_id": 0})
if settings is None:
db.settings.insert(default_settings.copy())
return default_settings
return settings
def merge_new_settings():
"""Add any new default_settings into the database."""
def merge(a, b):
"""Merge new keys from nested dict a into b."""
out = deepcopy(b)
for k, v in a.items():
if k not in out:
out[k] = v
elif isinstance(v, dict):
out[k] = merge(v, out[k])
return out
db_settings = get_settings()
merged = merge(default_settings, db_settings)
db = api.db.get_conn()
db.settings.find_one_and_update({}, {"$set": merged})
def change_settings(changes):
"""
Update settings in the database.
Raises:
PicoException: if an updated key did not previously exist in settings,
or the updated value is of a different type
"""
settings = get_settings()
# @TODO validate incoming settings at the request level
def check_keys(real, changed):
keys = list(changed.keys())
for key in keys:
if key not in real:
raise PicoException(
"Cannot update setting for '{}'".format(key)
+ " (setting not found)",
status_code=400,
)
elif type(real[key]) != type(changed[key]): # noqa:E721
raise PicoException(
"Cannot update setting for '{}'".format(key) + " (incorrect type)",
status_code=400,
)
elif isinstance(real[key], dict):
check_keys(real[key], changed[key])
# change the key so mongo $set works correctly
for key2 in changed[key]:
changed["{}.{}".format(key, key2)] = changed[key][key2]
changed.pop(key)
check_keys(settings, changes)
db = api.db.get_conn()
db.settings.find_one_and_update({}, {"$set": changes})
def check_competition_active():
"""Check whether the competition is currently running."""
settings = get_settings()
return (
settings["start_time"].timestamp()
< datetime.datetime.utcnow().timestamp()
< settings["end_time"].timestamp()
)
def block_before_competition(f):
"""
Wrap routing functions that are blocked prior to the competition.
Admins can bypass.
"""
@wraps(f)
def wrapper(*args, **kwargs):
if api.user.is_logged_in() and api.user.get_user().get("admin", False):
return f(*args, **kwargs)
elif (
datetime.datetime.utcnow().timestamp()
<= get_settings()["start_time"].timestamp()
):
raise PicoException("The competition has not begun yet!", 422)
return f(*args, **kwargs)
return wrapper
def block_after_competition(f):
"""
Wrap routing functions that are blocked after the competition.
Admins can bypass.
"""
@wraps(f)
def wrapper(*args, **kwargs):
if api.user.is_logged_in() and api.user.get_user().get("admin", False):
return f(*args, **kwargs)
elif (
datetime.datetime.utcnow().timestamp()
>= get_settings()["end_time"].timestamp()
):
raise PicoException("The competition has ended!", 422)
return f(*args, **kwargs)
return wrapper