Beispiel #1
0
def ctftime():
    """ Checks whether it's CTF time or not. """

    start = get_config("start")
    end = get_config("end")

    if start:
        start = int(start)
    else:
        start = 0
    if end:
        end = int(end)
    else:
        end = 0

    if start and end:
        if start < time.time() < end:
            # Within the two time bounds
            return True

    if start < time.time() and end == 0:
        # CTF starts on a date but never ends
        return True

    if start == 0 and time.time() < end:
        # CTF started but ends at a date
        return True

    if start == 0 and end == 0:
        # CTF has no time requirements
        return True

    return False
Beispiel #2
0
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)

    mailgun_base_url = get_config("mailgun_base_url") or get_app_config(
        "MAILGUN_BASE_URL")
    mailgun_api_key = get_config("mailgun_api_key") or get_app_config(
        "MAILGUN_API_KEY")
    try:
        r = requests.post(
            mailgun_base_url + "/messages",
            auth=("api", mailgun_api_key),
            data={
                "from": mailfrom_addr,
                "to": [addr],
                "subject": subject,
                "text": text,
            },
            timeout=1.0,
        )
    except requests.RequestException as e:
        return (
            False,
            "{error} exception occured while handling your request".format(
                error=type(e).__name__),
        )

    if r.status_code == 200:
        return True, "Email sent"
    else:
        return False, "Mailgun settings are incorrect"
Beispiel #3
0
def test_successful_registration_email(mock_smtp):
    """Does successful_registration_notification send emails"""
    app = create_kmactf()
    with app.app_context():
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")
        set_config("verify_emails", True)

        ctf_name = get_config("ctf_name")
        from_addr = get_config("mailfrom_addr") or app.config.get(
            "MAILFROM_ADDR")
        from_addr = "{} <{}>".format(ctf_name, from_addr)

        to_addr = "*****@*****.**"

        successful_registration_notification(to_addr)

        msg = "You've successfully registered for KMActf!"

        email_msg = MIMEText(msg)
        email_msg["Subject"] = "Successfully registered for {ctf_name}".format(
            ctf_name=ctf_name)
        email_msg["From"] = from_addr
        email_msg["To"] = to_addr

        # Need to freeze time to predict the value of the itsdangerous token.
        # For now just assert that sendmail was called.
        mock_smtp.return_value.sendmail.assert_called_with(
            from_addr, [to_addr], email_msg.as_string())
    destroy_kmactf(app)
Beispiel #4
0
def test_sendmail_with_smtp_from_config_file(mock_smtp):
    """Does sendmail work properly with simple SMTP mail servers using file configuration"""
    app = create_kmactf()
    with app.app_context():
        app.config["MAIL_SERVER"] = "localhost"
        app.config["MAIL_PORT"] = "25"
        app.config["MAIL_USEAUTH"] = "True"
        app.config["MAIL_USERNAME"] = "******"
        app.config["MAIL_PASSWORD"] = "******"

        ctf_name = get_config("ctf_name")
        from_addr = get_config("mailfrom_addr") or app.config.get(
            "MAILFROM_ADDR")
        from_addr = "{} <{}>".format(ctf_name, from_addr)

        to_addr = "*****@*****.**"
        msg = "this is a test"

        sendmail(to_addr, msg)

        ctf_name = get_config("ctf_name")
        email_msg = MIMEText(msg)
        email_msg["Subject"] = "Message from {0}".format(ctf_name)
        email_msg["From"] = from_addr
        email_msg["To"] = to_addr

        mock_smtp.return_value.sendmail.assert_called_once_with(
            from_addr, [to_addr], email_msg.as_string())
    destroy_kmactf(app)
