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

Issue:
Submission search
Search submissions akin to how users are searched


Discussion:

Resolving patch:
diff --git a/CTFd/admin/submissions.py b/CTFd/admin/submissions.py
--- a/CTFd/admin/submissions.py
+++ b/CTFd/admin/submissions.py
@@ -1,4 +1,4 @@
-from flask import render_template, request
+from flask import render_template, request, url_for
 
 from CTFd.admin import admin
 from CTFd.models import Challenges, Submissions
@@ -10,16 +10,21 @@
 @admin.route("/admin/submissions/<submission_type>")
 @admins_only
 def submissions_listing(submission_type):
-    filters = {}
+    filters_by = {}
     if submission_type:
-        filters["type"] = submission_type
+        filters_by["type"] = submission_type
+    filters = []
 
-    curr_page = abs(int(request.args.get("page", 1, type=int)))
-    results_per_page = 50
-    page_start = results_per_page * (curr_page - 1)
-    page_end = results_per_page * (curr_page - 1) + results_per_page
-    sub_count = Submissions.query.filter_by(**filters).count()
-    page_count = int(sub_count / results_per_page) + (sub_count % results_per_page > 0)
+    q = request.args.get("q")
+    field = request.args.get("field")
+    page = abs(request.args.get("page", 1, type=int))
+
+    if q:
+        submissions = []
+        if Submissions.__mapper__.has_property(
+            field
+        ):  # The field exists as an exposed column
+            filters.append(getattr(Submissions, field).like("%{}%".format(q)))
 
     Model = get_model()
 
@@ -34,18 +39,27 @@ def submissions_listing(submission_type):
             Challenges.name.label("challenge_name"),
             Model.name.label("team_name"),
         )
-        .filter_by(**filters)
+        .filter_by(**filters_by)
+        .filter(*filters)
         .join(Challenges)
         .join(Model)
         .order_by(Submissions.date.desc())
-        .slice(page_start, page_end)
-        .all()
+        .paginate(page=page, per_page=50)
     )
 
+    args = dict(request.args)
+    args.pop("page", 1)
+
     return render_template(
         "admin/submissions.html",
         submissions=submissions,
-        page_count=page_count,
-        curr_page=curr_page,
+        prev_page=url_for(
+            request.endpoint, type=submission_type, page=submissions.prev_num, **args
+        ),
+        next_page=url_for(
+            request.endpoint, type=submission_type, page=submissions.next_num, **args
+        ),
         type=submission_type,
+        q=q,
+        field=field,
     )


Test patch:
Repository: CTFd/CTFd

Issue:
Prevent importing data that doesn't meet our expected format
People like to manipulate import data despite it being not a great idea. This often leads to problems where the user is trying to import data that doesn't match our expected format. 

We should prevent that automatically. 


Discussion:

Resolving patch:
diff --git a/CTFd/utils/exports/__init__.py b/CTFd/utils/exports/__init__.py
--- a/CTFd/utils/exports/__init__.py
+++ b/CTFd/utils/exports/__init__.py
@@ -96,6 +96,14 @@ def import_ctf(backup, erase=True):
             if info.file_size > max_content_length:
                 raise zipfile.LargeZipFile
 
+    # Get list of directories in zipfile
+    member_dirs = [os.path.split(m)[0] for m in members if "/" in m]
+    if "db" not in member_dirs:
+        raise Exception(
+            'CTFd couldn\'t find the "db" folder in this backup. '
+            "The backup may be malformed or corrupted and the import process cannot continue."
+        )
+
     try:
         alembic_version = json.loads(backup.open("db/alembic_version.json").read())
         alembic_version = alembic_version["results"][0]["version_num"]


Test patch:
Repository: CTFd/CTFd

Issue:
Pagination objects for User interface
Flask sqlalchemy pagination objects should be used for the user interface instead of the custom pagination. 


Discussion:

Resolving patch:
diff --git a/CTFd/teams.py b/CTFd/teams.py
--- a/CTFd/teams.py
+++ b/CTFd/teams.py
@@ -21,24 +21,14 @@
 @require_team_mode
 def listing():
     page = abs(request.args.get("page", 1, type=int))
