internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: CTFd/CTFd

Issue:
Consider passing challenge class over into the view.html context
This makes it a little easier for the challenge view to access Python code. Not sure if this looks best as challenge.class.read() or challenge_class.read(). 


Discussion:

Resolving patch:
diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py
--- a/CTFd/models/__init__.py
+++ b/CTFd/models/__init__.py
@@ -122,6 +122,12 @@ def html(self):
 
         return markup(build_markdown(self.description))
 
+    @property
+    def plugin_class(self):
+        from CTFd.plugins.challenges import get_chal_class
+
+        return get_chal_class(self.type)
+
     def __init__(self, *args, **kwargs):
         super(Challenges, self).__init__(**kwargs)
 


Test patch:
diff --git a/tests/test_plugin_utils.py b/tests/test_plugin_utils.py
--- a/tests/test_plugin_utils.py
+++ b/tests/test_plugin_utils.py
@@ -14,7 +14,13 @@
     register_plugin_script,
     register_user_page_menu_bar,
 )
-from tests.helpers import create_ctfd, destroy_ctfd, login_as_user, setup_ctfd
+from tests.helpers import (
+    create_ctfd,
+    destroy_ctfd,
+    gen_challenge,
+    login_as_user,
+    setup_ctfd,
+)
 
 
 def test_register_plugin_asset():
@@ -204,3 +210,17 @@ def bypass_csrf_protection_test_route():
             assert r.status_code == 200
             assert output == "Success"
     destroy_ctfd(app)
+
+
+def test_challenges_model_access_plugin_class():
+    """
+    Test that the Challenges model can access its plugin class
+    """
+    app = create_ctfd()
+
+    with app.app_context():
+        from CTFd.plugins.challenges import get_chal_class
+
+        chal = gen_challenge(app.db)
+        assert chal.plugin_class == get_chal_class("standard")
+    destroy_ctfd(app)
Repository: CTFd/CTFd

Issue:
Error 500 when visiting /admin/users/1 - AttributeError: 'NoneType' object has no attribute 'get_score'
**Environment**:

- CTFd Version/Commit: HEAD
- Operating System: Docker image based off official Dockerfile
- Web Browser and Version: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Safari/605.1.15

**What happened?**
500 Internal server error

**What did you expect to happen?**
Show the admin user details when in team mode

**How to reproduce your issue**
* visited the `/admin/users/1`
* this seems due to the fact that, when in team mode, the admin user does not belong to any team and, for some reason, this one returns `None`

```python
    @hybrid_property
    def account(self):
        from CTFd.utils import get_config

        user_mode = get_config("user_mode")
        if user_mode == "teams":
            return self.team
        elif user_mode == "users":
            return self
```

**Any associated stack traces or error logs**
```
ERROR [CTFd] Exception on /admin/users/1 [GET]
--
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.7/site-packages/flask_restx/api.py", line 639, in error_router
return original_handler(e)
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/opt/CTFd/CTFd/utils/decorators/__init__.py", line 133, in admins_only_wrapper
return f(*args, **kwargs)
File "/opt/CTFd/CTFd/admin/users.py", line 91, in users_detail
score = user.account.get_score(admin=True)
AttributeError: 'NoneType' object has no attribute 'get_score'
```



Discussion:

Resolving patch:
diff --git a/CTFd/admin/users.py b/CTFd/admin/users.py
--- a/CTFd/admin/users.py
+++ b/CTFd/admin/users.py
@@ -87,9 +87,14 @@ def users_detail(user_id):
     # Get Awards
     awards = user.get_awards(admin=True)
 
