Beispiel #1
0
def update_password_request(params, uid=None, check_current=False):
    """
    Update account password.
    Assumes args are keys in params.

    Args:
        uid: uid to reset
        check_current: whether to ensure that current-password is correct
        params:
            current-password: the users current password
            new-password: the new password
            new-password-confirmation: confirmation of password
    """

    user = get_user(uid=uid)

    if check_current and not api.auth.confirm_password(params["current-password"], user['password_hash']):
        raise WebException("Your current password is incorrect.")

    if params["new-password"] != params["new-password-confirmation"]:
        raise WebException("Your passwords do not match.")

    if len(params["new-password"]) == 0:
        raise WebException("Your password cannot be empty.")

    update_password(user['uid'], params["new-password"])
Beispiel #2
0
def login(username, password):
    """
    Authenticates a user.
    """

    # Read in submitted username and password
    validate(user_login_schema, {"username": username, "password": password})

    user = safe_fail(api.user.get_user, name=username)
    if user is None:
        raise WebException("Incorrect username.")

    if user.get("disabled", False):
        raise WebException("This account has been disabled.")

    if confirm_password(password, user['password_hash']):
        if debug_disable_general_login:
            if session.get('debugaccount', False):
                raise WebException(
                    "Correct credentials! But the game has not started yet...")
        if user['uid'] is not None:
            session['uid'] = user['uid']
            session.permanent = True
        else:
            raise WebException("Login Error")
    else:
        raise WebException("Incorrect Password")
Beispiel #3
0
def join_group_request(params, tid=None):
    """
    Tries to place a team into a group. Validates forms.
    All required arguments are assumed to be keys in params.

    Args:
        group-name: The name of the group to join.
        group-owner: The name of the owner of the group
        Optional:
            tid: If omitted,the tid will be grabbed from the logged in user.
    """

    owner_uid = api.user.get_user(name=params["group-owner"])["uid"]

    validate(join_group_schema, params)
    if safe_fail(get_group, name=params["group-name"],
                 owner_uid=owner_uid) is None:
        raise WebException("No class exists with that name!")

    group = get_group(name=params["group-name"], owner_uid=owner_uid)

    if tid is None:
        tid = api.user.get_team()["tid"]

    if tid in group['members']:
        raise WebException("Your team is already a member of that class!")

    join_group(tid, group["gid"])
Beispiel #4
0
def insert_achievement(achievement):
    """
    Inserts an achievement object into the database.

    Args:
        achievement: the achievement object loaded from json.
    Returns:
        The achievment's aid.
    """

    db = api.common.get_conn()
    validate(achievement_schema, achievement)

    achievement["disabled"] = achievement.get("disabled", False)

    achievement["aid"] = api.common.hash(achievement["name"])

    if safe_fail(get_achievement, aid=achievement["aid"]) is not None:
        raise WebException("achievement with identical aid already exists.")


    if safe_fail(get_achievement, name=achievement["name"]) is not None:
        raise WebException("achievement with identical name already exists.")

    db.achievements.insert(achievement)
    api.cache.fast_cache.clear()

    return achievement["aid"]
Beispiel #5
0
 def check_keys(real, changed):
     keys = list(changed.keys())
     for key in keys:
         if key not in real:
             raise WebException("Cannot update setting for '{}'".format(key))
         elif type(real[key]) != type(changed[key]):
             raise WebException("Cannot update setting for '{}'".format(key))
         elif isinstance(real[key], dict):
             check_keys(real[key], changed[key])
             # change the key so mongo $set works correctly
             for key2 in changed[key]:
                 changed["{}.{}".format(key,key2)] = changed[key][key2]
             changed.pop(key)
Beispiel #6
0
def insert_problem(problem):
    """
    Inserts a problem into the database. Does sane validation.

    Args:
        Problem dict.
        score: points awarded for completing the problem.
        category: problem's category
        description: description of the problem.
        grader: path relative to grader_base_path
        threshold: Amount of points necessary for a team to unlock this problem.

        Optional:
        disabled: True or False. Defaults to False.
        hint: hint for completing the problem.
        tags: list of problem tags.
        relatedproblems: list of related problems.
        weightmap: problem's unlock weightmap
        autogen: Whether or not the problem will be auto generated.
    Returns:
        The newly created problem id.
    """

    db = api.common.get_conn()
    validate(problem_schema, problem)

    problem["disabled"] = problem.get("disabled", False)

    problem["pid"] = api.common.hash(problem["name"])

    weightmap = {}

    if problem.get("weightmap"):
        for name, weight in problem["weightmap"].items():
            name_hash = api.common.hash(name)
            weightmap[name_hash] = weight

    problem["weightmap"] = weightmap

    if safe_fail(get_problem, pid=problem["pid"]) is not None:
        raise WebException("Problem with identical pid already exists.")

    if safe_fail(get_problem, name=problem["name"]) is not None:
        raise WebException("Problem with identical name already exists.")

    db.problems.insert(problem)
    api.cache.fast_cache.clear()

    return problem["pid"]