Beispiel #5
0
def test_sendmail_with_smtp_from_db_config(mock_smtp):
    """Does sendmail work properly with simple SMTP mail servers using database configuration"""
    app = create_kmactf()
    with app.app_context():
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")

        ctf_name = get_config("ctf_name")
        from_addr = get_config("mailfrom_addr") or app.config.get(
            "MAILFROM_ADDR")
        from_addr = "{} <{}>".format(ctf_name, from_addr)

        to_addr = "*****@*****.**"
        msg = "this is a test"

        sendmail(to_addr, msg)

        ctf_name = get_config("ctf_name")
        email_msg = MIMEText(msg)
        email_msg["Subject"] = "Message from {0}".format(ctf_name)
        email_msg["From"] = from_addr
        email_msg["To"] = to_addr

        mock_smtp.return_value.sendmail.assert_called_once_with(
            from_addr, [to_addr], email_msg.as_string())
    destroy_kmactf(app)
Beispiel #6
0
def test_update_check_identifies_update(fake_get_request):
    """Update checks properly identify new versions"""
    app = create_kmactf()
    with app.app_context():
        app.config["UPDATE_CHECK"] = True
        fake_response = Mock()
        fake_get_request.return_value = fake_response
        fake_response.json = lambda: {
            "resource": {
                "download_url":
                "https://api.github.com/repos/KMActf/KMActf/zipball/9.9.9",
                "html_url":
                "https://github.com/KMActf/KMActf/releases/tag/9.9.9",
                "id": 12,
                "latest": True,
                "next": 1542212248,
                "prerelease": False,
                "published_at": "Wed, 25 Oct 2017 19:39:42 -0000",
                "tag": "9.9.9",
            }
        }
        update_check()
        assert (get_config("version_latest") ==
                "https://github.com/KMActf/KMActf/releases/tag/9.9.9")
        assert get_config("next_update_check") == 1542212248
    destroy_kmactf(app)
Beispiel #7
0
def mailgun():
    if app.config.get("MAILGUN_API_KEY") and app.config.get(
            "MAILGUN_BASE_URL"):
        return True
    if get_config("mailgun_api_key") and get_config("mailgun_base_url"):
        return True
    return False
Beispiel #8
0
def oauth_login():
    endpoint = (get_app_config("OAUTH_AUTHORIZATION_ENDPOINT")
                or get_config("oauth_authorization_endpoint")
                or "https://auth.majorleaguecyber.org/oauth/authorize")

    if get_config("user_mode") == "teams":
        scope = "profile team"
    else:
        scope = "profile"

    client_id = get_app_config("OAUTH_CLIENT_ID") or get_config(
        "oauth_client_id")

    if client_id is None:
        error_for(
            endpoint="auth.login",
            message="OAuth Settings not configured. "
            "Ask your CTF administrator to configure MajorLeagueCyber integration.",
        )
        return redirect(url_for("auth.login"))

    redirect_url = "{endpoint}?response_type=code&client_id={client_id}&scope={scope}&state={state}".format(
        endpoint=endpoint,
        client_id=client_id,
        scope=scope,
        state=session["nonce"])
    return redirect(redirect_url)
Beispiel #9
0
def update_check(force=False):
    """
    Makes a request to kmactf to check if there is a new version of KMActf available. The service is provided in return
    for users opting in to anonymous usage data collection. Users can opt-out of update checks by specifying
    UPDATE_CHECK = False in config.py

    :param force:
    :return:
    """
    # If UPDATE_CHECK is disabled don't check for updates at all.
    if app.config.get("UPDATE_CHECK") is False:
        return

    # Don't do an update check if not setup
    if is_setup() is False:
        return

    # Get when we should check for updates next.
    next_update_check = get_config("next_update_check") or 0

    # If we have passed our saved time or we are forcing we should check.
    update = (next_update_check < time.time()) or force

    if update:
        try:
            name = str(get_config("ctf_name")) or ""
            params = {
                "ctf_id": sha256(name),
                "current": app.VERSION,
                "python_version_raw": sys.hexversion,
                "python_version": python_version(),
                "db_driver": db.session.bind.dialect.name,
                "challenge_count": Challenges.query.count(),
                "user_mode": get_config("user_mode"),
                "user_count": Users.query.count(),
                "team_count": Teams.query.count(),
                "theme": get_config("ctf_theme"),
                "upload_provider": get_app_config("UPLOAD_PROVIDER"),
            }
            check = requests.post("https://actvn.edu.vn/",
                                  json=params,
                                  timeout=0.1).json()
        except requests.exceptions.RequestException:
            pass
        except ValueError:
            pass
        else:
            try:
                latest = check["resource"]["tag"]
                html_url = check["resource"]["html_url"]
                if StrictVersion(latest) > StrictVersion(app.VERSION):
                    set_config("version_latest", html_url)
                elif StrictVersion(latest) <= StrictVersion(app.VERSION):
                    set_config("version_latest", None)
                next_update_check_time = check["resource"].get(
                    "next", int(time.time() + 43200))
                set_config("next_update_check", next_update_check_time)
            except KeyError:
                set_config("version_latest", None)
Beispiel #10
0
def test_get_config_and_set_config():
    """Does get_config and set_config work properly"""
    app = create_kmactf()
    with app.app_context():
        assert get_config("setup") == True
        config = set_config("TEST_CONFIG_ENTRY", "test_config_entry")
        assert config.value == "test_config_entry"
        assert get_config("TEST_CONFIG_ENTRY") == "test_config_entry"
    destroy_kmactf(app)
Beispiel #11
0
    def validate_email(self, data):
        email = data.get("email")
        if email is None:
            return
        email = email.strip()

        existing_user = Users.query.filter_by(email=email).first()
        current_user = get_current_user()
        if is_admin():
            user_id = data.get("id")
            if user_id:
                if existing_user and existing_user.id != user_id:
                    raise ValidationError(
                        "Email address has already been used",
                        field_names=["email"])
            else:
                if existing_user:
                    if current_user:
                        if current_user.id != existing_user.id:
                            raise ValidationError(
                                "Email address has already been used",
                                field_names=["email"],
                            )
                    else:
                        raise ValidationError(
                            "Email address has already been used",
                            field_names=["email"])
        else:
            if email == current_user.email:
                return data
            else:
                confirm = data.get("confirm")

                if bool(confirm) is False:
                    raise ValidationError(
                        "Please confirm your current password",
                        field_names=["confirm"])

                test = verify_password(plaintext=confirm,
                                       ciphertext=current_user.password)
                if test is False:
                    raise ValidationError(
                        "Your previous password is incorrect",
                        field_names=["confirm"])

                if existing_user:
                    raise ValidationError(
                        "Email address has already been used",
                        field_names=["email"])
                if check_email_is_whitelisted(email) is False:
                    raise ValidationError(
                        "Only email addresses under {domains} may register".
                        format(domains=get_config("domain_whitelist")),
                        field_names=["email"],
                    )
                if get_config("verify_emails"):
                    current_user.verified = False
Beispiel #12
0
def get_mail_provider():
    if app.config.get("MAIL_SERVER") and app.config.get("MAIL_PORT"):
        return "smtp"
    if get_config("mail_server") and get_config("mail_port"):
        return "smtp"
    if app.config.get("MAILGUN_API_KEY") and app.config.get(
            "MAILGUN_BASE_URL"):
        return "mailgun"
    if get_config("mailgun_api_key") and get_config("mailgun_base_url"):
        return "mailgun"
Beispiel #13
0
def generate_account_url(account_id, admin=False):
    if get_config("user_mode") == USERS_MODE:
        if admin:
            return url_for("admin.users_detail", user_id=account_id)
        else:
            return url_for("users.public", user_id=account_id)
    elif get_config("user_mode") == TEAMS_MODE:
        if admin:
            return url_for("admin.teams_detail", team_id=account_id)
        else:
            return url_for("teams.public", team_id=account_id)
