Beispiel #1
0
def replace_source_tokens(file_path, lookup, out_path):
    """
    Replace tokens in a file and write out the substitution.

    Args:
        file_path: the path of the file to be read.
        lookup: a dictionary containing the substitutions.
        out_path: file in which the substitutions will be written.
    """

    if not path.isfile(file_path):
        raise InternalException("File: {} does not exist.".format(file_path))

    try:
        source_file = open(file_path, "r")
        source = source_file.read()
    except Exception:
        raise InternalException("Unable to read from source file.")

    template = Template(source)

    output = template.safe_substitute(lookup)

    try:
        output_file = open(out_path, "w")
        output_file.write(output)
    except Exception:
        raise InternalException("Unable to write to output file.")
Beispiel #2
0
def get_group(gid=None, name=None, owner_tid=None):
    """
    Retrieve a group based on its name or gid.

    Args:
        name: the name of the group
        gid: the gid of the group
        owner_tid: the tid of the group owner
    Returns:
        The group object.
    """

    db = api.common.get_conn()

    match = {}
    if name is not None and owner_tid is not None:
        match.update({"name": name})
        match.update({"owner": owner_tid})
    elif gid is not None:
        match.update({"gid": gid})
    else:
        raise InternalException(
            "Classroom name and owner or gid must be specified to look up a classroom."
        )

    group = db.groups.find_one(match, {"_id": 0})
    if group is None:
        raise InternalException("Could not find that classroom!")

    return group
Beispiel #3
0
def get_user(name=None, uid=None):
    """
    Retrieve a user based on a property. If the user is logged in,
    it will return that user object.

    Args:
        name: the user's username
        uid: the user's uid
    Returns:
        Returns the corresponding user object or None if it could not be found
    """

    db = api.common.get_conn()

    match = {}

    if uid is not None:
        match.update({'uid': uid})
    elif name is not None:
        match.update({'username': name})
    elif api.auth.is_logged_in():
        match.update({'uid': api.auth.get_uid()})
    else:
        raise InternalException("Uid or name must be specified for get_user")

    user = db.users.find_one(match)

    if user is None:
        raise InternalException("User does not exist")

    return user
Beispiel #4
0
def create_new_team_request(params, uid=None):
    """
    Fulfills new team requests for users who have already registered.

    Args:
        team_name: The desired name for the team. Must be unique across users and teams.
        team_password: The team's password.
    Returns:
        True if successful, exception thrown otherwise.
    """

    user = api.user.get_user(uid=uid)
    if user["teacher"]:
        raise InternalException("Teachers may not create teams!")

    validate(new_team_schema, params)

    current_team = api.team.get_team(tid=user["tid"])

    if current_team["team_name"] != user["username"]:
        raise InternalException(
            "You can only create one new team per user account!")

    desired_tid = create_team({
        "team_name": params["team_name"],
        "password": api.common.hash_password(params["team_password"]),
        "affiliation": current_team["affiliation"],
        "creator": user["uid"],
        "country": user["country"],
    })

    return join_team(params["team_name"], params["team_password"], user["uid"])
Beispiel #5
0
def assign_shell_account(tid=None):
    """
    Assigns a webshell account for the team.

    Args:
        tid: the team id
    """

    db = api.common.get_conn()

    tid = get_team(tid=tid)["tid"]

    if db.ssh.find({"tid": tid}).count() > 0:
        raise InternalException(
            "Team {} was already assigned a shell account.".format(tid))

    if not shell_accounts_available():
        raise InternalException("There are no available shell accounts.")

    db.ssh.update({"tid": {
        "$exists": False
    }}, {"$set": {
        "tid": tid
    }},
                  multi=False)
Beispiel #6
0
def create_user(username, firstname, lastname, email, password_hash, tid,
                teacher=False, country="US", admin=False, verified=False):
    """
    This inserts a user directly into the database. It assumes all data is valid.

    Args:
        username: user's username
        firstname: user's first name
        lastname: user's last name
        email: user's email
        password_hash: a hash of the user's password
        tid: the team id to join
        teacher: whether this account is a teacher
    Returns:
        Returns the uid of the newly created user
    """

    db = api.common.get_conn()
    settings = api.config.get_settings()
    uid = api.common.token()

    if safe_fail(get_user, name=username) is not None:
        raise InternalException("User already exists!")

    max_team_size = api.config.get_settings()["max_team_size"]

    updated_team = db.teams.find_and_modify(
        query={"tid": tid, "size": {"$lt": max_team_size}},
        update={"$inc": {"size": 1}},
        new=True)

    if not updated_team:
        raise InternalException("There are too many users on this team!")

    #All teachers are admins.
    if admin or db.users.count() == 0:
        admin = True
        teacher = True

    user = {
        'uid': uid,
        'firstname': firstname,
        'lastname': lastname,
        'username': username,
        'email': email,
        'password_hash': password_hash,
        'tid': tid,
        'teacher': teacher,
        'admin': admin,
        'disabled': False,
        'country': country,
        'verified': not settings["email"]["email_verification"] or verified,
    }

    db.users.insert(user)

    if settings["email"]["email_verification"] and not user["verified"]:
        api.email.send_user_verification_email(username)

    return uid