Beispiel #7
0
def create_group_request(params, uid=None):
    """
    Creates a new group. Validates forms.
    All required arguments are assumed to be keys in params.

    Args:
        group-name: The name of the group

        Optional:
            uid: The uid of the user creating the group. If omitted,
            the uid will be grabbed from the logged in user.
    Returns:
        The new gid
    """

    if uid is None:
        uid = api.user.get_user()["uid"]

    validate(register_group_schema, params)

    if safe_fail(get_group, name=params["group-name"],
                 owner_uid=uid) is not None:
        raise WebException("A class with that name already exists!")

    return create_group(uid, params["group-name"])
Beispiel #8
0
def update_problem(pid, updated_problem):
    """
    Updates a problem with new properties.

    Args:
        pid: the pid of the problem to update.
        updated_problem: an updated problem object.
    Returns:
        The updated problem object.
    """

    db = api.common.get_conn()

    if updated_problem.get("name", None) is not None:
        if safe_fail(get_problem, name=updated_problem["name"]) is not None:
            raise WebException("Problem with identical name already exists.")

    problem = get_problem(pid=pid, show_disabled=True).copy()
    problem.update(updated_problem)

    # pass validation by removing/readding pid
    problem.pop("pid", None)
    validate(problem_schema, problem)
    problem["pid"] = pid



    db.problems.update({"pid": pid}, problem)
    api.cache.fast_cache.clear()

    return problem
Beispiel #9
0
def delete_group_hook():
    """
    Tries to delete a group. Validates forms.
    All required arguments are assumed to be keys in params.
    """

    params = api.common.flat_multi(request.form)

    validate(delete_group_schema, params)

    if params.get("group-owner"):
        owner_team = api.team.get_team(name=params["group-owner"])
    else:
        owner_team = api.team.get_team()

    group = api.group.get_group(name=params["group-name"],
                                owner_tid=owner_team["tid"])

    user = api.user.get_user()
    roles = api.group.get_roles_in_group(group["gid"], uid=user["uid"])

    if roles["owner"]:
        api.group.delete_group(group["gid"])
    else:
        raise WebException("Only the owner of a group can delete it!")

    return WebSuccess("Successfully deleted group")
Beispiel #10
0
def update_achievement(aid, updated_achievement):
    """
    Updates a achievement with new properties.

    Args:
        aid: the aid of the achievement to update.
        updated_achievement: an updated achievement object.
    Returns:
        The updated achievement object.
    """

    db = api.common.get_conn()

    if updated_achievement.get("name", None) is not None:
        if safe_fail(get_achievement, name=updated_achievement["name"]) is not None:
            raise WebException("Achievement with identical name already exists.")

    achievement = get_achievement(aid=aid, show_disabled=True).copy()
    achievement.update(updated_achievement)

    # pass validation by removing/readding aid
    achievement.pop("aid")
    validate(achievement_schema, achievement)
    achievement["aid"] = aid

    db.achievements.update({"aid": aid}, achievement)
    api.cache.fast_cache.clear()

    return achievement
Beispiel #11
0
def delete_group_request(params, uid=None):
    """
    Tries to delete a group. Validates forms.
    All required arguments are assumed to be keys in params.

    Args:
        group-name: The name of the group to join.
        Optional:
            uid: If omitted, the uid will be grabbed from the logged in user.
    """

    validate(delete_group_schema, params)

    if uid is None:
        uid = api.user.get_user()['uid']

    if safe_fail(get_group, name=params['group-name'], owner_uid=uid) is None:
        raise WebException("No class exists with that name!")

    if uid is None:
        uid = api.user.get_user()["uid"]

    group = get_group(name=params["group-name"], owner_uid=uid)

    delete_group(group['gid'])
