Example #1
0
    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())
Example #2
0
    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
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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)