Beispiel #7
0
def get_team(tid=None, name=None):
    """
    Retrieve a team based on a property (tid, name, etc.).

    Args:
        tid: team id
        name: team name
    Returns:
        Returns the corresponding team object or None if it could not be found
    """

    db = api.api.common.get_conn()

    match = {}
    if tid is not None:
        match.update({'tid': tid})
    elif name is not None:
        match.update({'team_name': name})
    elif api.auth.is_logged_in():
        match.update({"tid": api.user.get_user()["tid"]})
    else:
        raise InternalException("Must supply tid or team name to get_team")

    team = db.teams.find_one(match, {"_id": 0})

    if team is None:
        raise InternalException("Team does not exist.")

    return team
Beispiel #8
0
def switch_role(gid, tid, role):
    """
    Switch a user's given role in his group.

    Cannot switch to/from owner.
    """

    db = api.common.get_conn()
    team = api.team.get_team(tid=tid)

    roles = get_roles_in_group(gid, tid=team["tid"])
    if role == "member":
        if roles["teacher"] and not roles["member"]:
            db.groups.update({"gid": gid}, {"$pull": {"teachers": tid}, "$push": {"members": tid}})
        else:
            raise InternalException("Team is already a member of that group.")

    elif role == "teacher":
        if roles["member"] and not roles["teacher"]:
            db.groups.update({"gid": gid}, {"$push": {"teachers": tid}, "$pull": {"members": tid}})
        else:
            raise InternalException("Team is already a teacher of that group.")

    else:
        raise InternalException("Only supported roles are member and teacher.")

    for uid in api.team.get_team_uids(tid=team["tid"]):
        sync_teacher_status(tid, uid)
Beispiel #9
0
def get_server(sid=None, name=None):
    """
    Returns the server object corresponding to the sid provided

    Args:
        sid: the server id to lookup

    Returns:
        The server object
    """

    db = api.common.get_conn()

    if sid is None:
        if name is None:
            raise InternalException("You must specify either an sid or name")
        else:
            sid = api.common.hash(name)

    server = db.shell_servers.find_one({"sid": sid})
    if server is None:
        raise InternalException(
            "Server with sid '{}' does not exist".format(sid))

    return server
Beispiel #10
0
def assign_instance_to_team(pid, tid=None, reassign=False):
    """
    Assigns an instance of problem pid to team tid. Updates it in the database.

    Args:
        pid: the problem id
        tid: the team id
        reassign: whether or not we should assign over an old assignment

    Returns:
        The iid that was assigned
    """

    team = api.team.get_team(tid=tid)
    problem = get_problem(pid=pid)

    if pid in team["instances"] and not reassign:
        raise InternalException(
            "Team with tid {} already has an instance of pid {}.".format(
                tid, pid))

    if len(problem["instances"]) == 0:
        raise InternalException(
            "Problem {} has no instances to assign.".format(pid))

    instance_number = randint(0, len(problem["instances"]) - 1)
    iid = problem["instances"][instance_number]["iid"]

    team["instances"][pid] = iid

    db = api.common.get_conn()
    db.teams.update({"tid": tid}, {"$set": team})

    return instance_number
 def wrapper(*args, **kwds):
     if 'token' not in session:
         raise InternalException("CSRF token not in session")
     if 'token' not in request.form:
         raise InternalException("CSRF token not in form")
     if session['token'] != request.form['token']:
         raise InternalException("CSRF token is not correct")
     return f(*args, **kwds)