-    # Get user properties
-    score = user.account.get_score(admin=True)
-    place = user.account.get_place(admin=True)
+    # Check if the user has an account (team or user)
+    # so that we don't throw an error if they dont
+    if user.account:
+        score = user.account.get_score(admin=True)
+        place = user.account.get_place(admin=True)
+    else:
+        score = None
+        place = None
 
     return render_template(
         "admin/users/user.html",


Test patch:
Repository: CTFd/CTFd

Issue:
Make the distinction between user and team mode clearer during setup
We should probably have some kind of big checkbox div that explains what it means to be in user/teams mode. It would reduce the likelihood of needing to switch. 


Discussion:

Resolving patch:
diff --git a/CTFd/views.py b/CTFd/views.py
--- a/CTFd/views.py
+++ b/CTFd/views.py
@@ -4,6 +4,7 @@
 from flask import current_app as app
 from flask import redirect, render_template, request, send_file, session, url_for
 from flask.helpers import safe_join
+from jinja2.exceptions import TemplateNotFound
 from sqlalchemy.exc import IntegrityError
 
 from CTFd.cache import cache
@@ -256,7 +257,12 @@ def setup():
                 cache.clear()
 
             return redirect(url_for("views.static_html"))
-        return render_template("setup.html", state=serialize(generate_nonce()))
+        try:
+            return render_template("setup.html", state=serialize(generate_nonce()))
+        except TemplateNotFound:
+            # Set theme to default and try again
+            set_config("ctf_theme", DEFAULT_THEME)
+            return render_template("setup.html", state=serialize(generate_nonce()))
     return redirect(url_for("views.static_html"))
 
 


Test patch:
Repository: CTFd/CTFd

Issue:
Add CSV examples for CSV Import
Add CSV examples for CSV Import


Discussion:

Resolving patch:
diff --git a/CTFd/utils/csv/__init__.py b/CTFd/utils/csv/__init__.py
--- a/CTFd/utils/csv/__init__.py
+++ b/CTFd/utils/csv/__init__.py
@@ -252,8 +252,8 @@ def load_challenges_csv(dict_reader):
         hints = line.pop("hints", None)
         challenge_type = line.pop("type", "standard")
 
-        # Load in custome type_data
-        type_data = json.loads(line.pop("type_data", "{}"))
+        # Load in custom type_data
+        type_data = json.loads(line.pop("type_data", "{}") or "{}")
         line.update(type_data)
 
         ChallengeClass = get_chal_class(challenge_type)


Test patch:
Repository: CTFd/CTFd

Issue:
Consider enabling THEME_FALLBACK by default
In order to improve theme development and usage, I think we should enable THEME_FALLBACK or perhaps create a way for themes to specify their parent theme. 

Wordpress has a sort of similar idea: https://developer.wordpress.org/themes/advanced-topics/child-themes/

I am not sure what downsides there could be with this but I think the potential gain of much easier themes is pretty compelling. 


Discussion:

Resolving patch:
diff --git a/CTFd/config.py b/CTFd/config.py
--- a/CTFd/config.py
+++ b/CTFd/config.py
@@ -178,7 +178,7 @@ class ServerConfig(object):
 
     TEMPLATES_AUTO_RELOAD: bool = process_boolean_str(empty_str_cast(config_ini["optional"]["TEMPLATES_AUTO_RELOAD"], default=True))
 
-    THEME_FALLBACK: bool = process_boolean_str(empty_str_cast(config_ini["optional"]["THEME_FALLBACK"], default=False))
+    THEME_FALLBACK: bool = process_boolean_str(empty_str_cast(config_ini["optional"]["THEME_FALLBACK"], default=True))
 
     SQLALCHEMY_TRACK_MODIFICATIONS: bool = process_boolean_str(empty_str_cast(config_ini["optional"]["SQLALCHEMY_TRACK_MODIFICATIONS"], default=False))
 


Test patch:
diff --git a/tests/test_themes.py b/tests/test_themes.py
--- a/tests/test_themes.py
+++ b/tests/test_themes.py
@@ -152,7 +152,11 @@ def test_that_request_path_hijacking_works_properly():
 
 def test_theme_fallback_config():
     """Test that the `THEME_FALLBACK` config properly falls themes back to the core theme"""
-    app = create_ctfd()
+
+    class ThemeFallbackConfig(TestingConfig):
+        THEME_FALLBACK = False
+
+    app = create_ctfd(config=ThemeFallbackConfig)
     # Make an empty theme
     try:
         os.mkdir(os.path.join(app.root_path, "themes", "foo_fallback"))
@@ -161,6 +165,7 @@ def test_theme_fallback_config():
 
     # Without theme fallback, missing themes should disappear
     with app.app_context():
+        app.config["THEME_FALLBACK"] = False
         set_config("ctf_theme", "foo_fallback")
         assert app.config["THEME_FALLBACK"] == False
         with app.test_client() as client:
@@ -174,10 +179,7 @@ def test_theme_fallback_config():
                 pass
     destroy_ctfd(app)
 
-    class ThemeFallbackConfig(TestingConfig):
-        THEME_FALLBACK = True
-
-    app = create_ctfd(config=ThemeFallbackConfig)
+    app = create_ctfd()
     with app.app_context():
         set_config("ctf_theme", "foo_fallback")
         assert app.config["THEME_FALLBACK"] == True
Repository: CTFd/CTFd

Issue:
Make challenge submission attempt rate limiting configurable
Sometimes people end up hitting this rate limit too soon so we should make it configurable in the config panel. 

https://github.com/CTFd/CTFd/blob/7fc05bd4e3bf18606871a0ba6ad11f70e2be77e0/CTFd/api/v1/challenges.py#L624-L648

Instead of a hard limit of 10 we should have an option to set the number and perhaps disable it.


Discussion:

Resolving patch:
diff --git a/CTFd/api/v1/challenges.py b/CTFd/api/v1/challenges.py
--- a/CTFd/api/v1/challenges.py
+++ b/CTFd/api/v1/challenges.py
@@ -622,7 +622,8 @@ def post(self):
 
         # Anti-bruteforce / submitting Flags too quickly
         kpm = current_user.get_wrong_submissions_per_minute(user.account_id)
-        if kpm > 10:
+        kpm_limit = int(get_config("incorrect_submissions_per_min", default=10))
+        if kpm > kpm_limit:
             if ctftime():
                 chal_class.fail(
                     user=user, team=team, challenge=challenge, request=request
diff --git a/CTFd/forms/config.py b/CTFd/forms/config.py
--- a/CTFd/forms/config.py
+++ b/CTFd/forms/config.py
@@ -69,6 +69,11 @@ class AccountSettingsForm(BaseForm):
         choices=[("true", "Enabled"), ("false", "Disabled")],
         default="true",
     )
+    incorrect_submissions_per_min = IntegerField(
+        "Incorrect Submissions per Minute",
+        widget=NumberInput(min=1),
+        description="Amount of submissions allowed per minute for flag bruteforce protection (default: 10)",
+    )
 
     submit = SubmitField("Update")
 


Test patch:
Repository: CTFd/CTFd

Issue:
SubmissionSchema needs more nested fields
I'm having trouble accessing a user's name from a SubmissionSchema dump. This is probably because we need more Nested Fields on the Schema in addition to just the nested challenge schema. 


Discussion:

Resolving patch:
diff --git a/CTFd/schemas/submissions.py b/CTFd/schemas/submissions.py
--- a/CTFd/schemas/submissions.py
+++ b/CTFd/schemas/submissions.py
@@ -2,11 +2,15 @@
 
 from CTFd.models import Submissions, ma
 from CTFd.schemas.challenges import ChallengeSchema
+from CTFd.schemas.teams import TeamSchema
+from CTFd.schemas.users import UserSchema
 from CTFd.utils import string_types
 
 
 class SubmissionSchema(ma.ModelSchema):
-    challenge = fields.Nested(ChallengeSchema, only=["name", "category", "value"])
+    challenge = fields.Nested(ChallengeSchema, only=["id", "name", "category", "value"])
+    user = fields.Nested(UserSchema, only=["id", "name"])
+    team = fields.Nested(TeamSchema, only=["id", "name"])
 
     class Meta:
         model = Submissions


Test patch:
Repository: CTFd/CTFd

Issue:
Team Creation before CTF starts
We should prompt users to create their teams before a CTF officially starts so they can just start playing when the time does start. 


Discussion:
The issue here is actually that nothing prompts for team creation after the registration flow. I don't think there's a problem with this but I think there could be an improvement here perhaps. 

Resolving patch:
diff --git a/CTFd/views.py b/CTFd/views.py
--- a/CTFd/views.py
+++ b/CTFd/views.py
@@ -29,7 +29,7 @@
 from CTFd.utils import config, get_config, set_config
 from CTFd.utils import user as current_user
 from CTFd.utils import validators
-from CTFd.utils.config import is_setup
+from CTFd.utils.config import is_setup, is_teams_mode
 from CTFd.utils.config.pages import build_markdown, get_page
 from CTFd.utils.config.visibility import challenges_visible
 from CTFd.utils.dates import ctf_ended, ctftime, view_after_ctf
@@ -56,7 +56,7 @@
     unserialize,
 )
 from CTFd.utils.uploads import get_uploader, upload_file
