Example #1
0
    def choose_contest(self):
        """Fill self.contest using contest passed as argument or path.

        If a contest was specified as argument to CWS, fill
        self.contest with that; otherwise extract it from the URL path.

        """
        if self.is_multi_contest():
            # Choose the contest found in the path argument
            # see: https://github.com/tornadoweb/tornado/issues/1673
            contest_name = self.path_args[0]

            # Select the correct contest or return an error
            self.contest = self.sql_session.query(Contest)\
                .filter(Contest.name == contest_name).first()
            if self.contest is None:
                self.contest = Contest(
                    name=contest_name, description=contest_name)
                # render_params in this class assumes the contest is loaded,
                # so we cannot call it without a fully defined contest. Luckily
                # the one from the base class is enough to display a 404 page.
                self.r_params = super().render_params()
                raise tornado.web.HTTPError(404)
        else:
            # Select the contest specified on the command line
            self.contest = Contest.get_from_id(
                self.service.contest_id, self.sql_session)
Example #2
0
    def choose_contest(self):
        """Fill self.contest using contest passed as argument or path.

        If a contest was specified as argument to CWS, fill
        self.contest with that; otherwise extract it from the URL path.

        """
        if self.is_multi_contest():
            # Choose the contest found in the path argument
            # see: https://github.com/tornadoweb/tornado/issues/1673
            contest_name = self.path_args[0]

            # Select the correct contest or return an error
            self.contest = self.sql_session.query(Contest)\
                .filter(Contest.name == contest_name).first()
            if self.contest is None:
                self.contest = Contest(name=contest_name,
                                       description=contest_name)
                # render_params in this class assumes the contest is loaded,
                # so we cannot call it without a fully defined contest. Luckily
                # the one from the base class is enough to display a 404 page.
                super().prepare()
                self.r_params = super().render_params()
                raise tornado.web.HTTPError(404)
        else:
            # Select the contest specified on the command line
            self.contest = Contest.get_from_id(self.service.contest_id,
                                               self.sql_session)
Example #3
0
    def post(self):
        fallback_page = self.url("contests", "add")

        try:
            attrs = dict()

            self.get_string(attrs, "name", empty=None)
            assert attrs.get("name") is not None, "No contest name specified."
            attrs["description"] = attrs["name"]

            # Create the contest.
            contest = Contest(**attrs)

            # Add the default group
            group = Group(name="default")
            contest.groups.append(group)
            contest.main_group = group

            self.sql_session.add(contest)

        except Exception as error:
            self.service.add_notification(make_datetime(), "Invalid field(s)",
                                          repr(error))
            self.redirect(fallback_page)
            return

        if self.try_commit():
            # Create the contest on RWS.
            self.service.proxy_service.reinitialize()
            self.redirect(self.url("contest", contest.id))
        else:
            self.redirect(fallback_page)
Example #4
0
    def _makecontest(self):
        """
        Return a Contest object which can be saved to the database.

        return (Contest): database object for the contest

        """
        if self.defaultgroup is None:
            raise Exception("You have to specify a default group")
        cdb = Contest(name=self.contestname, description=self._description)

        cdb.timezone = self._timezone
        cdb.allowed_localizations = self._allowed_localizations
        cdb.languages = self._languages
        self._set_tokens(cdb)
        cdb.max_submission_number = self.max_submission_number
        cdb.min_submission_interval = self.min_submission_interval
        cdb.max_user_test_number = self.max_user_test_number
        cdb.min_user_test_interval = self.min_user_test_interval

        self.usersdb = {}
        self.participationsdb = {}

        self.cdb = cdb

        gdbs = {}
        for g in self.groups:
            gdbs[g] = self._makegroup(g, cdb)
        cdb.main_group = gdbs[self.defaultgroup.name]

        return cdb
Example #5
0
    def search_jobs_not_done(self):
        """Sweep the database and search for work to do.

        Iterate over all submissions and look if they are in a suitable
        status to be sent (scored and not hidden) but, for some reason,
        haven't been sent yet (that is, their ID doesn't appear in the
        *_sent_to_rankings sets). In case, arrange for them to be sent.

        """
        logger.info("Going to search for unsent subchanges.")

        job_count = 0

        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            for submission in contest.get_submissions():
                if submission.user.hidden:
                    continue

                if submission.get_result().scored() and \
                        submission.id not in self.scores_sent_to_rankings:
                    self.send_score(submission)
                    job_count += 1

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    self.send_token(submission)
                    job_count += 1

        logger.info("Found %d unsent subchanges." % job_count)
Example #6
0
def harvest_contest_data(contest_id):
    """Retrieve the couples username, password and the task list for a
    given contest.

    contest_id (int): the id of the contest we want.
    return (tuple): the first element is a dictionary mapping
                    usernames to passwords; the second one is the list
                    of the task names.

    """
    users = {}
    tasks = []
    with SessionGen() as session:
        contest = Contest.get_from_id(contest_id, session)
        for participation in contest.participations:
            user = participation.user
            # Pick participation's password if present, or the user's.
            password_source = participation.password
            if password_source is None:
                password_source = user.password
            # We can log in only if we know the plaintext password.
            method, password = parse_authentication(password_source)
            if method != "plaintext":
                print("Not using user %s with non-plaintext password."
                      % user.username)
                continue
            users[user.username] = {'password': password}
        for task in contest.tasks:
            tasks.append((task.id, task.name, list(iterkeys(task.statements))))
    return users, tasks
Example #7
0
def harvest_contest_data(contest_id):
    """Retrieve the couples username, password and the task list for a
    given contest.

    contest_id (int): the id of the contest we want.
    return (tuple): the first element is a dictionary mapping
                    usernames to passwords; the second one is the list
                    of the task names.

    """
    users = {}
    tasks = []
    with SessionGen() as session:
        contest = Contest.get_from_id(contest_id, session)
        for participation in contest.participations:
            user = participation.user
            # Pick participation's password if present, or the user's.
            password_source = participation.password
            if password_source is None:
                password_source = user.password
            # We can log in only if we know the plaintext password.
            method, password = parse_authentication(password_source)
            if method != "plaintext":
                print("Not using user %s with non-plaintext password." %
                      user.username)
                continue
            users[user.username] = {'password': password}
        for task in contest.tasks:
            tasks.append((task.id, task.name, list(iterkeys(task.statements))))
    return users, tasks
Example #8
0
    def _missing_operations(self):
        """Return a generator of data to be sent to the rankings..

        """
        counter = 0
        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            for submission in contest.get_submissions():
                if submission.user.hidden:
                    continue

                if submission.get_result().scored() and \
                        submission.id not in self.scores_sent_to_rankings:
                    for operation in self.operations_for_score(submission):
                        self.enqueue(operation)
                        counter += 1

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    for operation in self.operations_for_token(submission):
                        self.enqueue(operation)
                        counter += 1

        return counter
Example #9
0
    def post(self):
        fallback_page = "/contests/add"

        try:
            attrs = dict()

            self.get_string(attrs, "name", empty=None)
            assert attrs.get("name") is not None, "No contest name specified."
            attrs["description"] = attrs["name"]

            # Create the contest.
            contest = Contest(**attrs)
            self.sql_session.add(contest)

        except Exception as error:
            self.application.service.add_notification(
                make_datetime(), "Invalid field(s)", repr(error))
            self.redirect(fallback_page)
            return

        if self.try_commit():
            # Create the contest on RWS.
            self.application.service.proxy_service.reinitialize()
            self.redirect("/contest/%s" % contest.id)
        else:
            self.redirect(fallback_page)
Example #10
0
    def _missing_operations(self):
        """Return a generator of data to be sent to the rankings..

        """
        counter = 0
        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            for submission in contest.get_submissions():
                if submission.user.hidden:
                    continue

                # The submission result can be None if the dataset has
                # been just made live.
                sr = submission.get_result()
                if sr is None:
                    continue

                if sr.evaluated() and \
                        submission.id not in self.scores_sent_to_rankings:
                    for operation in self.operations_for_score(submission):
                        self.enqueue(operation)
                        counter += 1

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    for operation in self.operations_for_token(submission):
                        self.enqueue(operation)
                        counter += 1

        return counter
Example #11
0
    def precache_files(self, contest_id):
        """RPC to ask the worker to precache of files in the contest.

        contest_id (int): the id of the contest

        """
        # In order to avoid a long-living connection, first fetch the
        # complete list of files and then download the files; since
        # this is just pre-caching, possible race conditions are not
        # dangerous
        logger.info("Precaching files for contest %d.", contest_id)
        with SessionGen() as session:
            contest = Contest.get_from_id(contest_id, session)
            files = enumerate_files(session,
                                    contest,
                                    skip_submissions=True,
                                    skip_user_tests=True,
                                    skip_print_jobs=True)
        for digest in files:
            try:
                self.file_cacher.load(digest, if_needed=True)
            except KeyError:
                # No problem (at this stage) if we cannot find the
                # file
                pass

        logger.info("Precaching finished.")
