Esempio n. 1
0
def upsert_bundle(bundle):
    """
    Add or update a bundle.

    Args:
        bundle: bundle dict

    Returns:
        The created/updated bundle ID.

    """
    # Validate the bundle object
    validate(bundle_schema, bundle)

    db = api.db.get_conn()
    bid = api.common.hash("{}-{}".format(bundle["name"], bundle["author"]))

    # If the bundle already exists, update it instead
    existing = db.bundles.find_one({'bid': bid}, {'_id': 0})
    if existing is not None:
        db.bundles.find_one_and_update(
            {'bid': bid},
            {'$set': bundle}
        )
        return bid

    bundle["bid"] = bid
    bundle["dependencies_enabled"] = False
    db.bundles.insert(bundle)
    return bid
Esempio n. 2
0
def upsert_problem(problem, sid):
    """
    Add or update a problem.

    Args:
        problem: problem dict
        sid: shell server ID
    Returns:
        The created/updated problem ID.
    """
    db = api.db.get_conn()

    # Validate the problem object
    # @TODO it may make more sense to do this with e.g. Marshmallow at the
    #       routing level
    validate(problem_schema, problem)
    for instance in problem["instances"]:
        validate(instance_schema, instance)

    problem["pid"] = api.common.hash("{}-{}".format(problem["name"],
                                                    problem["author"]))
    # Initially disable problems
    problem["disabled"] = True

    # Assign instance IDs and server numbers
    server_number = api.shell_servers.get_server(sid)['server_number']
    for instance in problem["instances"]:
        instance["iid"] = api.common.hash(
            str(instance["instance_number"]) + sid + problem["pid"])
        instance["sid"] = sid
        if server_number is not None:
            instance["server_number"] = server_number

    if problem.get("walkthrough"):  # Falsy for None and empty string
        problem["has_walkthrough"] = True
    else:
        problem["has_walkthrough"] = False

    # If the problem already exists, update it instead
    existing = db.problems.find_one({'pid': problem['pid']}, {'_id': 0})
    if existing is not None:
        # Copy over instances on other shell servers from the existing version
        other_server_instances = [
            i for i in existing['instances'] if i['sid'] != sid
        ]
        problem['instances'].extend(other_server_instances)

        # Copy over the disabled state from the old problem, or
        # set to true if there are no instances
        problem["disabled"] = (existing["disabled"]
                               or len(problem["instances"]) == 0)

        db.problems.find_one_and_update({'pid': problem['pid']},
                                        {'$set': problem})
        return problem['pid']

    db.problems.insert(problem)
    return problem['pid']
Esempio n. 3
0
def upsert_feedback(pid, feedback):
    """
    Add or update problem feedback in the database.

    Args:
        pid: the problem id
        feedback: the problem feedback.
    Raises:
        PicoException if provided pid does not exist

    """
    db = api.db.get_conn()

    uid = api.user.get_user()['uid']

    # Make sure the problem actually exists.
    if not api.problem.get_problem(pid, {"pid": 1}):
        raise PicoException('Problem not found', 404)

    team = api.user.get_team(uid=uid)
    solved = pid in api.problem.get_solved_pids(tid=team["tid"])

    validate(feedback_schema, feedback)

    # update feedback if already present
    if get_problem_feedback(pid=pid, uid=uid) != []:
        db.problem_feedback.update({
            "pid": pid,
            "uid": uid
        }, {"$set": {
            "timestamp": datetime.utcnow(),
            "feedback": feedback
        }})
    else:
        db.problem_feedback.insert({
            "pid": pid,
            "uid": uid,
            "tid": team["tid"],
            "solved": solved,
            "timestamp": datetime.utcnow(),
            "feedback": feedback
        })
Esempio n. 4
0
def change_group_settings(gid, settings):
    """
    Replace the current settings with the supplied ones.

    Args:
        gid: ID of the group to update
        settings: new settings object

    Raises:
        PicoException if attempting to change 'hidden' from true to false
    """
    db = api.db.get_conn()

    validate(group_settings_schema, settings)

    group = api.group.get_group(gid=gid)
    if group["settings"]["hidden"] and not settings["hidden"]:
        raise PicoException("Cannot make a previously hidden group public",
                            status_code=422)

    db.groups.update({"gid": group["gid"]}, {"$set": {"settings": settings}})
Esempio n. 5
0
    async def message_handler(self, text, message_id):
        if not api.validate(text):
            logging.debug('invalid url received: {0}'.format(text))
            await self.reply(replies.INVALID_URL, message_id)
            return

        await self.reply(replies.UPLOAD_STARTED, message_id)
        video_data = api.download(text, config['tmp_path'])
        video_vk_url = api.upload_video(vk_api, video_data.path,
                                        video_data.name)
        os.remove(video_data.path)

        logging.info("uploaded: {0} -> {1}".format(text, video_vk_url))
        await self.reply(replies.UPLOADED_VIDEO.format(video_vk_url),
                         message_id)