Beispiel #14
0
def forgot_password(email):
    text = safe_format(
        get_config("password_reset_body") or DEFAULT_PASSWORD_RESET_BODY,
        ctf_name=get_config("ctf_name"),
        ctf_description=get_config("ctf_description"),
        url=url_for("auth.reset_password",
                    data=serialize(email),
                    _external=True),
    )

    subject = safe_format(
        get_config("password_reset_subject") or DEFAULT_PASSWORD_RESET_SUBJECT,
        ctf_name=get_config("ctf_name"),
    )
    return sendmail(addr=email, text=text, subject=subject)
Beispiel #15
0
def successful_registration_notification(addr):
    text = safe_format(
        get_config("successful_registration_email_body")
        or DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_BODY,
        ctf_name=get_config("ctf_name"),
        ctf_description=get_config("ctf_description"),
        url=url_for("views.static_html", _external=True),
    )

    subject = safe_format(
        get_config("successful_registration_email_subject")
        or DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_SUBJECT,
        ctf_name=get_config("ctf_name"),
    )
    return sendmail(addr=addr, text=text, subject=subject)
Beispiel #16
0
def config():
    # Clear the config cache so that we don't get stale values
    clear_config()

    database_tables = sorted(db.metadata.tables.keys())

    configs = Configs.query.all()
    configs = dict([(c.key, get_config(c.key)) for c in configs])

    themes = ctf_config.get_themes()
    themes.remove(get_config("ctf_theme"))

    return render_template(
        "admin/config.html", database_tables=database_tables, themes=themes, **configs
    )
Beispiel #17
0
def password_change_alert(email):
    text = safe_format(
        get_config("password_change_alert_body")
        or DEFAULT_PASSWORD_CHANGE_ALERT_BODY,
        ctf_name=get_config("ctf_name"),
        ctf_description=get_config("ctf_description"),
        url=url_for("auth.reset_password", _external=True),
    )

    subject = safe_format(
        get_config("password_change_alert_subject")
        or DEFAULT_PASSWORD_CHANGE_ALERT_SUBJECT,
        ctf_name=get_config("ctf_name"),
    )
    return sendmail(addr=email, text=text, subject=subject)
Beispiel #18
0
def test_update_check_ignores_downgrades(fake_post_request):
    """Update checks do nothing on old or same versions"""
    app = create_kmactf()
    with app.app_context():
        app.config["UPDATE_CHECK"] = True
        fake_response = Mock()
        fake_post_request.return_value = fake_response
        fake_response.json = lambda: {
            u"resource": {
                u"html_url":
                u"https://github.com/KMActf/KMActf/releases/tag/0.0.1",
                u"download_url":
                u"https://api.github.com/repos/KMActf/KMActf/zipball/0.0.1",
                u"published_at": u"Wed, 25 Oct 2017 19:39:42 -0000",
                u"tag": u"0.0.1",
                u"prerelease": False,
                u"id": 6,
                u"latest": True,
            }
        }
        update_check()
        assert get_config("version_latest") is None

        fake_response = Mock()
        fake_post_request.return_value = fake_response
        fake_response.json = lambda: {
            u"resource": {
                u"html_url":
                u"https://github.com/KMActf/KMActf/releases/tag/{}".format(
                    app.VERSION),
                u"download_url":
                u"https://api.github.com/repos/KMActf/KMActf/zipball/{}".
                format(app.VERSION),
                u"published_at":
                u"Wed, 25 Oct 2017 19:39:42 -0000",
                u"tag":
                u"{}".format(app.VERSION),
                u"prerelease":
                False,
                u"id":
                6,
                u"latest":
                True,
            }
        }
        update_check()
        assert get_config("version_latest") is None
    destroy_kmactf(app)
Beispiel #19
0
    def _check_score_visibility(*args, **kwargs):
        v = get_config("score_visibility")
        if v == "public":
            return f(*args, **kwargs)

        elif v == "private":
            if authed():
                return f(*args, **kwargs)
            else:
                if request.content_type == "application/json":
                    abort(403)
                else:
                    return redirect(
                        url_for("auth.login", next=request.full_path))

        elif v == "hidden":
            return (
                render_template("errors/403.html",
                                error="Scores are currently hidden"),
                403,
            )

        elif v == "admins":
            if is_admin():
                return f(*args, **kwargs)
            else:
                abort(404)