Example #12
0
    def _missing_operations(self):
        """Return a generator of data to be sent to the rankings..

        """
        counter = 0
        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            for submission in contest.get_submissions():
                if submission.user.hidden:
                    continue

                if submission.get_result().scored() and \
                        submission.id not in self.scores_sent_to_rankings:
                    for operation in self.operations_for_score(submission):
                        self.enqueue(operation)
                        counter += 1

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    for operation in self.operations_for_token(submission):
                        self.enqueue(operation)
                        counter += 1

        return counter
Example #13
0
    def search_jobs_not_done(self):
        """Sweep the database and search for work to do.

        Iterate over all submissions and look if they are in a suitable
        status to be sent (scored and not hidden) but, for some reason,
        haven't been sent yet (that is, their ID doesn't appear in the
        *_sent_to_rankings sets). In case, arrange for them to be sent.

        """
        logger.info("Going to search for unsent subchanges.")

        job_count = 0

        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            for submission in contest.get_submissions():
                if submission.user.hidden:
                    continue

                if submission.get_result().scored() and \
                        submission.id not in self.scores_sent_to_rankings:
                    self.send_score(submission)
                    job_count += 1

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    self.send_token(submission)
                    job_count += 1

        logger.info("Found %d unsent subchanges." % job_count)
Example #14
0
 def get_contest(self):
     # Check for required files
     if not (self.__require_file("contest.json")
             and self.__require_file("problem-list.txt")):
         return None
     # Load name and description
     contest = self.__load_contest(self.path)
     args = {}
     args['name'] = contest['name']
     args['description'] = contest['description']
     logger.info("Loading parameters for contest %s.", args['name'])
     args['token_mode'] = 'infinite'
     # Load datetime
     time_info = contest['time']
     start_time_local = datetime.datetime.strptime(time_info['start'],
                                                   '%Y-%m-%d %H:%M')
     local_tz = get_localzone()
     start_time = local_tz.localize(start_time_local,
                                    is_dst=None).astimezone(
                                        pytz.utc).replace(tzinfo=None)
     contest_len = datetime.timedelta(minutes=time_info['length'])
     args['start'] = start_time
     args['stop'] = start_time + contest_len
     # Tasks
     tasks = list(
         map((lambda x: x[:-1]),
             open(os.path.join(self.path, 'problem-list.txt'), 'r')))
     # Import was successful
     logger.info("Contest parameters loaded.")
     return Contest(**args), tasks, []
Example #15
0
    def _missing_operations(self):
        """Return a generator of data to be sent to the rankings..

        """
        counter = 0
        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            for submission in contest.get_submissions():
                if submission.participation.hidden:
                    continue

                # The submission result can be None if the dataset has
                # been just made live.
                sr = submission.get_result()
                if sr is None:
                    continue

                if sr.scored() and \
                        submission.id not in self.scores_sent_to_rankings:
                    for operation in self.operations_for_score(submission):
                        self.enqueue(operation)
                        counter += 1

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    for operation in self.operations_for_token(submission):
                        self.enqueue(operation)
                        counter += 1

        return counter
Example #16
0
def add_participations(contest_id, groupname):
    with SessionGen() as session:
        users = session.query(User)
        contest = Contest.get_from_id(contest_id, session)
        if contest is None:
            logger.error("No contest with id `%s' found.", contest_id)
            return False
        if groupname is None:
            group = contest.main_group
        else:
            group = \
                session.query(Group) \
                    .filter(Group.contest_id == contest_id,
                            Group.name == groupname).first()
            if group is None:
                logger.error("No group with name `%s' found.", groupname)
                return False

        for user in users:
            if session.query(Participation) \
                    .filter(Participation.contest_id == contest_id,
                            Participation.user_id == user.id).first():
                logger.info(
                    "Participation already exists (left untouched; group not verified): '%s'",
                    user.username)
            else:
                participation = Participation(user=user,
                                              contest=contest,
                                              group=group)
                session.add(participation)
                logger.info("Participation added: '%s'", user.username)

        session.commit()

    return True
Example #17
0
    def __init__(self, contest_id):
        super(MyContest, self).__init__(_session)

        self.contest_id = contest_id
        self.contest = Contest.get_from_id(contest_id, self.sql_session)
        self.name = self.contest.name
        self.description = self.contest.description
        self.questions = QuestionList(self.contest_id)
        self.announcements = AnnouncementList(self.contest_id)
Example #18
0
    def initialize(self):
        """Send basic data to all the rankings.

        It's data that's supposed to be sent before the contest, that's
        needed to understand what we're talking about when we send
        submissions: contest, users, tasks.

        No support for teams, flags and faces.

        """
        logger.info("Initializing rankings.")

        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            if contest is None:
                logger.error(
                    "Received request for unexistent contest "
                    "id %s.", self.contest_id)
                raise KeyError("Contest not found.")

            contest_id = encode_id(contest.name)
            contest_data = {
                "name": contest.description,
                "begin": int(make_timestamp(contest.start)),
                "end": int(make_timestamp(contest.stop)),
                "score_precision": contest.score_precision
            }

            users = dict()

            for user in contest.users:
                if not user.hidden:
                    users[encode_id(user.username)] = \
                        {"f_name": user.first_name,
                         "l_name": user.last_name,
                         "team": None}

            tasks = dict()

            for task in contest.tasks:
                score_type = get_score_type(dataset=task.active_dataset)
                tasks[encode_id(task.name)] = \
                    {"short_name": task.name,
                     "name": task.title,
                     "contest": encode_id(contest.name),
                     "order": task.num,
                     "max_score": score_type.max_score,
                     "extra_headers": score_type.ranking_headers,
                     "score_precision": task.score_precision,
                     "score_mode": task.score_mode}

        self.enqueue(
            ProxyOperation(ProxyExecutor.CONTEST_TYPE,
                           {contest_id: contest_data}))
        self.enqueue(ProxyOperation(ProxyExecutor.USER_TYPE, users))
        self.enqueue(ProxyOperation(ProxyExecutor.TASK_TYPE, tasks))
Example #19
0
 def get_contest(cls, **kwargs):
     """Create a contest"""
     args = {
         "name": unique_unicode_id(),
         "description": unique_unicode_id(),
     }
     args.update(kwargs)
     contest = Contest(**args)
     return contest
Example #20
0
def add_participation(username, contest_id, ip, delay_time, extra_time,
                      password, method, is_hashed, team_code, hidden,
                      unrestricted):
    logger.info("Creating the user's participation in the database.")
    delay_time = delay_time if delay_time is not None else 0
    extra_time = extra_time if extra_time is not None else 0

    if hidden:
        logger.warning("The participation will be hidden")
    if unrestricted:
        logger.warning("The participation will be unrestricted")

    try:
        with SessionGen() as session:
            user = \
                session.query(User).filter(User.username == username).first()
            if user is None:
                logger.error("No user with username `%s' found.", username)
                return False
            contest = Contest.get_from_id(contest_id, session)
            if contest is None:
                logger.error("No contest with id `%s' found.", contest_id)
                return False
            team = None
            if team_code is not None:
                team = \
                    session.query(Team).filter(Team.code == team_code).first()
                if team is None:
                    logger.error("No team with code `%s' found.", team_code)
                    return False
            if password is not None:
                if is_hashed:
                    password = build_password(password, method)
                else:
                    password = hash_password(password, method)

            participation = Participation(
                user=user,
                contest=contest,
                ip=[ipaddress.ip_network(ip)] if ip is not None else None,
                delay_time=datetime.timedelta(seconds=delay_time),
                extra_time=datetime.timedelta(seconds=extra_time),
                password=password,
                team=team,
                hidden=hidden,
                unrestricted=unrestricted)

            session.add(participation)
            session.commit()
    except IntegrityError:
        logger.error("A participation for this user in this contest "
                     "already exists.")
        return False

    logger.info("Participation added.")
    return True
Example #21
0
def add_participation(username, contest_id, ip, delay_time, extra_time,
                      password, method, is_hashed, team_code, hidden,
                      unrestricted):
    logger.info("Creating the user's participation in the database.")
    delay_time = delay_time if delay_time is not None else 0
    extra_time = extra_time if extra_time is not None else 0

    if hidden:
        logger.warning("The participation will be hidden")
    if unrestricted:
        logger.warning("The participation will be unrestricted")

    try:
        with SessionGen() as session:
            user = \
                session.query(User).filter(User.username == username).first()
            if user is None:
                logger.error("No user with username `%s' found.", username)
                return False
            contest = Contest.get_from_id(contest_id, session)
            if contest is None:
                logger.error("No contest with id `%s' found.", contest_id)
                return False
            team = None
            if team_code is not None:
                team = \
                    session.query(Team).filter(Team.code == team_code).first()
                if team is None:
                    logger.error("No team with code `%s' found.", team_code)
                    return False
            if password is not None:
                if is_hashed:
                    password = build_password(password, method)
                else:
                    password = hash_password(password, method)

            participation = Participation(
                user=user,
                contest=contest,
                ip=[ipaddress.ip_network(ip)] if ip is not None else None,
                delay_time=datetime.timedelta(seconds=delay_time),
                extra_time=datetime.timedelta(seconds=extra_time),
                password=password,
                team=team,
                hidden=hidden,
                unrestricted=unrestricted)

            session.add(participation)
            session.commit()
    except IntegrityError:
        logger.error("A participation for this user in this contest "
                     "already exists.")
        return False

    logger.info("Participation added.")
    return True
