allow usage of OIDC authentication (via py-scopes) where appropriate, and provide corresponding views in loops/server/auth.zcml

This commit is contained in:
Helmut Merz 2025-05-05 09:58:38 +02:00
parent 80c83d5c9f
commit 1d264fc54f
9 changed files with 65 additions and 21 deletions

View file

@ -24,6 +24,8 @@
<include package="cyberapps.ccmkg" />
<include package="cyberapps.knowledge" />-->
<include package="loops.server" file="auth.zcml" />
<!-- Override registrations -->
<includeOverrides package="loops" file="overrides.zcml" />
<includeOverrides file="overrides.zcml" />

View file

@ -9,6 +9,7 @@ server_id = getenv('SERVER_ID')
zope_conf = getenv('ZOPE_CONF', 'zope.conf')
server_port = getenv('SERVER_PORT',
server_id and getenv(f'SERVER_PORT_{server_id}')) or '8080'
base_url = getenv('BASE_URL', 'https://test.example.com')
shell_pw = (getenv('SHELL_PW', 'dummy'))
loops_path = (getenv('LOOPS_PATH', 'loops/demo'))
@ -20,3 +21,20 @@ dbname = getenv('DBNAME', 'demo')
dbuser = getenv('DBUSER', 'demo')
dbpassword = getenv('DBPASSWORD', 'secret')
dbschema = getenv('DBSCHEMA', 'demo')
# OpenID Connect (OIDC, e.g. via zitadel) authentication settings
oidc_provider = getenv('OIDC_PROVIDER', '') #'https://instance1-abcdef.zitadel.cloud')
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
oidc_params = dict(
op_config_url=oidc_provider + '/.well-known/openid-configuration',
op_uris=None,
op_keys=None,
callback_url=getenv('OIDC_CALLBACK_URL', base_url + '/auth_callback'),
client_id=oidc_client_id,
principal_prefix=getenv('OIDC_PRINCIPAL_PREFIX', 'loops.'),
cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id),
cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None),
cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'),
cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None)
)

View file

@ -18,7 +18,6 @@ from loops.interfaces import HtmlText
from loops.organize.util import getPrincipalFolder, getPrincipalForUserId
from loops import util
from loops.util import _
from scopes.web.auth import oidc
ANNOTATION_KEY = 'loops.organize.person'
@ -34,7 +33,7 @@ def raiseValidationError(info):
class UserId(schema.TextLine):
""" Obsolete, as member registration does not use zope.formlib any more.
""" Note: member registration does not use zope.formlib any more.
TODO: transfer validation to loops.organize.browser.
"""
@ -44,11 +43,6 @@ class UserId(schema.TextLine):
from loops.organize.party import getPersonForUser
context = removeSecurityProxy(self.context).context
principal = getPrincipalForUserId(userId, context)
#auth = component.getUtility(IAuthentication, context=context)
#try:
#principal = auth.getPrincipal(userId)
#except PrincipalLookupError:
#principal = oidc.Principal(userId, dict(name=userId))
if principal is None:
raiseValidationError(_('User $userId does not exist',
mapping={'userId': userId}))

View file

@ -33,7 +33,6 @@ from loops.security.common import getCurrentPrincipal
from loops.security.interfaces import ISecuritySetter
from loops.type import TypeInterfaceSourceList
from loops import util
from scopes.web.auth import oidc
# register type interfaces - (TODO: use a function for this)
@ -87,7 +86,6 @@ class Person(AdapterBase, BasePerson):
setter = ISecuritySetter(self)
if userId:
principal = self.getPrincipalForUserId(userId)
print('***', userId, principal)
if principal is None:
return
person = getPersonForUser(self.context, principal=principal)
@ -144,14 +142,6 @@ class Person(AdapterBase, BasePerson):
def getPrincipalForUserId(self, userId=None):
userId = userId or self.userId
return getPrincipalForUserId(userId, self.context, self.authentication)
if not userId:
return None
auth = self.authentication
try:
return auth.getPrincipal(userId)
except PrincipalLookupError:
return oidc.Principal(userId, dict(name=userId))
#return None
def getAuthenticationUtility(context):

View file

@ -15,7 +15,6 @@ from zope.traversing.api import getParents
from loops.common import adapted
from loops.security.common import getCurrentPrincipal
from loops.type import getOptionsDict
from scopes.web.auth import oidc
defaultAuthPluginId = 'loops'
@ -93,6 +92,7 @@ def getPrincipalForUserId(id, context=None, auth=None):
try:
return auth.getPrincipal(id)
except PrincipalLookupError:
from scopes.web.auth import oidc
return oidc.Principal(id, dict(name=id))
#return None

18
loops/server/auth.zcml Normal file
View file

@ -0,0 +1,18 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="zope.interface.Interface"
name="auth_login"
class="loops.server.auth.LoginView"
permission="zope.Public" />
<browser:page
for="zope.interface.Interface"
name="auth_callback"
class="loops.server.auth.CallbackView"
permission="zope.Public" />
</configure>

View file

@ -15,6 +15,7 @@ import waitress
from zope.app.wsgi import config, getWSGIApplication
def run(app, config):
if config.oidc_provider:
oidc.startup()
port = int(config.server_port)
print(f'Serving on port {port}.')

View file

@ -1,8 +1,8 @@
# py-scopes/demo/config.py
# loops/tests/config.py
from dotenv import load_dotenv
from os import getenv
from scopes.server.app import zope_app_factory
from scopes.web.app import zope_app_factory
load_dotenv()
@ -18,3 +18,21 @@ dbuser = getenv('DBUSER', 'demo')
dbpassword = getenv('DBPASSWORD', 'secret')
dbschema = getenv('DBSCHEMA', 'demo')
base_url = 'test://'
# authentication settings
oidc_provider = ''
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
oidc_params = dict(
op_config_url=oidc_provider + '/.well-known/openid-configuration',
op_uris=None,
op_keys=None,
callback_url=getenv('OIDC_CALLBACK_URL', base_url + '/auth/callback'),
client_id=oidc_client_id,
principal_prefix=getenv('OIDC_PRINCIPAL_PREFIX', 'loops.'),
cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id),
cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None),
cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'),
cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None)
)

View file

@ -1,5 +1,8 @@
# loops.tests.test_loops
import os, sys
sys.path = [os.path.dirname(__file__)] + sys.path
import unittest, doctest
import warnings
from zope.interface.verify import verifyClass