-from CTFd.utils.user import authed, get_current_user, is_admin
+from CTFd.utils.user import authed, get_current_team, get_current_user, is_admin
 
 views = Blueprint("views", __name__)
 
@@ -313,6 +313,14 @@ def settings():
     affiliation = user.affiliation
     country = user.country
 
+    if is_teams_mode() and get_current_team() is None:
+        team_url = url_for("teams.private")
+        infos.append(
+            markup(
+                f'In order to participate you must either <a href="{team_url}">join or create a team</a>.'
+            )
+        )
+
     tokens = UserTokens.query.filter_by(user_id=user.id).all()
 
     prevent_name_change = get_config("prevent_name_change")


Test patch:
Repository: CTFd/CTFd

Issue:
Size limits on logo, favicon, image uploads
Sometimes people upload really big images for small things like the logo or the favicon. We should impose some kind of size limit or automatic resizing. 


Discussion:
hey, i would like to help out with this issue. is there a particular file which I should work on? 

i have worked on CTFd multiple times and i am quite a bit familiar with it. pointing me in the right direction would speed things up :)
I think this might be simplest as a client side limitation. So I would look at the setup.js and the configs.js files. We could do this validation in the server side but I feel like the code would look a little weird since there generally isn't file size limits anywhere else. 

Resolving patch:
diff --git a/CTFd/forms/setup.py b/CTFd/forms/setup.py
--- a/CTFd/forms/setup.py
+++ b/CTFd/forms/setup.py
@@ -49,16 +49,18 @@ class SetupForm(BaseForm):
 
     ctf_logo = FileField(
         "Logo",
-        description="Logo to use for the website instead of a CTF name. Used as the home page button.",
+        description="Logo to use for the website instead of a CTF name. Used as the home page button. Optional.",
+    )
+    ctf_banner = FileField(
+        "Banner", description="Banner to use for the homepage. Optional."
     )