Example #22
0
 def add_contest(self, **kwargs):
     """Add a contest."""
     args = {
         "name": unique_unicode_id(),
         "description": unique_unicode_id(),
     }
     args.update(kwargs)
     contest = Contest(**args)
     self.session.add(contest)
     return contest
Example #23
0
    def initialize(self):
        """Send basic data to all the rankings.

        It's data that's supposed to be sent before the contest, that's
        needed to understand what we're talking about when we send
        submissions: contest, users, tasks.

        No support for teams, flags and faces.

        """
        logger.info("Initializing rankings.")

        with SessionGen() as session:
            contest = Contest.get_from_id(self.contest_id, session)

            if contest is None:
                logger.error("Received request for unexistent contest "
                             "id %s.", self.contest_id)
                raise KeyError("Contest not found.")

            contest_id = encode_id(contest.name)
            contest_data = {
                "name": contest.description,
                "begin": int(make_timestamp(contest.start)),
                "end": int(make_timestamp(contest.stop)),
                "score_precision": contest.score_precision}

            users = dict()

            for participation in contest.participations:
                user = participation.user
                if not participation.hidden:
                    users[encode_id(user.username)] = \
                        {"f_name": user.first_name,
                         "l_name": user.last_name,
                         "team": None}

            tasks = dict()

            for task in contest.tasks:
                score_type = get_score_type(dataset=task.active_dataset)
                tasks[encode_id(task.name)] = \
                    {"short_name": task.name,
                     "name": task.title,
                     "contest": encode_id(contest.name),
                     "order": task.num,
                     "max_score": score_type.max_score,
                     "extra_headers": score_type.ranking_headers,
                     "score_precision": task.score_precision,
                     "score_mode": task.score_mode}

        self.enqueue(ProxyOperation(ProxyExecutor.CONTEST_TYPE,
                                    {contest_id: contest_data}))
        self.enqueue(ProxyOperation(ProxyExecutor.USER_TYPE, users))
        self.enqueue(ProxyOperation(ProxyExecutor.TASK_TYPE, tasks))
Example #24
0
    def prepare(self):
        """This method is executed at the beginning of each request.

        """
        super(BaseHandler, self).prepare()
        self.contest = Contest.get_from_id(self.application.service.contest,
                                           self.sql_session)

        self._ = self.locale.translate

        self.r_params = self.render_params()
Example #25
0
    def prepare(self):
        """This method is executed at the beginning of each request.

        """
        super(BaseHandler, self).prepare()
        self.contest = Contest.get_from_id(self.application.service.contest,
                                           self.sql_session)

        self._ = self.locale.translate

        self.r_params = self.render_params()
Example #26
0
File: AddUser.py Project: cbolk/cms
def add_user(contest_id, first_name, last_name, username,
             password, ip_address, email, hidden):
    with SessionGen(commit=True) as session:
        contest = Contest.get_from_id(contest_id, session)
        user = User(first_name=first_name,
                    last_name=last_name,
                    username=username,
                    password=password,
                    email=email,
                    ip=ip_address,
                    hidden=hidden,
                    contest=contest)
        session.add(user)
Example #27
0
def add_user(contest_id, first_name, last_name, username, password, ip_address,
             email, hidden):
    with SessionGen() as session:
        contest = Contest.get_from_id(contest_id, session)
        user = User(first_name=first_name,
                    last_name=last_name,
                    username=username,
                    password=password,
                    email=email,
                    ip=ip_address,
                    hidden=hidden,
                    contest=contest)
        session.add(user)
        session.commit()
Example #28
0
    def prepare(self):
        """This method is executed at the beginning of each request.

        """
        self.timestamp = make_datetime()

        self.set_header("Cache-Control", "no-cache, must-revalidate")

        self.sql_session = Session()
        self.contest = Contest.get_from_id(self.application.service.contest,
                                           self.sql_session)

        self._ = self.locale.translate

        self.r_params = self.render_params()
Example #29
0
    def choose_contest(self):
        """Fill self.contest using contest passed as argument or path.

        If a contest was specified as argument to CWS, fill
        self.contest with that; otherwise extract it from the URL path.

        """
        if self.is_multi_contest():
            # Choose the contest found in the path argument
            # see: https://github.com/tornadoweb/tornado/issues/1673
            contest_name = self.path_args[0]

            # Select the correct contest or return an error
            try:
                self.contest = self.contest_list[contest_name]
            except KeyError:
                self.contest = Contest(
                    name=contest_name, description=contest_name)
                self.r_params = self.render_params()
                raise tornado.web.HTTPError(404)
        else:
            # Select the contest specified on the command line
            self.contest = Contest.get_from_id(
                self.application.service.contest_id, self.sql_session)
Example #30
0
    def precache_files(self, contest_id):
        """RPC to ask the worker to precache of files in the contest.

        contest_id (int): the id of the contest

        """
        # Lock is not needed if the admins correctly placed cache and
        # temp directories in the same filesystem. This is what
        # usually happens since they are children of the same,
        # cms-created, directory.
        logger.info("Precaching files for contest %d." % contest_id)
        with SessionGen() as session:
            contest = Contest.get_from_id(contest_id, session)
            for digest in contest.enumerate_files(skip_submissions=True, skip_user_tests=True):
                self.file_cacher.load(digest)
        logger.info("Precaching finished.")
Example #31
0
 def get_contest(cls, **kwargs):
     """Create a contest"""
     grpargs = {}
     for a in ["start", "stop", "per_user_time"]:
         if a in kwargs:
             grpargs[a] = kwargs[a]
             del kwargs[a]
     args = {
         "name": unique_unicode_id(),
         "description": unique_unicode_id(),
         "main_group": cls.get_group(**grpargs),
     }
     args["groups"] = [args["main_group"]]
     args.update(kwargs)
     contest = Contest(**args)
     return contest
Example #32
0
    def precache_files(self, contest_id):
        """RPC to ask the worker to precache of files in the contest.

        contest_id (int): the id of the contest

        """
        # Lock is not needed if the admins correctly placed cache and
        # temp directories in the same filesystem. This is what
        # usually happens since they are children of the same,
        # cms-created, directory.
        logger.info("Precaching files for contest %d." % contest_id)
        with SessionGen() as session:
            contest = Contest.get_from_id(contest_id, session)
            for digest in contest.enumerate_files(skip_submissions=True,
                                                  skip_user_tests=True):
                self.file_cacher.load(digest)
        logger.info("Precaching finished.")
Example #33
0
    def rankings_initialize(self):
        """Send to all the rankings all the data that are supposed to be
        sent before the contest: contest, users, tasks. No support for
        teams, flags and faces.

        """
        logger.info("Initializing rankings.")

        with SessionGen(commit=False) as session:
            contest = Contest.get_from_id(self.contest_id, session)

            if contest is None:
                logger.error("Received request for unexistent contest "
                             "id %s." % self.contest_id)
                raise KeyError

            contest_id = encode_id(contest.name)
            contest_data = {
                "name": contest.description,
                "begin": int(make_timestamp(contest.start)),
                "end": int(make_timestamp(contest.stop)),
                "score_precision": contest.score_precision}

            users = dict((encode_id(user.username),
                          {"f_name": user.first_name,
                           "l_name": user.last_name,
                           "team": None})
                         for user in contest.users
                         if not user.hidden)

            tasks = dict((encode_id(task.name),
                          {"name": task.title,
                           "contest": encode_id(contest.name),
                           "max_score": 100.0,
                           "score_precision": task.score_precision,
                           "extra_headers": [],
                           "order": task.num,
                           "short_name": task.name})
                         for task in contest.tasks)

        for ranking in self.rankings:
            ranking.data_queue.put((ranking.CONTEST_TYPE,
                                    {contest_id: contest_data}))
            ranking.data_queue.put((ranking.USER_TYPE, users))
            ranking.data_queue.put((ranking.TASK_TYPE, tasks))
Example #34
0
    def get_contest(self):
        """See docstring in class Loader.

        """

        name = os.path.split(self.path)[1]
        conf = yaml.safe_load(
            io.open(os.path.join(self.path, "contest.yaml"),
                    "rt", encoding="utf-8"))

        logger.info("Loading parameters for contest %s." % name)

        args = {}

        load(conf, args, ["name", "nome_breve"])
        load(conf, args, ["description", "nome"])

        assert name == args["name"]

        load(conf, args, "token_initial")
        load(conf, args, "token_max")
        load(conf, args, "token_total")
        load(conf, args, "token_min_interval", conv=make_timedelta)
        load(conf, args, "token_gen_time", conv=make_timedelta)
        load(conf, args, "token_gen_number")

        load(conf, args, ["start", "inizio"], conv=make_datetime)
        load(conf, args, ["stop", "fine"], conv=make_datetime)

        load(conf, args, "max_submission_number")
        load(conf, args, "max_user_test_number")
        load(conf, args, "min_submission_interval", conv=make_timedelta)
        load(conf, args, "min_user_test_interval", conv=make_timedelta)

        logger.info("Contest parameters loaded.")

        tasks = load(conf, None, ["tasks", "problemi"])
        self.tasks_order = dict((name, num)
                                for num, name in enumerate(tasks))
        self.users_conf = dict((user['username'], user)
                               for user
                               in load(conf, None, ["users", "utenti"]))
        users = self.users_conf.keys()

        return Contest(**args), tasks, users
