Compare commits
No commits in common. "c6155a40e577b66e4ca8fa6373ea1cd835169401" and "5676194e560522a4a4ed6ba470f6812234b2053d" have entirely different histories.
c6155a40e5
...
5676194e56
14 changed files with 9 additions and 129 deletions
|
|
@ -24,9 +24,6 @@ dbpassword = getenv('DBPASSWORD', 'secret')
|
||||||
dbschema = getenv('DBSCHEMA', 'demo')
|
dbschema = getenv('DBSCHEMA', 'demo')
|
||||||
|
|
||||||
# OpenID Connect (OIDC, e.g. via zitadel) authentication settings
|
# 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_provider = getenv('OIDC_PROVIDER', '') #'https://instance1-abcdef.zitadel.cloud')
|
||||||
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
|
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
|
||||||
oidc_params = dict(
|
oidc_params = dict(
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,4 @@
|
||||||
<configure xmlns="http://namespaces.zope.org/zope"
|
<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>
|
</configure>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,24 +40,3 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</metal:login>
|
</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>
|
|
||||||
|
|
|
||||||
|
|
@ -57,24 +57,6 @@ class Logout(object):
|
||||||
return nx
|
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):
|
class Unauthorized(ConceptView):
|
||||||
|
|
||||||
isTopLevel = True
|
isTopLevel = True
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,6 @@
|
||||||
class="loops.browser.auth.Logout"
|
class="loops.browser.auth.Logout"
|
||||||
permission="zope.View" />
|
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" -->
|
<!-- see also view/adapter "login.html" in section "query views" -->
|
||||||
|
|
||||||
<!-- macros -->
|
<!-- macros -->
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@
|
||||||
tal:define="results reportView/results">
|
tal:define="results reportView/results">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="white-space: nowrap"
|
<th style="white-space: nowrap"
|
||||||
tal:attributes="class col/cssClass"
|
|
||||||
tal:repeat="col results/displayedColumns">
|
tal:repeat="col results/displayedColumns">
|
||||||
|
<span tal:attributes="class col/cssClass">
|
||||||
<a title="tooltip_sort_column"
|
<a title="tooltip_sort_column"
|
||||||
tal:define="colName col/name"
|
tal:define="colName col/name"
|
||||||
tal:omit-tag="python:not item.isSortableColumn(tableName, colName)"
|
tal:omit-tag="python:not item.isSortableColumn(tableName, colName)"
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
tal:condition="src"
|
tal:condition="src"
|
||||||
tal:attributes="src src" />
|
tal:attributes="src src" />
|
||||||
</a>
|
</a>
|
||||||
|
</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr tal:repeat="row results"
|
<tr tal:repeat="row results"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ msgstr ""
|
||||||
|
|
||||||
"Project-Id-Version: 3.0.1\n"
|
"Project-Id-Version: 3.0.1\n"
|
||||||
"POT-Creation-Date: 2007-05-22 12:00 CET\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"
|
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
|
@ -604,18 +604,9 @@ msgstr "Nicht angemeldet"
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr "Anmelden"
|
msgstr "Anmelden"
|
||||||
|
|
||||||
msgid "Log in again"
|
|
||||||
msgstr "Erneut anmelden"
|
|
||||||
|
|
||||||
msgid "Presence"
|
msgid "Presence"
|
||||||
msgstr "Anwesenheit"
|
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
|
# general
|
||||||
|
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
|
|
@ -1503,7 +1494,7 @@ msgid "authentication-method-legacy"
|
||||||
msgstr "Klassisches Login-Verfahren"
|
msgstr "Klassisches Login-Verfahren"
|
||||||
|
|
||||||
msgid "authentication-method-oidc"
|
msgid "authentication-method-oidc"
|
||||||
msgstr "Immer mit neuem Login-Verfahren anmelden"
|
msgstr "Login mit OpenID Connect"
|
||||||
|
|
||||||
msgid "authentication-method-select"
|
msgid "authentication-method-select"
|
||||||
msgstr "Verfahren beim Login auswählen"
|
msgstr "Verfahren beim Login auswählen"
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,6 @@ class MediaAsset(MediaAssetFile, ExternalFileAdapter):
|
||||||
data = property(ExternalFileAdapter.getData, setData)
|
data = property(ExternalFileAdapter.getData, setData)
|
||||||
|
|
||||||
def setExternalAddress(self, addr):
|
def setExternalAddress(self, addr):
|
||||||
if isinstance(addr, bytes):
|
|
||||||
addr = addr.decode('UTF-8')
|
|
||||||
ExternalFileAdapter.setExternalAddress(self, addr)
|
ExternalFileAdapter.setExternalAddress(self, addr)
|
||||||
if addr and self.getMimeType().startswith('image/'):
|
if addr and self.getMimeType().startswith('image/'):
|
||||||
self.transform(self.rules)
|
self.transform(self.rules)
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ dayTo = TrackDateField('dayTo', u'End Day',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
day = TrackDateField('day', u'Day',
|
day = TrackDateField('day', u'Day',
|
||||||
description=u'The day the work was done.',
|
description=u'The day the work was done.',
|
||||||
cssClass='left',
|
cssClass='center',
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['sort', 'output'])
|
||||||
dayStart = TrackDateField('dayStart', u'Start Day',
|
dayStart = TrackDateField('dayStart', u'Start Day',
|
||||||
description=u'The day the unit of work was started.',
|
description=u'The day the unit of work was started.',
|
||||||
|
|
@ -190,18 +190,15 @@ dayEnd = TrackDateField('dayEnd', u'End Day',
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['sort', 'output'])
|
||||||
timeStart = TrackTimeField('start', u'Start',
|
timeStart = TrackTimeField('start', u'Start',
|
||||||
description=u'The time the unit of work was started.',
|
description=u'The time the unit of work was started.',
|
||||||
cssClass='noprint',
|
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['sort', 'output'])
|
||||||
timeEnd = TrackTimeField('end', u'End',
|
timeEnd = TrackTimeField('end', u'End',
|
||||||
description=u'The time the unit of work was finished.',
|
description=u'The time the unit of work was finished.',
|
||||||
cssClass='noprint',
|
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
task = TargetField('taskId', u'Task',
|
task = TargetField('taskId', u'Task',
|
||||||
description=u'The task to which work items belong.',
|
description=u'The task to which work items belong.',
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['sort', 'output'])
|
||||||
party = PartyQueryField('userName', u'Party',
|
party = PartyQueryField('userName', u'Party',
|
||||||
description=u'The party (usually a person) who did the work.',
|
description=u'The party (usually a person) who did the work.',
|
||||||
cssClass='noprint',
|
|
||||||
fieldType='selection',
|
fieldType='selection',
|
||||||
executionSteps=['sort', 'output', 'query'])
|
executionSteps=['sort', 'output', 'query'])
|
||||||
#partyQuery = TargetField('userName', u'Party',
|
#partyQuery = TargetField('userName', u'Party',
|
||||||
|
|
@ -232,7 +229,6 @@ partyState = PartyStateField('partyState', u'Party State',
|
||||||
executionSteps=['query', 'output'])
|
executionSteps=['query', 'output'])
|
||||||
activity = ActivityField('activity', u'LA',
|
activity = ActivityField('activity', u'LA',
|
||||||
description=u'The activity assigned to the work item.',
|
description=u'The activity assigned to the work item.',
|
||||||
cssClass='noprint',
|
|
||||||
fieldType='selection',
|
fieldType='selection',
|
||||||
executionSteps=['query', 'sort', 'output'])
|
executionSteps=['query', 'sort', 'output'])
|
||||||
# process
|
# process
|
||||||
|
|
|
||||||
|
|
@ -550,8 +550,6 @@ class IndexAttributes(object):
|
||||||
txt = transformToText(actx)
|
txt = transformToText(actx)
|
||||||
if txt is not None:
|
if txt is not None:
|
||||||
return txt
|
return txt
|
||||||
if isinstance(actx.contentType, bytes):
|
|
||||||
actx.contentType = actx.contentType.decode('UTF-8')
|
|
||||||
if not actx.contentType.startswith('text'):
|
if not actx.contentType.startswith('text'):
|
||||||
return u''
|
return u''
|
||||||
data = actx.data
|
data = actx.data
|
||||||
|
|
@ -615,8 +613,6 @@ def transformToText(obj, data=None, contentType=None):
|
||||||
data = obj.data
|
data = obj.data
|
||||||
if contentType is None:
|
if contentType is None:
|
||||||
contentType = obj.contentType
|
contentType = obj.contentType
|
||||||
if type(contentType) == bytes:
|
|
||||||
contentType = contentType.decode('UTF-8')
|
|
||||||
transform = component.queryAdapter(obj, ITextTransform, name=contentType)
|
transform = component.queryAdapter(obj, ITextTransform, name=contentType)
|
||||||
if transform is not None:
|
if transform is not None:
|
||||||
#rfa = component.queryAdapter(IReadFile, obj)
|
#rfa = component.queryAdapter(IReadFile, obj)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@
|
||||||
# and other authentication and authorization stuff.
|
# and other authentication and authorization stuff.
|
||||||
|
|
||||||
from scopes.web.auth import oidc
|
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.browserpage import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
from zope.component import provideAdapter, getUtility, provideUtility
|
from zope.component import provideAdapter, getUtility, provideUtility
|
||||||
from zope.interface import implementer, Interface
|
from zope.interface import implementer, Interface
|
||||||
from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserPage
|
from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserPage
|
||||||
|
|
@ -15,9 +14,6 @@ from zope.security.proxy import removeSecurityProxy
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
logger = getLogger("loops.server.auth")
|
|
||||||
|
|
||||||
def registerAuthUtility(config):
|
def registerAuthUtility(config):
|
||||||
baseAuth = getUtility(IAuthentication)
|
baseAuth = getUtility(IAuthentication)
|
||||||
print('*** registerAuthUtility, baseAuth:', baseAuth)
|
print('*** registerAuthUtility, baseAuth:', baseAuth)
|
||||||
|
|
@ -42,10 +38,6 @@ class LoginPage:
|
||||||
return self.authOidc()
|
return self.authOidc()
|
||||||
return self.index()
|
return self.index()
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def isAnonymous(self):
|
|
||||||
return IUnauthenticatedPrincipal.providedBy(self.request.principal)
|
|
||||||
|
|
||||||
def authOidc(self):
|
def authOidc(self):
|
||||||
oidc.Authenticator(self.request).login()
|
oidc.Authenticator(self.request).login()
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -57,33 +49,12 @@ class LoginPageSelect(LoginPage):
|
||||||
def showSelection(self):
|
def showSelection(self):
|
||||||
return getConfigAuthMethod() == 'cookie'
|
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():
|
def getConfigAuthMethod():
|
||||||
return getattr(config, 'authentication_method', 'legacy')
|
return getattr(config, 'authentication_method', 'legacy')
|
||||||
|
|
||||||
def getAuthMethodCookieValue(request):
|
def getAuthMethodCookieValue(request):
|
||||||
default = getattr(config, 'authentication_method_cookie_default', 'legacy')
|
return request.cookies.get('loops_auth_method') or 'legacy'
|
||||||
return request.cookies.get('loops_auth_method') or default
|
|
||||||
|
|
||||||
|
|
||||||
# OIDC authentication
|
# OIDC authentication
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@
|
||||||
<select name="auth_method"
|
<select name="auth_method"
|
||||||
onchange="document.cookie=`loops_auth_method=${this.value}; path=/; expires=Sun, 31 Jan 2027 12:00:00 UTC`"
|
onchange="document.cookie=`loops_auth_method=${this.value}; path=/; expires=Sun, 31 Jan 2027 12:00:00 UTC`"
|
||||||
tal:define="meth view/authMethod"
|
tal:define="meth view/authMethod"
|
||||||
tal:attributes="value meth;
|
tal:attributes="value meth">
|
||||||
onchange view/authMethodCookieString">
|
|
||||||
<option value="legacy" i18n:translate="authentication-method-legacy"
|
<option value="legacy" i18n:translate="authentication-method-legacy"
|
||||||
tal:attributes="selected python:meth=='legacy'">Legacy</option>
|
tal:attributes="selected python:meth=='legacy'">Legacy</option>
|
||||||
<option value="oidc" i18n:translate="authentication-method-oidc"
|
<option value="oidc" i18n:translate="authentication-method-oidc"
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ license = {text = "MIT"}
|
||||||
keywords = ["loops"]
|
keywords = ["loops"]
|
||||||
authors = [{name = "Helmut Merz", email = "helmutm@cy55.de"}]
|
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 = [
|
dependencies = [
|
||||||
"cybertools",
|
"cybertools",
|
||||||
"py-scopes",
|
"py-scopes",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue