Compare commits

..

No commits in common. "c6155a40e577b66e4ca8fa6373ea1cd835169401" and "5676194e560522a4a4ed6ba470f6812234b2053d" have entirely different histories.

14 changed files with 9 additions and 129 deletions

View file

@ -24,9 +24,6 @@ dbpassword = getenv('DBPASSWORD', 'secret')
dbschema = getenv('DBSCHEMA', 'demo')
# OpenID Connect (OIDC, e.g. via zitadel) authentication settings
authentication_method = 'cookie' # 'legacy'|'select'|'oidc'|'cookie'
# OpenID Connect (OIDC) authentication settings
oidc_provider = getenv('OIDC_PROVIDER', '') #'https://instance1-abcdef.zitadel.cloud')
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
oidc_params = dict(

View file

@ -1,27 +1,4 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="zope.security.interfaces.IUnauthorized"
name="index.html"
class="loops.server.auth.Unauthorized"
permission="zope.Public" />
<!--<browser:page
for="zope.publisher.interfaces.INotFound"
name="index.html"
class="loops.browser.common.NotFound"
permission="zope.Public" />
<browser:page
for="zope.interface.common.interfaces.IException"
name="index.html"
class="loops.browser.common.SystemErrorView"
permission="zope.Public" />-->
<!--<browser:defaultSkin name="Loops" />-->
<include package="loops.server" file="loginform.zcml" />
<configure xmlns="http://namespaces.zope.org/zope">
</configure>

View file

@ -40,24 +40,3 @@
</form>
</div>
</metal:login>
<metal:loggedout define-macro="loggedout"
i18n:domain="loops"
tal:define="principal request/principal/id">
<h2 i18n:translate="">Logged out</h2>
<div>
<p i18n:translate=""
tal:condition="python: principal == 'zope.anybody'">Your have been successfully logged out from this application. Please login again if needed or close this browser tab</p>
<p i18n:translate=""
tal:condition="python: principal != 'zope.anybody'">
You are not authorized to perform this action. However, you may login as a
different user who is authorized.</p>
<div class="row">
<button i18n:translate="">
<a i18n:translate=""
tal:attributes="href view/baseUrl">Log in again</a></button>
</div>
</div>
</metal:loggedout>

View file

@ -57,24 +57,6 @@ class Logout(object):
return nx
class Loggedout(NodeView):
template = template
@Lazy
def macro(self):
return self.template.macros['loggedout']
@Lazy
def item(self):
return self
@Lazy
def baseUrl(self):
url = self.menu.url
return url
class Unauthorized(ConceptView):
isTopLevel = True

View file

@ -44,11 +44,6 @@
class="loops.browser.auth.Logout"
permission="zope.View" />
<page for="loops.interfaces.INode"
name="loggedout.html"
class="loops.browser.auth.Loggedout"
permission="zope.View" />
<!-- see also view/adapter "login.html" in section "query views" -->
<!-- macros -->

View file

@ -51,8 +51,8 @@
tal:define="results reportView/results">
<tr>
<th style="white-space: nowrap"
tal:attributes="class col/cssClass"
tal:repeat="col results/displayedColumns">
<span tal:attributes="class col/cssClass">
<a title="tooltip_sort_column"
tal:define="colName col/name"
tal:omit-tag="python:not item.isSortableColumn(tableName, colName)"
@ -64,6 +64,7 @@
tal:condition="src"
tal:attributes="src src" />
</a>
</span>
</th>
</tr>
<tr tal:repeat="row results"

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: 3.0.1\n"
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
"PO-Revision-Date: 2026-01-09 12:00 CET\n"
"PO-Revision-Date: 2026-01-23 12:00 CET\n"
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
"Language-Team: loops developers <helmutm@cy55.de>\n"
"MIME-Version: 1.0\n"
@ -604,18 +604,9 @@ msgstr "Nicht angemeldet"
msgid "Log in"
msgstr "Anmelden"
msgid "Log in again"
msgstr "Erneut anmelden"
msgid "Presence"
msgstr "Anwesenheit"
msgid "Logged out"
msgstr "Abgemeldet"
msgid "Your have been successfully logged out from this application. Please login again if needed or close this browser tab"
msgstr "Sie haben sich erfolgreich von dieser Anwendung abgemeldet. Bitte loggen Sie sich bei Bedarf wieder ein oder schließen Sie das Browserfenster"
# general
msgid "Actions"
@ -1503,7 +1494,7 @@ msgid "authentication-method-legacy"
msgstr "Klassisches Login-Verfahren"
msgid "authentication-method-oidc"
msgstr "Immer mit neuem Login-Verfahren anmelden"
msgstr "Login mit OpenID Connect"
msgid "authentication-method-select"
msgstr "Verfahren beim Login auswählen"

View file

@ -57,8 +57,6 @@ class MediaAsset(MediaAssetFile, ExternalFileAdapter):
data = property(ExternalFileAdapter.getData, setData)
def setExternalAddress(self, addr):
if isinstance(addr, bytes):
addr = addr.decode('UTF-8')
ExternalFileAdapter.setExternalAddress(self, addr)
if addr and self.getMimeType().startswith('image/'):
self.transform(self.rules)

View file

@ -178,7 +178,7 @@ dayTo = TrackDateField('dayTo', u'End Day',
executionSteps=['query'])
day = TrackDateField('day', u'Day',
description=u'The day the work was done.',
cssClass='left',
cssClass='center',
executionSteps=['sort', 'output'])
dayStart = TrackDateField('dayStart', u'Start Day',
description=u'The day the unit of work was started.',
@ -190,18 +190,15 @@ dayEnd = TrackDateField('dayEnd', u'End Day',
executionSteps=['sort', 'output'])
timeStart = TrackTimeField('start', u'Start',
description=u'The time the unit of work was started.',
cssClass='noprint',
executionSteps=['sort', 'output'])
timeEnd = TrackTimeField('end', u'End',
description=u'The time the unit of work was finished.',
cssClass='noprint',
executionSteps=['output'])
task = TargetField('taskId', u'Task',
description=u'The task to which work items belong.',
executionSteps=['sort', 'output'])
party = PartyQueryField('userName', u'Party',
description=u'The party (usually a person) who did the work.',
cssClass='noprint',
fieldType='selection',
executionSteps=['sort', 'output', 'query'])
#partyQuery = TargetField('userName', u'Party',
@ -232,7 +229,6 @@ partyState = PartyStateField('partyState', u'Party State',
executionSteps=['query', 'output'])
activity = ActivityField('activity', u'LA',
description=u'The activity assigned to the work item.',
cssClass='noprint',
fieldType='selection',
executionSteps=['query', 'sort', 'output'])
# process

View file

@ -550,8 +550,6 @@ class IndexAttributes(object):
txt = transformToText(actx)
if txt is not None:
return txt
if isinstance(actx.contentType, bytes):
actx.contentType = actx.contentType.decode('UTF-8')
if not actx.contentType.startswith('text'):
return u''
data = actx.data
@ -615,8 +613,6 @@ def transformToText(obj, data=None, contentType=None):
data = obj.data
if contentType is None:
contentType = obj.contentType
if type(contentType) == bytes:
contentType = contentType.decode('UTF-8')
transform = component.queryAdapter(obj, ITextTransform, name=contentType)
if transform is not None:
#rfa = component.queryAdapter(IReadFile, obj)

View file

@ -4,9 +4,8 @@
# and other authentication and authorization stuff.
from scopes.web.auth import oidc
from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.authentication.interfaces import IAuthentication
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.component import provideAdapter, getUtility, provideUtility
from zope.interface import implementer, Interface
from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserPage
@ -15,9 +14,6 @@ from zope.security.proxy import removeSecurityProxy
import config
from logging import getLogger
logger = getLogger("loops.server.auth")
def registerAuthUtility(config):
baseAuth = getUtility(IAuthentication)
print('*** registerAuthUtility, baseAuth:', baseAuth)
@ -42,10 +38,6 @@ class LoginPage:
return self.authOidc()
return self.index()
@Lazy
def isAnonymous(self):
return IUnauthenticatedPrincipal.providedBy(self.request.principal)
def authOidc(self):
oidc.Authenticator(self.request).login()
return ''
@ -57,33 +49,12 @@ class LoginPageSelect(LoginPage):
def showSelection(self):
return getConfigAuthMethod() == 'cookie'
def authMethodCookieString(self):
domain = getattr(config, 'authentication_method_cookie_domain', None)
return 'document.cookie=`loops_auth_method=${this.value}; path=/; expires=Sun, 31 Jan 2027 12:00:00 UTC%s`' % (domain and f'; domain={domain}' or '')
class Unauthorized(LoginPage):
def __call__(self):
response = self.request.response
# make sure that squid does not keep the response in the cache
response.setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
response.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
response.setHeader('Pragma', 'no-cache')
logger.warn(f'unauthorized: user={self.request.principal.id}, authMethod={self.authMethod}')
if self.isAnonymous:
return super(Unauthorized, self).__call__() # open or redirect to login page
else:
response.setStatus(403)
return 'Unauthorized: You are not allowed to access this ressource.'
def getConfigAuthMethod():
return getattr(config, 'authentication_method', 'legacy')
def getAuthMethodCookieValue(request):
default = getattr(config, 'authentication_method_cookie_default', 'legacy')
return request.cookies.get('loops_auth_method') or default
return request.cookies.get('loops_auth_method') or 'legacy'
# OIDC authentication

View file

@ -31,8 +31,7 @@
<select name="auth_method"
onchange="document.cookie=`loops_auth_method=${this.value}; path=/; expires=Sun, 31 Jan 2027 12:00:00 UTC`"
tal:define="meth view/authMethod"
tal:attributes="value meth;
onchange view/authMethodCookieString">
tal:attributes="value meth">
<option value="legacy" i18n:translate="authentication-method-legacy"
tal:attributes="selected python:meth=='legacy'">Legacy</option>
<option value="oidc" i18n:translate="authentication-method-oidc"

View file

@ -11,8 +11,6 @@ license = {text = "MIT"}
keywords = ["loops"]
authors = [{name = "Helmut Merz", email = "helmutm@cy55.de"}]
# zope.publisher: v5.2.1 needed for handling of :records fields,
# should be replaced by patched zope.publisher fork (with multipart monkey patch)
dependencies = [
"cybertools",
"py-scopes",