Beispiel #12
0
def update_server(sid, params):
    """
    Update a shell server from the pool of servers.

    Args:
        sid: The sid of the server to update
        params: A dict containing:
            port
            username
            password
    """

    db = api.common.get_conn()

    validate(server_schema, params)

    server = safe_fail(get_server, sid=sid)
    if server is None:
        raise WebException(
            "Shell server with sid '{}' does not exist.".format(sid))

    params["name"] = server["name"]

    validate(server_schema, params)

    if isinstance(params["port"], str):
        params["port"] = int(params["port"])

    db.shell_servers.update({"sid": server["sid"]}, {"$set": params})
Beispiel #13
0
def request_password_reset(username):
    """
    Emails a user a link to reset their password.

    Checks that a username was submitted to the function and grabs the relevant team info from the db.
    Generates a secure token and inserts it into the team's document as 'password_reset_token'.
    A link is emailed to the registered email address with the random token in the url.  The user can go to this
    link to submit a new password, if the token submitted with the new password matches the db token the password
    is hashed and updated in the db.

    Args:
        username: the username of the account
    """

    validate(password_reset_request_schema, {"username": username})
    user = safe_fail(api.user.get_user, name=username)
    if user is None:
        raise WebException("No registration found for '{}'.".format(username))

    token = api.common.token()
    api.user.set_password_reset_token(user['uid'], token)

    msgBody = """We recently received a request to reset the password for the following {0} account:\n\n\t{2}\n\nOur records show that this is the email address used to register the above account.  If you did not request to reset the password for the above account then you need not take any further steps.  If you did request the password reset please follow the link below to set your new password. \n\n {1}/reset#{3} \n\n Best of luck! \n\n ~The {0} Team
    """.format(api.config.competition_name, api.config.competition_urls[0],
               username, token)

    send_email(user['email'],
               "{} Password Reset".format(api.config.competition_name),
               msgBody)
Beispiel #14
0
def add_server(params):
    """
    Add a shell server to the pool of servers.

    Args:
        params: A dict containing:
            host
            port
            username
            password
    Returns:
       The sid.
    """

    db = api.common.get_conn()

    validate(server_schema, params)

    if isinstance(params["port"], str):
        params["port"] = int(params["port"])

    if safe_fail(get_server, name=params["name"]) is not None:
        raise WebException("Shell server with this name already exists")

    params["sid"] = api.common.hash(params["name"])
    db.shell_servers.insert(params)

    return params["sid"]
Beispiel #15
0
def ensure_setup(shell):
    """
    Runs sanity checks on the shell connection to ensure that
    shell_manager is set up correctly.
    """

    result = shell.run(["sudo", "shell_manager", "status"], allow_error=True)
    if result.return_code == 1 and "command not found" in result.stderr_output.decode("utf-8"):
        raise WebException("shell_manager not installed on server.")
Beispiel #16
0
def login(username=None, password=None, data=None):
    data = join_kwargs(data, username=username, password=password)

    validate(login_schema, data)

    username = data.pop('username')
    password = data.pop('password')

    user = safe_fail(get_user, name=username)

    if user is not None and confirm_password(password, user['password_hash']):
        if user['uid'] is not None:
            session['uid'] = user['uid']
            session.permanent = True
        else:
            raise WebException('Login error')
    else:
        raise WebException('Username or password incorrect')
Beispiel #17
0
def insert_problem(problem, sid=None):
    """
    Inserts a problem into the database. Does sane validation.

    Args:
        Problem dict.
        score: points awarded for completing the problem.
        category: problem's category
        author: author of the problem
        description: description of the problem.

        Optional:
        version: version of the problem
        tags: list of problem tags.
        hints: hints for completing the problem.
        organization: Organization that author is associated with
    Returns:
        The newly created problem id.
    """

    db = api.common.get_conn()
    validate(problem_schema, problem)

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

    for instance in problem["instances"]:
        validate(instance_schema, instance)

    set_instance_ids(problem, sid)

    if safe_fail(get_problem, pid=problem["pid"]) is not None:
        # problem is already inserted, so update instead
        old_problem = copy(get_problem(pid=problem["pid"]))

        # leave all instances from different shell server
        instances = list(filter(lambda i: i["sid"] != sid, old_problem["instances"]))

        # add instances from this shell server
        instances.extend(problem["instances"])
        problem["instances"] = instances

        # disable problems with zero instances
        problem["disabled"] = old_problem["disabled"] or len(problem["instances"]) == 0

        # run the update
        update_problem(problem["pid"], problem)
        return

    if safe_fail(get_problem, name=problem["name"]) is not None:
        raise WebException("Problem with identical name \"{}\" already exists.".format(problem["name"]))

    db.problems.insert(problem)
    api.cache.fast_cache.clear()

    return problem["pid"]