-    ctf_banner = FileField("Banner", description="Banner to use for the homepage.")
     ctf_small_icon = FileField(
         "Small Icon",
-        description="favicon used in user's browsers. Only PNGs accepted. Must be 32x32px.",
+        description="favicon used in user's browsers. Only PNGs accepted. Must be 32x32px. Optional.",
     )
     ctf_theme = SelectField(
         "Theme",
-        description="CTFd Theme to use",
+        description="CTFd Theme to use. Can be changed later.",
         choices=list(zip(get_themes(), get_themes())),
         default=DEFAULT_THEME,
         validators=[InputRequired()],


Test patch:
Repository: CTFd/CTFd

Issue:
Cascading Hints
Hints should have a sense of unlocking where one hint cannot be used until a previous one or others are used.


Discussion:

Resolving patch:
diff --git a/CTFd/api/v1/hints.py b/CTFd/api/v1/hints.py
--- a/CTFd/api/v1/hints.py
+++ b/CTFd/api/v1/hints.py
@@ -120,6 +120,33 @@ def get(self, hint_id):
         user = get_current_user()
         hint = Hints.query.filter_by(id=hint_id).first_or_404()
 
+        if hint.requirements:
+            requirements = hint.requirements.get("prerequisites", [])
+
+            # Get the IDs of all hints that the user has unlocked
+            all_unlocks = HintUnlocks.query.filter_by(account_id=user.account_id).all()
+            unlock_ids = {unlock.id for unlock in all_unlocks}
+
+            # Filter out hint IDs that don't exist
+            all_hint_ids = {h.id for h in Hints.query.with_entities(Hints.id).all()}
+            prereqs = set(requirements).intersection(all_hint_ids)
+
+            # If the user has the necessary unlocks or is admin we should allow them to view
+            if unlock_ids >= prereqs or is_admin():
+                pass
+            else:
+                return (
+                    {
+                        "success": False,
+                        "errors": {
+                            "requirements": [
+                                "You must unlock other hints before accessing this hint"
+                            ]
+                        },
+                    },
+                    403,
+                )
+
         view = "unlocked"
         if hint.cost:
             view = "locked"


Test patch:
Repository: CTFd/CTFd

Issue:
Next challenge after solve
This could be pulled off with a next column that points to the next ID to be viewed. It would need API changes, Admin Panel changes and a migration. 


Discussion:
Is this feature broken? I thought this was already possible by adding a challenge as a requirement so it could not been until the requirement was complete?
This is for a different, new feature. Automatically showing the "next" challenge after solving one challenge. 
This is very difficult to do because of how limiting `challenges.js` is. I think it would be better to push this off to 4.0 instead of hacking more things onto 3.x. 
Aiming for this to come out in CTFd 3.5

Resolving patch:
diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py
--- a/CTFd/models/__init__.py
+++ b/CTFd/models/__init__.py
@@ -86,6 +86,7 @@ class Challenges(db.Model):
     name = db.Column(db.String(80))
     description = db.Column(db.Text)
     connection_info = db.Column(db.Text)
+    next_id = db.Column(db.Integer, db.ForeignKey("challenges.id", ondelete="SET NULL"))
     max_attempts = db.Column(db.Integer, default=0)
     value = db.Column(db.Integer)
     category = db.Column(db.String(80))
diff --git a/CTFd/plugins/challenges/__init__.py b/CTFd/plugins/challenges/__init__.py
--- a/CTFd/plugins/challenges/__init__.py
+++ b/CTFd/plugins/challenges/__init__.py
@@ -54,6 +54,7 @@ def read(cls, challenge):
             "value": challenge.value,
             "description": challenge.description,
             "connection_info": challenge.connection_info,
+            "next_id": challenge.next_id,
             "category": challenge.category,
             "state": challenge.state,
             "max_attempts": challenge.max_attempts,
diff --git a/migrations/versions/4d3c1b59d011_add_next_id_to_challenges_table.py b/migrations/versions/4d3c1b59d011_add_next_id_to_challenges_table.py
new file mode 100644
--- /dev/null
+++ b/migrations/versions/4d3c1b59d011_add_next_id_to_challenges_table.py
@@ -0,0 +1,32 @@
+"""Add next_id to Challenges table
+
+Revision ID: 4d3c1b59d011
+Revises: 6012fe8de495
+Create Date: 2022-04-07 03:53:27.554190
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "4d3c1b59d011"
+down_revision = "6012fe8de495"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column("challenges", sa.Column("next_id", sa.Integer(), nullable=True))
+    op.create_foreign_key(
+        None, "challenges", "challenges", ["next_id"], ["id"], ondelete="SET NULL"
+    )
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_constraint(None, "challenges", type_="foreignkey")
+    op.drop_column("challenges", "next_id")
+    # ### end Alembic commands ###


Test patch:
Repository: CTFd/CTFd

Issue:
Page preview doesn't consider format
Page preview needs to take into account format when previewing


Discussion:

Resolving patch:
diff --git a/CTFd/admin/pages.py b/CTFd/admin/pages.py
--- a/CTFd/admin/pages.py
+++ b/CTFd/admin/pages.py
@@ -25,7 +25,10 @@ def pages_new():
 def pages_preview():
     # We only care about content.
     # Loading other attributes improperly will cause Marshmallow to incorrectly return a dict
-    data = {"content": request.form.get("content")}
+    data = {
+        "content": request.form.get("content"),
+        "format": request.form.get("format"),
+    }
     schema = PageSchema()
     page = schema.load(data)
     return render_template("page.html", content=page.data.html)


Test patch:
diff --git a/tests/admin/test_pages.py b/tests/admin/test_pages.py
--- a/tests/admin/test_pages.py
+++ b/tests/admin/test_pages.py
@@ -13,6 +13,32 @@ def test_previewing_pages_works():
                 "route": "route",
                 "content": "content_testing",
                 "nonce": sess.get("nonce"),
+                "draft": True,
+                "hidden": True,
+                "auth_required": True,
+            }
+
+        r = client.post("/admin/pages/preview", data=data)
+        assert r.status_code == 200
+        resp = r.get_data(as_text=True)
+        assert "content_testing" in resp
+
+    destroy_ctfd(app)
+
+
+def test_previewing_page_with_format_works():
+    """Test that pages can be previewed properly"""
+    app = create_ctfd()
+    with app.app_context():
+        client = login_as_user(app, name="admin", password="password")
+
+        with client.session_transaction() as sess:
+            data = {
+                "title": "title",
+                "route": "route",
+                "content": "# content_testing",
+                "format": "markdown",
+                "nonce": sess.get("nonce"),
                 "draft": "y",
                 "hidden": "y",
                 "auth_required": "y",
@@ -21,6 +47,23 @@ def test_previewing_pages_works():
         r = client.post("/admin/pages/preview", data=data)
         assert r.status_code == 200
         resp = r.get_data(as_text=True)
-        assert "content_testing" in resp
+        assert "<h1>content_testing</h1>" in resp
+
+        with client.session_transaction() as sess:
+            data = {
+                "title": "title",
+                "route": "route",
+                "content": "<test>content_testing</test>",
+                "format": "html",
+                "nonce": sess.get("nonce"),
+                "draft": "y",
+                "hidden": "y",
+                "auth_required": "y",
+            }
+
+        r = client.post("/admin/pages/preview", data=data)
+        assert r.status_code == 200
+        resp = r.get_data(as_text=True)
+        assert "<test>content_testing</test>" in resp
 
     destroy_ctfd(app)