def test_cookie_contains_password(self): self.contest.ip_autologin = False # Cookies are of no use if one cannot login by password. self.contest.allow_password_authentication = False self.assertFailure() # The cookie works with all methods as it holds the plaintext password. self.contest.allow_password_authentication = True self.user.password = hash_password("mypass", method="bcrypt") self.assertSuccessAndCookieRefreshed() self.user.password = hash_password("mypass", method="plaintext") self.assertSuccessAndCookieRefreshed() # Cookies contain the password, which is validated every time. self.user.password = build_password("newpass") self.assertFailure() # Contest-specific passwords take precedence over global ones. self.participation.password = build_password("mypass") self.assertSuccessAndCookieRefreshed() # And they do so in the negative case too. self.user.password = build_password("mypass") self.participation.password = build_password("newpass") self.assertFailure()
def test_cookie_contains_password(self): self.contest.ip_autologin = False # Cookies are of no use if one cannot login by password. self.contest.allow_password_authentication = False self.assertFailure() # The cookie works with all methods as it holds the plaintext password. self.contest.allow_password_authentication = True self.user.password = hash_password("mypass", method="bcrypt") self.assertSuccessAndCookieRefreshed() self.user.password = hash_password("mypass", method="plaintext") self.assertSuccessAndCookieRefreshed() # Cookies contain the password, which is validated every time. self.user.password = build_password("newpass") self.assertFailure() # Contest-specific passwords take precedence over global ones. self.participation.password = build_password("mypass") self.assertSuccessAndCookieRefreshed() # And they do so in the negative case too. self.user.password = build_password("mypass") self.participation.password = build_password("newpass") self.assertFailure()
def test_no_participation_for_user_in_contest(self): other_contest = self.add_contest(allow_password_authentication=True) other_user = self.add_user( username="******", password=build_password("mypass")) self.add_participation(contest=other_contest, user=other_user) self.assertFailure("myotheruser", "mypass", "127.0.0.1")
def update_password(username, password, method, is_hashed): logger.info("Updating user password in the database.") if password is None: return False # shell will interfere with special characters, so a hashed string must be protected by # single quotes. We must remove them if password[0] == "'": password[0] = ' ' if password[-1] == "'": password[-1] = ' ' password = password.strip() if is_hashed: stored_password = build_password(password, method) else: stored_password = hash_password(password, method) with SessionGen() as session: user = session.query(User)\ .filter(User.username == username).first() if user is None: logger.error("User %s does not exist.", username) return False session.query(User).filter(User.username == username).\ update({"password": stored_password}, synchronize_session="fetch") session.commit() logger.info("User %s password updated, method=%s. " % (username, method)) return True
def test_no_participation_for_user_in_contest(self): other_contest = self.add_contest(allow_password_authentication=True) other_user = self.add_user( username="******", password=build_password("mypass")) self.add_participation(contest=other_contest, user=other_user) self.assertFailure("myotheruser", "mypass", "127.0.0.1")
def setUp(self): super(TestValidateLogin, self).setUp() self.timestamp = make_datetime() self.contest = self.add_contest(allow_password_authentication=True) self.user = self.add_user( username="******", password=build_password("mypass")) self.participation = self.add_participation( contest=self.contest, user=self.user)
def test_invalid_password_in_database(self): self.contest.ip_autologin = False self.contest.allow_password_authentication = True self.user.password = "******" self.assertFailure() self.user.password = build_password("mypass") self.participation.password = "******" self.assertFailure()
def test_invalid_password_in_database(self): self.contest.ip_autologin = False self.contest.allow_password_authentication = True self.user.password = "******" self.assertFailure() self.user.password = build_password("mypass") self.participation.password = "******" self.assertFailure()
def post(self): try: username = self.get_argument("username") email = self.get_argument("email") if not 1 <= len(username): raise ValueError() if not 1 <= len(email): raise ValueError() except (tornado.web.MissingArgumentError, ValueError): raise tornado.web.HTTPError(400) # Check that the username exists tot_users = self.sql_session.query(User)\ .filter(User.username == username)\ .filter(User.email == email)\ .count() if tot_users == 0: # HTTP 409: Conflict raise tornado.web.HTTPError(409) if not hasattr(config, "saco_admin_email_password"): logger.error( "'saco_admin_email_password' not defined in cms.conf - can't email new password" ) return pwd = generate_random_password() self.sql_session.query(User)\ .filter(User.username == username)\ .filter(User.email == email)\ .update({'password': build_password(pwd)}) self.sql_session.commit() sender = '*****@*****.**' receivers = [email] message = password_reset_email_format.format(sender, email, pwd) result = 'successful' try: s = smtplib.SMTP('smtp.saco-evaluator.org.za', 587) s.login('*****@*****.**', config.saco_admin_email_password) s.sendmail(sender, receivers + [sender], message) except smtplib.SMTPException as e: logger.error('Unable to send email:', e) result = 'unsuccessful' if hasattr(config, 'test_logs_dir'): with open(os.path.join(config.test_logs_dir, 'pwd_reset_requests'), 'a') as f: time = datetime.datetime.now().strftime('%y-%m-%d %H:%M') f.write('{} {} {} {}\n'.format(username, email, time, result)) else: logger.error( "test_logs_dir undefined in cms.conf, can't write logs")
class User(Base): """Class to store a user. """ __tablename__ = 'users' # Auto increment primary key. id = Column( Integer, primary_key=True) # Real name (human readable) of the user. first_name = Column( Unicode, nullable=False) last_name = Column( Unicode, nullable=False) # Username and password to log in the CWS. username = Column( Unicode, CodenameConstraint("username"), nullable=False, unique=True) password = Column( Unicode, nullable=False, default=lambda: build_password(generate_random_password())) # Email for any communications in case of remote contest. email = Column( Unicode, nullable=True) # Timezone for the user. All timestamps in CWS will be shown using # the timezone associated to the logged-in user or (if it's None # or an invalid string) the timezone associated to the contest or # (if it's None or an invalid string) the local timezone of the # server. This value has to be a string like "Europe/Rome", # "Australia/Sydney", "America/New_York", etc. timezone = Column( Unicode, nullable=True) # The language codes accepted by this user (from the "most # preferred" to the "least preferred"). If in a contest there is a # statement available in some of these languages, then the most # preferred of them will be highlighted. # FIXME: possibly move it to Participation and change it back to # primary_statements preferred_languages = Column( ARRAY(String), nullable=False, default=[])
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
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
def add_user(first_name, last_name, username, password, method, is_hashed, email, timezone, preferred_languages, overwrite=False): logger.info("Creating the user in the database.") pwd_generated = False if password is None: assert not is_hashed password = generate_random_password() pwd_generated = True if is_hashed: stored_password = build_password(password, method) else: stored_password = hash_password(password, method) if preferred_languages is None or preferred_languages == "": preferred_languages = "[]" else: preferred_languages = \ "[" + ",".join("\"" + lang + "\"" for lang in preferred_languages.split(",")) + "]" user = User(first_name=first_name, last_name=last_name, username=username, password=stored_password, email=email, timezone=timezone, preferred_languages=preferred_languages) with SessionGen() as session: if overwrite: existing_user = session.query(User) \ .filter(User.username == username).first() if existing_user is not None: user = existing_user user.first_name = first_name user.last_name = last_name user.username = username if not pwd_generated: user.password = stored_password else: pwd_generated = False user.email = email or user.email user.timezone = timezone or user.timezone user.preferred_languages = preferred_languages or \ user.preferred_languages try: session.add(user) session.commit() except IntegrityError: logger.error("A user with the given username already exists.") return False logger.info("User added%s. " "Use AddParticipation to add this user to a contest." % (" with password %s" % password if pwd_generated else "")) return True
def setUp(self): super(TestAuthenticateRequest, self).setUp() self.timestamp = make_datetime() self.contest = self.add_contest() self.user = self.add_user( username="******", password=build_password("mypass")) self.participation = self.add_participation( contest=self.contest, user=self.user) _, self.cookie = validate_login( self.session, self.contest, self.timestamp, self.user.username, "mypass", ipaddress.ip_address("10.0.0.1"))
def _makeparticipation(self, username, cdb, udb, gdb, teamdb): user = self.users[username] pdb = Participation(user=udb, contest=cdb) pdb.password = build_password(user.password) pdb.group = gdb pdb.ip = user.ip pdb.hidden = user.hidden pdb.unrestricted = user.unrestricted pdb.team = teamdb return pdb
def _makeparticipation(self, username, cdb, udb, gdb, teamdb): user = self.users[username] pdb = Participation(user=udb, contest=cdb) pdb.password = build_password(user.password) pdb.group = gdb pdb.ip = user.ip pdb.hidden = user.hidden pdb.unofficial = user.unofficial pdb.unrestricted = user.unrestricted pdb.team = teamdb return pdb
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
def get_user(self): """See docstring in class Loader. """ username = os.path.basename(self.path) userdata = None # This is not standard Polygon feature, but useful for CMS users # we assume contestants.txt contains one line for each user: # # username;password;first_name;last_name;hidden # # For example: # # contestant1;123;Cont;Estant;0 # jury;1234;Ju;Ry;1 users_path = os.path.join(os.path.dirname(self.path), 'contestants.txt') if os.path.exists(users_path): with io.open(users_path, "rt", encoding="utf-8") as users_file: for user in users_file.readlines(): user = user.strip().split(';') name = user[0].strip() if name == username: userdata = [x.strip() for x in user] if userdata is not None: logger.info("Loading parameters for user %s.", username) args = {} args['username'] = userdata[0] args['password'] = build_password(userdata[1]) args['first_name'] = userdata[2] args['last_name'] = userdata[3] args['hidden'] = (len(userdata) > 4 and userdata[4] == '1') logger.info("User parameters loaded.") return User(**args) else: logger.critical("User %s not found in contestants.txt file.", username) return None
def get_user(self): """See docstring in class Loader. """ username = os.path.basename(self.path) userdata = None # This is not standard Polygon feature, but useful for CMS users # we assume contestants.txt contains one line for each user: # # username;password;first_name;last_name;hidden # # For example: # # contestant1;123;Cont;Estant;0 # jury;1234;Ju;Ry;1 users_path = os.path.join( os.path.dirname(self.path), 'contestants.txt') if os.path.exists(users_path): with io.open(users_path, "rt", encoding="utf-8") as users_file: for user in users_file.readlines(): user = user.strip().split(';') name = user[0].strip() if name == username: userdata = [x.strip() for x in user] if userdata is not None: logger.info("Loading parameters for user %s.", username) args = {} args['username'] = userdata[0] args['password'] = build_password(userdata[1]) args['first_name'] = userdata[2] args['last_name'] = userdata[3] args['hidden'] = (len(userdata) > 4 and userdata[4] == '1') logger.info("User parameters loaded.") return User(**args) else: logger.critical( "User %s not found in contestants.txt file.", username) return None
def add_user(first_name, last_name, username, password, method, is_hashed, email, timezone, preferred_languages): logger.info("Creating the user in the database.") pwd_generated = False if password is None: assert not is_hashed password = generate_random_password() pwd_generated = True if is_hashed: stored_password = build_password(password, method) else: stored_password = hash_password(password, method) if preferred_languages is None: preferred_languages = [] else: preferred_languages = list( lang.strip() for lang in preferred_languages.split(",") if lang.strip()) user = User(first_name=first_name, last_name=last_name, username=username, password=stored_password, email=email, timezone=timezone, preferred_languages=preferred_languages) try: with SessionGen() as session: session.add(user) session.commit() except IntegrityError: logger.error("A user with the given username already exists.") return False logger.info("User added%s. " "Use AddParticipation to add this user to a contest." % (" with password %s" % password if pwd_generated else "")) return True
def add_user(first_name, last_name, username, password, method, is_hashed, email, timezone, preferred_languages): logger.info("Creating the user in the database.") pwd_generated = False if password is None: assert not is_hashed password = generate_random_password() pwd_generated = True if is_hashed: stored_password = build_password(password, method) else: stored_password = hash_password(password, method) if preferred_languages is None: preferred_languages = [] else: preferred_languages = list(lang.strip() for lang in preferred_languages.split(",") if lang.strip()) user = User(first_name=first_name, last_name=last_name, username=username, password=stored_password, email=email, timezone=timezone, preferred_languages=preferred_languages) try: with SessionGen() as session: session.add(user) session.commit() except IntegrityError: logger.error("A user with the given username already exists.") return False logger.info("User added%s. " "Use AddParticipation to add this user to a contest." % (" with password %s" % password if pwd_generated else "")) return True
def _makeuser(self, username): """ Return a User object for the specified user which can be saved to the database. username (unicode): the name of the user to generate return (User,Participation): database object for the user """ user = self.users[username] # The user should never actually use this password, because we set # different passwords for each participation. udb = User(username=user.username, first_name=user.firstname, last_name=user.lastname, password=build_password(user.password)) udb.timezone = user.timezone udb.preferred_languages = user.primary_statements return udb
def _makeuser(self, username): """ Return a User object for the specified user which can be saved to the database. username (unicode): the name of the user to generate return (User,Participation): database object for the user """ user = self.users[username] # The user should never actually use this password, because we set # different passwords for each participation. udb = User(username=user.username, first_name=user.firstname, last_name=user.lastname, password=build_password(user.password)) udb.timezone = user.timezone udb.preferred_languages = user.primary_statements return udb
def get(self): self.create_client() # When we have a code, request id token if self.get_argument('code', False): response = self.request.query response = self.client.parse_response( AuthorizationResponse, info=response, sformat="urlencoded") # The query is checked against the state in the cookie code = response['code'] if 'code' in response else None state = response['state'] if 'state' in response else None cookie = self.get_secure_cookie(self.contest.name + "_auth") if code and cookie and json.loads(cookie)[0] == state: args = { "code": code, "token_endpoint": self.op_info["token_endpoint"], "redirect_uri": self.redirect_uri, } # Request an access token to the authentication server resp = yield self.get_access_token( state=state, request_args=args, authn_method="client_secret_basic" ) else: self.redirect_login_error() self.clear_cookie(self.contest.name + "_auth") # The token is checked against the nonce in the cookie id_token = resp["id_token"] if id_token["nonce"] != json.loads(cookie)[1]: self.redirect_login_error() first_name = ( id_token["given_name"] if "given_name" in id_token else "") last_name = ( id_token["family_name"] if "family_name" in id_token else "") email = ( id_token["email"] if "email" in id_token else "") username = id_token["sub"] # Check if the user already exists user = self.sql_session.query(User)\ .filter(User.username == username).first() # Create the user if it doesn't exist yet if user is None: user = User( first_name, last_name, username, email=email, password=build_password(generate_random_password())) self.sql_session.add(user) self.sql_session.commit() if not [p for p in user.participations if p.contest_id == self.contest.id]: participation = Participation( contest=self.contest, user=user) self.sql_session.add(participation) self.sql_session.commit() self.try_user_login(user) # Request a code else: state = rndstr() nonce = rndstr() self.set_secure_cookie( self.contest.name + "_auth", json.dumps([state, nonce]), expires_days=None) claims_request = ClaimsRequest( id_token=Claims( sub={"essential": True} ), userinfo=Claims( given_name={"essential": True}, family_name={"essential": True}, preferred_username={"essential": True}, ) ) args = { "client_id": self.client.client_id, "response_type": "code", "scope": ["openid", "offline_access"], "nonce": nonce, "redirect_uri": self.redirect_uri, "state": state, "claims": claims_request, } next_page = self.get_argument('next', None) if next_page: args["next"] = next_page auth_req = (self.client.construct_AuthorizationRequest (request_args=args)) login_url = auth_req.request( self.op_info["authorization_endpoint"]) self.redirect(login_url)
def get_user(self): """See docstring in class Loader. """ username = os.path.basename(self.path) userdata = None # Shaker user format (CSV-based): # # This is not standard Polygon feature, but useful for CMS users # we assume contestants.txt contains one line for each user, and # a header line, as follow: # # ...,Prénom,Nom,Pseudo,Mot de passe,... # (additionnal columns are accepted, and order isn't important) # users_path = os.path.join(os.path.dirname(self.path), 'contestants.csv') if not os.path.exists(users_path): users_path = os.path.join(os.path.dirname(self.path), '../contestants.csv') if not os.path.exists(users_path): logger.critical("contestants.csv not found!") exit(1) return None try: headers = dict() if os.path.exists(users_path): with io.open(users_path, "rt", encoding="utf-8") as users_file: reader = unicode_csv_reader(users_file) # Headers headers_list = next(reader) headers = dict(zip(headers_list, range(len(headers_list)))) # Content for user in reader: if len(user) == 0: continue name = user[headers['Pseudo']].strip() if name == username: userdata = [x.strip() for x in user] break def get_param(param, default=None): try: return userdata[headers[param]] except KeyError as _: if default is None: raise _ return default if userdata is not None: logger.info("Loading parameters for user %s.", username) args = { 'username': get_param('Pseudo'), 'password': get_param('Mot de passe', ''), 'first_name': get_param('Prénom', ''), 'last_name': get_param('Nom', '') } # args['hidden'] = get_param('hidden', False) == '1' # Generate a password if none is defined if len(args['password']) == 0: args['password'] = random_password() # Build an auth string from the password args['password'] = build_password(args['password']) logger.info("User parameters loaded.") return User(**args) else: logger.critical("User %s not found in contestants.csv file.", username) exit(1) return None except KeyError as e: logger.critical( "contestants.csv is ill-formed: column %s not found!", e) exit(1) return None
def test_default_plaintext(self): self.assertEqual(build_password("plain"), "plaintext:plain")
def get_contest(self): """See docstring in class Loader. """ name = os.path.split(self.path)[1] logger.info("Loading parameters for contest %s.", name) args = {} tree = ET.parse(os.path.join(self.path, "contest.xml")) root = tree.getroot() args['name'] = name # TODO: find proper way to choose contest primary language. self.primary_language = root.find('names') \ .find('name').attrib['language'] # All available contest languages are allowed to be used. self.languages = [] for alternative_name in root.find('names'): self.languages.append(alternative_name.attrib['language']) logger.info("Contest languages are %s %s", self.primary_language, str(self.languages)) args['description'] = root.find('names') \ .find("name[@language='%s']" % self.primary_language) \ .attrib['value'] logger.info("Contest description is %s", args['description']) # For now Polygon doesn't support custom contest-wide files, # so we need to hardcode some contest settings. args['start'] = datetime(1970, 1, 1) args['stop'] = datetime(1970, 1, 1) # Uncomment the following to set specific values for these # options. # args['max_submission_number'] = 100 # args['max_user_test_number'] = 100 # args['min_submission_interval'] = make_timedelta(60) # args['min_user_test_interval'] = make_timedelta(60) # args['max_user_test_number'] = 10 # args['min_user_test_interval'] = make_timedelta(60) # args['token_mode'] = 'infinite' # args['token_max_number'] = 100 # args['token_min_interval'] = make_timedelta(60) # args['token_gen_initial'] = 1 # args['token_gen_number'] = 1 # args['token_gen_interval'] = make_timedelta(1800) # args['token_gen_max'] = 2 logger.info("Contest parameters loaded.") tasks = [] for problem in root.find('problems'): tasks.append(os.path.basename(problem.attrib['url'])) participations = [] # This is not standard Polygon feature, but useful for CMS users # we assume contestants.txt contains one line for each user: # # username;password;first_name;last_name;hidden # # For example: # # contestant1;123;Cont;Estant;0 # jury;1234;Ju;Ry;1 users_path = os.path.join(self.path, 'contestants.txt') if os.path.exists(users_path): with io.open(users_path, "rt", encoding="utf-8") as users_file: for user in users_file.readlines(): user = user.strip() user = user.split(';') participations.append({ "username": user[0].strip(), "password": build_password(user[1].strip()), "hidden": user[4].strip() # "ip" is not passed }) return Contest(**args), tasks, participations
def test_non_ascii(self): self.assertEqual(build_password("p你好", "m你好"), "m你好:p你好")
def test_participation_specific_password(self): self.participation.password = build_password("myotherpass") self.assertFailure("myuser", "mypass", "127.0.0.1") self.assertSuccess("myuser", "myotherpass", "127.0.0.1")
def test_success(self): self.assertEqual(build_password("pwd", "method"), "method:pwd")
def test_non_ascii(self): self.assertEqual(build_password("p你好", "m你好"), "m你好:p你好")
def test_participation_specific_password(self): self.participation.password = build_password("myotherpass") self.assertFailure("myuser", "mypass", "127.0.0.1") self.assertSuccess("myuser", "myotherpass", "127.0.0.1")
def get_contest(self): """See docstring in class ContestLoader.""" if not os.path.exists(os.path.join(self.path, "contest.yaml")): logger.critical("File missing: \"contest.yaml\"") return None conf = load_yaml_from_path(os.path.join(self.path, "contest.yaml")) # Here we update the time of the last import touch(os.path.join(self.path, ".itime_contest")) # If this file is not deleted, then the import failed touch(os.path.join(self.path, ".import_error_contest")) args = {} # Contest Information load(conf, args, ["name", "nome_breve"]) logger.info("Loading parameters for contest %s.", args["name"]) load(conf, args, ["description", "nome"]) load(conf, args, "allowed_localizations") load(conf, args, ["languages", "allowed_programming_languages"]) load(conf, args, "submissions_download_allowed") load(conf, args, "allow_questions") load(conf, args, "allow_user_tests") load(conf, args, ["score_precision", "score_decimal_places"]) # Logging in load(conf, args, "block_hidden_participations") load(conf, args, "allow_password_authentication") load(conf, args, "allow_registration") load(conf, args, ["ip_restriction","ip_based_login_restriction"]) load(conf, args, ["ip_autologin","ip_based_autologin"]) # Tokens parameters # Use the new token settings format if detected. if "token_mode" in conf: load(conf, args, "token_mode") load(conf, args, ["token_max_number","maximum_number_of_tokens"]) load(conf, args, ["token_min_interval","minimum_interval_between_tokens"], conv=make_timedelta) load(conf, args, ["token_gen_initial","initial_number_of_tokens"]) load(conf, args, ["token_gen_number","token_generation_number"]) load(conf, args, ["token_gen_interval","token_generation_period"], conv=make_timedelta) load(conf, args, ["token_gen_max","maximum_accumulated_tokens"]) # Otherwise fall back on the old one. else: logger.warning( "contest.yaml uses a deprecated format for token settings " "which will soon stop being supported, you're advised to " "update it.") # Determine the mode. if conf.get("token_initial", None) is None: args["token_mode"] = TOKEN_MODE_DISABLED elif conf.get("token_gen_number", 0) > 0 and \ conf.get("token_gen_time", 0) == 0: args["token_mode"] = TOKEN_MODE_INFINITE else: args["token_mode"] = TOKEN_MODE_FINITE # Set the old default values. args["token_gen_initial"] = 0 args["token_gen_number"] = 0 args["token_gen_interval"] = timedelta() # Copy the parameters to their new names. load(conf, args, "token_total", "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_initial", "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_time", "token_gen_interval", conv=make_timedelta) load(conf, args, "token_max", "token_gen_max") # Remove some corner cases. if args["token_gen_initial"] is None: args["token_gen_initial"] = 0 if args["token_gen_interval"].total_seconds() == 0: args["token_gen_interval"] = timedelta(minutes=1) # Times load(conf, args, ["start", "start_time", "start_time_in_utc", "inizio"], conv=make_utc_datetime) load(conf, args, ["stop", "end_time", "end_time_in_utc", "fine"], conv=make_utc_datetime) load(conf, args, ["per_user_time","length_of_the_contest"], conv=make_timedelta) load(conf, args, ["timezone"]) # Limits load(conf, args, ["max_submission_number","maximum_number_of_submissions"]) load(conf, args, ["max_user_test_number","maximum_number_of_user_tests"]) load(conf, args, ["min_submission_interval","minimum_interval_between_submissions"], conv=make_timedelta) load(conf, args, ["min_user_test_interval","minimum_interval_between_user_tests"], conv=make_timedelta) # Analysis load(conf, args, ["analysis_enabled", "enabled"]) load(conf, args, ["analysis_start", "analysis_mode_start_time_in_utc"], conv=make_utc_datetime) load(conf, args, ["analysis_stop", "analysis_mode_end_time_in_utc"], conv=make_utc_datetime) taskpaths = load(conf, None, ["tasks", "problemi"]) # read true name of tasks tasks = [] self.task_path_map = {} for taskpath in taskpaths: name = taskpath try: conf = load_yaml_from_path(os.path.join(self.path, taskpath, "task.yaml")) name = conf.get("name", taskpath) except: try: conf = load_yaml_from_path(os.path.join(self.path, taskpath + ".yaml")) name = conf.get("name", taskpath) except: pass tasks.append(name) self.task_path_map[name] = os.path.join(self.path, taskpath) participations = load(conf, None, ["users", "participations"]) if participations: for p in participations: p["password"] = build_password(p["password"]) # Import was successful os.remove(os.path.join(self.path, ".import_error_contest")) logger.info("Contest parameters loaded.") return Contest(**args), tasks, participations
def get_user(self): """See docstring in class Loader. """ username = os.path.basename(self.path) userdata = None # Shaker user format (CSV-based): # # This is not standard Polygon feature, but useful for CMS users # we assume contestants.txt contains one line for each user, and # a header line, as follow: # # ...,Prénom,Nom,Pseudo,Mot de passe,... # (additionnal columns are accepted, and order isn't important) # users_path = os.path.join( os.path.dirname(self.path), 'contestants.csv') if not os.path.exists(users_path): users_path = os.path.join( os.path.dirname(self.path), '../contestants.csv') if not os.path.exists(users_path): logger.critical("contestants.csv not found!") exit(1) return None try: headers = dict() if os.path.exists(users_path): with io.open(users_path, "rt", encoding="utf-8") as users_file: reader = unicode_csv_reader(users_file) # Headers headers_list = next(reader) headers = dict(zip(headers_list, range(len(headers_list)))) # Content for user in reader: if len(user) == 0: continue name = user[headers['Pseudo']].strip() if name == username: userdata = [x.strip() for x in user] break def get_param(param, default=None): try: return userdata[headers[param]] except KeyError as _: if default is None: raise _ return default if userdata is not None: logger.info("Loading parameters for user %s.", username) args = { 'username': get_param('Pseudo'), 'password': get_param('Mot de passe', ''), 'first_name': get_param('Prénom', ''), 'last_name': get_param('Nom', '') } # args['hidden'] = get_param('hidden', False) == '1' # Generate a password if none is defined if len(args['password']) == 0: args['password'] = random_password() # Build an auth string from the password args['password'] = build_password(args['password']) logger.info("User parameters loaded.") return User(**args) else: logger.critical( "User %s not found in contestants.csv file.", username) exit(1) return None except KeyError as e: logger.critical( "contestants.csv is ill-formed: column %s not found!", e) exit(1) return None
def test_default_plaintext(self): self.assertEqual(build_password("plain"), "plaintext:plain")
def get_contest(self): """See docstring in class ContestLoader.""" if not os.path.exists(os.path.join(self.path, "contest.yaml")): logger.critical("File missing: \"contest.yaml\"") return None conf = load_yaml_from_path(os.path.join(self.path, "contest.yaml")) # Here we update the time of the last import touch(os.path.join(self.path, ".itime_contest")) # If this file is not deleted, then the import failed touch(os.path.join(self.path, ".import_error_contest")) args = {} load(conf, args, ["name", "nome_breve"]) load(conf, args, ["description", "nome"]) logger.info("Loading parameters for contest %s.", args["name"]) # Use the new token settings format if detected. if "token_mode" in conf: load(conf, args, "token_mode") load(conf, args, "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_interval", conv=make_timedelta) load(conf, args, "token_gen_max") # Otherwise fall back on the old one. else: logger.warning( "contest.yaml uses a deprecated format for token settings " "which will soon stop being supported, you're advised to " "update it.") # Determine the mode. if conf.get("token_initial", None) is None: args["token_mode"] = TOKEN_MODE_DISABLED elif conf.get("token_gen_number", 0) > 0 and \ conf.get("token_gen_time", 0) == 0: args["token_mode"] = TOKEN_MODE_INFINITE else: args["token_mode"] = TOKEN_MODE_FINITE # Set the old default values. args["token_gen_initial"] = 0 args["token_gen_number"] = 0 args["token_gen_interval"] = timedelta() # Copy the parameters to their new names. load(conf, args, "token_total", "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_initial", "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_time", "token_gen_interval", conv=make_timedelta) load(conf, args, "token_max", "token_gen_max") # Remove some corner cases. if args["token_gen_initial"] is None: args["token_gen_initial"] = 0 if args["token_gen_interval"].total_seconds() == 0: args["token_gen_interval"] = timedelta(minutes=1) load(conf, args, ["start", "inizio"], conv=make_datetime) load(conf, args, ["stop", "fine"], conv=make_datetime) load(conf, args, ["per_user_time"], conv=make_timedelta) load(conf, args, ["timezone"]) 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) tasks = load(conf, None, ["tasks", "problemi"]) participations = load(conf, None, ["users", "utenti"]) for p in participations: p["password"] = build_password(p["password"]) # Import was successful os.remove(os.path.join(self.path, ".import_error_contest")) logger.info("Contest parameters loaded.") return Contest(**args), tasks, participations
def load_participations(path): logger.info("Loading...") with open(path, 'r') as io: data = json.load(io) participations = data['participations'] with SessionGen() as session: for entry in participations: logger.info('Loading: %s' % (entry)) contest = Contest.get_from_id(entry['contest_id'], session) if contest is None: logger.error(" Contest ID %d not found" % (entry['contest_id'])) session.rollback() return False userdata = entry['user'] user = session.query(User).filter( User.username == userdata['username']).first() if user is None: user = User(username=userdata['username'], first_name=userdata['first_name'], last_name=userdata['last_name'], password=build_password( generate_random_password())) logger.info(' Creating new user: %s' % (user.username)) session.add(user) else: logger.info(' Using existing user: %s (id=%d)' % (user.username, user.id)) if 'plaintext_password' in userdata: logger.info(' * password') user.password = build_password(userdata['plaintext_password'], 'plaintext') if 'first_name' in userdata: logger.info(' * first_name: %s' % (userdata['first_name'])) user.first_name = userdata['first_name'] if 'last_name' in userdata: logger.info(' * last_name: %s' % (userdata['last_name'])) user.last_name = userdata['last_name'] participation = session.query(Participation).join( Participation.user).filter( Participation.contest == contest).filter( User.username == user.username).first() if participation is None: participation = Participation(user=user, contest=contest) logger.info( ' Creating new participation for contest_id=%d user=%s' % (contest.id, user.username)) session.add(participation) else: logger.info( ' Updating participation: id=%d contest_id=%d user=%s' % (participation.id, participation.contest_id, participation.user.username)) if 'plaintext_password' in entry: logger.info(' * plaintext_password') participation.password = build_password( entry['plaintext_password'], 'plaintext') if 'ip' in entry: logger.info(' * ip: %s' % (entry['ip'])) participation.ip = [ipaddress.ip_network(entry['ip'])] if 'delay_time' in entry: logger.info(' * delay_time: %d' % (entry['delay_time'])) participation.delay_time = datetime.timedelta( seconds=entry['delay_time']) if 'extra_time' in entry: logger.info(' * extra_time: %d' % (entry['extra_time'])) participation.extra_time = datetime.timedelta( seconds=entry['extra_time']) if 'hidden' in entry: logger.info(' * hidden: %s' % (entry['hidden'])) participation.hidden = entry['hidden'] if 'unrestricted' in entry: logger.info(' * unrestricted: %s' % (entry['unrestricted'])) participation.unrestricted = entry['unrestricted'] if 'team' in userdata: team = session.query(Team).filter( Team.code == userdata['team']['code']).first() if team is None: team = Team(code=userdata['team']['code'], name=userdata['team']['name']) logger.info(' Creating new team: %s' % (team.code)) session.add(team) else: logger.info(' Using existing team: %s' % (team.code)) if 'name' in userdata['team']: logger.info(' * name: %s' % (userdata['team']['name'])) team.name = userdata['team']['name'] participation.team = team session.commit() logger.info("Done.") return True
def get_contest(self): """See docstring in class ContestLoader.""" if not os.path.exists(os.path.join(self.path, "contest.yaml")): logger.critical("File missing: \"contest.yaml\"") return None conf = yaml.safe_load( io.open(os.path.join(self.path, "contest.yaml"), "rt", encoding="utf-8")) # Here we update the time of the last import touch(os.path.join(self.path, ".itime_contest")) # If this file is not deleted, then the import failed touch(os.path.join(self.path, ".import_error_contest")) args = {} load(conf, args, ["name", "nome_breve"]) load(conf, args, ["description", "nome"]) logger.info("Loading parameters for contest %s.", args["name"]) # Use the new token settings format if detected. if "token_mode" in conf: load(conf, args, "token_mode") load(conf, args, "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_interval", conv=make_timedelta) load(conf, args, "token_gen_max") # Otherwise fall back on the old one. else: logger.warning( "contest.yaml uses a deprecated format for token settings " "which will soon stop being supported, you're advised to " "update it.") # Determine the mode. if conf.get("token_initial", None) is None: args["token_mode"] = TOKEN_MODE_DISABLED elif conf.get("token_gen_number", 0) > 0 and \ conf.get("token_gen_time", 0) == 0: args["token_mode"] = TOKEN_MODE_INFINITE else: args["token_mode"] = TOKEN_MODE_FINITE # Set the old default values. args["token_gen_initial"] = 0 args["token_gen_number"] = 0 args["token_gen_interval"] = timedelta() # Copy the parameters to their new names. load(conf, args, "token_total", "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_initial", "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_time", "token_gen_interval", conv=make_timedelta) load(conf, args, "token_max", "token_gen_max") # Remove some corner cases. if args["token_gen_initial"] is None: args["token_gen_initial"] = 0 if args["token_gen_interval"].total_seconds() == 0: args["token_gen_interval"] = timedelta(minutes=1) load(conf, args, ["start", "inizio"], conv=make_datetime) load(conf, args, ["stop", "fine"], conv=make_datetime) load(conf, args, ["per_user_time"], conv=make_timedelta) 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) tasks = load(conf, None, ["tasks", "problemi"]) participations = load(conf, None, ["users", "utenti"]) for p in participations: p["password"] = build_password(p["password"]) # Import was successful os.remove(os.path.join(self.path, ".import_error_contest")) logger.info("Contest parameters loaded.") return Contest(**args), tasks, participations
def get_contest(self): """See docstring in class ContestLoader.""" if not os.path.exists(os.path.join(self.path, "contest.yaml")): logger.critical("File missing: \"contest.yaml\"") return None conf = yaml.safe_load( io.open(os.path.join(self.path, "contest.yaml"), "rt", encoding="utf-8")) # Here we update the time of the last import touch(os.path.join(self.path, ".itime_contest")) # If this file is not deleted, then the import failed touch(os.path.join(self.path, ".import_error_contest")) args = {} load(conf, args, ["name", "nome_breve"]) load(conf, args, ["description", "nome"]) load(conf, args, "presentation") load(conf, args, "path_to_logo") load(conf, args, "timezone") pr_temp = load(conf, None, "presentation_translations") if pr_temp: presentation_translations = { language: Presentation(language, pr_temp[language]) for language in pr_temp } else: presentation_translations = {} args["presentation_translations"] = presentation_translations # Authentication methods oic_info = load(conf, None, "oic_info") if oic_info: args["auth_type"] = "OpenIDConnect" with open(os.path.join(self.path, oic_info), "r") as f: args["openidconnect_info"] = f.read() # To limit the interface to a few languages load(conf, args, "allowed_localizations") # Allowed programming languages plang = load(conf, None, "allowed_languages") if plang: args["languages"] = plang logger.info("Loading parameters for contest %s.", args["name"]) # Use the new token settings format if detected. if "token_mode" in conf: load(conf, args, "token_mode") load(conf, args, "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_interval", conv=make_timedelta) load(conf, args, "token_gen_max") # Otherwise fall back on the old one. else: logger.warning( "contest.yaml uses a deprecated format for token settings " "which will soon stop being supported, you're advised to " "update it.") # Determine the mode. if conf.get("token_initial", None) is None: args["token_mode"] = TOKEN_MODE_DISABLED elif conf.get("token_gen_number", 0) > 0 and \ conf.get("token_gen_time", 0) == 0: args["token_mode"] = TOKEN_MODE_INFINITE else: args["token_mode"] = TOKEN_MODE_FINITE # Set the old default values. args["token_gen_initial"] = 0 args["token_gen_number"] = 0 args["token_gen_interval"] = timedelta() # Copy the parameters to their new names. load(conf, args, "token_total", "token_max_number") load(conf, args, "token_min_interval", conv=make_timedelta) load(conf, args, "token_initial", "token_gen_initial") load(conf, args, "token_gen_number") load(conf, args, "token_gen_time", "token_gen_interval", conv=make_timedelta) load(conf, args, "token_max", "token_gen_max") # Remove some corner cases. if args["token_gen_initial"] is None: args["token_gen_initial"] = 0 if args["token_gen_interval"].total_seconds() == 0: args["token_gen_interval"] = timedelta(minutes=1) load(conf, args, ["start", "inizio"], conv=make_datetime) load(conf, args, ["stop", "fine"], conv=make_datetime) load(conf, args, ["per_user_time"], conv=make_timedelta) 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) tasks = load(conf, None, ["tasks", "problemi"]) participations = load(conf, None, ["users", "utenti"]) if participations is not None: for p in participations: p["password"] = build_password(p["password"]) # Import was successful os.remove(os.path.join(self.path, ".import_error_contest")) logger.info("Contest parameters loaded.") return Contest(**args), tasks, participations
def get_contest(self): """See docstring in class Loader. """ name = os.path.split(self.path)[1] logger.info("Loading parameters for contest %s.", name) args = {} tree = ET.parse(os.path.join(self.path, "contest.xml")) root = tree.getroot() args['name'] = name # TODO: find proper way to choose contest primary language. self.primary_language = root.find('names') \ .find('name').attrib['language'] # All available contest languages are allowed to be used. self.languages = [] for alternative_name in root.find('names'): self.languages.append(alternative_name.attrib['language']) logger.info("Contest languages are %s %s", self.primary_language, str(self.languages)) args['description'] = root.find('names') \ .find("name[@language='%s']" % self.primary_language) \ .attrib['value'] logger.info("Contest description is %s", args['description']) # For now Polygon doesn't support custom contest-wide files, # so we need to hardcode some contest settings. args['start'] = datetime(1970, 1, 1) args['stop'] = datetime(1970, 1, 1) # Uncomment the following to set specific values for these # options. # args['max_submission_number'] = 100 # args['max_user_test_number'] = 100 # args['min_submission_interval'] = make_timedelta(60) # args['min_user_test_interval'] = make_timedelta(60) # args['max_user_test_number'] = 10 # args['min_user_test_interval'] = make_timedelta(60) # args['token_mode'] = 'infinite' # args['token_max_number'] = 100 # args['token_min_interval'] = make_timedelta(60) # args['token_gen_initial'] = 1 # args['token_gen_number'] = 1 # args['token_gen_interval'] = make_timedelta(1800) # args['token_gen_max'] = 2 logger.info("Contest parameters loaded.") tasks = [] for problem in root.find('problems'): tasks.append(os.path.basename(problem.attrib['url'])) participations = [] # This is not standard Polygon feature, but useful for CMS users # we assume contestants.txt contains one line for each user: # # username;password;first_name;last_name;hidden # # For example: # # contestant1;123;Cont;Estant;0 # jury;1234;Ju;Ry;1 users_path = os.path.join(self.path, 'contestants.txt') if os.path.exists(users_path): with io.open(users_path, "rt", encoding="utf-8") as users_file: for user in users_file.readlines(): user = user.strip() user = user.split(';') participations.append({ "username": user[0].strip(), "password": build_password(user[1].strip()), "hidden": user[4].strip() # "ip" is not passed }) return Contest(**args), tasks, participations
def test_success(self): self.assertEqual(build_password("pwd", "method"), "method:pwd")