-    results_per_page = 50
-    page_start = results_per_page * (page - 1)
-    page_end = results_per_page * (page - 1) + results_per_page
-
-    # TODO: Should teams confirm emails?
-    # if get_config('verify_emails'):
-    #     count = Teams.query.filter_by(verified=True, banned=False).count()
-    #     teams = Teams.query.filter_by(verified=True, banned=False).slice(page_start, page_end).all()
-    # else:
-    count = Teams.query.filter_by(hidden=False, banned=False).count()
+
     teams = (
         Teams.query.filter_by(hidden=False, banned=False)
-        .slice(page_start, page_end)
-        .all()
+        .order_by(Teams.id.asc())
+        .paginate(page=page, per_page=50)
     )
 
-    pages = int(count / results_per_page) + (count % results_per_page > 0)
-    return render_template("teams/teams.html", teams=teams, pages=pages, curr_page=page)
+    return render_template("teams/teams.html", teams=teams)
 
 
 @teams.route("/teams/join", methods=["GET", "POST"])
diff --git a/CTFd/users.py b/CTFd/users.py
--- a/CTFd/users.py
+++ b/CTFd/users.py
@@ -16,19 +16,14 @@
 @check_account_visibility
 def listing():
     page = abs(request.args.get("page", 1, type=int))
-    results_per_page = 50
-    page_start = results_per_page * (page - 1)
-    page_end = results_per_page * (page - 1) + results_per_page
 
-    count = Users.query.filter_by(banned=False, hidden=False).count()
     users = (
         Users.query.filter_by(banned=False, hidden=False)
-        .slice(page_start, page_end)
-        .all()
+        .order_by(Users.id.asc())
+        .paginate(page=page, per_page=10)
     )
 
-    pages = int(count / results_per_page) + (count % results_per_page > 0)
-    return render_template("users/users.html", users=users, pages=pages, curr_page=page)
+    return render_template("users/users.html", users=users)
 
 
 @users.route("/profile")


Test patch:
Repository: CTFd/CTFd

Issue:
Theme settings
There needs to be some way to change settings in themes themselves. People complain about a lot of nonsensical things that they should fix in their forks and not need to be a concern in master. 


Discussion:
I think the best approach for this is to have a config.html in each theme that has a form. The form gets loaded into the admin panel and then is used to create a config key that has JSON. The JSON is loaded into the theme where it can be used. 

Resolving patch:
diff --git a/CTFd/constants/config.py b/CTFd/constants/config.py
--- a/CTFd/constants/config.py
+++ b/CTFd/constants/config.py
@@ -1,3 +1,5 @@
+import json
+
 from CTFd.utils import get_config
 from CTFd.utils.helpers import markup
 
@@ -18,5 +20,9 @@ def theme_header(self):
     def theme_footer(self):
         return markup(get_config("theme_footer", default=""))
 
+    @property
+    def theme_settings(self):
+        return json.loads(get_config("theme_settings", default="null"))
+
 
 Configs = _ConfigsWrapper()


Test patch:
Repository: CTFd/CTFd

Issue:
Show current attempts in challenge view
Showing how many attempts the user has tried on a given challenge. Helps with max attempts as well. 


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
@@ -6,7 +6,17 @@
 
 from CTFd.cache import clear_standings
 from CTFd.models import ChallengeFiles as ChallengeFilesModel
-from CTFd.models import Challenges, Fails, Flags, Hints, HintUnlocks, Solves, Tags, db
+from CTFd.models import (
+    Challenges,
+    Fails,
+    Flags,
+    Hints,
+    HintUnlocks,
+    Solves,
+    Submissions,
+    Tags,
+    db,
+)
 from CTFd.plugins.challenges import CHALLENGE_CLASSES, get_chal_class
 from CTFd.schemas.flags import FlagSchema
 from CTFd.schemas.hints import HintSchema
@@ -273,6 +283,15 @@ def get(self, challenge_id):
         else:
             response["solves"] = None
 
