def _force_clear_cookie(self, handler: JupyterHandler, name: str, path: str = "/", domain: str | None = None) -> None: """Deletes the cookie with the given name. Tornado's cookie handling currently (Jan 2018) stores cookies in a dict keyed by name, so it can only modify one cookie with a given name per response. The browser can store multiple cookies with the same name but different domains and/or paths. This method lets us clear multiple cookies with the same name. Due to limitations of the cookie protocol, you must pass the same path and domain to clear a cookie as were used when that cookie was set (but there is no way to find out on the server side which values were used for a given cookie). """ name = escape.native_str(name) expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) morsel: Morsel = Morsel() morsel.set(name, "", '""') morsel["expires"] = httputil.format_timestamp(expires) morsel["path"] = path if domain: morsel["domain"] = domain handler.add_header("Set-Cookie", morsel.OutputString())
def process_login_form(self, handler: JupyterHandler) -> User | None: """Process login form data Return authenticated User if successful, None if not. """ typed_password = handler.get_argument("password", default="") new_password = handler.get_argument("new_password", default="") user = None if not self.auth_enabled: self.log.warning( "Accepting anonymous login because auth fully disabled!") return self.generate_anonymous_user(handler) if self.passwd_check(typed_password) and not new_password: return self.generate_anonymous_user(handler) elif self.token and self.token == typed_password: user = self.generate_anonymous_user(handler) if new_password and self.allow_password_change: config_dir = handler.settings.get("config_dir", "") config_file = os.path.join(config_dir, "jupyter_server_config.json") self.hashed_password = set_password(new_password, config_file=config_file) self.log.info(_i18n(f"Wrote hashed password to {config_file}")) return user
def clear_login_cookie(self, handler: JupyterHandler) -> None: """Clear the login cookie, effectively logging out the session.""" cookie_options = {} cookie_options.update(self.cookie_options) path = cookie_options.setdefault("path", handler.base_url) cookie_name = self.get_cookie_name(handler) handler.clear_cookie(cookie_name, path=path) if path and path != "/": # also clear cookie on / to ensure old cookies are cleared # after the change in path behavior. # N.B. This bypasses the normal cookie handling, which can't update # two cookies with the same name. See the method above. self._force_clear_cookie(handler, cookie_name)
def set_login_cookie(self, handler: JupyterHandler, user: User) -> None: """Call this on handlers to set the login cookie for success""" cookie_options = {} cookie_options.update(self.cookie_options) cookie_options.setdefault("httponly", True) # tornado <4.2 has a bug that considers secure==True as soon as # 'secure' kwarg is passed to set_secure_cookie secure_cookie = self.secure_cookie if secure_cookie is None: secure_cookie = handler.request.protocol == "https" if secure_cookie: cookie_options.setdefault("secure", True) cookie_options.setdefault("path", handler.base_url) cookie_name = self.get_cookie_name(handler) handler.set_secure_cookie(cookie_name, self.user_to_cookie(user), **cookie_options)
async def _get_user(self, handler: JupyterHandler) -> User | None: if getattr(handler, "_jupyter_current_user", None): # already authenticated return handler._jupyter_current_user _token_user: User | None | Awaitable[User | None] = self.get_user_token( handler) if isinstance(_token_user, Awaitable): _token_user = await _token_user token_user: User | None = _token_user # need second variable name to collapse type _cookie_user = self.get_user_cookie(handler) if isinstance(_cookie_user, Awaitable): _cookie_user = await _cookie_user cookie_user: User | None = _cookie_user # prefer token to cookie if both given, # because token is always explicit user = token_user or cookie_user if user is not None and token_user is not None: # if token-authenticated, persist user_id in cookie # if it hasn't already been stored there if user != cookie_user: self.set_login_cookie(handler, user) # Record that the current request has been authenticated with a token. # Used in is_token_authenticated above. handler._token_authenticated = True if user is None: # If an invalid cookie was sent, clear it to prevent unnecessary # extra warnings. But don't do this on a request with *no* cookie, # because that can erroneously log you out (see gh-3365) cookie_name = self.get_cookie_name(handler) cookie = handler.get_cookie(cookie_name) if cookie is not None: self.log.warning( f"Clearing invalid/expired login cookie {cookie_name}") self.clear_login_cookie(handler) if not self.auth_enabled: # Completely insecure! No authentication at all. # No need to warn here, though; validate_security will have already done that. user = self.generate_anonymous_user(handler) return user
def process_login_form(self, handler: JupyterHandler) -> User | None: """Process login form data Return authenticated User if successful, None if not. """ typed_password = handler.get_argument("password", default="") user = None if not self.auth_enabled: self.log.warning( "Accepting anonymous login because auth fully disabled!") return self.generate_anonymous_user(handler) if self.token and self.token == typed_password: return self.user_for_token(typed_password) return user
def get_token(self, handler: JupyterHandler) -> str | None: """Get the user token from a request Default: - in URL parameters: ?token=<token> - in header: Authorization: token <token> """ user_token = handler.get_argument("token", "") if not user_token: # get it from Authorization header m = self.auth_header_pat.match( handler.request.headers.get("Authorization", "")) if m: user_token = m.group(2) return user_token
def get_user_cookie( self, handler: JupyterHandler) -> User | None | Awaitable[User | None]: """Get user from a cookie Calls user_from_cookie to deserialize cookie value """ _user_cookie = handler.get_secure_cookie( self.get_cookie_name(handler), **self.get_secure_cookie_kwargs, ) if not _user_cookie: return None user_cookie = _user_cookie.decode() # TODO: try/catch in case of change in config? try: return self.user_from_cookie(user_cookie) except Exception as e: # log bad cookie itself, only at debug-level self.log.debug( f"Error unpacking user from cookie: cookie={user_cookie}", exc_info=True) self.log.error(f"Error unpacking user from cookie: {e}") return None
def check_origin(self, origin=None): return JupyterHandler.check_origin(self, origin)