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
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)
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)
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)
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']
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']
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)
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)
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)
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)