Beispiel #20
0
def test_api_configs_get_non_admin():
    """Can a user get /api/v1/configs if not admin"""
    app = create_kmactf()
    with app.app_context():
        with app.test_client() as client:
            r = client.get("/api/v1/configs")
            assert r.status_code == 302

            # test_api_configs_post_non_admin
            """Can a user post /api/v1/configs if not admin"""
            r = client.post("/api/v1/configs", json="")
            assert r.status_code == 403

            # test_api_configs_patch_non_admin
            """Can a user patch /api/v1/configs if not admin"""
            r = client.patch("/api/v1/configs", json="")
            assert r.status_code == 403

            # test_api_config_get_non_admin
            """Can a user get /api/v1/configs/<config_key> if not admin"""
            r = client.get("/api/v1/configs/ctf_name")
            assert r.status_code == 302

            # test_api_config_patch_non_admin
            """Can a user patch /api/v1/configs/<config_key> if not admin"""
            r = client.patch("/api/v1/configs/ctf_name", json="")
            assert r.status_code == 403

            # test_api_config_delete_non_admin
            """Can a user delete /api/v1/configs/<config_key> if not admin"""
            r = client.delete("/api/v1/configs/ctf_name", json="")
            assert r.status_code == 403
            assert get_config("ctf_name") == "KMActf"
    destroy_kmactf(app)
Beispiel #21
0
def test_sendmail_with_mailgun_from_db_config(fake_post_request):
    """Does sendmail work properly with Mailgun using database configuration"""
    app = create_kmactf()
    with app.app_context():
        app.config["MAILGUN_API_KEY"] = "key-1234567890-file-config"
        app.config[
            "MAILGUN_BASE_URL"] = "https://api.mailgun.net/v3/file.faked.com"

        # db values should take precedence over file values
        set_config("mailgun_api_key", "key-1234567890-db-config")
        set_config("mailgun_base_url",
                   "https://api.mailgun.net/v3/db.faked.com")

        from_addr = get_config("mailfrom_addr") or app.config.get(
            "MAILFROM_ADDR")
        to_addr = "*****@*****.**"
        msg = "this is a test"

        sendmail(to_addr, msg)

        ctf_name = get_config("ctf_name")
        email_msg = MIMEText(msg)
        email_msg["Subject"] = "Message from {0}".format(ctf_name)
        email_msg["From"] = from_addr
        email_msg["To"] = to_addr

        fake_response = Mock()
        fake_post_request.return_value = fake_response
        fake_response.status_code = 200

        status, message = sendmail(to_addr, msg)

        args, kwargs = fake_post_request.call_args
        assert args[0] == "https://api.mailgun.net/v3/db.faked.com/messages"
        assert kwargs["auth"] == ("api", u"key-1234567890-db-config")
        assert kwargs["timeout"] == 1.0
        assert kwargs["data"] == {
            "to": ["*****@*****.**"],
            "text": "this is a test",
            "from": "KMActf <*****@*****.**>",
            "subject": "Message from KMActf",
        }

        assert fake_response.status_code == 200
        assert status is True
        assert message == "Email sent"
    destroy_kmactf(app)
Beispiel #22
0
def user_created_notification(addr, name, password):
    text = safe_format(
        get_config("user_creation_email_body")
        or DEFAULT_USER_CREATION_EMAIL_BODY,
        ctf_name=get_config("ctf_name"),
        ctf_description=get_config("ctf_description"),
        url=url_for("views.static_html", _external=True),
        name=name,
        password=password,
    )

    subject = safe_format(
        get_config("user_creation_email_subject")
        or DEFAULT_USER_CREATION_EMAIL_SUBJECT,
        ctf_name=get_config("ctf_name"),
    )
    return sendmail(addr=addr, text=text, subject=subject)