Esempio n. 6
0
def submit_key(tid, pid, key, method, uid, ip=None):
    """
    User problem submission.

    Args:
        tid: user's team id
        pid: problem's pid
        key: answer text
        method: submission method (e.g. 'game')
        uid: user's uid
        ip: user's ip
    Returns:
        tuple: (correct, previously_solved_by_user,
                previously_solved_by_team)
    """
    db = api.db.get_conn()
    validate(submission_schema, {"tid": tid, "pid": pid, "key": key})

    if pid not in api.problem.get_unlocked_pids(tid):
        raise PicoException(
            "You can't submit flags to problems you haven't unlocked.", 422)

    previously_solved_by_user = db.submissions.find_one(filter={
        'pid': pid,
        'uid': uid,
        'correct': True
    }) is not None

    previously_solved_by_team = db.submissions.find_one(filter={
        'pid': pid,
        'tid': tid,
        'correct': True
    }) is not None

    correct = grade_problem(pid, key, tid)

    if not previously_solved_by_user:
        db.submissions.insert({
            'uid':
            uid,
            'tid':
            tid,
            'timestamp':
            datetime.utcnow(),
            'pid':
            pid,
            'ip':
            ip,
            'key':
            key,
            'method':
            method,
            'category':
            api.problem.get_problem(pid)['category'],
            'correct':
            correct,
        })

    if correct and not previously_solved_by_team:
        # Immediately invalidate some caches
        cache.invalidate(api.stats.get_score, tid)
        cache.invalidate(api.stats.get_score, uid)
        cache.invalidate(api.problem.get_unlocked_pids, tid)
        cache.invalidate(api.problem.get_solved_problems,
                         tid=tid,
                         uid=uid,
                         category=None)
        cache.invalidate(api.problem.get_solved_problems,
                         tid=tid,
                         uid=None,
                         category=None)
        cache.invalidate(api.problem.get_solved_problems, tid=tid, uid=uid)
        cache.invalidate(api.problem.get_solved_problems, tid=tid)
        cache.invalidate(api.problem.get_solved_problems, uid=uid)
        cache.invalidate(api.stats.get_score_progression,
                         tid=tid,
                         category=None)
        cache.invalidate(api.stats.get_score_progression, tid=tid)

        # @TODO achievement processing needs to be fixed/reviewed
        # api.achievement.process_achievements("submit", {
        #     "uid": uid,
        #     "tid": tid,
        #     "pid": pid
        # })

    return (correct, previously_solved_by_user, previously_solved_by_team)
Esempio n. 7
0
def submit_key(tid, pid, key, method, uid, ip=None):
    """
    User problem submission.

    Args:
        tid: user's team id
        pid: problem's pid
        key: answer text
        method: submission method (e.g. 'game')
        uid: user's uid
        ip: user's ip
    Returns:
        tuple: (correct, previously_solved_by_user,
                previously_solved_by_team)
    """
    db = api.db.get_conn()
    validate(submission_schema, {"tid": tid, "pid": pid, "key": key})

    if pid not in api.problem.get_unlocked_pids(tid):
        raise PicoException(
            "You can't submit flags to problems you haven't unlocked.", 422)

    previously_solved_by_user = (db.submissions.find_one(filter={
        "pid": pid,
        "uid": uid,
        "correct": True
    }) is not None)

    previously_solved_by_team = (db.submissions.find_one(filter={
        "pid": pid,
        "tid": tid,
        "correct": True
    }) is not None)

    correct, suspicious = grade_problem(pid, key, tid)

    if not previously_solved_by_user:
        db.submissions.insert({
            "uid":
            uid,
            "tid":
            tid,
            "timestamp":
            datetime.utcnow(),
            "pid":
            pid,
            "ip":
            ip,
            "key":
            key,
            "method":
            method,
            "category":
            api.problem.get_problem(pid, {"category": 1})["category"],
            "correct":
            correct,
            "suspicious":
            suspicious,
        })

    if correct and not previously_solved_by_team:
        # Immediately invalidate some caches
        cache.invalidate(api.stats.get_score, tid)
        cache.invalidate(api.stats.get_score, uid)
        cache.invalidate(api.problem.get_unlocked_pids, tid)
        cache.invalidate(api.problem.get_solved_problems,
                         tid=tid,
                         uid=uid,
                         category=None)
        cache.invalidate(api.problem.get_solved_problems,
                         tid=tid,
                         uid=None,
                         category=None)
        cache.invalidate(api.problem.get_solved_problems, tid=tid, uid=uid)
        cache.invalidate(api.problem.get_solved_problems, tid=tid)
        cache.invalidate(api.problem.get_solved_problems, uid=uid)
        cache.invalidate(api.stats.get_score_progression,
                         tid=tid,
                         category=None)
        cache.invalidate(api.stats.get_score_progression, tid=tid)

    if suspicious:
        cache.invalidate(api.submissions.get_suspicious_submissions, tid)

    return (correct, previously_solved_by_user, previously_solved_by_team)
