Esempio n. 1
0
def create_group(tid, group_name):
    """
    Insert group into the db. Assumes everything is validated.

    Args:
        tid: The id of the team creating the group.
        group_name: The name of the group.
    Returns:
        The new group's gid.

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

    gid = api.common.token()

    db.groups.insert({
        "name": group_name,
        "owner": tid,
        "teachers": [],
        "members": [],
        "settings": {
            "email_filter": [],
            "hidden": False
        },
        "gid": gid,
    })
    cache.invalidate(api.team.get_groups, tid)

    return gid
Esempio n. 2
0
def elevate_team(gid, tid):
    """
    Elevate a team within a group.

    Args:
        tid: the team id
        gid: the group id to elevate to teacher status
    """
    db = api.db.get_conn()
    db.groups.update({"gid": gid}, {"$pull": {"members": tid}})
    db.groups.update({"gid": gid}, {"$addToSet": {"teachers": tid}})
    cache.invalidate(api.team.get_groups, tid)
Esempio n. 3
0
def leave_group(gid, tid):
    """
    Remove a team from a group.

    Args:
        tid: the team id
        gid: the group id to leave
    """
    db = api.db.get_conn()
    db.groups.update({"gid": gid}, {"$pull": {"teachers": tid}})
    db.groups.update({"gid": gid}, {"$pull": {"members": tid}})
    cache.invalidate(api.team.get_groups, tid)
Esempio n. 4
0
def join_group(gid, tid, teacher=False):
    """
    Add a team to a group. Assumes everything is valid.

    Args:
        tid: the team id
        gid: the group id to join
        teacher: whether or not the user is a teacher
    """
    db = api.db.get_conn()

    role_group = "teachers" if teacher else "members"

    if teacher:
        uids = api.team.get_team_uids(tid=tid)
        for uid in uids:
            db.users.update({"uid": uid}, {"$set": {"teacher": True}})

    db.groups.update({"gid": gid}, {"$addToSet": {role_group: tid}})
    cache.invalidate(api.team.get_groups, tid)
Esempio n. 5
0
def join_team(team_name, password, user):
    """
    Switch a user from their individual team to a proper team.

    You can not use this to freely switch between teams.

    Args:
        team_name: The name of the team to join
        password: The new team's password
        user: The user
    Returns:
        ID of the new team
    """
    current_team = api.user.get_team(uid=user["uid"])
    desired_team = api.team.get_team(name=team_name)

    if current_team["team_name"] != user["username"]:
        raise PicoException(
            "You can not switch teams once you have joined one.", 403)

    # Make sure the password is correct and there is room on the team
    max_team_size = api.config.get_settings()["max_team_size"]
    if desired_team['size'] >= max_team_size:
        raise PicoException('That team is already at maximum capacity.', 403)
    if not api.user.confirm_password(password, desired_team["password"]):
        raise PicoException(
            'That is not the correct password to join that team.', 403)

    # Join the new team
    db = api.db.get_conn()
    user_team_update = db.users.find_and_modify(
        query={
            "uid": user["uid"],
            "tid": current_team["tid"]
        },
        update={"$set": {
            "tid": desired_team["tid"]
        }},
        new=True)

    if not user_team_update:
        raise PicoException("There was an issue switching your team!")

    # Update the sizes of the old and new teams
    db.teams.find_one_and_update({"tid": desired_team["tid"]},
                                 {"$inc": {
                                     "size": 1
                                 }})

    db.teams.find_one_and_update({"tid": current_team["tid"]},
                                 {"$inc": {
                                     "size": -1
                                 }})

    # If country is no longer consistent amongst members, set as mixed
    if user["country"] != desired_team["country"]:
        db.teams.update({"tid": desired_team["tid"]},
                        {"$set": {
                            "country": "??"
                        }})

    # Remove old team from any groups and attempt to add new team
    previous_groups = get_groups(current_team['tid'])
    for group in previous_groups:
        api.group.leave_group(gid=group["gid"], tid=current_team["tid"])
        # Rejoin with new tid if not already member, and classroom
        # email filter is not enabled.
        if (desired_team['tid'] not in group['members']
                and desired_team['tid'] not in group['teachers']):
            group_settings = api.group.get_group_settings(gid=group["gid"])
            if not group_settings["email_filter"]:
                api.group.join_group(gid=group["gid"], tid=desired_team["tid"])

    # Immediately invalidate some caches

    cache.invalidate(api.stats.get_score, desired_team['tid'])
    cache.invalidate(api.stats.get_score, user['uid'])
    cache.invalidate(api.problem.get_unlocked_pids, desired_team['tid'])
    cache.invalidate(api.problem.get_solved_problems,
                     tid=desired_team['tid'],
                     uid=user['uid'],
                     category=None)
    cache.invalidate(api.problem.get_solved_problems,
                     tid=desired_team['tid'],
                     uid=None,
                     category=None)
    cache.invalidate(api.problem.get_solved_problems,
                     tid=desired_team['tid'],
                     uid=user['uid'])
    cache.invalidate(api.problem.get_solved_problems, tid=desired_team['tid'])
    cache.invalidate(api.problem.get_solved_problems, uid=user['uid'])
    cache.invalidate(api.stats.get_score_progression,
                     tid=desired_team['tid'],
                     category=None)
    cache.invalidate(api.stats.get_score_progression, tid=desired_team['tid'])

    return desired_team['tid']
Esempio n. 6
0
def join_team(team_name, password, user):
    """
    Switch a user from their individual team to a proper team.

    You can not use this to freely switch between teams.

    Args:
        team_name: The name of the team to join
        password: The new team's password
        user: The user
    Returns:
        ID of the new team
    """
    current_team = api.user.get_team(uid=user["uid"])
    desired_team = api.team.get_team(name=team_name)
    desired_team_info = api.team.get_team_information(desired_team['tid'])

    if current_team["team_name"] != user["username"]:
        raise PicoException(
            "You can not switch teams once you have joined one.", 403)

    # Make sure the password is correct and there is room on the team
    max_team_size = api.config.get_settings()["max_team_size"]
    if desired_team['size'] >= max_team_size:
        raise PicoException('That team is already at maximum capacity.', 403)
    if not api.user.confirm_password(password, desired_team["password"]):
        raise PicoException(
            'That is not the correct password to join that team.', 403)

    # Update the team's eligibilities
    if desired_team_info['size'] == 0:
        updated_eligible_scoreboards = [
            scoreboard for scoreboard in api.scoreboards.get_all_scoreboards()
            if api.scoreboards.is_eligible(user, scoreboard)
        ]
    else:
        currently_eligible_scoreboards = [
            api.scoreboards.get_scoreboard(sid)
            for sid in desired_team_info['eligibilities']
        ]
        updated_eligible_scoreboards = [
            scoreboard for scoreboard in currently_eligible_scoreboards
            if api.scoreboards.is_eligible(user, scoreboard)
        ]
        lost_eligibilities = [
            scoreboard for scoreboard in currently_eligible_scoreboards
            if scoreboard not in updated_eligible_scoreboards
        ]
        if (len(lost_eligibilities) > 0 and
                not desired_team_info.get('allow_ineligible_members', False)):
            raise PicoException(
                'You cannot join this team as doing so would make it ' +
                'ineligible for the {} scoreboard.'.format(
                    lost_eligibilities[0]["name"]), 403)

    # Join the new team
    db = api.db.get_conn()
    user_team_update = db.users.find_and_modify(
        query={
            "uid": user["uid"],
            "tid": current_team["tid"]
        },
        update={"$set": {
            "tid": desired_team["tid"]
        }},
        new=True)

    if not user_team_update:
        raise PicoException("There was an issue switching your team!")

    # Update the eligiblities of the new team
    db.teams.find_one_and_update({"tid": desired_team["tid"]}, {
        "$set": {
            "eligibilities": [s['sid'] for s in updated_eligible_scoreboards]
        }
    })

    # Update the sizes of the old and new teams
    db.teams.find_one_and_update({"tid": desired_team["tid"]},
                                 {"$inc": {
                                     "size": 1
                                 }})

    db.teams.find_one_and_update({"tid": current_team["tid"]},
                                 {"$inc": {
                                     "size": -1
                                 }})

    # Remove old team from any groups and attempt to add new team
    previous_groups = get_groups(current_team['tid'])
    for group in previous_groups:
        api.group.leave_group(gid=group["gid"], tid=current_team["tid"])
        # Rejoin with new tid if not already member, and classroom
        # email filter is not enabled.
        if (desired_team['tid'] not in group['members']
                and desired_team['tid'] not in group['teachers']):
            group_settings = api.group.get_group_settings(gid=group["gid"])
            if not group_settings["email_filter"]:
                api.group.join_group(gid=group["gid"], tid=desired_team["tid"])

    # Immediately invalidate some caches
    cache.invalidate(api.stats.get_score, desired_team['tid'])
    cache.invalidate(api.stats.get_score, user['uid'])
    cache.invalidate(api.problem.get_unlocked_pids, desired_team['tid'])
    cache.invalidate(api.problem.get_solved_problems,
                     tid=desired_team['tid'],
                     uid=user['uid'],
                     category=None)
    cache.invalidate(api.problem.get_solved_problems,
                     tid=desired_team['tid'],
                     uid=None,
                     category=None)
    cache.invalidate(api.problem.get_solved_problems,
                     tid=desired_team['tid'],
                     uid=user['uid'])
    cache.invalidate(api.problem.get_solved_problems, tid=desired_team['tid'])
    cache.invalidate(api.problem.get_solved_problems, uid=user['uid'])
    cache.invalidate(api.stats.get_score_progression,
                     tid=desired_team['tid'],
                     category=None)
    cache.invalidate(api.stats.get_score_progression, tid=desired_team['tid'])

    return desired_team['tid']
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 = 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. 8
0
def disable_account(uid, disable_reason=None):
    """
    Note: The original disable account has now been updated to perform delete account instead.

    Disables a user account.

    Disabled user accounts can't login or consume space on a team.

    Args:
        uid: ID of the user to disable
        disable_reason (optional): Reason to inform user about in email
    """
    if disable_reason is None:
        disable_reason = "Not specified"

    db = api.db.get_conn()
    user = api.user.get_user(uid=uid)
    api.email.send_deletion_notification(
        user["username"], user["email"], disable_reason
    )

    db.users.find_one_and_update(
        {"uid": uid, "disabled": False},
        {
            "$set": {
                "disabled": True,
                "firstname": "",
                "lastname": "",
                "email": "",
                "country": "",
                "demo": {},
                "disable_reason": disable_reason,
            }
        },
    )

    # Drop them from their team
    former_tid = api.user.get_team(uid=uid)["tid"]
    db.teams.find_one_and_update(
        {"tid": former_tid, "size": {"$gt": 0}}, {"$inc": {"size": -1}}
    )

    # Drop empty team from groups
    former_team = db.teams.find_one({"tid": former_tid})
    if former_team["size"] == 0:
        groups = api.team.get_groups(tid=former_tid)
        for group in groups:
            api.group.leave_group(gid=group["gid"], tid=former_tid)

    # Clean up cache
    cache.invalidate(api.team.get_groups, former_tid)
    cache.invalidate(api.stats.get_score, former_tid)
    cache.invalidate(api.stats.get_score, uid)
    cache.invalidate(api.problem.get_unlocked_pids, former_tid)
    cache.invalidate(
        api.problem.get_solved_problems, tid=former_tid, uid=uid, category=None
    )
    cache.invalidate(
        api.problem.get_solved_problems, tid=former_tid, uid=None, category=None
    )
    cache.invalidate(api.problem.get_solved_problems, tid=former_tid, uid=uid)
    cache.invalidate(api.problem.get_solved_problems, tid=former_tid)
    cache.invalidate(api.problem.get_solved_problems, uid=uid)
    cache.invalidate(api.stats.get_score_progression, tid=former_tid, category=None)
    cache.invalidate(api.stats.get_score_progression, tid=former_tid)
Esempio n. 9
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. 10
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)