auth: store user data in cookie, retrieve in authenticate()

This commit is contained in:
Helmut Merz 2025-04-05 12:31:26 +02:00
parent 7bca60e74c
commit 8d3ff5b667
3 changed files with 50 additions and 19 deletions

View file

@ -36,6 +36,12 @@ def zope_app_factory(config):
class Publication(DefaultPublication):
def beforeTraversal(self, request):
super(Publication, self).beforeTraversal(request)
from scopes.server.auth import authentication
prc = authentication.authenticate(request)
request.setPrincipal(prc)
def traverseName(self, request, ob, name):
next = getView(request, ob, name)
if next is not None:

View file

@ -6,7 +6,7 @@ import json
import requests
from time import time
from urllib.parse import urlencode
from zope.authentication.interfaces import IAuthentication
from zope.authentication.interfaces import IAuthentication, IPrincipal
from zope.interface import implementer
from zope.publisher.interfaces import Unauthorized
@ -17,11 +17,6 @@ from scopes import util
import config
def authenticate(request):
#print('*** authenticate')
return None
@implementer(IAuthentication)
class OidcAuthentication:
@ -29,8 +24,8 @@ class OidcAuthentication:
self.baseAuth = baseAuth
def authenticate(self, request):
prc = authenticate(request)
# prc = Authenticator().authenticate(request)
auth = Authenticator(request)
prc = auth.authenticate()
if prc is None and self.baseAuth is not None:
prc = self.baseAuth.authenticate(request)
return prc
@ -52,6 +47,21 @@ class OidcAuthentication:
JwtAuthentication = OidcAuthentication # old name - still used?
authentication = OidcAuthentication(None)
@implementer(IPrincipal)
class Principal:
def __init__(self, id, data):
self.id = id
self.data = data
def asDict(self):
data = self.data.copy()
data['id'] = self.id
return data
class Authenticator(DummyFolder):
@ -61,16 +71,21 @@ class Authenticator(DummyFolder):
self.request = request
self.params = config.oidc_params
self.reqUrl = config.base_url
self.setCrypt(self.params['cookie_crypt'])
self.setCrypt(self.params.get('cookie_crypt'))
def setReqUrl(self, base, path):
self.reqUrl = '/'.join((base, path))
def setCrypt(self, key):
self.cookieCrypt = key and Fernet(key.encode('ASCII')) or None
self.cookieCrypt = key and Fernet(key) or None
def authenticate(request):
''' return user data or None '''
def authenticate(self):
''' return principal or None'''
data = self.loadSession()
print('*** authenticate', data)
if data and 'userid' in data:
id = data.pop('userid')
return Principal(id, data)
return None
def login(self):
@ -115,10 +130,13 @@ class Authenticator(DummyFolder):
print('*** token response', tdata)
headers = dict(Authorization='Bearer ' + tdata['access_token'])
userInfo = requests.get(self.params['userinfo_url'], headers=headers)
print('***', userInfo.json())
# get relevant data from userInfo
# set up session data for authenticate()
userData = userInfo.json()
print('*** user data', userData)
ndata = dict(
userid=userData['preferred_username'],
name=userData['name'],
email=userData['email'],
access_token=tdata['access_token'],
)
self.storeSession(ndata)
req.response.redirect(self.reqUrl, trusted=True)
@ -127,7 +145,7 @@ class Authenticator(DummyFolder):
pass
def storeSession(self, data):
options = {}
options = dict(path='/')
lifetime = int(self.params['cookie_lifetime'])
options['expires'] = formatdate(time() + lifetime, localtime=False, usegmt=True)
options['max-age'] = lifetime
@ -137,16 +155,20 @@ class Authenticator(DummyFolder):
#options['httponly'] = True
name = self.params['cookie_name']
value = json.dumps(data)
print('*** storeSession', name, value, options)
if self.cookieCrypt:
value = self.cookieCrypt.encrypt(value.encode('ASCII')).decode('ASCII')
value = self.cookieCrypt.encrypt(value.encode('UTF-8')).decode('ASCII')
self.request.response.setCookie(name, value, **options)
def loadSession(self):
cookie = self.request.getCookies().get(self.params['cookie_name'])
if cookie is None:
raise ValueError('Missing authentication cookie')
return {}
#raise ValueError('Missing authentication cookie')
if self.cookieCrypt:
cookie = self.cookieCrypt.decrypt(cookie).decode('ASCII')
cookie = self.cookieCrypt.decrypt(cookie)
print('*** loadSession', self.params['cookie_name'], cookie)
# !error check: return None - or raise error?
data = json.loads(cookie)
return data

View file

@ -55,6 +55,9 @@ class DefaultView:
result['target'] = target.asDict()
if IContainer.providedBy(target):
result['target']['items'] = [v.asDict() for v in target.values()]
prc = self.request.principal
if prc is not None:
result['principal'] = prc.asDict()
return result
def render(self, result):