+        if authed():
+            # Get current attempts for the user
+            attempts = Submissions.query.filter_by(
+                account_id=user.account_id, challenge_id=challenge_id
+            ).count()
+        else:
+            attempts = 0
+
+        response["attempts"] = attempts
         response["files"] = files
         response["tags"] = tags
         response["hints"] = hints
@@ -283,6 +302,8 @@ def get(self, challenge_id):
             files=files,
             tags=tags,
             hints=[Hints(**h) for h in hints],
+            max_attempts=chal.max_attempts,
+            attempts=attempts,
             challenge=chal,
         )
 
diff --git a/CTFd/utils/humanize/words.py b/CTFd/utils/humanize/words.py
new file mode 100644
--- /dev/null
+++ b/CTFd/utils/humanize/words.py
@@ -0,0 +1,5 @@
+def pluralize(number, singular="", plural="s"):
+    if number == 1:
+        return singular
+    else:
+        return plural
diff --git a/CTFd/utils/initialization/__init__.py b/CTFd/utils/initialization/__init__.py
--- a/CTFd/utils/initialization/__init__.py
+++ b/CTFd/utils/initialization/__init__.py
@@ -23,6 +23,7 @@
 from CTFd.utils.countries import get_countries, lookup_country_code
 from CTFd.utils.dates import isoformat, unix_time, unix_time_millis
 from CTFd.utils.events import EventManager, RedisEventManager
