api: Move cookie session management logic to the authenticator for more flexibility

This commit is contained in:
Alexandre Aubin 2021-12-22 19:07:19 +01:00
parent 83f7721fdd
commit f49f03d11e
3 changed files with 66 additions and 3 deletions

View file

@ -34,7 +34,6 @@
#############################
_global:
namespace: yunohost
cookie_name: yunohost.admin
authentication:
api: ldap_admin
cli: null

View file

@ -8,10 +8,14 @@ import time
from moulinette import m18n
from moulinette.authentication import BaseAuthenticator
from yunohost.utils.error import YunohostError
from moulinette.utils.text import random_ascii
from yunohost.utils.error import YunohostError, YunohostAuthenticationError
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
session_secret = random_ascii()
class Authenticator(BaseAuthenticator):
@ -66,3 +70,57 @@ class Authenticator(BaseAuthenticator):
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
if con:
con.unbind_s()
def set_session_cookie(self, infos):
from bottle import response
assert isinstance(infos, dict)
# This allows to generate a new session id or keep the existing one
current_infos = self.get_session_cookie(raise_if_no_session_exists=False)
new_infos = {"id": current_infos["id"]}
new_infos.update(infos)
response.set_cookie(
"yunohost.admin",
new_infos,
secure=True,
secret=session_secret,
httponly=True,
# samesite="strict", # Bottle 0.12 doesn't support samesite, to be added in next versions
)
def get_session_cookie(self, raise_if_no_session_exists=True):
from bottle import request
try:
# N.B. : here we implicitly reauthenticate the cookie
# because it's signed via the session_secret
# If no session exists (or if session is invalid?)
# it's gonna return the default empty dict,
# which we interpret as an authentication failure
infos = request.get_cookie(
"yunohost.admin", secret=session_secret, default={}
)
except Exception:
if not raise_if_no_session_exists:
return {"id": random_ascii()}
raise YunohostAuthenticationError("unable_authenticate")
if "id" not in infos:
infos["id"] = random_ascii()
# FIXME: Here, maybe we want to re-authenticate the session via the authenticator
# For example to check that the username authenticated is still in the admin group...
return infos
@staticmethod
def delete_session_cookie(self):
from bottle import response
response.set_cookie("yunohost.admin", "", max_age=-1)
response.delete_cookie("yunohost.admin")

View file

@ -19,7 +19,7 @@
"""
from moulinette.core import MoulinetteError
from moulinette.core import MoulinetteError, MoulinetteAuthenticationError
from moulinette import m18n
@ -60,3 +60,9 @@ class YunohostValidationError(YunohostError):
def content(self):
return {"error": self.strerror, "error_key": self.key, **self.kwargs}
class YunohostAuthenticationError(MoulinetteAuthenticationError):
pass