Example #35
0
def contest_from_db(contest_id, session):
    """Return the contest object with the given id

    contest_id (int|None): the id of the contest, or None to return None.
    session (Session): SQLAlchemy session to use.

    return (Contest|None): None if contest_id is None, or the contest.
    raise (ImportDataError): if there is no contest with the given id.

    """
    if contest_id is None:
        return None

    contest = Contest.get_from_id(contest_id, session)
    if contest is None:
        raise ImportDataError("The specified contest (id %s) does not exist." %
                              contest_id)
    return contest
Example #36
0
def contest_from_db(contest_id, session):
    """Return the contest object with the given id

    contest_id (int|None): the id of the contest, or None to return None.
    session (Session): SQLAlchemy session to use.

    return (Contest|None): None if contest_id is None, or the contest.
    raise (ImportDataError): if there is no contest with the given id.

    """
    if contest_id is None:
        return None

    contest = Contest.get_from_id(contest_id, session)
    if contest is None:
        raise ImportDataError(
            "The specified contest (id %s) does not exist." % contest_id)
    return contest
Example #37
0
def harvest_contest_data(contest_id):
    """Retrieve the couples username, password and the task list for a
    given contest.

    contest_id (int): the id of the contest we want.
    return (tuple): the first element is a dictionary mapping
                    usernames to passwords; the second one is the list
                    of the task names.

    """
    users = {}
    tasks = []
    with SessionGen() as session:
        contest = Contest.get_from_id(contest_id, session)
        for user in contest.users:
            users[user.username] = {'password': user.password}
        for task in contest.tasks:
            tasks.append((task.id, task.name))
    return users, tasks
Example #38
0
    def search_jobs_not_done(self):
        """Look in the database for submissions that have not been
        scored for no good reasons. Put the missing job in the queue.

        """
        # Do this only if we are not still loading old submission
        # (from the start of the service).
        if self.scoring_old_submission:
            return True

        with SessionGen(commit=False) as session:
            contest = Contest.get_from_id(self.contest_id, session)

            new_submission_results_to_score = set()

            for submission in contest.get_submissions():
                if submission.user.hidden:
                    continue

                for dataset in get_datasets_to_judge(submission.task):
                    sr = submission.get_result(dataset)
                    sr_id = (submission.id, dataset.id)

                    if sr is not None and (sr.evaluated() or
                            sr.compilation_outcome == "fail") and \
                            sr_id not in self.submission_results_scored:
                        new_submission_results_to_score.add(sr_id)

                if submission.tokened() and \
                        submission.id not in self.tokens_sent_to_rankings:
                    self.rankings_send_token(submission)

        new_s = len(new_submission_results_to_score)
        old_s = len(self.submission_results_to_score)
        logger.info("Submissions found to score: %d." % new_s)
        if new_s > 0:
            self.submission_results_to_score |= new_submission_results_to_score
            if old_s == 0:
                self.add_timeout(self.score_old_submissions, None,
                                 0.5, immediately=False)

        # Run forever.
        return True
Example #39
0
def harvest_contest_data(contest_id):
    """Retrieve the couples username, password and the task list for a
    given contest.

    contest_id (int): the id of the contest we want.
    return (tuple): the first element is a dictionary mapping
                    usernames to passwords; the second one is the list
                    of the task names.

    """
    users = {}
    tasks = []
    with SessionGen() as session:
        contest = Contest.get_from_id(contest_id, session)
        for user in contest.users:
            users[user.username] = {'password': user.password}
        for task in contest.tasks:
            tasks.append((task.id, task.name))
    return users, tasks
Example #40
0
def add_participation(username, contest_id, ip, delay_time, extra_time,
                      password, team_code, hidden):
    logger.info("Creating the user in the database.")
    delay_time = delay_time if delay_time is not None else 0
    extra_time = extra_time if extra_time is not None else 0

    try:
        with SessionGen() as session:
            user = \
                session.query(User).filter(User.username == username).first()
            if user is None:
                logger.error("No user with username `%s' found.", username)
                return False
            contest = Contest.get_from_id(contest_id, session)
            if contest is None:
                logger.error("No contest with id `%s' found.", contest_id)
                return False
            team = None
            if team_code is not None:
                team = \
                    session.query(Team).filter(Team.code == team_code).first()
                if team is None:
                    logger.error("No team with code `%s' found.", team_code)
                    return False
            participation = Participation(
                user=user,
                contest=contest,
                ip=ip,
                delay_time=datetime.timedelta(seconds=delay_time),
                extra_time=datetime.timedelta(seconds=extra_time),
                password=password,
                team=team,
                hidden=hidden)

            session.add(participation)
            session.commit()
    except IntegrityError:
        logger.error("A participation for this user in this contest "
                     "already exists.")
        return False

    logger.info("Participation added.")
    return True
Example #41
0
def generate_passwords(contest_id, exclude_hidden, exclude_unrestricted,
                       output_path):
    logger.info("Updating passwords...")

    with open(output_path, 'w') as io:
        io.write("contest_id,team,fullname,username,password\n")
        with SessionGen() as session:
            if contest_id is not None:
                contest = Contest.get_from_id(contest_id, session)
                objects = session.query(Participation).join(
                    Participation.user).join(Participation.team)
                if exclude_unrestricted:
                    objects = objects.filter(
                        Participation.unrestricted == False)
                if exclude_hidden:
                    objects = objects.filter(Participation.hidden == False)
            else:
                objects = session.query(User)

            for obj in objects:
                password = generate_random_password()
                obj.password = build_password(password, 'plaintext')

                user = obj if isinstance(obj, User) else obj.user
                fullname = "%s %s" % (user.first_name, user.last_name)
                if isinstance(obj, Participation):
                    team = obj.team.code if obj.team is not None else ''
                    logger.info(
                        "Updating participation of user %s (team=%s) on contest id %d",
                        user.username, team, contest.id)
                    io.write(
                        "%d,%s,%s,%s,%s\n" %
                        (contest.id, team, fullname, user.username, password))
                else:
                    logger.info("Updating user %s", user.username)
                    io.write(",,%s,%s,%s\n" %
                             (fullname, user.username, password))

            session.commit()

    logger.info("Done.")
    return True
Example #42
0
    def do_export(self):
        """Run the actual export code.

        """
        logger.operation = "exporting contest %s" % self.contest_id
        logger.info("Starting export.")

        logger.info("Creating dir structure.")
        try:
            os.mkdir(self.spool_dir)
        except OSError:
            logger.critical("The specified directory already exists, "
                            "I won't overwrite it.")
            return False
        os.mkdir(self.upload_dir)

        with SessionGen() as session:
            self.contest = Contest.get_from_id(self.contest_id, session)
            self.submissions = sorted(
                (submission
                 for submission in self.contest.get_submissions()
                 if not submission.participation.hidden),
                key=lambda submission: submission.timestamp)

            # Creating users' directory.
            for participation in self.contest.participations:
                if not participation.hidden:
                    os.mkdir(os.path.join(
                        self.upload_dir, participation.user.username))

            try:
                self.export_submissions()
                self.export_ranking()
            except Exception:
                logger.critical("Generic error.", exc_info=True)
                return False

        logger.info("Export finished.")
        logger.operation = ""

        return True
Example #43
0
    def do_export(self):
        """Run the actual export code.

        """
        logger.operation = "exporting contest %s" % self.contest_id
        logger.info("Starting export.")

        logger.info("Creating dir structure.")
        try:
            os.mkdir(self.spool_dir)
        except OSError:
            logger.critical("The specified directory already exists, "
                            "I won't overwrite it.")
            return False
        os.mkdir(self.upload_dir)

        with SessionGen() as session:
            self.contest = Contest.get_from_id(self.contest_id, session)
            self.submissions = sorted(
                (submission for submission in self.contest.get_submissions()
                 if not submission.participation.hidden),
                key=lambda submission: submission.timestamp)

            # Creating users' directory.
            for participation in self.contest.participations:
                if not participation.hidden:
                    os.mkdir(
                        os.path.join(self.upload_dir,
                                     participation.user.username))

            try:
                self.export_submissions()
                self.export_ranking()
            except Exception:
                logger.critical("Generic error.", exc_info=True)
                return False

        logger.info("Export finished.")
        logger.operation = ""

        return True
Example #44
0
    def __init__(self, contest_id, export_target,
                 dump_files, dump_model, skip_generated,
                 skip_submissions, skip_user_tests):
        self.contest_id = contest_id
        self.dump_files = dump_files
        self.dump_model = dump_model
        self.skip_generated = skip_generated
        self.skip_submissions = skip_submissions
        self.skip_user_tests = skip_user_tests

        # If target is not provided, we use the contest's name.
        if export_target == "":
            with SessionGen() as session:
                contest = Contest.get_from_id(self.contest_id, session)
                self.export_target = "dump_%s.tar.gz" % contest.name
                logger.warning("export_target not given, using \"%s\""
                               % self.export_target)
        else:
            self.export_target = export_target

        self.file_cacher = FileCacher()
