def create_team_action(request): """ Create a team for the context's user. An administrator can also perform the action on a user's behalf. """ # Create the team. now = datetime.utcnow() user_id = request.context.user_id user = load_user(request.db, user_id) # Select a round based on the user's badges. round_ids = find_round_ids_with_badges(request.db, user['badges'], now) if len(round_ids) == 0: # The user does not have access to any open round. raise ApiError('not qualified for any open round') if len(round_ids) > 1: # XXX The case where a user has badges for multiple open rounds # is currently handled by picking the first one, which is the # one that has the greatest id. This is unsatisfactory. pass round_id = round_ids[0] round_ = load_round(request.db, round_id, now) if not round_['is_registration_open']: raise ApiError('registration is closed') # Create the team. team_id = create_user_team(request.db, user_id, now) # Create a participation. create_participation(request.db, team_id, round_id, now=now) # Ensure the user gets team credentials. reset_user_principals(request) return {'success': True}
def add_badge_action(request): data = request.json_body user_id = request.context.user_id user = load_user(request.db, user_id) foreign_id = user['foreign_id'] access_token = get_oauth2_token(request, refresh=True) params = { 'badgeUrl': app['requested_badge'], 'idUser': foreign_id, 'qualCode': data.get('code') } headers = { 'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(access_token) } req = requests.post(app['add_badge_uri'], headers=headers, data=params, verify='/etc/ssl/certs/ca-certificates.crt') req.raise_for_status() result = req.json() print("\033[91mresult\033[0m {}".format(result)) success = result.get('success') if not success: return { 'success': False, 'profileUpdated': False, 'error': result.get('error', 'undefined') } profileUpdated = False profile = get_user_profile(request, foreign_id) if profile is not None: update_user(request.db, user['id'], profile) profileUpdated = True return {'success': True, 'profileUpdated': profileUpdated}
def leave_team(db, user_id, team_id, now): """ Remove a user from their team. """ user = load_user(db, user_id, for_update=True) team_id = user['team_id'] # The user must be member of a team. if team_id is None: raise ModelError('no team') team = load_team(db, team_id) # Load the participation. participation_id = get_team_latest_participation_id(db, team_id) participation = load_participation(db, participation_id) round_id = participation['round_id'] round_ = load_round(db, round_id) # If the team already has an attempt (is_locked=True), verify # that the team remains valid if the user is removed. if team['is_locked']: if round_['allow_team_changes']: validate_team(db, team_id, round_id, without_member=user, now=now) else: raise ModelError('team is locked') # Clear the user's team_id. set_user_team_id(db, user_id, None) # Delete the team_members row. team_members = db.tables.team_members tm_query = db.query(team_members) \ .where(team_members.team_id == team_id) \ .where(team_members.user_id == user_id) (is_creator,) = db.first( tm_query.fields(team_members.is_creator)) db.delete(tm_query) # If the user was the team creator, select the earliest member # as the new creator. if is_creator: query = db.query(team_members) \ .where(team_members.team_id == team_id) row = db.first( query.fields(team_members.user_id) .order_by(team_members.joined_at), for_update=True) if row is None: # Team has become empty, delete it. teams = db.tables.teams team_query = db.query(teams) \ .where(teams.id == team_id) db.delete(team_query) else: # Promote the selected user as the creator. new_creator_id = row[0] db.update( query.where(team_members.user_id == new_creator_id), {team_members.is_creator: True})
def create_user_team(db, user_id, now): # Check that the user does not already belong to a team. user = load_user(db, user_id) if user['team_id'] is not None: # User is already in a team. raise ModelError('already in a team') # Create an empty team. team_id = create_empty_team(db, now) # Create the team_members row. add_team_member( db, team_id, user_id, now=now, is_qualified=True, is_creator=True) return team_id
def leave_team(db, user_id, team_id, now): """ Remove a user from their team. """ user = load_user(db, user_id, for_update=True) team_id = user['team_id'] # The user must be member of a team. if team_id is None: raise ModelError('no team') team = load_team(db, team_id) # Load the participation. participation_id = get_team_latest_participation_id(db, team_id) participation = load_participation(db, participation_id) round_id = participation['round_id'] round_ = load_round(db, round_id) # If the team already has an attempt (is_locked=True), verify # that the team remains valid if the user is removed. if team['is_locked']: if round_['allow_team_changes']: validate_team(db, team_id, round_id, without_member=user, now=now) else: raise ModelError('team is locked') # Clear the user's team_id. set_user_team_id(db, user_id, None) # Delete the team_members row. team_members = db.tables.team_members tm_query = db.query(team_members) \ .where(team_members.team_id == team_id) \ .where(team_members.user_id == user_id) (is_creator, ) = db.first(tm_query.fields(team_members.is_creator)) db.delete(tm_query) # If the user was the team creator, select the earliest member # as the new creator. if is_creator: query = db.query(team_members) \ .where(team_members.team_id == team_id) row = db.first(query.fields(team_members.user_id).order_by( team_members.joined_at), for_update=True) if row is None: # Team has become empty, delete it. teams = db.tables.teams team_query = db.query(teams) \ .where(teams.id == team_id) db.delete(team_query) else: # Promote the selected user as the creator. new_creator_id = row[0] db.update(query.where(team_members.user_id == new_creator_id), {team_members.is_creator: True})
def create_user_team(db, user_id, now): # Check that the user does not already belong to a team. user = load_user(db, user_id) if user['team_id'] is not None: # User is already in a team. raise ModelError('already in a team') # Create an empty team. team_id = create_empty_team(db, now) # Create the team_members row. add_team_member(db, team_id, user_id, now=now, is_qualified=True, is_creator=True) return team_id
def join_team(db, user_id, team_id, now): """ Add a user to a team. Registration for the team's round must be open. Return a boolean indicating if the team member was added. """ # Verify that the user does not already belong to a team. user = load_user(db, user_id, for_update=True) if user['team_id'] is not None: raise ModelError('already in a team') # Verify that the team exists. team = load_team(db, team_id) # Verify that the team is open. if not team['is_open']: # Team is closed (by its creator). raise ModelError('team is closed') # Load the participation. participation_id = get_team_latest_participation_id(db, team_id) participation = load_participation(db, participation_id) round_id = participation['round_id'] # Look up the badges that grant access to the team's round, to # figure out whether the user is qualified for that round. user_badges = user['badges'] badges = db.tables.badges if len(user_badges) == 0: is_qualified = False else: query = db.query(badges) \ .where(badges.round_id == round_id) \ .where(badges.symbol.in_(user_badges)) \ .where(badges.is_active) \ .fields(badges.id) is_qualified = db.scalar(query) is not None # If the team has already accessed a task instance (is_locked=True), # verify that the team remains valid if the user is added. if team['is_locked']: round_ = load_round(round_id) if round_['allow_team_changes']: user['is_qualified'] = is_qualified validate_team(db, team_id, round_id, with_member=user, now=now) else: raise ModelError('team is locked') # Create the team_members row. user_id = user['id'] add_team_member(db, team_id, user_id, now=now, is_qualified=is_qualified) # Update the user's team_id. set_user_team_id(db, user_id, team_id)
def update_team_action(request): user_id = request.context.user_id user = load_user(request.db, user_id) team_id = user['team_id'] if team_id is None: raise ApiError('no team') # If the user is not an admin, they must be the team's creator. if request.by_admin: if user_id != get_team_creator(request.db, team_id): raise ApiError('not team creator') # The creator can only change some settings. allowed_keys = ['is_open'] body = request.json_body settings = {key: body[key] for key in allowed_keys} else: # Admins can change all settings. settings = request.json_body update_team(request.db, team_id, settings) return {'success': True}
def view_requesting_user( db, user_id=None, participation_id=None, attempt_id=None, is_admin=False): now = datetime.utcnow() view = { 'now': now, 'is_admin': is_admin } if user_id is None: return view # # Add the user. # user = load_user(db, user_id) if user is None: return view view['user_id'] = user_id view['user'] = view_user(user) team_id = user['team_id'] # # Quick return path when the user has no team. # if team_id is None: # If the user has no team, we look for a round to which a # badge grants access. badges = user['badges'] round_ids = find_round_ids_with_badges(db, badges, now) if len(round_ids) > 0: # TODO: resolve this somehow, for example by returning # the round views to the user and letting them choose. # For now, pick the first one (which has the greatest id). round_id = round_ids[0] round_ = load_round(db, round_id, now) view['round'] = view_round(round_) return view # # Add the team and team members. # team = load_team(db, team_id) members = load_team_members(db, team['id'], users=True) team_view = view['team'] = view_team(team, members) # # Add the team's participations. # participations = load_team_participations(db, team_id) round_ids = set() for participation in participations: round_ids.add(participation['round_id']) rounds = load_rounds(db, round_ids, now) view['participations'] = [ view_team_participation( participation, rounds[participation['round_id']]) for participation in participations ] if len(participations) == 0: return view # Mark the lastest (or selected) participation as current. if participation_id is None: participation = participations[-1] else: participation = get_by_id(participations, participation_id) if participation is None: return view view['participation_id'] = participation['id'] for pview in view['participations']: if pview['id'] == participation['id']: pview['is_current'] = True # # Add the current participation's round. # round_id = participation['round_id'] round_ = rounds[round_id] view['round'] = view_round(round_) # # Add the tasks for the current round. # round_tasks = load_round_tasks(db, round_id) view['round']['task_ids'] = [str(rt['id']) for rt in round_tasks] round_task_views = view['round_tasks'] = { str(rt['id']): view_round_task(rt) for rt in round_tasks } region_id = team['region_id'] if round_['status'] == 'closed' and team['region_id'] is not None: region = load_region(db, region_id) national_count = count_teams_in_round(db, round_id) big_region_count = count_teams_in_round_big_region( db, round_id, region['big_region_code']) region_count = count_teams_in_round_region(db, round_id, region_id) view['ranking'] = { 'national': { 'rank': participation['rank_national'], 'count': national_count }, 'big_region': { 'name': region['big_region_name'], 'rank': participation['rank_big_regional'], 'count': big_region_count }, 'region': { 'name': region['name'], 'rank': participation['rank_regional'], 'count': region_count } } # XXX A team's validity should be checked against settings for a # competition rather than a round. causes = validate_members_for_round(members, round_) team_view['round_access'] = list(causes.keys()) team_view['is_invalid'] = len(causes) != 0 # Do not return attempts if the team is invalid. if team_view['is_invalid']: return view # Load the participation attempts. attempts = load_participation_attempts(db, participation['id'], now) view_task_attempts(attempts, round_task_views) print("attempts {} {}".format(attempt_id, attempts)) # Find the requested attempt. current_attempt = get_by_id(attempts, attempt_id) if current_attempt is None: return view view['attempt_id'] = attempt_id # Focus on the current attempt. current_round_task = round_task_views[str(current_attempt['round_task_id'])] current_attempt_view = None for attempt_view in current_round_task['attempts']: if attempt_id == attempt_view.get('id'): current_attempt_view = attempt_view view['attempt'] = current_attempt_view view['round_task'] = current_round_task # XXX duplicates attempts :( if False: # Access codes are disabled members_view = view['team']['members'] access_codes = load_unlocked_access_codes(db, attempt_id) add_members_access_codes(members_view, access_codes) if current_attempt['is_training']: needs_codes = not have_one_code(members_view) else: needs_codes = not have_code_majority(members_view) current_attempt_view['needs_codes'] = needs_codes # Add task instance data, if available. try: # XXX Previously load_task_instance_team_data which did not parse # full_data. # /!\ task contains sensitive data # XXX If the round is closed, load and pass full_data? task_instance = load_user_task_instance(db, attempt_id) except ModelError: return view # If the round has a time limit, return the countdown. if round_['duration'] is not None: started_at = participation['started_at'] if started_at is not None: duration = timedelta(minutes=round_['duration']) countdown = started_at + duration view['countdown'] = started_at + duration if countdown < now: return view view['team_data'] = task_instance['team_data'] # Add a list of the workspace revisions for this attempt. add_revisions(db, view, attempt_id) # Give the user the id of their latest revision for the # current attempt, to be loaded into the crypto tab on # first access. revision_id = load_user_latest_revision_id( db, user_id, attempt_id) view['my_latest_revision_id'] = revision_id return view
def view_requesting_user(db, user_id=None, participation_id=None, attempt_id=None, is_admin=False): now = datetime.utcnow() view = {'now': now, 'is_admin': is_admin} if user_id is None: return view # # Add the user. # user = load_user(db, user_id) if user is None: return view view['user_id'] = user_id view['user'] = view_user(user) team_id = user['team_id'] # # Quick return path when the user has no team. # if team_id is None: # If the user has no team, we look for a round to which a # badge grants access. badges = user['badges'] round_ids = find_round_ids_with_badges(db, badges, now) if len(round_ids) > 0: # TODO: resolve this somehow, for example by returning # the round views to the user and letting them choose. # For now, pick the first one (which has the greatest id). round_id = round_ids[0] round_ = load_round(db, round_id, now) view['round'] = view_round(round_) return view # # Add the team and team members. # team = load_team(db, team_id) members = load_team_members(db, team['id'], users=True) team_view = view['team'] = view_team(team, members) # # Add the team's participations. # participations = load_team_participations(db, team_id) round_ids = set() for participation in participations: round_ids.add(participation['round_id']) rounds = load_rounds(db, round_ids, now) view['participations'] = [ view_team_participation(participation, rounds[participation['round_id']]) for participation in participations ] if len(participations) == 0: return view # Mark the lastest (or selected) participation as current. if participation_id is None: participation = participations[-1] else: participation = get_by_id(participations, participation_id) if participation is None: return view view['participation_id'] = participation['id'] for pview in view['participations']: if pview['id'] == participation['id']: pview['is_current'] = True # # Add the current participation's round. # round_id = participation['round_id'] round_ = rounds[round_id] view['round'] = view_round(round_) # # Add the tasks for the current round. # round_tasks = load_round_tasks(db, round_id) view['round']['task_ids'] = [str(rt['id']) for rt in round_tasks] round_task_views = view['round_tasks'] = { str(rt['id']): view_round_task(rt) for rt in round_tasks } region_id = team['region_id'] if round_['status'] == 'closed' and team['region_id'] is not None: region = load_region(db, region_id) national_count = count_teams_in_round(db, round_id) big_region_count = count_teams_in_round_big_region( db, round_id, region['big_region_code']) region_count = count_teams_in_round_region(db, round_id, region_id) view['ranking'] = { 'national': { 'rank': participation['rank_national'], 'count': national_count }, 'big_region': { 'name': region['big_region_name'], 'rank': participation['rank_big_regional'], 'count': big_region_count }, 'region': { 'name': region['name'], 'rank': participation['rank_regional'], 'count': region_count } } # XXX A team's validity should be checked against settings for a # competition rather than a round. causes = validate_members_for_round(members, round_) team_view['round_access'] = list(causes.keys()) team_view['is_invalid'] = len(causes) != 0 # Do not return attempts if the team is invalid. if team_view['is_invalid']: return view # Load the participation attempts. attempts = load_participation_attempts(db, participation['id'], now) view_task_attempts(attempts, round_task_views) print("attempts {} {}".format(attempt_id, attempts)) # Find the requested attempt. current_attempt = get_by_id(attempts, attempt_id) if current_attempt is None: return view view['attempt_id'] = attempt_id # Focus on the current attempt. current_round_task = round_task_views[str( current_attempt['round_task_id'])] current_attempt_view = None for attempt_view in current_round_task['attempts']: if attempt_id == attempt_view.get('id'): current_attempt_view = attempt_view view['attempt'] = current_attempt_view view['round_task'] = current_round_task # XXX duplicates attempts :( if False: # Access codes are disabled members_view = view['team']['members'] access_codes = load_unlocked_access_codes(db, attempt_id) add_members_access_codes(members_view, access_codes) if current_attempt['is_training']: needs_codes = not have_one_code(members_view) else: needs_codes = not have_code_majority(members_view) current_attempt_view['needs_codes'] = needs_codes # Add task instance data, if available. try: # XXX Previously load_task_instance_team_data which did not parse # full_data. # /!\ task contains sensitive data # XXX If the round is closed, load and pass full_data? task_instance = load_user_task_instance(db, attempt_id) except ModelError: return view # If the round has a time limit, return the countdown. if round_['duration'] is not None: started_at = participation['started_at'] if started_at is not None: duration = timedelta(minutes=round_['duration']) countdown = started_at + duration view['countdown'] = started_at + duration if countdown < now: return view view['team_data'] = task_instance['team_data'] # Add a list of the workspace revisions for this attempt. add_revisions(db, view, attempt_id) # Give the user the id of their latest revision for the # current attempt, to be loaded into the crypto tab on # first access. revision_id = load_user_latest_revision_id(db, user_id, attempt_id) view['my_latest_revision_id'] = revision_id return view