Beispiel #18
0
def join_group_hook():
    """
    Tries to place a team into a group. Validates forms.
    All required arguments are assumed to be keys in params.
    """

    params = api.common.flat_multi(request.form)
    validate(join_group_schema, params)

    owner_team = safe_fail(api.team.get_team, name=params["group-owner"])

    if not owner_team:
        raise WebException("No teacher exists with that name!")

    if safe_fail(api.group.get_group,
                 name=params["group-name"],
                 owner_tid=owner_team["tid"]) is None:
        raise WebException("No classroom exists with that name!")

    group = api.group.get_group(name=params["group-name"],
                                owner_tid=owner_team["tid"])

    group_settings = api.group.get_group_settings(gid=group["gid"])

    team = api.team.get_team()

    if group_settings["email_filter"]:
        for member_uid in api.team.get_team_uids(tid=team["tid"]):
            member = api.user.get_user(uid=member_uid)
            if not api.user.verify_email_in_whitelist(
                    member["email"], group_settings["email_filter"]):
                raise WebException(
                    "{}'s email does not belong to the whitelist for that classroom. Your team may not join this classroom at this time."
                    .format(member["username"]))

    roles = api.group.get_roles_in_group(group["gid"], tid=team["tid"])
    if roles["teacher"] or roles["member"]:
        raise WebException("Your team is already a member of that classroom!")

    api.group.join_group(group["gid"], team["tid"])

    return WebSuccess("Successfully joined classroom")
Beispiel #19
0
def find_user_by_reset_token(token):
    """
    Searches the database for a team with the given password reset token
    """

    db = api.common.get_conn()
    user = db.users.find_one({'password_reset_token': token})

    if user is None:
        raise WebException("That is not a valid password reset token!")

    return user
Beispiel #20
0
def get_bundle(bid):
    """
    Returns the bundle object corresponding to the given bid
    """

    db = api.common.get_conn()

    bundle = db.bundles.find_one({"bid": bid})

    if bundle is None:
        raise WebException("Bundle with bid {} does not exist".format(bid))

    return bundle
Beispiel #21
0
def remove_server(sid):
    """
    Remove a shell server from the pool of servers.

    Args:
        sid: the sid of the server to be removed
    """

    db = api.common.get_conn()

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

    db.shell_servers.remove({"sid": sid})
Beispiel #22
0
def update_password_request(params):
    """
    Update team password.
    Assumes args are keys in params.

    Args:
        params:
            new-password: the new password
            new-password-confirmation: confirmation of password
    """

    validate(update_team_schema, params)
    user = api.user.get_user()
    current_team = api.team.get_team(tid=user["tid"])
    if current_team["team_name"] == user["username"]:
        raise WebException("You have not created a team yet.")

    if params["new-password"] != params["new-password-confirmation"]:
        raise WebException("Your team passwords do not match.")

    if len(params["new-password"]) == 0:
        raise WebException("Your team password cannot be empty.")

    update_password(user["tid"], params["new-password"])
Beispiel #23
0
def set_problem_availability(pid, disabled):
    """
    Updates a problem's availability.

    Args:
        pid: the problem's pid
        disabled: whether or not the problem should be disabled.
    Returns:
        The updated problem object.
    """

    problem = api.problem.get_problem(pid=pid)
    if len(problem["instances"]) > 0:
        result = api.problem.update_problem(pid, {"disabled": disabled})
        api.cache.clear_all()
    else:
        raise WebException("You cannnot change the availability of \"{}\".".format(problem["name"]))
    return result
Beispiel #24
0
def update_bundle(bid, updates):
    """
    Updates the bundle object in the database with the given updates.
    """

    db = api.common.get_conn()

    bundle = db.bundles.find_one({"bid": bid}, {"_id": 0})
    if bundle is None:
        raise WebException("Bundle with bid {} does not exist".format(bid))

    # pop the bid temporarily to check with schema
    bid = bundle.pop("bid")
    bundle.update(updates)
    validate(bundle_schema, bundle)
    bundle["bid"] = bid

    db.bundles.update({"bid": bid}, {"$set": bundle})