Example #45
0
    def __init__(self, contest_id, export_target,
                 dump_files, dump_model, light,
                 skip_submissions, skip_user_tests):
        self.contest_id = contest_id
        self.dump_files = dump_files
        self.dump_model = dump_model
        self.light = light
        self.skip_submissions = skip_submissions
        self.skip_user_tests = skip_user_tests

        # If target is not provided, we use the contest's name.
        if export_target == "":
            with SessionGen(commit=False) as session:
                contest = Contest.get_from_id(self.contest_id, session)
                self.export_target = "dump_%s.tar.gz" % contest.name
                logger.warning("export_target not given, using \"%s\""
                               % self.export_target)
        else:
            self.export_target = export_target

        self.file_cacher = FileCacher()
Example #46
0
def add_participation(username, contest_id):
    try:
        with SessionGen() as session:
            user = session.query(User).filter(User.username == username).first()
            if user is None:
                return False
            contest = Contest.get_from_id(contest_id, session)
            if contest is None:
                return False
            participation = Participation(
                user=user,
                contest=contest,
                hidden=False,
                unrestricted=False
            )

            session.add(participation)
            session.commit()
    except IntegrityError:
        return False
    logger.info("Added participation for user {}".format(username))
    return True
Example #47
0
    def get_contest_object(self):
        """
        Return the Contest database object.
        """
        args = {}

        # Names.
        args["name"] = self.params["short_name"]
        args["description"] = self.params["long_name"]

        # Languages.
        args["languages"] = self.params["languages"]

        # Communication
        args["allow_questions"] = self.params.get("allow_questions", False)

        # Times.
        start_time = time_from_str(self.params["start_time"])
        stop_time = time_from_str(self.params["end_time"])
        args["start"] = make_datetime(time.mktime(start_time.timetuple()))
        args["stop"] = make_datetime(time.mktime(stop_time.timetuple()))

        # Limits.
        args["max_submission_number"] = self.params["max_submission_number"]
        args["max_user_test_number"] = self.params["max_user_test_number"]

        interval_seconds = self.params["min_submission_interval"]
        if interval_seconds is not None:
            delta = timedelta(seconds=interval_seconds)
            args["min_submission_interval"] = delta

        interval_seconds = self.params["min_user_test_interval"]
        if interval_seconds is not None:
            delta = timedelta(seconds=interval_seconds)
            args["min_user_test_interval"] = delta

        return Contest(**args)
Example #48
0
    def __init__(self, contest_id, export_target,
                 dump_files, dump_model, skip_generated,
                 skip_submissions, skip_user_tests):
        self.contest_id = contest_id
        self.dump_files = dump_files
        self.dump_model = dump_model
        self.skip_generated = skip_generated
        self.skip_submissions = skip_submissions
        self.skip_user_tests = skip_user_tests
        self.export_target = export_target

        # If target is not provided, we use the contest's name.
        if export_target == "":
            with SessionGen() as session:
                contest = Contest.get_from_id(self.contest_id, session)
                if contest is None:
                    logger.critical("Please specify a valid contest id.")
                    self.contest_id = None
                else:
                    self.export_target = "dump_%s.tar.gz" % contest.name
                    logger.warning("export_target not given, using \"%s\""
                                   % self.export_target)

        self.file_cacher = FileCacher()
Example #49
0
File: Worker.py Project: Takt29/cms
    def precache_files(self, contest_id):
        """RPC to ask the worker to precache of files in the contest.

        contest_id (int): the id of the contest

        """
        # In order to avoid a long-living connection, first fetch the
        # complete list of files and then download the files; since
        # this is just pre-caching, possible race conditions are not
        # dangerous
        logger.info("Precaching files for contest %d.", contest_id)
        with SessionGen() as session:
            contest = Contest.get_from_id(contest_id, session)
            files = contest.enumerate_files(skip_submissions=True,
                                            skip_user_tests=True)
        for digest in files:
            try:
                self.file_cacher.load(digest, if_needed=True)
            except KeyError:
                # No problem (at this stage) if we cannot find the
                # file
                pass

        logger.info("Precaching finished.")
Example #50
0
class ContestHandler(BaseHandler):
    """A handler that has a contest attached.

    Most of the RequestHandler classes in this application will be a
    child of this class.

    """
    def __init__(self, *args, **kwargs):
        super(ContestHandler, self).__init__(*args, **kwargs)
        self.contest_url = None

    def prepare(self):
        self.choose_contest()

        if self.contest.allowed_localizations:
            lang_codes = filter_language_codes(
                list(iterkeys(self.available_translations)),
                self.contest.allowed_localizations)
            self.available_translations = dict(
                (k, v) for k, v in iteritems(self.available_translations)
                if k in lang_codes)

        super(ContestHandler, self).prepare()

        if self.is_multi_contest():
            self.contest_url = \
                create_url_builder(self.url(self.contest.name))
        else:
            self.contest_url = self.url

        # Run render_params() now, not at the beginning of the request,
        # because we need contest_name
        self.r_params = self.render_params()

    def choose_contest(self):
        """Fill self.contest using contest passed as argument or path.

        If a contest was specified as argument to CWS, fill
        self.contest with that; otherwise extract it from the URL path.

        """
        if self.is_multi_contest():
            # Choose the contest found in the path argument
            # see: https://github.com/tornadoweb/tornado/issues/1673
            contest_name = self.path_args[0]

            # Select the correct contest or return an error
            self.contest = self.sql_session.query(Contest)\
                .filter(Contest.name == contest_name).first()
            if self.contest is None:
                self.contest = Contest(
                    name=contest_name, description=contest_name)
                # render_params in this class assumes the contest is loaded,
                # so we cannot call it without a fully defined contest. Luckily
                # the one from the base class is enough to display a 404 page.
                self.r_params = super(ContestHandler, self).render_params()
                raise tornado.web.HTTPError(404)
        else:
            # Select the contest specified on the command line
            self.contest = Contest.get_from_id(
                self.service.contest_id, self.sql_session)

    def get_current_user(self):
        """Return the currently logged in participation.

        The name is get_current_user because tornado requires that
        name.

        The participation is obtained from one of the possible sources:
        - if IP autologin is enabled, the remote IP address is matched
          with the participation IP address; if a match is found, that
          participation is returned; in case of errors, None is returned;
        - if username/password authentication is enabled, and the cookie
          is valid, the corresponding participation is returned, and the
          cookie is refreshed.

        After finding the participation, IP login and hidden users
        restrictions are checked.

        In case of any error, or of a login by other sources, the
        cookie is deleted.

        return (Participation|None): the participation object for the
            user logged in for the running contest.

        """
        cookie_name = self.contest.name + "_login"

        participation = None

        if self.contest.ip_autologin:
            try:
                participation = self._get_current_user_from_ip()
                # If the login is IP-based, we delete previous cookies.
                if participation is not None:
                    self.clear_cookie(cookie_name)
            except RuntimeError:
                return None

        if participation is None \
                and self.contest.allow_password_authentication:
            participation = self._get_current_user_from_cookie()

        if participation is None:
            self.clear_cookie(cookie_name)
            return None

        # Check if user is using the right IP (or is on the right subnet),
        # and that is not hidden if hidden users are blocked.
        ip_login_restricted = \
            self.contest.ip_restriction and participation.ip is not None \
            and not check_ip(self.request.remote_ip, participation.ip)
        hidden_user_restricted = \
            participation.hidden and self.contest.block_hidden_participations
        if ip_login_restricted or hidden_user_restricted:
            self.clear_cookie(cookie_name)
            participation = None

        return participation

    def _get_current_user_from_ip(self):
        """Return the current participation based on the IP address.

        return (Participation|None): the only participation matching
            the remote IP address, or None if no participations could
            be matched.

        raise (RuntimeError): if there is more than one participation
            matching the remote IP address.

        """
        try:
            # We encode it as a network (i.e., we assign it a /32 or
            # /128 mask) since we're comparing it for equality with
            # other networks.
            remote_ip = ipaddress.ip_network(str(self.request.remote_ip))
        except ValueError:
            return None
        participations = self.sql_session.query(Participation)\
            .filter(Participation.contest == self.contest)\
            .filter(Participation.ip.any(remote_ip))

        # If hidden users are blocked we ignore them completely.
        if self.contest.block_hidden_participations:
            participations = participations\
                .filter(Participation.hidden.is_(False))

        participations = participations.all()

        if len(participations) == 1:
            return participations[0]

        # Having more than participation with the same IP,
        # is a mistake and should not happen. In such case,
        # we disallow login for that IP completely, in order to
        # make sure the problem is noticed.
        if len(participations) > 1:
            logger.error("%d participants have IP %s while"
                         "auto-login feature is enabled." % (
                             len(participations), remote_ip))
            raise RuntimeError("More than one participants with the same IP.")

    def _get_current_user_from_cookie(self):
        """Return the current participation based on the cookie.

        If a participation can be extracted, the cookie is refreshed.

        return (Participation|None): the participation extracted from
            the cookie, or None if not possible.

        """
        cookie_name = self.contest.name + "_login"

        if self.get_secure_cookie(cookie_name) is None:
            return None

        # Parse cookie.
        try:
            cookie = pickle.loads(self.get_secure_cookie(cookie_name))
            username = cookie[0]
            password = cookie[1]
            last_update = make_datetime(cookie[2])
        except:
            return None

        # Check if the cookie is expired.
        if self.timestamp - last_update > \
                timedelta(seconds=config.cookie_duration):
            return None

        # Load participation from DB and make sure it exists.
        participation = self.sql_session.query(Participation)\
            .join(Participation.user)\
            .options(contains_eager(Participation.user))\
            .filter(Participation.contest == self.contest)\
            .filter(User.username == username)\
            .first()
        if participation is None:
            return None

        # Check that the password is correct (if a contest-specific
        # password is defined, use that instead of the user password).
        if participation.password is None:
            correct_password = participation.user.password
        else:
            correct_password = participation.password
        if password != correct_password:
            return None

        if self.refresh_cookie:
            self.set_secure_cookie(cookie_name,
                                   pickle.dumps((username,
                                                 password,
                                                 make_timestamp())),
                                   expires_days=None)

        return participation

    def render_params(self):
        ret = super(ContestHandler, self).render_params()

        ret["contest"] = self.contest

        if self.contest_url is not None:
            ret["contest_url"] = self.contest_url

        ret["phase"] = self.contest.phase(self.timestamp)

        ret["printing_enabled"] = (config.printer is not None)
        ret["questions_enabled"] = self.contest.allow_questions
        ret["testing_enabled"] = self.contest.allow_user_tests

        if self.current_user is not None:
            participation = self.current_user
            ret["participation"] = participation
            ret["user"] = participation.user

            res = compute_actual_phase(
                self.timestamp, self.contest.start, self.contest.stop,
                self.contest.analysis_start if self.contest.analysis_enabled
                else None,
                self.contest.analysis_stop if self.contest.analysis_enabled
                else None,
                self.contest.per_user_time, participation.starting_time,
                participation.delay_time, participation.extra_time)

            ret["actual_phase"], ret["current_phase_begin"], \
                ret["current_phase_end"], ret["valid_phase_begin"], \
                ret["valid_phase_end"] = res

            if ret["actual_phase"] == 0:
                ret["phase"] = 0

            # set the timezone used to format timestamps
            ret["timezone"] = get_timezone(participation.user, self.contest)

        # some information about token configuration
        ret["tokens_contest"] = self.contest.token_mode

        t_tokens = set(t.token_mode for t in self.contest.tasks)
        if len(t_tokens) == 1:
            ret["tokens_tasks"] = next(iter(t_tokens))
        else:
            ret["tokens_tasks"] = TOKEN_MODE_MIXED

        return ret

    def get_login_url(self):
        """The login url depends on the contest name, so we can't just
        use the "login_url" application parameter.

        """
        return self.contest_url()