Beispiel #12
0
def create_user(username, firstname, lastname, email, password_hash, tid, teacher=False,
                background="undefined", country="undefined", receive_ctf_emails=False):
    """
    This inserts a user directly into the database. It assumes all data is valid.

    Args:
        username: user's username
        firstname: user's first name
        lastname: user's last name
        email: user's email
        password_hash: a hash of the user's password
        tid: the team id to join
        teacher: whether this account is a teacher
    Returns:
        Returns the uid of the newly created user
    """

    db = api.common.get_conn()
    uid = api.common.token()

    if safe_fail(get_user, name=username) is not None:
        raise InternalException("User already exists!")

    updated_team = db.teams.find_and_modify(
        query={"tid": tid, "size": {"$lt": api.team.max_team_users}},
        update={"$inc": {"size": 1}},
        new=True)

    if not updated_team:
        raise InternalException("There are too many users on this team!")

    user = {
        'uid': uid,
        'firstname': firstname,
        'lastname': lastname,
        'username': username,
        'email': email,
        'password_hash': password_hash,
        'tid': tid,
        'teacher': teacher,
        'avatar': 3,
        'eventid': 0,
        'disabled': False,
        'level': 'Not Started',
        'background': background,
        'country': country,
        'receive_ctf_emails': receive_ctf_emails
    }

    db.users.insert(user)

    return uid
Beispiel #13
0
def get_server_number(sid):
    """
    Gets the server_number designation from sid
    """
    if sid is None:
        raise InternalException("You must specify a sid")

    server = get_server(sid=sid)
    if server is None:
        raise InternalException(
            "Server with sid '{}' does not exist".format(sid))

    return server.get("server_number")
Beispiel #14
0
def assign_instance_to_team(pid, tid=None, reassign=False):
    """
    Assigns an instance of problem pid to team tid. Updates it in the database.

    Args:
        pid: the problem id
        tid: the team id
        reassign: whether or not we should assign over an old assignment

    Returns:
        The iid that was assigned
    """

    team = api.team.get_team(tid=tid)
    problem = get_problem(pid=pid)

    available_instances = problem["instances"]

    settings = api.config.get_settings()
    if settings["shell_servers"]["enable_sharding"]:
        available_instances = list(
            filter(
                lambda i: i.get("server_number") == team.get(
                    "server_number", 1), problem["instances"]))

    if pid in team["instances"] and not reassign:
        raise InternalException(
            "Team with tid {} already has an instance of pid {}.".format(
                tid, pid))

    if len(available_instances) == 0:
        if settings["shell_servers"]["enable_sharding"]:
            raise InternalException(
                "Your assigned shell server is currently down. Please contact an admin."
            )
        else:
            raise InternalException(
                "Problem {} has no instances to assign.".format(pid))

    instance_number = randint(0, len(available_instances) - 1)
    iid = available_instances[instance_number]["iid"]

    team["instances"][pid] = iid

    db = api.common.get_conn()
    db.teams.update({"tid": tid}, {"$set": team})

    return instance_number
Beispiel #15
0
def clear_submissions(uid=None, tid=None, pid=None):
    """
    Clear submissions for a given team, user, or problems.

    Args:
        uid: the user's uid to clear from.
        tid: the team's tid to clear from.
        pid: the pid to clear from.
    """

    db = api.common.get_conn()

    match = {}


    if pid is not None:
        match.update({"pid", pid})
    elif uid is not None:
        match.update({"uid": uid})
    elif tid is not None:
        match.update({"tid": tid})
    else:
        raise InternalException("You must supply either a tid, uid, or pid")

    return db.submissions.remove(match)
Beispiel #16
0
def send_user_verification_email(username):
    """
    Emails the user a link to verify his account. If email_verification is
    enabled in the config then the user won't be able to login until this step is completed.
    """

    settings = api.config.get_settings()
    db = api.common.get_conn()

    user = api.user.get_user(name=username)

    key_query = {
        "$and": [{
            "uid": user["uid"]
        }, {
            "email_verification_count": {
                "$exists": True
            }
        }]
    }
    previous_key = api.token.find_key(key_query)

    if previous_key is None:
        token_value = api.token.set_token(
            {
                "uid": user["uid"],
                "email_verification_count": 1
            }, "email_verification")
    else:
        if previous_key["email_verification_count"] < settings["email"][
                "max_verification_emails"]:
            token_value = previous_key["tokens"]["email_verification"]
            db.tokens.find_and_modify(
                key_query, {"$inc": {
                    "email_verification_count": 1
                }})
        else:
            raise InternalException(
                "User has been sent the maximum number of verification emails."
            )

    #Is there a better way to do this without dragging url_for + app_context into it?
    verification_link = "{}/api/user/verify?uid={}&token={}".\
        format(api.config.competition_urls[0], user["uid"], token_value)

    body = """
Welcome to {0}!

You will need to visit the verification link below to finalize your account's creation. If you believe this to be a mistake, and you haven't recently created an account for {0} then you can safely ignore this email.

Verification link: {1}

Good luck and have fun!
The {0} Team.
    """.format(api.config.competition_name, verification_link)

    subject = "{} Account Verification".format(api.config.competition_name)

    message = Message(body=body, recipients=[user['email']], subject=subject)
    mail.send(message)