Esempio n. 8
0
def submit_key(tid, pid, key, method, uid, ip=None):
    """
    User problem submission.

    Args:
        tid: user's team id
        pid: problem's pid
        key: answer text
        method: submission method (e.g. 'game')
        uid: user's uid
        ip: user's ip
    Returns:
        tuple: (correct, previously_solved_by_user,
                previously_solved_by_team)
    """
    db = api.db.get_conn()
    validate(submission_schema, {"tid": tid, "pid": pid, "key": key})

    if pid not in api.problem.get_unlocked_pids(tid):
        raise PicoException(
            "You can't submit flags to problems you haven't unlocked.", 422
        )

    previously_solved_by_user = (
        db.submissions.find_one(filter={"pid": pid, "uid": uid, "correct": True})
        is not None
    )

    previously_solved_by_team = (
        db.submissions.find_one(filter={"pid": pid, "tid": tid, "correct": True})
        is not None
    )

    correct, suspicious = grade_problem(pid, key, tid)

    if not previously_solved_by_user:
        count_solves = api.stats.get_problem_solves(pid)
        bonus = 10 - count_solves
        db.submissions.insert(
            {
                "uid": uid,
                "tid": tid,
                "timestamp": datetime.utcnow(),
                "pid": pid,
                "ip": ip,
                "key": key,
                "method": method,
                "category": api.problem.get_problem(pid, {"category": 1})["category"],
                "correct": correct,
                "suspicious": suspicious,
                "bonus": bonus,
            }
        )

    if correct and not previously_solved_by_team:
        # Immediately invalidate some caches
        cache.invalidate(api.stats.get_score, tid)
        cache.invalidate(api.stats.get_score, uid)
        cache.invalidate(api.problem.get_unlocked_pids, tid)
        cache.invalidate(
            api.problem.get_solved_problems, tid=tid, uid=uid, category=None
        )
        cache.invalidate(
            api.problem.get_solved_problems, tid=tid, uid=None, category=None
        )
        cache.invalidate(api.problem.get_solved_problems, tid=tid, uid=uid)
        cache.invalidate(api.problem.get_solved_problems, tid=tid)
        cache.invalidate(api.problem.get_solved_problems, uid=uid)
        cache.invalidate(api.stats.get_score_progression, tid=tid, category=None)
        cache.invalidate(api.stats.get_score_progression, tid=tid)

    # if the solve is correct there is no need to maintain the container
    if correct:
        instance = api.problem.get_instance_data(pid, tid)
        if "docker_challenge" in instance and instance["docker_challenge"]:
            containers = api.docker.submission_to_cid(tid, pid)
            for c in containers:
                cid = c["cid"]
                api.docker.delete(cid)

    if suspicious:
        cache.invalidate(api.submissions.get_suspicious_submissions, tid)

    return (correct, previously_solved_by_user, previously_solved_by_team)
Esempio n. 9
0
def upsert_problem(problem, sid):
    """
    Add or update a problem.

    Args:
        problem: problem dict
        sid: shell server ID
    Returns:
        The created/updated problem ID.
    """
    db = api.db.get_conn()

    # Validate the problem object
    # @TODO it may make more sense to do this with e.g. Marshmallow at the
    #       routing level
    validate(problem_schema, problem)
    for instance in problem["instances"]:
        validate(instance_schema, instance)

    problem["pid"] = problem["unique_name"]

    # Initially disable problems
    problem["disabled"] = True

    # Assign instance IDs and server numbers
    server_number = api.shell_servers.get_server(sid)["server_number"]
    for instance in problem["instances"]:
        instance["iid"] = api.common.hash(
            str(instance["instance_number"]) + sid + problem["pid"]
        )
        instance["sid"] = sid
        if server_number is not None:
            instance["server_number"] = server_number

    # Docker Instance tracking
    # XXX: also track port information and TTL
    digests = []
    for i in problem["instances"]:
        if "docker_challenge" in i and i["docker_challenge"]:
            digests.append(i["instance_digest"])

            try:
                docker_pub = current_app.config["DOCKER_PUB"]
            except KeyError:
                raise PicoException("Attempted to load a DockerChallenge but DOCKER_PUB not configured")

            # update port display style with docker host value
            for p, v in i["port_info"].items():
                v["fmt"] = v["fmt"].format(host=docker_pub)

    # track problem to image information for docker instances
    pid = problem["pid"]
    if len(digests) > 0:
        data = {"pid": pid, "digests": digests}
        db.images.update({"pid": pid}, data, upsert=True)

    if problem.get("walkthrough"):  # Falsy for None and empty string
        problem["has_walkthrough"] = True
    else:
        problem["has_walkthrough"] = False

    # If the problem already exists, update it instead
    existing = db.problems.find_one({"pid": problem["pid"]}, {"_id": 0})
    if existing is not None:
        # Copy over instances on other shell servers from the existing version
        other_server_instances = [i for i in existing["instances"] if i["sid"] != sid]
        problem["instances"].extend(other_server_instances)

        # Copy over the disabled state from the old problem, or
        # set to true if there are no instances
        problem["disabled"] = existing["disabled"] or len(problem["instances"]) == 0

        db.problems.find_one_and_update({"pid": problem["pid"]}, {"$set": problem})
        return problem["pid"]

    db.problems.insert(problem)
    return problem["pid"]