Example #51
0
    def do_export(self):
        """Run the actual export code."""
        logger.info("Starting export.")

        # Export users
        users = []
        with SessionGen() as session:
            # Get the contest
            contest = Contest.get_from_id(self.contest_id, session)
            if contest is None:
                logger.critical("Contest %d not found in database.", self.contest_id)
                exit(1)

            # Get participations of the contest
            participations = contest.participations
            for p in participations:
                users.append({
                    u'username': p.user.username,
                    u'password': p.user.password,
                    u'first_name': p.user.first_name,
                    u'last_name': p.user.last_name
                })

        if self.json:
            j = {'users': users}
            with io.open(os.path.join(self.export_target), "wb") as fout:
                json.dump(j, fout, encoding="utf-8", indent=2, sort_keys=True)
        else:
            html = """
            <table>
                <tr>
                    <th>Prénom</th>
                    <th>Nom</th>
                    <th>Pseudo</th>
                    <th>Mot de passe</th>
                </tr>
            """
            for u in users:
                html += """
                <tr>
                    <td>{first_name}</td>
                    <td>{last_name}</td>
                    <td>{username}</td>
                    <td>{password}</td>
                </tr>
                """.format(
                    first_name=u['first_name'],
                    last_name=u['last_name'],
                    username=u['username'],
                    password=u['password'],
                )
            html += "</table>"
            html = """
<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Export des utilisateurs</title>
        <style>
table {{
    font-family: arial, sans-serif;
    border-collapse: separate;
    border-spacing: 0 1em;
    width: 100%;
}}
td, th {{
    border-top: 1px solid #dddddd;
    text-align: left;
    padding: 8px;
}}
        </style>
    </head>
    <body>
        {}
    </body>
</html>
            """.format(html)
            with io.open(os.path.join(self.export_target), "wb") as fout:
                fout.write(html.encode('utf8'))

        logger.info("Export finished.")
        return True
Example #52
0
    def do_export(self):
        """Run the actual export code."""
        logger.info("Starting export.")

        export_dir = self.export_target
        archive_info = get_archive_info(self.export_target)

        if archive_info["write_mode"] != "":
            # We are able to write to this archive.
            if os.path.exists(self.export_target):
                logger.critical("The specified file already exists, "
                                "I won't overwrite it.")
                return False
            export_dir = os.path.join(tempfile.mkdtemp(),
                                      archive_info["basename"])

        logger.info("Creating dir structure.")
        try:
            os.mkdir(export_dir)
        except OSError:
            logger.critical("The specified directory already exists, "
                            "I won't overwrite it.")
            return False

        files_dir = os.path.join(export_dir, "files")
        descr_dir = os.path.join(export_dir, "descriptions")
        os.mkdir(files_dir)
        os.mkdir(descr_dir)

        with SessionGen() as session:
            # Export files.
            logger.info("Exporting files.")
            if self.dump_files:
                for contest_id in self.contests_ids:
                    contest = Contest.get_from_id(contest_id, session)
                    files = contest.enumerate_files(self.skip_submissions,
                                                    self.skip_user_tests,
                                                    self.skip_generated)
                    for file_ in files:
                        if not self.safe_get_file(file_,
                                                  os.path.join(files_dir,
                                                               file_),
                                                  os.path.join(descr_dir,
                                                               file_)):
                            return False

            # Export data in JSON format.
            if self.dump_model:
                logger.info("Exporting data to a JSON file.")

                # We use strings because they'll be the keys of a JSON
                # object
                self.ids = {}
                self.queue = []

                data = dict()

                for cls, lst in [(Contest, self.contests_ids),
                                 (User, self.users_ids),
                                 (Task, self.tasks_ids)]:
                    for i in lst:
                        obj = cls.get_from_id(i, session)
                        self.get_id(obj)

                # Specify the "root" of the data graph
                data["_objects"] = self.ids.values()

                while len(self.queue) > 0:
                    obj = self.queue.pop(0)
                    data[self.ids[obj.sa_identity_key]] = \
                        self.export_object(obj)

                data["_version"] = model_version

                with io.open(os.path.join(export_dir,
                                          "contest.json"), "wb") as fout:
                    json.dump(data, fout, encoding="utf-8",
                              indent=4, sort_keys=True)

        # If the admin requested export to file, we do that.
        if archive_info["write_mode"] != "":
            archive = tarfile.open(self.export_target,
                                   archive_info["write_mode"])
            archive.add(export_dir, arcname=archive_info["basename"])
            archive.close()
            rmtree(export_dir)

        logger.info("Export finished.")

        return True