+from CTFd.utils.humanize.words import pluralize
 from CTFd.utils.modes import generate_account_url, get_mode_as_word
 from CTFd.utils.plugins import (
     get_configurable_plugins,
@@ -48,6 +49,7 @@ def init_template_filters(app):
     app.jinja_env.filters["unix_time"] = unix_time
     app.jinja_env.filters["unix_time_millis"] = unix_time_millis
     app.jinja_env.filters["isoformat"] = isoformat
+    app.jinja_env.filters["pluralize"] = pluralize
 
 
 def init_template_globals(app):


Test patch:
Repository: CTFd/CTFd

Issue:
Access media library from Challenge UI
Accessing the media library from the challenge UI is a useful idea if you're using images in the challenge interface. Saves some clicks. 


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
@@ -27,7 +27,7 @@ def pages_preview():
     data = request.form.to_dict()
     schema = PageSchema()
     page = schema.load(data)
-    return render_template("page.html", content=build_html(page.data["content"]))
+    return render_template("page.html", content=build_html(page.data.content))
 
 
 @admin.route("/admin/pages/<int:page_id>")


Test patch:
Repository: CTFd/CTFd

Issue:
Change Configs detail API GET/PATCH for a more structured response
The API endpoints for GET, PATCH /api/v1/configs/{config_key} return badly structured data. This should return better structured data. 


Discussion:

Resolving patch:
diff --git a/CTFd/api/v1/config.py b/CTFd/api/v1/config.py
--- a/CTFd/api/v1/config.py
+++ b/CTFd/api/v1/config.py
@@ -11,7 +11,7 @@
 from CTFd.constants import RawEnum
 from CTFd.models import Configs, db
 from CTFd.schemas.config import ConfigSchema
-from CTFd.utils import get_config, set_config
+from CTFd.utils import set_config
 from CTFd.utils.decorators import admins_only
 
 configs_namespace = Namespace("configs", description="Endpoint to retrieve Configs")
@@ -121,13 +121,33 @@ def patch(self):
 @configs_namespace.route("/<config_key>")
 class Config(Resource):
     @admins_only
-    # TODO: This returns weirdly structured data. It should more closely match ConfigDetailedSuccessResponse #1506
+    @configs_namespace.doc(
+        description="Endpoint to get a specific Config object",
+        responses={
+            200: ("Success", "ConfigDetailedSuccessResponse"),
+            400: (
+                "An error occured processing the provided or stored data",
+                "APISimpleErrorResponse",
+            ),
+        },
+    )
     def get(self, config_key):
-
-        return {"success": True, "data": get_config(config_key)}
+        config = Configs.query.filter_by(key=config_key).first_or_404()
+        schema = ConfigSchema()
+        response = schema.dump(config)
+        return {"success": True, "data": response.data}
 
     @admins_only
-    # TODO: This returns weirdly structured data. It should more closely match ConfigDetailedSuccessResponse #1506
+    @configs_namespace.doc(
+        description="Endpoint to edit a specific Config object",
+        responses={
+            200: ("Success", "ConfigDetailedSuccessResponse"),
+            400: (
+                "An error occured processing the provided or stored data",
+                "APISimpleErrorResponse",
+            ),
+        },
+    )
     def patch(self, config_key):
         config = Configs.query.filter_by(key=config_key).first()
         data = request.get_json()


Test patch:
Repository: CTFd/CTFd

Issue:
solves undefined if visibility is set to hidden
**Environment**:

- CTFd Version/Commit: adc70fb320242d5e4df1a7ce2d107c0e2b8039e7
- Operating System: Ubuntu 18.04
- Web Browser and Version: Safari 13.1.1, Chrome 83.0.4103.106

**What happened?**

When _Score Visibility_ or _Account Visibility_ is not public, users cannot see challenges.

**What did you expect to happen?**

Challenges should be able to be seen and solves are hidden.

**How to reproduce your issue**

In `CTFd/api/v1/challenges.py`, line 361 to line 376, `solves` is only defined if `scores_visible() is True and accounts_visible() is True`. Yet, `solves` is referenced later in the code.

**Any associated stack traces or error logs**

<img width="747" alt="error" src="https://user-images.githubusercontent.com/56060664/86547616-1bae4380-bf6c-11ea-89ac-cd6c5db663f0.png">




Discussion:
Thanks for filing this, I believe this is fixed with `solves = None` on line 377. I'll put at fix up when I get a chance. 

Also mildly surprised that this isn't caught by a linter.

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
@@ -374,6 +374,7 @@ def get(self, challenge_id):
             response["solves"] = solves
         else:
             response["solves"] = None
+            solves = None
 
         if authed():
             # Get current attempts for the user


Test patch:
diff --git a/tests/api/v1/test_challenges.py b/tests/api/v1/test_challenges.py
--- a/tests/api/v1/test_challenges.py
+++ b/tests/api/v1/test_challenges.py
@@ -244,6 +244,21 @@ def test_api_challenge_get_visibility_private():
     destroy_ctfd(app)
 
 
+def test_api_challenge_get_with_admin_only_account_visibility():
+    """Can a private user get /api/v1/challenges/<challenge_id> if account_visibility is admins_only"""
+    app = create_ctfd()
+    with app.app_context():
+        gen_challenge(app.db)
+        register_user(app)
+        client = login_as_user(app)
+        r = client.get("/api/v1/challenges/1")
+        assert r.status_code == 200
+        set_config("account_visibility", "admins")
+        r = client.get("/api/v1/challenges/1")
+        assert r.status_code == 200
+    destroy_ctfd(app)
+
+
 def test_api_challenge_get_ctftime_private():
     """Can a private user get /api/v1/challenges/<challenge_id> if ctftime is over"""
     app = create_ctfd()
Repository: CTFd/CTFd

Issue:
Colon in CTF name breaks emails
This is because of:

https://tools.ietf.org/html/rfc5322#section-2.2

This can probably be fixed with `"HE:tech" <tech@example.com>`.


Discussion:
```
from email.utils import formataddr                       

 ctf_name = "Python: 3"                                  

 mailfrom_addr = "python@python.com"                     

 "{} <{}>".format(ctf_name, mailfrom_addr)               
 'Python: 3 <python@python.com>'

 formataddr((ctf_name, mailfrom_addr))                   
 '"Python: 3" <python@python.com>'
```

I believe this code works properly and also lets us pass the hard work over to our good friend Python. 

Resolving patch:
diff --git a/CTFd/utils/email/mailgun.py b/CTFd/utils/email/mailgun.py
--- a/CTFd/utils/email/mailgun.py
+++ b/CTFd/utils/email/mailgun.py
@@ -1,3 +1,5 @@
+from email.utils import formataddr
+
 import requests
 
 from CTFd.utils import get_app_config, get_config
@@ -6,7 +8,7 @@
 def sendmail(addr, text, subject):
     ctf_name = get_config("ctf_name")
     mailfrom_addr = get_config("mailfrom_addr") or get_app_config("MAILFROM_ADDR")
-    mailfrom_addr = "{} <{}>".format(ctf_name, mailfrom_addr)
+    mailfrom_addr = formataddr((ctf_name, mailfrom_addr))
 
     mailgun_base_url = get_config("mailgun_base_url") or get_app_config(
         "MAILGUN_BASE_URL"
diff --git a/CTFd/utils/email/smtp.py b/CTFd/utils/email/smtp.py
--- a/CTFd/utils/email/smtp.py
+++ b/CTFd/utils/email/smtp.py
@@ -1,5 +1,6 @@
 import smtplib
 from email.message import EmailMessage
+from email.utils import formataddr
 from socket import timeout
 
 from CTFd.utils import get_app_config, get_config
@@ -22,7 +23,7 @@ def get_smtp(host, port, username=None, password=None, TLS=None, SSL=None, auth=
 def sendmail(addr, text, subject):
     ctf_name = get_config("ctf_name")
     mailfrom_addr = get_config("mailfrom_addr") or get_app_config("MAILFROM_ADDR")
-    mailfrom_addr = "{} <{}>".format(ctf_name, mailfrom_addr)
+    mailfrom_addr = formataddr((ctf_name, mailfrom_addr))
 
     data = {
         "host": get_config("mail_server") or get_app_config("MAIL_SERVER"),


Test patch:
Repository: CTFd/CTFd

Issue:
Render hints server side instead of client side
Render hints server side instead of client side to match challenges and pages


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
@@ -68,7 +68,7 @@ def get(self, query_args):
         filters = build_model_filters(model=Hints, query=q, field=field)
 
         hints = Hints.query.filter_by(**query_args).filter(*filters).all()
-        response = HintSchema(many=True).dump(hints)
+        response = HintSchema(many=True, view="locked").dump(hints)
 
         if response.errors:
             return {"success": False, "errors": response.errors}, 400
@@ -88,7 +88,7 @@ def get(self, query_args):
     )
     def post(self):
         req = request.get_json()
-        schema = HintSchema("admin")
+        schema = HintSchema(view="admin")
         response = schema.load(req, session=db.session)
 
         if response.errors:
@@ -155,7 +155,7 @@ def patch(self, hint_id):
         hint = Hints.query.filter_by(id=hint_id).first_or_404()
         req = request.get_json()
 
-        schema = HintSchema()
+        schema = HintSchema(view="admin")
         response = schema.load(req, instance=hint, partial=True, session=db.session)
 
         if response.errors:
diff --git a/CTFd/models/__init__.py b/CTFd/models/__init__.py
--- a/CTFd/models/__init__.py
+++ b/CTFd/models/__init__.py
@@ -134,6 +134,13 @@ def category(self):
     def description(self):
         return "Hint for {name}".format(name=self.challenge.name)
 
+    @property
+    def html(self):
+        from CTFd.utils.config.pages import build_html
+        from CTFd.utils.helpers import markup
+
+        return markup(build_html(self.content))
+
     def __init__(self, *args, **kwargs):
         super(Hints, self).__init__(**kwargs)
 
diff --git a/CTFd/schemas/hints.py b/CTFd/schemas/hints.py
--- a/CTFd/schemas/hints.py
+++ b/CTFd/schemas/hints.py
@@ -6,12 +6,12 @@ class HintSchema(ma.ModelSchema):
     class Meta:
         model = Hints
         include_fk = True
-        dump_only = ("id", "type")
+        dump_only = ("id", "type", "html")
 
     views = {
         "locked": ["id", "type", "challenge", "cost"],
-        "unlocked": ["id", "type", "challenge", "content", "cost"],
-        "admin": ["id", "type", "challenge", "content", "cost", "requirements"],
+        "unlocked": ["id", "type", "challenge", "content", "html", "cost"],
+        "admin": ["id", "type", "challenge", "content", "html", "cost", "requirements"],
     }
 
     def __init__(self, view=None, *args, **kwargs):


Test patch:
Repository: CTFd/CTFd

Issue:
Review usage of error components
Looks like there needs to be more usage of the error components jinja snippet. It looks like it's missing in core/teams/public and core/teams/private at least. 


Discussion:

Resolving patch:
diff --git a/CTFd/teams.py b/CTFd/teams.py
--- a/CTFd/teams.py
+++ b/CTFd/teams.py
@@ -154,6 +154,9 @@ def new():
 @authed_only
 @require_team_mode
 def private():
+    infos = get_infos()
+    errors = get_errors()
+
     user = get_current_user()
     if not user.team_id:
         return render_template("teams/team_enrollment.html")
@@ -167,6 +170,9 @@ def private():
     place = team.place
     score = team.score
 
+    if config.is_scoreboard_frozen():
+        infos.append("Scoreboard has been frozen")
+
     return render_template(
         "teams/private.html",
         solves=solves,
@@ -176,6 +182,8 @@ def private():
         score=score,
         place=place,
         score_frozen=config.is_scoreboard_frozen(),
+        infos=infos,
+        errors=errors,
     )
 
 
@@ -184,6 +192,7 @@ def private():
 @check_score_visibility
 @require_team_mode
 def public(team_id):
+    infos = get_infos()
     errors = get_errors()
     team = Teams.query.filter_by(id=team_id, banned=False, hidden=False).first_or_404()
     solves = team.get_solves()
@@ -195,6 +204,9 @@ def public(team_id):
     if errors:
         return render_template("teams/public.html", team=team, errors=errors)
 
+    if config.is_scoreboard_frozen():
+        infos.append("Scoreboard has been frozen")
+
     return render_template(
         "teams/public.html",
         solves=solves,
@@ -203,4 +215,6 @@ def public(team_id):
         score=score,
         place=place,
         score_frozen=config.is_scoreboard_frozen(),
+        infos=infos,
+        errors=errors,
     )


Test patch:
Repository: CTFd/CTFd

Issue:
Add email sender address override
Right now the email sender is set to the From address which isn't right in all situations. We need a way to override the email sender if it's not supposed to be the same as the From address. 

https://help.mailgun.com/hc/en-us/articles/202236494-What-is-the-difference-between-the-From-and-Sender-
https://stackoverflow.com/questions/4367358/whats-the-difference-between-sender-from-and-return-path


Discussion:

Resolving patch:
diff --git a/CTFd/config.py b/CTFd/config.py
--- a/CTFd/config.py
+++ b/CTFd/config.py
@@ -147,6 +147,8 @@ class ServerConfig(object):
 
     MAIL_SSL: bool = process_boolean_str(config_ini["email"]["MAIL_SSL"])
 
+    MAILSENDER_ADDR: str = empty_str_cast(config_ini["email"]["MAILSENDER_ADDR"])
+
     MAILGUN_API_KEY: str = empty_str_cast(config_ini["email"]["MAILGUN_API_KEY"])
 
     MAILGUN_BASE_URL: str = empty_str_cast(config_ini["email"]["MAILGUN_API_KEY"])
diff --git a/CTFd/utils/email/smtp.py b/CTFd/utils/email/smtp.py
--- a/CTFd/utils/email/smtp.py
+++ b/CTFd/utils/email/smtp.py
@@ -56,7 +56,15 @@ def sendmail(addr, text, subject):
         msg["From"] = mailfrom_addr
         msg["To"] = addr
 
-        smtp.send_message(msg)
+        # Check whether we are using an admin-defined SMTP server
+        custom_smtp = bool(get_config("mail_server"))
+
+        # We should only consider the MAILSENDER_ADDR value on servers defined in config
+        if custom_smtp:
+            smtp.send_message(msg)
+        else:
+            mailsender_addr = get_app_config("MAILSENDER_ADDR")
+            smtp.send_message(msg, from_addr=mailsender_addr)
 
         smtp.quit()
         return True, "Email sent"


Test patch: