Repository: CTFd/CTFd
Pull request #462 (2017-11-15 08:14:47)

Adding tests for verify_emails and user_can_view_challenges are set
Clean up the verify_emails logic a little. Test the behavior for viewing challenges while unregistered and requiring verified emails. 

Also ensuring that if both are set, users should be able to see challenges but be prevented from seeing them until they confirm their email address. 


Patch:
diff --git a/CTFd/challenges.py b/CTFd/challenges.py
index 69b4c2f6..9ced0812 100644
--- a/CTFd/challenges.py
+++ b/CTFd/challenges.py
@@ -82,8 +82,12 @@ def challenges_view():
                 if (utils.get_config('end') and utils.ctf_ended()) and not utils.view_after_ctf():
                     errors.append('{} has ended'.format(utils.ctf_name()))
                 return render_template('chals.html', errors=errors, start=int(start), end=int(end))
-        if utils.get_config('verify_emails') and not utils.is_verified():  # User is not confirmed
-            return redirect(url_for('auth.confirm_user'))
+
+    if utils.get_config('verify_emails'):
+        if utils.authed():
+            if utils.is_admin() is False and utils.is_verified() is False:  # User is not confirmed
+                return redirect(url_for('auth.confirm_user'))
+
     if utils.user_can_view_challenges():  # Do we allow unauthenticated users?
         if utils.get_config('start') and not utils.ctf_started():
             errors.append('{} has not started yet'.format(utils.ctf_name()))
@@ -102,6 +106,12 @@ def chals():
                 pass
             else:
                 abort(403)
+
+    if utils.get_config('verify_emails'):
+        if utils.authed():
+            if utils.is_admin() is False and utils.is_verified() is False:  # User is not confirmed
+                abort(403)
+
     if utils.user_can_view_challenges() and (utils.ctf_started() or utils.is_admin()):
         teamid = session.get('id')
         chals = Challenges.query.filter(or_(Challenges.hidden != True, Challenges.hidden == None)).order_by(Challenges.value).all()


Test patch:
diff --git a/tests/user/test_auth.py b/tests/user/test_auth.py
new file mode 100644
index 00000000..1db47623
--- /dev/null
+++ b/tests/user/test_auth.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from CTFd.models import Teams, Solves, WrongKeys
+from CTFd.utils import get_config, set_config
+from CTFd import utils
+from tests.helpers import *
+from freezegun import freeze_time
+from mock import patch
+import json
+
+
+def test_user_can_view_challenges():
+    """Test that user_can_view_challenges allows users to view challenges while not logged in"""
+    app = create_ctfd()
+    with app.app_context():
+        set_config('view_challenges_unregistered', True)
+        utils.cache.clear()  # Need to clear the cached configuration
+        chal = gen_challenge(app.db)
+        with app.test_client() as client:
+            r = client.get('/challenges')
+            assert r.status_code == 200
+            r = client.get('/chals')
+            assert r.status_code == 200
+        utils.cache.clear()  # Need to clear the cached configuration
+        set_config('view_challenges_unregistered', False)
+        with app.test_client() as client:
+            r = client.get('/challenges')
+            assert r.status_code == 302
+            r = client.get('/chals')
+            assert r.status_code == 403
+    destroy_ctfd(app)
+
+
+def test_verify_emails_config():
+    """Test that users can only solve challenges if they are logged in and verified if verify_emails is set"""
+    app = create_ctfd()
+    with app.app_context():
+        utils.cache.clear()  # Need to clear the cached configuration
+        set_config('verify_emails', True)
+        chal = gen_challenge(app.db)
+
+        register_user(app)
+        client = login_as_user(app)
+
+        r = client.get('/challenges')
+        assert r.location == "http://localhost/confirm"
+        assert r.status_code == 302
+
+        r = client.get('/chals')
+        assert r.status_code == 403
+
+        user = Teams.query.filter_by(id=2).first()
+        user.verified = True
+        app.db.session.commit()
+
+        r = client.get('/challenges')
+        assert r.status_code == 200
+
+        r = client.get('/chals')
+        assert r.status_code == 200
+    destroy_ctfd(app)
+
+
+def test_verify_and_view_unregistered():
+    """If both verify_emails and user_can_view_challenges are set, the user should see challenges while unregistered
+    but be locked out if they register until they confirm their email address"""
+    app = create_ctfd()
+    with app.app_context():
+        utils.cache.clear()  # Need to clear the cached configuration
+        set_config('view_challenges_unregistered', True)
+        set_config('verify_emails', True)
+        chal = gen_challenge(app.db)
+
+        # We are not authed but we should still be able to see challenges
+        with app.test_client() as client:
+            r = client.get('/challenges')
+            assert r.status_code == 200
+
+            r = client.get('/chals')
+            assert r.status_code == 200
+
+        # Logging in...
+        register_user(app)
+        client = login_as_user(app)
+
+        # We are now logged in so we should be redirected to the confirmation page
+        r = client.get('/challenges')
+        assert r.location == "http://localhost/confirm"
+        assert r.status_code == 302
+
+        r = client.get('/chals')
+        assert r.status_code == 403
+
+        user = Teams.query.filter_by(id=2).first()
+        user.verified = True
+        app.db.session.commit()
+
+        # Double check that we can see challenges
+        r = client.get('/challenges')
+        assert r.status_code == 200
+
+        r = client.get('/chals')
+        assert r.status_code == 200
+    destroy_ctfd(app)
diff --git a/tests/user/test_user_facing.py b/tests/user/test_user_facing.py
index 3fc9171d..9f3dd5bf 100644
--- a/tests/user/test_user_facing.py
+++ b/tests/user/test_user_facing.py
@@ -91,9 +91,10 @@ def test_user_isnt_admin():
     with app.app_context():
         register_user(app)
         client = login_as_user(app)
-        r = client.get('/admin/graphs')
-        assert r.location == "http://localhost/login"
-        assert r.status_code == 302
+        for page in ['graphs', 'pages', 'teams', 'scoreboard', 'chals', 'statistics', 'config']:
+            r = client.get('/admin/{}'.format(page))
+            assert r.location == "http://localhost/login"
+            assert r.status_code == 302
     destroy_ctfd(app)