Repository: CTFd/CTFd

Issue:
dynamic challenges not work when using user mode

**Environment**:

  - CTFd Version/Commit:`2bd310b5d99f4305f8b61076f9af3b7e3c91ba8f`
  - Operating System: `ubuntu 16.04`
  - Web Browser and Version: `Chrome`

when using user mode , `solve_count` always get 0 

https://github.com/CTFd/CTFd/blob/master/CTFd/plugins/dynamic_challenges/__init__.py#L95
```
        solve_count = Solves.query \
            .join(Teams, Solves.team_id == Teams.id) \
            .filter(Solves.challenge_id == challenge.id, Teams.banned == False) \
            .count()
```



Discussion:
@hzshang Thanks for reporting this, I should have this resolved soon. 

Resolving patch:
diff --git a/CTFd/plugins/dynamic_challenges/__init__.py b/CTFd/plugins/dynamic_challenges/__init__.py
--- a/CTFd/plugins/dynamic_challenges/__init__.py
+++ b/CTFd/plugins/dynamic_challenges/__init__.py
@@ -7,6 +7,7 @@
 from CTFd.utils.migrations import upgrade
 from CTFd.utils.user import get_ip
 from CTFd.utils.uploads import upload_file, delete_file
+from CTFd.utils.modes import get_model
 from flask import Blueprint
 import math
 
@@ -92,9 +93,11 @@ def update(challenge, request):
         for attr, value in data.items():
             setattr(challenge, attr, value)
 
+        Model = get_model()
+
         solve_count = Solves.query \
-            .join(Teams, Solves.team_id == Teams.id) \
-            .filter(Solves.challenge_id == challenge.id, Teams.banned == False) \
+            .join(Model, Solves.account_id == Model.id) \
+            .filter(Solves.challenge_id == challenge.id, Model.hidden == False, Model.banned == False) \
             .count()
 
         # It is important that this calculation takes into account floats.
@@ -165,9 +168,11 @@ def solve(user, team, challenge, request):
         data = request.form or request.get_json()
         submission = data['submission'].strip()
 
-        solve_count = Solves.query\
-            .join(Teams, Solves.team_id == Teams.id)\
-            .filter(Solves.challenge_id == chal.id, Teams.banned == False)\
+        Model = get_model()
+
+        solve_count = Solves.query \
+            .join(Model, Solves.account_id == Model.id) \
+            .filter(Solves.challenge_id == challenge.id, Model.hidden == False, Model.banned == False) \
             .count()
 
         # It is important that this calculation takes into account floats.


Test patch:
diff --git a/tests/challenges/test_dynamic.py b/tests/challenges/test_dynamic.py
--- a/tests/challenges/test_dynamic.py
+++ b/tests/challenges/test_dynamic.py
@@ -114,3 +114,58 @@ def test_can_delete_dynamic_challenge():
         challenges = DynamicChallenge.query.all()
         assert len(challenges) == 0
     destroy_ctfd(app)
+
+
+def test_dynamic_challenge_loses_value_properly():
+    app = create_ctfd(enable_plugins=True)
+    with app.app_context():
+        register_user(app)
+        client = login_as_user(app, name="admin", password="password")
+
+        challenge_data = {
+            "name": "name",
+            "category": "category",
+            "description": "description",
+            "value": 100,
+            "decay": 20,
+            "minimum": 1,
+            "state": "visible",
+            "type": "dynamic"
+        }
+
+        r = client.post('/api/v1/challenges', json=challenge_data)
+        assert r.get_json().get('data')['id'] == 1
+
+        flag = gen_flag(app.db, challenge_id=1, content='flag')
+
+        for i, team_id in enumerate(range(2, 26)):
+            name = "user{}".format(team_id)
+            email = "user{}@ctfd.io".format(team_id)
+            # We need to bypass rate-limiting so gen_user instead of register_user
+            gen_user(app.db, name=name, email=email)
+
+            with app.test_client() as client:
+                # We need to bypass rate-limiting so creating a fake user instead of logging in
+                with client.session_transaction() as sess:
+                    sess['id'] = team_id
+                    sess['name'] = name
+                    sess['type'] = 'user'
+                    sess['email'] = email
+                    sess['nonce'] = 'fake-nonce'
+
+                data = {
+                    "submission": 'flag',
+                    "challenge_id": 1
+                }
+
+                r = client.post('/api/v1/challenges/attempt', json=data)
+                resp = r.get_json()['data']
+                assert resp['status'] == 'correct'
+
+                chal = DynamicChallenge.query.filter_by(id=1).first()
+                if i >= 20:
+                    assert chal.value == chal.minimum
+                else:
+                    assert chal.initial >= chal.value
+                    assert chal.value > chal.minimum
+    destroy_ctfd(app)