From 36b4b9001f6e52b2f03d806221bd377daccb7bcf Mon Sep 17 00:00:00 2001 From: Divyansh Choudhary Date: Tue, 18 Apr 2023 00:02:55 +0530 Subject: [PATCH 1/3] Add utility methods to User class --- jupyter_server/auth/identity.py | 68 +++++++++++++++++---------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index 42c4ce839..872008027 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -81,6 +81,39 @@ def fill_defaults(self): if not self.display_name: self.display_name = self.name + @staticmethod + def user_to_cookie(user: User) -> str: + """Serialize a user to a string for storage in a cookie + + If overriding in a subclass, make sure to define user_from_cookie as well. + + Default is just the user's username. + """ + # default: username is enough + cookie = json.dumps( + { + "username": user.username, + "name": user.name, + "display_name": user.display_name, + "initials": user.initials, + "color": user.color, + } + ) + return cookie + + @staticmethod + def user_from_cookie(cookie_value: str) -> User | None: + """Inverse of user_to_cookie""" + user = json.loads(cookie_value) + return User( + user["username"], + user["name"], + user["display_name"], + user["initials"], + None, + user["color"], + ) + def _backward_compat_user(got_user: Any) -> User: """Backward-compatibility for LoginHandler.get_user @@ -290,37 +323,6 @@ def get_handlers(self) -> list: handlers.append((r"/logout", self.logout_handler_class)) return handlers - def user_to_cookie(self, user: User) -> str: - """Serialize a user to a string for storage in a cookie - - If overriding in a subclass, make sure to define user_from_cookie as well. - - Default is just the user's username. - """ - # default: username is enough - cookie = json.dumps( - { - "username": user.username, - "name": user.name, - "display_name": user.display_name, - "initials": user.initials, - "color": user.color, - } - ) - return cookie - - def user_from_cookie(self, cookie_value: str) -> User | None: - """Inverse of user_to_cookie""" - user = json.loads(cookie_value) - return User( - user["username"], - user["name"], - user["display_name"], - user["initials"], - None, - user["color"], - ) - def get_cookie_name(self, handler: JupyterHandler) -> str: """Return the login cookie name @@ -347,7 +349,7 @@ def set_login_cookie(self, handler: JupyterHandler, user: User) -> None: 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) + handler.set_secure_cookie(cookie_name, User.user_to_cookie(user), **cookie_options) def _force_clear_cookie( self, handler: JupyterHandler, name: str, path: str = "/", domain: str | None = None @@ -404,7 +406,7 @@ def get_user_cookie(self, handler: JupyterHandler) -> User | None | Awaitable[Us user_cookie = _user_cookie.decode() # TODO: try/catch in case of change in config? try: - return self.user_from_cookie(user_cookie) + return User.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) From 1c8798fc65dde6bb557a50acd1138d0f17b2d1c4 Mon Sep 17 00:00:00 2001 From: Divyansh Choudhary Date: Tue, 18 Apr 2023 23:32:16 +0530 Subject: [PATCH 2/3] Refactor methods --- jupyter_server/auth/identity.py | 45 +++++++++++++++++---------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index 872008027..e7e74bfaf 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -82,37 +82,38 @@ def fill_defaults(self): self.display_name = self.name @staticmethod - def user_to_cookie(user: User) -> str: + def to_string(self) -> str: """Serialize a user to a string for storage in a cookie - - If overriding in a subclass, make sure to define user_from_cookie as well. - + If overriding in a subclass, make sure to define from_string as well. Default is just the user's username. """ # default: username is enough cookie = json.dumps( { - "username": user.username, - "name": user.name, - "display_name": user.display_name, - "initials": user.initials, - "color": user.color, + "username": self.username, + "name": self.name, + "display_name": self.display_name, + "initials": self.initials, + "color": self.color, } ) return cookie - @staticmethod - def user_from_cookie(cookie_value: str) -> User | None: + @classmethod + def from_string(cls, serialized_user: str) -> User: """Inverse of user_to_cookie""" - user = json.loads(cookie_value) - return User( - user["username"], - user["name"], - user["display_name"], - user["initials"], - None, - user["color"], - ) + user = json.loads(serialized_user) + try: + return User( + user["username"], + user["name"], + user["display_name"], + user["initials"], + None, + user["color"], + ) + except: + raise def _backward_compat_user(got_user: Any) -> User: @@ -349,7 +350,7 @@ def set_login_cookie(self, handler: JupyterHandler, user: User) -> None: 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, User.user_to_cookie(user), **cookie_options) + handler.set_secure_cookie(cookie_name, user.to_string(), **cookie_options) def _force_clear_cookie( self, handler: JupyterHandler, name: str, path: str = "/", domain: str | None = None @@ -406,7 +407,7 @@ def get_user_cookie(self, handler: JupyterHandler) -> User | None | Awaitable[Us user_cookie = _user_cookie.decode() # TODO: try/catch in case of change in config? try: - return User.user_from_cookie(user_cookie) + return User.from_string(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) From 78801205929c765f480ad78763f11731c6d1d864 Mon Sep 17 00:00:00 2001 From: Divyansh Choudhary Date: Tue, 18 Apr 2023 23:42:33 +0530 Subject: [PATCH 3/3] Remove staticmethod decorator from `to_string` --- jupyter_server/auth/identity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jupyter_server/auth/identity.py b/jupyter_server/auth/identity.py index e7e74bfaf..f67cb7d3c 100644 --- a/jupyter_server/auth/identity.py +++ b/jupyter_server/auth/identity.py @@ -81,7 +81,6 @@ def fill_defaults(self): if not self.display_name: self.display_name = self.name - @staticmethod def to_string(self) -> str: """Serialize a user to a string for storage in a cookie If overriding in a subclass, make sure to define from_string as well.