Beispiel #25
0
def login(username, password):
    """
    Authenticates a user.
    """

    # Read in submitted username and password
    validate(user_login_schema, {"username": username, "password": password})

    user = safe_fail(api.user.get_user, name=username)
    if user is None:
        raise WebException("Incorrect username.")

    if user.get("disabled", False):
        raise WebException("This account has been disabled.")

    if not user["verified"]:
        raise WebException("This account has not been verified yet.")

    if confirm_password(password, user['password_hash']):
        if not user["verified"]:
            try:
                api.email.send_user_verification_email(username)
                raise WebException(
                    "This account is not verified. An additional email has been sent to {}."
                    .format(user["email"]))
            except InternalException as e:
                raise WebException(
                    "You have hit the maximum number of verification emails. Please contact support."
                )

        if debug_disable_general_login:
            if session.get('debugaccount', False):
                raise WebException(
                    "Correct credentials! But the game has not started yet...")
        if user['uid'] is not None:
            session['uid'] = user['uid']
            session.permanent = True
        else:
            raise WebException("Login Error")
    else:
        raise WebException("Incorrect password")
Beispiel #26
0
def create_user(username=None, password=None, data=None):
    data = join_kwargs(data, username=username, password=password)

    validate(login_schema, data)

    username = data.pop('username')
    password = data.pop('password')

    uid = api.common.token()

    with get_conn() as cursor:
        query = 'INSERT INTO `users` (`uid`, `username`, `password_hash`) VALUES (%s, %s, %s);'

        try:
            cursor.execute(query, (uid, username, hash_password(password)))
        except pymysql.err.IntegrityError as e:
            raise WebException('User already exists')

        return uid
Beispiel #27
0
def create_group_hook():
    """
    Creates a new group. Validates forms.
    All required arguments are assumed to be keys in params.
    """

    params = api.common.flat_multi(request.form)
    validate(register_group_schema, params)

    # Current user is the prospective owner.
    team = api.user.get_team()

    if safe_fail(api.group.get_group,
                 name=params["group-name"],
                 owner_tid=team["tid"]) is not None:
        raise WebException("A group with that name already exists!")

    gid = api.group.create_group(team["tid"], params["group-name"])
    return WebSuccess("Successfully created group.", data=gid)
Beispiel #28
0
def disable_account_request(params, uid=None, check_current=False):
    """
    Disable user account so they can't login or consume space on a team.
    Assumes args are keys in params.

    Args:
        uid: uid to reset
        check_current: whether to ensure that current-password is correct
        params:
            current-password: the users current password
    """

    user = get_user(uid=uid)

    if check_current and not api.auth.confirm_password(params["current-password"], user['password_hash']):
        raise WebException("Your current password is incorrect.")
    disable_account(user['uid'])

    api.auth.logout()
Beispiel #29
0
def load_published(data):
    """
    Load in the problems from the shell_manager publish blob.

    Args:
        data: The output of "shell_manager publish"
    """

    if "problems" not in data:
        raise WebException("Please provide a problems list in your json.")

    for problem in data["problems"]:
        insert_problem(problem, sid=data["sid"])

    if "bundles" in data:
        db = api.common.get_conn()
        for bundle in data["bundles"]:
            insert_bundle(bundle)

    api.cache.clear_all()
Beispiel #30
0
def get_connection(host, port, username, password):
    """
    Attempts to connect to the given server and
    returns a connection.
    """

    try:
        shell = spur.SshShell(hostname=host,
                              username=username,
                              password=password,
                              port=port,
                              missing_host_key=spur.ssh.MissingHostKey.accept,
                              connect_timeout=10)
        shell.run(["echo", "connected"])
    except spur.ssh.ConnectionError as e:
        raise WebException(
            "Cannot connect to {}@{}:{} with the specified password".format(
                username, host, port))

    return shell