Example #53
0
class ContestHandler(BaseHandler):
    """A handler that has a contest attached.

    Most of the RequestHandler classes in this application will be a
    child of this class.

    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.contest_url = None

    def prepare(self):
        self.choose_contest()

        if self.contest.allowed_localizations:
            lang_codes = filter_language_codes(
                list(self.available_translations.keys()),
                self.contest.allowed_localizations)
            self.available_translations = dict(
                (k, v) for k, v in self.available_translations.items()
                if k in lang_codes)

        super().prepare()

        if self.is_multi_contest():
            self.contest_url = self.url[self.contest.name]
        else:
            self.contest_url = self.url

        # Run render_params() now, not at the beginning of the request,
        # because we need contest_name
        self.r_params = self.render_params()

    def choose_contest(self):
        """Fill self.contest using contest passed as argument or path.

        If a contest was specified as argument to CWS, fill
        self.contest with that; otherwise extract it from the URL path.

        """
        if self.is_multi_contest():
            # Choose the contest found in the path argument
            # see: https://github.com/tornadoweb/tornado/issues/1673
            contest_name = self.path_args[0]

            # Select the correct contest or return an error
            self.contest = self.sql_session.query(Contest)\
                .filter(Contest.name == contest_name).first()
            if self.contest is None:
                self.contest = Contest(
                    name=contest_name, description=contest_name)
                # render_params in this class assumes the contest is loaded,
                # so we cannot call it without a fully defined contest. Luckily
                # the one from the base class is enough to display a 404 page.
                self.r_params = super().render_params()
                raise tornado.web.HTTPError(404)
        else:
            # Select the contest specified on the command line
            self.contest = Contest.get_from_id(
                self.service.contest_id, self.sql_session)

    def get_current_user(self):
        """Return the currently logged in participation.

        The name is get_current_user because tornado requires that
        name.

        The participation is obtained from one of the possible sources:
        - if IP autologin is enabled, the remote IP address is matched
          with the participation IP address; if a match is found, that
          participation is returned; in case of errors, None is returned;
        - if username/password authentication is enabled, and the cookie
          is valid, the corresponding participation is returned, and the
          cookie is refreshed.

        After finding the participation, IP login and hidden users
        restrictions are checked.

        In case of any error, or of a login by other sources, the
        cookie is deleted.

        return (Participation|None): the participation object for the
            user logged in for the running contest.

        """
        cookie_name = self.contest.name + "_login"
        cookie = self.get_secure_cookie(cookie_name)

        try:
            # In py2 Tornado gives us the IP address as a native binary
            # string, whereas ipaddress wants text (unicode) strings.
            ip_address = ipaddress.ip_address(str(self.request.remote_ip))
        except ValueError:
            logger.warning("Invalid IP address provided by Tornado: %s",
                           self.request.remote_ip)
            return None

        participation, cookie = authenticate_request(
            self.sql_session, self.contest, self.timestamp, cookie, ip_address)

        if cookie is None:
            self.clear_cookie(cookie_name)
        elif self.refresh_cookie:
            self.set_secure_cookie(cookie_name, cookie, expires_days=None)

        return participation

    def render_params(self):
        ret = super().render_params()

        ret["contest"] = self.contest

        if self.contest_url is not None:
            ret["contest_url"] = self.contest_url

        ret["phase"] = self.contest.phase(self.timestamp)

        ret["printing_enabled"] = (config.printer is not None)
        ret["questions_enabled"] = self.contest.allow_questions
        ret["testing_enabled"] = self.contest.allow_user_tests

        if self.current_user is not None:
            participation = self.current_user
            ret["participation"] = participation
            ret["user"] = participation.user

            res = compute_actual_phase(
                self.timestamp, self.contest.start, self.contest.stop,
                self.contest.analysis_start if self.contest.analysis_enabled
                else None,
                self.contest.analysis_stop if self.contest.analysis_enabled
                else None,
                self.contest.per_user_time, participation.starting_time,
                participation.delay_time, participation.extra_time)

            ret["actual_phase"], ret["current_phase_begin"], \
                ret["current_phase_end"], ret["valid_phase_begin"], \
                ret["valid_phase_end"] = res

            if ret["actual_phase"] == 0:
                ret["phase"] = 0

            # set the timezone used to format timestamps
            ret["timezone"] = get_timezone(participation.user, self.contest)

        # some information about token configuration
        ret["tokens_contest"] = self.contest.token_mode

        t_tokens = set(t.token_mode for t in self.contest.tasks)
        if len(t_tokens) == 1:
            ret["tokens_tasks"] = next(iter(t_tokens))
        else:
            ret["tokens_tasks"] = TOKEN_MODE_MIXED

        return ret

    def get_login_url(self):
        """The login url depends on the contest name, so we can't just
        use the "login_url" application parameter.

        """
        return self.contest_url()

    def get_task(self, task_name):
        """Return the task in the contest with the given name.

        task_name (str): the name of the task we are interested in.

        return (Task|None): the corresponding task object, if found.

        """
        return self.sql_session.query(Task) \
            .filter(Task.contest == self.contest) \
            .filter(Task.name == task_name) \
            .one_or_none()

    def get_submission(self, task, submission_num):
        """Return the num-th contestant's submission on the given task.

        task (Task): a task for the contest that is being served.
        submission_num (str): a positive number, in decimal encoding.

        return (Submission|None): the submission_num-th submission, in
            chronological order, that was sent by the currently logged
            in contestant on the given task (None if not found).

        """
        return self.sql_session.query(Submission) \
            .filter(Submission.participation == self.current_user) \
            .filter(Submission.task == task) \
            .order_by(Submission.timestamp) \
            .offset(int(submission_num) - 1) \
            .first()

    def get_user_test(self, task, user_test_num):
        """Return the num-th contestant's test on the given task.

        task (Task): a task for the contest that is being served.
        user_test_num (str): a positive number, in decimal encoding.

        return (UserTest|None): the user_test_num-th user test, in
            chronological order, that was sent by the currently logged
            in contestant on the given task (None if not found).

        """
        return self.sql_session.query(UserTest) \
            .filter(UserTest.participation == self.current_user) \
            .filter(UserTest.task == task) \
            .order_by(UserTest.timestamp) \
            .offset(int(user_test_num) - 1) \
            .first()

    def add_notification(self, subject, text, level, text_params=None):
        subject = self._(subject)
        text = self._(text)
        if text_params is not None:
            text %= text_params
        self.service.add_notification(self.current_user.user.username,
                                      self.timestamp, subject, text, level)

    def notify_success(self, subject, text, text_params=None):
        self.add_notification(subject, text, NOTIFICATION_SUCCESS, text_params)

    def notify_warning(self, subject, text, text_params=None):
        self.add_notification(subject, text, NOTIFICATION_WARNING, text_params)

    def notify_error(self, subject, text, text_params=None):
        self.add_notification(subject, text, NOTIFICATION_ERROR, text_params)
Example #54
0
class ContestHandler(BaseHandler):
    """A handler that has a contest attached.

    Most of the RequestHandler classes in this application will be a
    child of this class.

    """
    def prepare(self):
        super(ContestHandler, self).prepare()
        self.choose_contest()

        self._ = self.locale.translate

        if self.is_multi_contest():
            self.contest_url = \
                create_url_builder(self.url(self.contest.name))
        else:
            self.contest_url = self.url

        # Run render_params() now, not at the beginning of the request,
        # because we need contest_name
        self.r_params = self.render_params()

    def choose_contest(self):
        """Fill self.contest using contest passed as argument or path.

        If a contest was specified as argument to CWS, fill
        self.contest with that; otherwise extract it from the URL path.

        """
        if self.is_multi_contest():
            # Choose the contest found in the path argument
            # see: https://github.com/tornadoweb/tornado/issues/1673
            contest_name = self.path_args[0]

            # Select the correct contest or return an error
            try:
                self.contest = self.contest_list[contest_name]
            except KeyError:
                self.contest = Contest(
                    name=contest_name, description=contest_name)
                self.r_params = self.render_params()
                raise tornado.web.HTTPError(404)
        else:
            # Select the contest specified on the command line
            self.contest = Contest.get_from_id(
                self.application.service.contest_id, self.sql_session)

    def get_current_user(self):
        """Return the currently logged in participation.

        The name is get_current_user because tornado requires that
        name.

        The participation is obtained from one of the possible sources:
        - if IP autologin is enabled, the remote IP address is matched
          with the participation IP address; if a match is found, that
          participation is returned; in case of errors, None is returned;
        - if username/password authentication is enabled, and the cookie
          is valid, the corresponding participation is returned, and the
          cookie is refreshed.

        After finding the participation, IP login and hidden users
        restrictions are checked.

        In case of any error, or of a login by other sources, the
        cookie is deleted.

        return (Participation|None): the participation object for the
            user logged in for the running contest.

        """
        cookie_name = self.contest.name + "_login"

        participation = None

        if self.contest.ip_autologin:
            try:
                participation = self._get_current_user_from_ip()
                # If the login is IP-based, we delete previous cookies.
                if participation is not None:
                    self.clear_cookie(cookie_name)
            except RuntimeError:
                return None

        if participation is None \
                and self.contest.allow_password_authentication:
            participation = self._get_current_user_from_cookie()

        if participation is None:
            self.clear_cookie(cookie_name)
            return None

        # Check if user is using the right IP (or is on the right subnet),
        # and that is not hidden if hidden users are blocked.
        ip_login_restricted = \
            self.contest.ip_restriction and participation.ip is not None \
            and not check_ip(self.request.remote_ip, participation.ip)
        hidden_user_restricted = \
            participation.hidden and self.contest.block_hidden_participations
        if ip_login_restricted or hidden_user_restricted:
            self.clear_cookie(cookie_name)
            participation = None

        return participation

    def _get_current_user_from_ip(self):
        """Return the current participation based on the IP address.

        return (Participation|None): the only participation matching
            the remote IP address, or None if no participations could
            be matched.

        raise (RuntimeError): if there is more than one participation
            matching the remote IP address.

        """
        try:
            # We encode it as a network (i.e., we assign it a /32 or
            # /128 mask) since we're comparing it for equality with
            # other networks.
            remote_ip = ipaddress.ip_network(unicode(self.request.remote_ip))
        except ValueError:
            return None
        participations = self.sql_session.query(Participation)\
            .filter(Participation.contest == self.contest)\
            .filter(Participation.ip.any(remote_ip))

        # If hidden users are blocked we ignore them completely.
        if self.contest.block_hidden_participations:
            participations = participations\
                .filter(Participation.hidden.is_(False))

        participations = participations.all()

        if len(participations) == 1:
            return participations[0]

        # Having more than participation with the same IP,
        # is a mistake and should not happen. In such case,
        # we disallow login for that IP completely, in order to
        # make sure the problem is noticed.
        if len(participations) > 1:
            logger.error("%d participants have IP %s while"
                         "auto-login feature is enabled." % (
                             len(participations), remote_ip))
            raise RuntimeError("More than one participants with the same IP.")

    def _get_current_user_from_cookie(self):
        """Return the current participation based on the cookie.

        If a participation can be extracted, the cookie is refreshed.

        return (Participation|None): the participation extracted from
            the cookie, or None if not possible.

        """
        cookie_name = self.contest.name + "_login"

        if self.get_secure_cookie(cookie_name) is None:
            return None

        # Parse cookie.
        try:
            cookie = pickle.loads(self.get_secure_cookie(cookie_name))
            username = cookie[0]
            password = cookie[1]
            last_update = make_datetime(cookie[2])
        except:
            return None

        # Check if the cookie is expired.
        if self.timestamp - last_update > \
                timedelta(seconds=config.cookie_duration):
            return None

        # Load participation from DB and make sure it exists.
        participation = self.sql_session.query(Participation)\
            .join(Participation.user)\
            .options(contains_eager(Participation.user))\
            .filter(Participation.contest == self.contest)\
            .filter(User.username == username)\
            .first()
        if participation is None:
            return None

        # Check that the password is correct (if a contest-specific
        # password is defined, use that instead of the user password).
        if participation.password is None:
            correct_password = participation.user.password
        else:
            correct_password = participation.password
        if password != correct_password:
            return None

        if self.refresh_cookie:
            self.set_secure_cookie(cookie_name,
                                   pickle.dumps((username,
                                                 password,
                                                 make_timestamp())),
                                   expires_days=None)

        return participation

    def get_user_locale(self):
        self.langs = self.application.service.langs
        lang_codes = self.langs.keys()

        if self.contest.allowed_localizations:
            lang_codes = filter_language_codes(
                lang_codes, self.contest.allowed_localizations)

        # Select the one the user likes most.
        basic_lang = 'en'

        if self.contest.allowed_localizations:
            basic_lang = lang_codes[0].replace("_", "-")

        http_langs = [lang_code.replace("_", "-") for lang_code in lang_codes]
        self.browser_lang = parse_accept_header(
            self.request.headers.get("Accept-Language", ""),
            LanguageAccept).best_match(http_langs, basic_lang)

        self.cookie_lang = self.get_cookie("language", None)

        if self.cookie_lang in http_langs:
            lang_code = self.cookie_lang
        else:
            lang_code = self.browser_lang

        self.set_header("Content-Language", lang_code)
        return self.langs[lang_code.replace("-", "_")]

    @staticmethod
    def _get_token_status(obj):
        """Return the status of the tokens for the given object.

        obj (Contest or Task): an object that has the token_* attributes.
        return (int): one of 0 (disabled), 1 (enabled/finite) and 2
                      (enabled/infinite).

        """
        if obj.token_mode == "disabled":
            return 0
        elif obj.token_mode == "finite":
            return 1
        elif obj.token_mode == "infinite":
            return 2
        else:
            raise RuntimeError("Unknown token_mode value.")

    def render_params(self):
        ret = super(ContestHandler, self).render_params()

        ret["contest"] = self.contest

        if hasattr(self, "contest_url"):
            ret["contest_url"] = self.contest_url

        ret["phase"] = self.contest.phase(self.timestamp)

        ret["printing_enabled"] = (config.printer is not None)
        ret["questions_enabled"] = self.contest.allow_questions
        ret["testing_enabled"] = self.contest.allow_user_tests

        if self.current_user is not None:
            participation = self.current_user

            res = compute_actual_phase(
                self.timestamp, self.contest.start, self.contest.stop,
                self.contest.analysis_start if self.contest.analysis_enabled
                else None,
                self.contest.analysis_stop if self.contest.analysis_enabled
                else None,
                self.contest.per_user_time, participation.starting_time,
                participation.delay_time, participation.extra_time)

            ret["actual_phase"], ret["current_phase_begin"], \
                ret["current_phase_end"], ret["valid_phase_begin"], \
                ret["valid_phase_end"] = res

            if ret["actual_phase"] == 0:
                ret["phase"] = 0

            # set the timezone used to format timestamps
            ret["timezone"] = get_timezone(participation.user, self.contest)

        # some information about token configuration
        ret["tokens_contest"] = self._get_token_status(self.contest)

        t_tokens = sum(self._get_token_status(t) for t in self.contest.tasks)
        if t_tokens == 0:
            ret["tokens_tasks"] = 0  # all disabled
        elif t_tokens == 2 * len(self.contest.tasks):
            ret["tokens_tasks"] = 2  # all infinite
        else:
            ret["tokens_tasks"] = 1  # all finite or mixed

        # TODO Now all language names are shown in the active language.
        # It would be better to show them in the corresponding one.
        ret["lang_names"] = {}

        # Get language codes for allowed localizations
        lang_codes = self.langs.keys()
        if len(self.contest.allowed_localizations) > 0:
            lang_codes = filter_language_codes(
                lang_codes, self.contest.allowed_localizations)
        for lang_code, trans in self.langs.iteritems():
            language_name = None
            # Filter lang_codes with allowed localizations
            if lang_code not in lang_codes:
                continue
            try:
                language_name = translate_language_country_code(
                    lang_code, trans)
            except ValueError:
                language_name = translate_language_code(
                    lang_code, trans)
            ret["lang_names"][lang_code.replace("_", "-")] = language_name

        ret["cookie_lang"] = self.cookie_lang
        ret["browser_lang"] = self.browser_lang

        return ret

    def get_login_url(self):
        """The login url depends on the contest name, so we can't just
        use the "login_url" application parameter.

        """
        return self.contest_url()
Example #55
0
    def do_reimport(self):
        """Get the contest from the Loader and merge it."""
        with SessionGen() as session:
            # Load the old contest from the database.
            old_contest = Contest.get_from_id(self.old_contest_id, session)
            old_users = dict((x.username, x) for x in old_contest.users)
            old_tasks = dict((x.name, x) for x in old_contest.tasks)

            # Load the new contest from the filesystem.
            new_contest, new_tasks, new_users = self.loader.get_contest()

            # Updates contest-global settings that are set in new_contest.
            self._update_columns(old_contest, new_contest)

            # Do the actual merge: compare all users of the old and of
            # the new contest and see if we need to create, update or
            # delete them. Delete only if authorized, fail otherwise.
            users = set(old_users.keys()) | set(new_users)
            for username in users:
                old_user = old_users.get(username, None)

                if old_user is None:
                    # Create a new user.
                    logger.info("Creating user %s" % username)
                    new_user = self.loader.get_user(username)
                    old_contest.users.append(new_user)
                elif username in new_users:
                    # Update an existing user.
                    logger.info("Updating user %s" % username)
                    new_user = self.loader.get_user(username)
                    self._update_object(old_user, new_user)
                else:
                    # Delete an existing user.
                    if self.force:
                        logger.info("Deleting user %s" % username)
                        old_contest.users.remove(old_user)
                    else:
                        logger.critical(
                            "User %s exists in old contest, but "
                            "not in the new one. Use -f to force." %
                            username)
                        return False

            # The same for tasks. Setting num for tasks requires a bit
            # of trickery, since we have to avoid triggering a
            # duplicate key constraint violation while we're messing
            # with the task order. To do that we just set sufficiently
            # high number on the first pass and then fix it on a
            # second pass.
            tasks = set(old_tasks.keys()) | set(new_tasks)
            current_num = max(len(old_tasks), len(new_tasks))
            for task in tasks:
                old_task = old_tasks.get(task, None)

                if old_task is None:
                    # Create a new task.
                    logger.info("Creating task %s" % task)
                    new_task = self.loader.get_task(task)
                    new_task.num = current_num
                    current_num += 1
                    old_contest.tasks.append(new_task)
                elif task in new_tasks:
                    # Update an existing task.
                    if self.full or self.loader.has_changed(task):
                        logger.info("Updating task %s" % task)
                        new_task = self.loader.get_task(task)
                        new_task.num = current_num
                        current_num += 1
                        self._update_object(old_task, new_task)
                    else:
                        logger.info("Task %s has not changed" % task)
                        # Even unchanged tasks should use a temporary number
                        # to avoid duplicate numbers when we fix them.
                        old_task.num = current_num
                        current_num += 1
                else:
                    # Delete an existing task.
                    if self.force:
                        logger.info("Deleting task %s" % task)
                        session.delete(old_task)
                    else:
                        logger.critical(
                            "Task %s exists in old contest, but "
                            "not in the new one. Use -f to force." %
                            task)
                        return False

                session.flush()

            # And finally we fix the numbers; old_contest must be
            # refreshed because otherwise SQLAlchemy doesn't get aware
            # that some tasks may have been deleted
            tasks_order = dict((name, num)
                               for num, name in enumerate(new_tasks))
            session.refresh(old_contest)
            for task in old_contest.tasks:
                task.num = tasks_order[task.name]

            session.commit()

        logger.info("Reimport finished (contest id: %s)." %
                    self.old_contest_id)

        return True