Beispiel #17
0
def create_new_team_request(params, uid=None):
    """
    Fulfills new team requests for users who have already registered.

    Args:
        team_name: The desired name for the team. Must be unique across users and teams.
        team_password: The team's password.
    Returns:
        True if successful, exception thrown elsewise.
    """

    validate(new_team_schema, params)

    user = api.user.get_user(uid=uid)
    current_team = api.team.get_team(tid=user["tid"])

    if current_team["team_name"] != user["username"]:
        raise InternalException(
            "You can only create one new team per user account!")

    desired_tid = create_team({
        "team_name": params["team_name"],
        "password": params["team_password"],
        # The team's affiliation becomes the creator's affiliation.
        "affiliation": current_team["affiliation"],
        "eligible": True
    })

    return join_team(params["team_name"], params["team_password"], user["uid"])
Beispiel #18
0
def get_achievement(aid=None, name=None, show_disabled=False):
    """
    Gets a single achievement

    Args:
        aid: the achievement id
        name: the name of the achievement
        show_disabled: Boolean indicating whether or not to show disabled achievements.
    """

    db = api.common.get_conn()

    match = {}

    if aid is not None:
        match.update({'aid': aid})
    elif name is not None:
        match.update({'name': name})
    else:
        raise InternalException("Must supply aid or display name")

    if not show_disabled:
        match.update({"disabled": False})

    db = api.common.get_conn()
    achievement = db.achievements.find_one(match, {"_id":0})

    if achievement is None:
        raise SevereInternalException("Could not find achievement! You gave " + str(match))

    return achievement
def check_question(question=None, answer=None, data=None):
    data = join_kwargs(data, question=question, answer=answer)
    data['question'] = int(data['question'])
    validate(submission_schema, data)
    num = data.pop('question')
    answer = data.pop('answer')

    question = safe_fail(get_question, num=num)

    if not question:
        raise InternalException('Question not found')

    correct = question['answer'] in answer
    solved = safe_fail(has_solved, question['qid'])

    with get_conn() as cursor:
        uid = session['uid']

        points = question['success'] if correct else -question['failure']

        query = 'INSERT INTO `submissions` (`uid`, `qid`, `answer`, `points`, `correct`) VALUES (%s, %s, %s, %s, %s);'
        args = (uid, question['qid'], answer, points if not solved else 0, 1 if correct else 0)

        cursor.execute(query, args)

        if correct:
            return WebSuccess('Correct!', data={'url': get_next_question_url()})
        else:
            return WebError('Incorrect')
Beispiel #20
0
def get_assigned_server_number(new_team=True, tid=None):
    """
    Assigns a server number based on current teams count and
    configured stepping

    Returns:
         (int) server_number
    """
    settings = api.config.get_settings()["shell_servers"]
    db = api.common.get_conn()

    if new_team:
        team_count = db.teams.count()
    else:
        if not tid:
            raise InternalException("tid must be specified.")
        oid = db.teams.find_one({"tid": tid}, {"_id": 1})
        if not oid:
            raise InternalException("Invalid tid.")
        team_count = db.teams.count({"_id": {"$lt": oid["_id"]}})

    assigned_number = 1

    steps = settings["steps"]

    if steps:
        if team_count < steps[-1]:
            for i, step in enumerate(steps):
                if team_count < step:
                    assigned_number = i + 1
                    break
        else:
            assigned_number = 1 + len(steps) + (
                team_count - steps[-1]) // settings["default_stepping"]

    else:
        assigned_number = team_count // settings["default_stepping"] + 1

    if settings["limit_added_range"]:
        max_number = list(
            db.shell_servers.find({}, {
                "server_number": 1
            }).sort("server_number", -1).limit(1))[0]["server_number"]
        return min(max_number, assigned_number)
    else:
        return assigned_number
Beispiel #21
0
def run_makefile(make_directory):
    """
    Runs a makefile in a given directory.

    Args:
        make_directory: directory where the Makefile is located.
    """

    make_path = path.join(make_directory, "Makefile")

    if not path.isfile(make_path):
        raise InternalException(make_path + " does not exist.")

    shell = spur.LocalShell()

    try:
        shell.run(["make", "-C", make_directory])
    except Exception as e:
        raise InternalException(str(e))
