def try_user_login(self, user): 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.info("Invalid IP address provided by Tornado: %s", self.request.remote_ip) ip_address = None _, password = parse_authentication(user.password) participation, cookie = validate_login( self.sql_session, self.contest, self.timestamp, user.username, password, ip_address) cookie_name = self.contest.name + "_login" if cookie is None: self.clear_cookie(cookie_name) else: self.set_secure_cookie(cookie_name, cookie, expires_days=None) if participation is None: self.redirect_login_error() else: self.redirect_next()
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
def get(self, contest_id, user_id): participation = self.get_participation(contest_id, user_id) # Check that the participation is valid. if participation is None: raise tornado.web.HTTPError(404) submission_query = self.sql_session.query(Submission)\ .filter(Submission.participation == participation) page = int(self.get_query_argument("page", 0)) self.render_params_for_submissions(submission_query, page) if participation.password is not None: method, payload = parse_authentication(participation.password) participation.method = method if method == 'text': participation.password = payload else: participation.password = "" else: participation.method = "text" participation.password = "" self.r_params["participation"] = participation self.r_params["selected_user"] = participation.user self.r_params["teams"] = self.sql_session.query(Team).all() self.render("participation.html", **self.r_params)
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
def get_password(self, dest, old_password, allow_unset): """Parse a (possibly hashed) password. Parse the value of the password and the method that should be used to hash it (if any) and fill it into the given destination making sure that a hashed password can be left unchanged and, if allowed, unset. dest (dict): a place to store the result in. old_password (string|None): the current password for the object if any, with a "<method>:" prefix. allow_unset (bool): whether the password is allowed to be left unset, which is represented as a value of None in dest. """ # The admin leaving the password field empty could mean one of # two things: they want the password to be unset (this applies # to participations only and it means they inherit the user's # password) or, if a password was set and it was hashed, they # want to keep using that old password. We distinguish between # the two essentially by looking at the method: an empty # plaintext means "unset", an empty hashed means "keep". # Find out whether a password was set and whether it was # plaintext or hashed. if old_password is not None: try: old_method, _ = parse_authentication(old_password) except ValueError: # Treated as if no password was set. old_method = None else: old_method = None password = self.get_argument("password") method = self.get_argument("method") # If a password is given, we use that. if len(password) > 0: dest["password"] = hash_password(password, method) # If the password was set and was hashed, and the admin kept # the method unchanged and didn't specify anything, they must # have meant to keep the old password unchanged. elif old_method is not None and old_method != "plaintext" \ and method == old_method: # Since the content of dest overwrites the current values # of the participation, by not adding anything to dest we # cause the current values to be kept. pass # Otherwise the fact that the password is empty means that the # admin wants the password to be unset. elif allow_unset: dest["password"] = None # Or that they really mean the password to be the empty string. else: dest["password"] = hash_password("", method)
def get_password(self, dest, old_password, allow_unset): """Parse a (possibly hashed) password. Parse the value of the password and the method that should be used to hash it (if any) and fill it into the given destination making sure that a hashed password can be left unchanged and, if allowed, unset. dest (dict): a place to store the result in. old_password (string|None): the current password for the object if any, with a "<method>:" prefix. allow_unset (bool): whether the password is allowed to be left unset, which is represented as a value of None in dest. """ # The admin leaving the password field empty could mean one of # two things: they want the password to be unset (this applies # to participations only and it means they inherit the user's # password) or, if a password was set and it was hashed, they # want to keep using that old password. We distinguish between # the two essentially by looking at the method: an empty # plaintext means "unset", an empty hashed means "keep". # Find out whether a password was set and whether it was # plaintext or hashed. if old_password is not None: try: old_method, _ = parse_authentication(old_password) except ValueError: # Treated as if no password was set. old_method = None else: old_method = None password = self.get_argument("password") method = self.get_argument("method") # If a password is given, we use that. if len(password) > 0: dest["password"] = hash_password(password, method) # If the password was set and was hashed, and the admin kept # the method unchanged and didn't specify anything, they must # have meant to keep the old password unchanged. elif old_method is not None and old_method != "plaintext" \ and method == old_method: # Since the content of dest overwrites the current values # of the participation, by not adding anything to dest we # cause the current values to be kept. pass # Otherwise the fact that the password is empty means that the # admin wants the password to be unset. elif allow_unset: dest["password"] = None # Or that they really mean the password to be the empty string. else: dest["password"] = hash_password("", method)
def get(self, user_id): user = self.safe_get_item(User, user_id) self.r_params = self.render_params() self.r_params["user"] = user method, payload = parse_authentication(user.password) user.method = method if method == 'text': user.password = payload else: user.password = "" self.r_params["participations"] = \ self.sql_session.query(Participation)\ .filter(Participation.user == user)\ .all() self.r_params["unassigned_contests"] = \ self.sql_session.query(Contest)\ .filter(not Contest.id.in_( self.sql_session.query(Participation.contest_id) .filter(Participation.user is user) .all()))\ .all() self.render("user.html", **self.r_params)
def safe_parse_authentication(auth): try: method, password = parse_authentication(auth) except ValueError: method, password = "******", "" return method, password
def safe_parse_authentication(auth): try: method, password = parse_authentication(auth) except ValueError: method, password = "******", "" return method, password
def test_fail(self): with self.assertRaises(ValueError): parse_authentication("no colon")
def test_success_nonascii(self): method, payload = parse_authentication("method:你好") self.assertEqual(method, "method") self.assertEqual(payload, "你好")
def test_success_colon(self): method, payload = parse_authentication("method:42:24") self.assertEqual(method, "method") self.assertEqual(payload, "42:24")
def test_success(self): method, payload = parse_authentication("plaintext:42") self.assertEqual(method, "plaintext") self.assertEqual(payload, "42")
def test_fail(self): with self.assertRaises(ValueError): parse_authentication("no colon")
def test_success_nonascii(self): method, payload = parse_authentication("method:你好") self.assertEqual(method, "method") self.assertEqual(payload, "你好")
def test_success_colon(self): method, payload = parse_authentication("method:42:24") self.assertEqual(method, "method") self.assertEqual(payload, "42:24")
def test_success(self): method, payload = parse_authentication("plaintext:42") self.assertEqual(method, "plaintext") self.assertEqual(payload, "42")