def post(self): username = self.get_argument("username", "") password = self.get_argument("password", "") next_page = self.get_argument("next", "/") user = self.sql_session.query(User)\ .filter(User.username == username)\ .first() participation = self.sql_session.query(Participation)\ .filter(Participation.contest == self.contest)\ .filter(Participation.user == user)\ .first() if user is None: # TODO: notify the user that they don't exist self.redirect("/?login_error=true") return if participation is None: # TODO: notify the user that they're uninvited self.redirect("/?login_error=true") return # If a contest-specific password is defined, use that. If it's # not, use the user's main password. if participation.password is None: correct_password = user.password else: correct_password = participation.password filtered_user = filter_ascii(username) filtered_pass = filter_ascii(password) if password != correct_password: logger.info("Login error: user=%s pass=%s remote_ip=%s." % (filtered_user, filtered_pass, self.request.remote_ip)) self.redirect("/?login_error=true") return if self.contest.ip_restriction and participation.ip is not None \ and not check_ip(self.request.remote_ip, participation.ip): logger.info("Unexpected IP: user=%s pass=%s remote_ip=%s.", filtered_user, filtered_pass, self.request.remote_ip) self.redirect("/?login_error=true") return if participation.hidden and self.contest.block_hidden_participations: logger.info("Hidden user login attempt: " "user=%s pass=%s remote_ip=%s.", filtered_user, filtered_pass, self.request.remote_ip) self.redirect("/?login_error=true") return logger.info("User logged in: user=%s remote_ip=%s.", filtered_user, self.request.remote_ip) self.set_secure_cookie("login", pickle.dumps((user.username, correct_password, make_timestamp())), expires_days=None) self.redirect(next_page)
def post(self): username = self.get_argument("username", "") password = self.get_argument("password", "") next_page = self.get_argument("next", "/") admin = self.sql_session.query(Admin)\ .filter(Admin.username == username)\ .first() if admin is None: logger.warning("Nonexistent admin account: %s", username) self.redirect("/login?login_error=true") return try: allowed = validate_password(admin.authentication, password) except ValueError: logger.warning("Unable to validate password for admin %s", filter_ascii(username), exc_info=True) allowed = False if not allowed or not admin.enabled: if not allowed: logger.info("Login error for admin %s from IP %s.", filter_ascii(username), self.request.remote_ip) elif not admin.enabled: logger.info("Login successful for admin %s from IP %s, " "but account is disabled.", filter_ascii(username), self.request.remote_ip) self.redirect("/login?login_error=true") return logger.info("Admin logged in: %s from IP %s.", filter_ascii(username), self.request.remote_ip) self.service.auth_handler.set(admin.id) self.redirect(next_page)
def post(self): username = self.get_argument("username", "") password = self.get_argument("password", "") user = self.sql_session.query(User)\ .filter(User.auth_type == "Password")\ .filter(User.contest == self.contest)\ .filter(User.username == username).first() filtered_user = filter_ascii(username) filtered_pass = filter_ascii(password) if user is None or user.password != password: logger.info("Login error: user=%s pass=%s remote_ip=%s." % (filtered_user, filtered_pass, self.request.remote_ip)) self.redirect("/?login_error=true") return self.try_user_login(user)
def _on_auth(self, user_t): if not user_t: raise tornado.web.HTTPError(500, "Google auth failed") username = user_t["email"] user = self.sql_session.query(User)\ .filter(User.auth_type == "Google")\ .filter(User.contest == self.contest)\ .filter(User.username == username).first() filtered_user = filter_ascii(username) if user is None: user = User(user_t["first_name"], user_t["last_name"], username, email=user_t["email"], auth_type="Google", contest=self.contest) self.sql_session.add(user) self.sql_session.commit() self.try_user_login(user)
def post(self): error_args = {"login_error": "true"} next_page = self.get_argument("next", None) if next_page is not None: error_args["next"] = next_page if next_page != "/": next_page = self.url(*next_page.strip("/").split("/")) else: next_page = self.url() else: next_page = self.contest_url() error_page = self.contest_url(**error_args) username = self.get_argument("username", "") password = self.get_argument("password", "") user = self.sql_session.query(User)\ .filter(User.username == username)\ .first() participation = self.sql_session.query(Participation)\ .filter(Participation.contest == self.contest)\ .filter(Participation.user == user)\ .first() if user is None: # TODO: notify the user that they don't exist self.redirect(error_page) return if participation is None: # TODO: notify the user that they're uninvited self.redirect(error_page) return # If a contest-specific password is defined, use that. If it's # not, use the user's main password. if participation.password is None: correct_password = user.password else: correct_password = participation.password filtered_user = filter_ascii(username) filtered_pass = filter_ascii(password) if not validate_password(correct_password, password): logger.info("Login error: user=%s pass=%s remote_ip=%s." % (filtered_user, filtered_pass, self.request.headers.get("X-Real-IP") or self.request.remote_ip)) self.redirect(error_page) return if self.contest.ip_restriction and participation.ip is not None \ and not check_ip(self.request.remote_ip, participation.ip): logger.info( "Unexpected IP: user=%s pass=%s remote_ip=%s.", filtered_user, filtered_pass, self.request.headers.get("X-Real-IP") or self.request.remote_ip) self.redirect(error_page) return if participation.hidden and self.contest.block_hidden_participations: logger.info( "Hidden user login attempt: " "user=%s pass=%s remote_ip=%s.", filtered_user, filtered_pass, self.request.headers.get("X-Real-IP") or self.request.remote_ip) self.redirect(error_page) return logger.info( "User logged in: user=%s remote_ip=%s.", filtered_user, self.request.headers.get("X-Real-IP") or self.request.remote_ip) self.set_secure_cookie(self.contest.name + "_login", pickle.dumps((user.username, correct_password, make_timestamp())), expires_days=None) self.redirect(next_page)
def validate_login(sql_session, contest, timestamp, username, password, ip_address): """Authenticate a user logging in, with username and password. Given the information the user provided (the username and the password) and some context information (contest, to determine which users are allowed to log in, how and with which restrictions; timestamp for cookie creation; IP address to check against) try to authenticate the user and return its participation and the cookie to set to help authenticate future visits. After finding the participation, IP login and hidden users restrictions are checked. sql_session (Session): the SQLAlchemy database session used to execute queries. contest (Contest): the contest the user is trying to access. timestamp (datetime): the date and the time of the request. username (str): the username the user provided. password (str): the password the user provided. ip_address (IPv4Address|IPv6Address): the IP address the request came from. return ((Participation, bytes)|(None, None)): if the user couldn't be authenticated then return None, otherwise return the participation that they wanted to authenticate as; if a cookie has to be set return it as well, otherwise return None. """ filtered_user = filter_ascii(username) def log_failed_attempt(msg, *args): logger.info( "Unsuccessful login attempt from IP address %s, as user " "%s, on contest %s, at %s: " + msg, ip_address, filtered_user, contest.name, timestamp, *args) if not contest.allow_password_authentication: log_failed_attempt("password authentication not allowed") return None, None participation = sql_session.query(Participation) \ .join(Participation.user) \ .options(contains_eager(Participation.user)) \ .filter(Participation.contest == contest)\ .filter(User.username == username)\ .first() if participation is None: log_failed_attempt("user not registered to contest") return None, None if not safe_validate_password(participation, password): log_failed_attempt("wrong password") return None, None if contest.ip_restriction and participation.ip is not None \ and not any(ip_address in network for network in participation.ip): log_failed_attempt("unauthorized IP address") return None, None if contest.block_hidden_participations and participation.hidden: log_failed_attempt("participation is hidden and unauthorized") return None, None logger.info( "Successful login attempt from IP address %s, as user %s, on " "contest %s, at %s", ip_address, filtered_user, contest.name, timestamp) return (participation, pickle.dumps((username, password, make_timestamp(timestamp))))
def _authenticate_request_from_cookie(sql_session, contest, timestamp, cookie): """Return the current participation based on the cookie. If a participation can be extracted, the cookie is refreshed. sql_session (Session): the SQLAlchemy database session used to execute queries. contest (Contest): the contest the user is trying to access. timestamp (datetime): the date and the time of the request. cookie (bytes|None): the cookie the user's browser provided in the request (if any). return ((Participation, bytes)|(None, None)): the participation extracted from the cookie and the cookie to set/refresh, or None in case of errors. """ if cookie is None: logger.info("Unsuccessful cookie authentication: no cookie provided") return None, None # Parse cookie. try: cookie = pickle.loads(cookie) username = cookie[0] password = cookie[1] last_update = make_datetime(cookie[2]) except Exception as e: # Cookies are stored securely and thus cannot be tampered with: # this is either a programming or a configuration error. logger.warning("Invalid cookie (%s): %s", e, cookie) return None, None filtered_user = filter_ascii(username) def log_failed_attempt(msg, *args): logger.info( "Unsuccessful cookie authentication as %s, returning from " "%s, at %s: " + msg, filtered_user, last_update, timestamp, *args) # Check if the cookie is expired. if timestamp - last_update > timedelta(seconds=config.cookie_duration): log_failed_attempt("cookie expired (lasts %d seconds)", config.cookie_duration) return None, None # Load participation from DB and make sure it exists. participation = sql_session.query(Participation) \ .join(Participation.user) \ .options(contains_eager(Participation.user)) \ .filter(Participation.contest == contest) \ .filter(User.username == username) \ .first() if participation is None: log_failed_attempt("user not registered to contest") return None, None if not safe_validate_password(participation, password): log_failed_attempt("wrong password") return None, None logger.info( "Successful cookie authentication as user %s, on contest %s, " "returning from %s, at %s", filtered_user, contest.name, last_update, timestamp) return (participation, pickle.dumps((username, password, make_timestamp(timestamp))))