Beispiel #22
0
def get_generator_path(pid):
    """
    Gets a problem generator path.

    Args:
        pid: the problem pid
    Returns:
        The path to the generator.
    """

    problem = api.problem.get_problem(pid=pid)

    if not is_autogen_problem(pid):
        raise InternalException("This problem is not autogenerated.")

    if not problem.get("generator", False):
        raise InternalException("Autogenerated problem '{}' does not have a generator.".format(problem["name"]))

    return path.join(api.problem.grader_base_path, problem["generator"])
Beispiel #23
0
def clear_all_submissions():
    """
    Removes all submissions from the database.
    """

    if DEBUG_KEY is not None:
        db = api.common.get_conn()
        db.submissions.remove()
        api.cache.clear_all()
    else:
        raise InternalException("DEBUG Mode must be enabled")
def has_solved(qid, uid=None):
    if not uid:
        if 'uid' not in session:
            raise InternalException('Need uid to see if solved')
        uid = session['uid']

    with get_conn() as cursor:
        query = 'SELECT 1 FROM `submissions` WHERE `correct` = 1 AND `qid` = %s AND `uid` = %s LIMIT 1;'

        cursor.execute(query, (qid, uid))

        return bool(len(cursor.fetchall()))
Beispiel #25
0
def change_group_settings(gid, settings):
    """
    Replace the current settings with the supplied ones.
    """

    db = api.common.get_conn()

    validate(group_settings_schema, settings)

    group = api.group.get_group(gid=gid)
    if group["settings"]["hidden"] and not settings["hidden"]:
        raise InternalException("You can not change a hidden group back to a public group.")

    db.groups.update({"gid": group["gid"]}, {"$set": {"settings": settings}})
Beispiel #26
0
def get_problem(pid=None, name=None, tid=None, show_disabled=True):
    """
    Gets a single problem.

    Args:
        pid: The problem id
        name: The name of the problem
        show_disabled: Boolean indicating whether or not to show disabled problems. Defaults to True
    Returns:
        The problem dictionary from the database
    """

    db = api.common.get_conn()

    match = {}

    if pid is not None:
        match.update({'pid': pid})
    elif name is not None:
        match.update({'name': name})
    else:
        raise InternalException("Must supply pid or display name")

    if tid is not None and pid not in get_unlocked_pids(tid, category=None):
        raise InternalException("You cannot get this problem")

    if not show_disabled:
        match.update({"disabled": False})

    db = api.common.get_conn()
    problem = db.problems.find_one(match, {"_id": 0})

    if problem is None:
        raise SevereInternalException("Could not find problem! You gave " +
                                      str(match))

    return problem
Beispiel #27
0
def get_processor(aid):
    """
    Returns the processor module for a given achievement.

    Args:
        aid: the achievement id
    Returns:
        The processor module
    """

    try:
        path = get_achievement(aid=aid, show_disabled=True)["processor"]
        return imp.load_source(path[:-3], join(processor_base_path, path))
    except FileNotFoundError:
        raise InternalException("Achievement processor is offline.")
Beispiel #28
0
def verify_user(uid, token_value):
    """
    Verify an unverified user account. Link should have been sent to the user's email.

    Args:
        uid: the user id
        token_value: the verification token value
    Returns:
        True if successful verification based on the (uid, token_value)
    """

    db = api.common.get_conn()

    if uid is None:
        raise InternalException("You must specify a uid.")

    token_user = api.token.find_key_by_token("email_verification", token_value)

    if token_user["uid"] == uid:
        db.users.find_and_modify({"uid": uid}, {"$set": {"verified": True}})
        api.token.delete_token({"uid": uid}, "email_verification")
        return True
    else:
        raise InternalException("This is not a valid token for your user.")
Beispiel #29
0
def get_number_of_instances(pid):
    """
    Gets the number of active instances of a problem.

    Args:
        pid: the problem id
    Returns:
        The number of instances.
    """

    # this is more reliable than before, but it may be a little slow
    try:
        return [dirname.isdigit() for dirname in os.listdir(get_instance_path(pid, public=False))].count(True)
    except FileNotFoundError:
        raise InternalException("Could not find problem instances.")
Beispiel #30
0
def get_grader(pid):
    """
    Returns the grader module for a given problem.

    Args:
        pid: the problem id
    Returns:
        The grader module
    """

    try:
        path = get_problem(pid=pid, show_disabled=True)["grader"]
        return imp.load_source(path[:-3], join(grader_base_path, path))
    except FileNotFoundError:
        raise InternalException("Problem grader for {} is offline.".format(get_problem(pid=pid)['name']))