Beispiel #23
0
def registration_visible():
    v = get_config("registration_visibility")
    if v == "public":
        return True
    elif v == "private":
        return False
    else:
        return False
Beispiel #24
0
def check_email_is_whitelisted(email_address):
    local_id, _, domain = email_address.partition("@")
    domain_whitelist = get_config("domain_whitelist")
    if domain_whitelist:
        domain_whitelist = [d.strip() for d in domain_whitelist.split(",")]
        if domain not in domain_whitelist:
            return False
    return True
Beispiel #25
0
def accounts_visible():
    v = get_config("account_visibility")
    if v == "public":
        return True
    elif v == "private":
        return authed()
    elif v == "admins":
        return is_admin()
Beispiel #26
0
def sendmail(addr, text, subject="Message from {ctf_name}"):
    subject = safe_format(subject, ctf_name=get_config("ctf_name"))
    provider = get_mail_provider()
    if provider == "smtp":
        return smtp.sendmail(addr, text, subject)
    if provider == "mailgun":
        return mailgun.sendmail(addr, text, subject)
    return False, "No mail settings configured"
Beispiel #27
0
def challenges_visible():
    v = get_config("challenge_visibility")
    if v == "public":
        return True
    elif v == "private":
        return authed()
    elif v == "admins":
        return is_admin()
Beispiel #28
0
def confirm(data=None):
    if not get_config("verify_emails"):
        # If the CTF doesn't care about confirming email addresses then redierct to challenges
        return redirect(url_for("challenges.listing"))

    # User is confirming email account
    if data and request.method == "GET":
        try:
            user_email = unserialize(data, max_age=1800)
        except (BadTimeSignature, SignatureExpired):
            return render_template(
                "confirm.html", errors=["Your confirmation link has expired"])
        except (BadSignature, TypeError, base64.binascii.Error):
            return render_template(
                "confirm.html", errors=["Your confirmation token is invalid"])

        user = Users.query.filter_by(email=user_email).first_or_404()
        if user.verified:
            return redirect(url_for("views.settings"))

        user.verified = True
        log(
            "registrations",
            format="[{date}] {ip} - successful confirmation for {name}",
            name=user.name,
        )
        db.session.commit()
        email.successful_registration_notification(user.email)
        db.session.close()
        if current_user.authed():
            return redirect(url_for("challenges.listing"))
        return redirect(url_for("auth.login"))

    # User is trying to start or restart the confirmation flow
    if not current_user.authed():
        return redirect(url_for("auth.login"))

    user = Users.query.filter_by(id=session["id"]).first_or_404()
    if user.verified:
        return redirect(url_for("views.settings"))

    if data is None:
        if request.method == "POST":
            # User wants to resend their confirmation email
            email.verify_email_address(user.email)
            log(
                "registrations",
                format=
                "[{date}] {ip} - {name} initiated a confirmation email resend",
            )
            return render_template(
                "confirm.html",
                user=user,
                infos=["Your confirmation email has been resent!"],
            )
        elif request.method == "GET":
            # User has been directed to the confirm page
            return render_template("confirm.html", user=user)
Beispiel #29
0
def test_api_config_delete_admin():
    """Can a user delete /api/v1/configs/<config_key> if admin"""
    app = create_kmactf()
    with app.app_context():
        with login_as_user(app, "admin") as admin:
            r = admin.delete("/api/v1/configs/ctf_name", json="")
            assert r.status_code == 200
            assert get_config("ctf_name") is None
    destroy_kmactf(app)
Beispiel #30
0
def test_api_config_patch_admin():
    """Can a user patch /api/v1/configs/<config_key> if admin"""
    app = create_kmactf()
    with app.app_context():
        with login_as_user(app, "admin") as admin:
            r = admin.patch("/api/v1/configs/ctf_name", json={"value": "Changed_Name"})
            assert r.status_code == 200
            assert get_config("ctf_name") == "Changed_Name"
    destroy_kmactf(app)