Beispiel #31
0
def request_hint(tid, pid, uid=None, ip=None):
    """
    User hint request. Problem hint requests are inserted into the database.

    Args:
        tid: user's team id
        pid: problem's pid
        uid: user's uid
    Returns:
        A dict.
        points: number of points deducted
        hint: message returned from the grader.
    """

    db = api.common.get_conn()
    validate(hint_request_schema, {"tid": tid, "pid": pid})

    if pid not in get_unlocked_pids(tid):
        raise InternalException("You can't request hints to problems you haven't unlocked.")

    if pid in get_solved_pids(tid=tid):
        exp = WebException("You have already solved this problem.")
        exp.data = {'code': 'solved'}
        raise exp

    user = api.user.get_user(uid=uid)
    if user is None:
        raise InternalException("User submitting flag does not exist.")

    uid = user["uid"]

    problem = get_problem(pid=pid)

    grader = get_grader(pid)
    hints = grader.get_hints()

    if not hints :
        exp = WebException("No hints available for this problem... good luck!")
        exp.data = {'code': 'repeat'}
        raise exp

    hint_requests = get_hint_requests(pid=pid, tid=tid)

    eligibility = api.team.get_team(tid=tid)['eligible']

    if len(hint_requests) >= len(hints) :
        exp = WebException("You've already requested all available hints.")
        exp.data = {'code': 'repeat'}
        raise exp

    new_hint_points = hints[len(hint_requests)][0]
    new_hint = hints[len(hint_requests)][1]

    hint_request = {
        'uid': uid,
        'tid': tid,
        'timestamp': datetime.utcnow(),
        'hint': new_hint,
        'points_deducted': new_hint_points,
        'ip': ip,
        'eligible': eligibility,
        'pid': pid,
        'category': problem['category']
    }

    db.hint_requests.insert(hint_request)

    api.cache.invalidate_memoization(api.stats.get_score, {"kwargs.tid":tid}, {"kwargs.uid":uid})
    api.cache.invalidate_memoization(get_unlocked_pids, {"args":tid})
    api.cache.invalidate_memoization(get_solved_pids, {"kwargs.tid":tid}, {"kwargs.uid":uid})

    api.cache.invalidate_memoization(api.stats.get_score_progression, {"kwargs.tid":tid}, {"kwargs.uid":uid})

    ret = WebSuccess(new_hint)
    ret.data = {'points': new_hint_points, 'hint': new_hint }
    return ret
Beispiel #32
0
def submit_key(tid, pid, key, uid=None, ip=None):
    """
    User problem submission. Problem submission is inserted into the database.

    Args:
        tid: user's team id
        pid: problem's pid
        key: answer text
        uid: user's uid
    Returns:
        A dict.
        correct: boolean
        points: number of points the problem is worth.
        message: message returned from the grader.
    """

    db = api.common.get_conn()
    validate(submission_schema, {"tid": tid, "pid": pid, "key": key})

    if pid not in get_unlocked_pids(tid):
        raise InternalException("You can't submit flags to problems you haven't unlocked.")

    if pid in get_solved_pids(tid=tid):
        exp = WebException("You have already solved this problem.")
        exp.data = {'code': 'solved'}
        raise exp

    user = api.user.get_user(uid=uid)
    if user is None:
        raise InternalException("User submitting flag does not exist.")

    uid = user["uid"]

    result = grade_problem(pid, key, tid)

    problem = get_problem(pid=pid)

    eligibility = api.team.get_team(tid=tid)['eligible']

    submission = {
        'uid': uid,
        'tid': tid,
        'timestamp': datetime.utcnow(),
        'pid': pid,
        'ip': ip,
        'key': key,
        'eligible': eligibility,
        'category': problem['category'],
        'correct': result['correct']
    }

    if (key, pid) in [(submission["key"], submission["pid"]) for submission in  get_submissions(tid=tid)]:
        exp = WebException("You or one of your teammates has already tried this solution.")
        exp.data = {'code': 'repeat'}
        raise exp

    db.submissions.insert(submission)

    if submission["correct"]:
        api.cache.invalidate_memoization(api.stats.get_score, {"kwargs.tid":tid}, {"kwargs.uid":uid})
        api.cache.invalidate_memoization(get_unlocked_pids, {"args":tid})
        api.cache.invalidate_memoization(get_solved_pids, {"kwargs.tid":tid}, {"kwargs.uid":uid})

        api.cache.invalidate_memoization(api.stats.get_score_progression, {"kwargs.tid":tid}, {"kwargs.uid":uid})

        api.achievement.process_achievements("submit", {"uid": uid, "tid": tid, "pid": pid})

    return result