Commit 5eb36066 authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

ERP5Security: JWT and DumbExtractor support reset and update Credentials

parent 02fb5fe7
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
# #
############################################################################## ##############################################################################
from base64 import standard_b64encode
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
...@@ -51,12 +53,52 @@ class ERP5DumbHTTPExtractionPlugin(BasePlugin): ...@@ -51,12 +53,52 @@ class ERP5DumbHTTPExtractionPlugin(BasePlugin):
#Register value #Register value
self._setId(id) self._setId(id)
self.title = title self.title = title
self.cookie_name = "__ac"
security.declarePrivate('extractCredentials') security.declarePrivate('extractCredentials')
@UnrestrictedMethod @UnrestrictedMethod
def extractCredentials(self, request): def extractCredentials(self, request):
return DumbHTTPExtractor().extractCredentials(request); return DumbHTTPExtractor().extractCredentials(request);
################################
# ICredentialsUpdatePlugin #
################################
security.declarePrivate('updateCredentials')
def updateCredentials(self, request, response, login, password):
""" Respond to change of credentials"""
kw = {}
portal = self.getPortalObject()
expire_interval = portal.portal_preferences.getPreferredMaxUserInactivityDuration()
if expire_interval in ('', None):
ac_renew = float('inf')
else:
expire_interval /= 86400. # seconds -> days
now = DateTime()
kw['expires'] = (now + expire_interval).toZone('GMT').rfc822()
ac_renew = (now + expire_interval / 2).millis()
portal.portal_sessions[
portal.Base_getAutoLogoutSessionKey(username=login)
]['ac_renew'] = ac_renew
response.setCookie(
name="__ac",
value=standard_b64encode('%s:%s' % (login, password)),
path='/',
secure=getattr(portal, 'REQUEST', {}).get('SERVER_URL', '').startswith('https:'),
http_only=True,
**kw
)
################################
# ICredentialsResetPlugin #
################################
security.declarePrivate( 'resetCredentials' )
def resetCredentials( self, request, response ):
""" Logout
"""
response.expireCookie("__ac", path="/")
#Form for new plugin in ZMI #Form for new plugin in ZMI
manage_addERP5DumbHTTPExtractionPluginForm = PageTemplateFile( manage_addERP5DumbHTTPExtractionPluginForm = PageTemplateFile(
'www/ERP5Security_addERP5DumbHTTPExtractionPlugin', globals(), 'www/ERP5Security_addERP5DumbHTTPExtractionPlugin', globals(),
...@@ -77,6 +119,8 @@ def addERP5DumbHTTPExtractionPlugin(dispatcher, id, title=None, REQUEST=None): ...@@ -77,6 +119,8 @@ def addERP5DumbHTTPExtractionPlugin(dispatcher, id, title=None, REQUEST=None):
#List implementation of class #List implementation of class
classImplements(ERP5DumbHTTPExtractionPlugin, classImplements(ERP5DumbHTTPExtractionPlugin,
plugins.ILoginPasswordHostExtractionPlugin plugins.ILoginPasswordHostExtractionPlugin,
plugins.ICredentialsResetPlugin,
plugins.ICredentialsUpdatePlugin,
) )
InitializeClass(ERP5DumbHTTPExtractionPlugin) InitializeClass(ERP5DumbHTTPExtractionPlugin)
...@@ -37,7 +37,7 @@ from AccessControl import ClassSecurityInfo ...@@ -37,7 +37,7 @@ from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PluggableAuthService.interfaces import plugins from Products.PluggableAuthService.interfaces import plugins
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.ERP5Security.ERP5UserManager import ERP5UserManager from Products.ERP5Security.ERP5UserManager import getUserByLogin
from Products.PluggableAuthService.permissions import ManageUsers from Products.PluggableAuthService.permissions import ManageUsers
from Products.PluggableAuthService.PluggableAuthService import DumbHTTPExtractor from Products.PluggableAuthService.PluggableAuthService import DumbHTTPExtractor
from ZODB.utils import u64 from ZODB.utils import u64
...@@ -69,8 +69,10 @@ def addERP5JSONWebTokenPlugin(dispatcher, id, title=None, REQUEST=None): ...@@ -69,8 +69,10 @@ def addERP5JSONWebTokenPlugin(dispatcher, id, title=None, REQUEST=None):
@implementer( @implementer(
plugins.ILoginPasswordHostExtractionPlugin, plugins.ILoginPasswordHostExtractionPlugin,
plugins.ICredentialsResetPlugin,
plugins.ICredentialsUpdatePlugin,
) )
class ERP5JSONWebTokenPlugin(ERP5UserManager): class ERP5JSONWebTokenPlugin(BasePlugin):
meta_type = "ERP5 JSON Web Token Plugin" meta_type = "ERP5 JSON Web Token Plugin"
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -88,7 +90,6 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager): ...@@ -88,7 +90,6 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
super(ERP5JSONWebTokenPlugin, self).__init__(*args, **kw)
self.manage_updateERP5JSONWebTokenPlugin() self.manage_updateERP5JSONWebTokenPlugin()
self.manage_setERP5JSONWebTokenPluginExtpirationDelay(0) self.manage_setERP5JSONWebTokenPluginExtpirationDelay(0)
...@@ -138,8 +139,7 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager): ...@@ -138,8 +139,7 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager):
jwt.InvalidTokenError, jwt.InvalidTokenError,
jwt.DecodeError, jwt.DecodeError,
): ):
request.response.expireCookie(self.same_site_cookie, path='/') self.resetCredentials(request, request.response)
request.response.expireCookie(self.cors_cookie, path='/')
return None return None
person_relative_url = data["sub"].encode() person_relative_url = data["sub"].encode()
...@@ -159,25 +159,23 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager): ...@@ -159,25 +159,23 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager):
creds['remote_address'] = request.get('REMOTE_ADDR', '') creds['remote_address'] = request.get('REMOTE_ADDR', '')
return creds return creds
# ################################
# IAuthenticationPlugin implementation # ICredentialsUpdatePlugin #
# ################################
security.declarePrivate( 'authenticateCredentials' ) security.declarePrivate('updateCredentials')
def authenticateCredentials(self, credentials): def updateCredentials(self, request, response, login, new_password):
authentication_result = super( """ Respond to change of credentials"""
ERP5JSONWebTokenPlugin,
self #Update credential for key auth or standard of.
).authenticateCredentials(credentials) #Remove conflict between both systems
if login is not None:
# In case the password is present in the request, the token is updated
if authentication_result is not None and "password" in credentials:
if jwt is None: if jwt is None:
LOG('ERP5JSONWebTokenPlugin', INFO, LOG('ERP5JSONWebTokenPlugin', INFO,
'No jwt module, install pyjwt package. ' 'No jwt module, install pyjwt package. '
'Authentication disabled.') 'Authentication disabled.')
return authentication_result return authentication_result
user = self.getUserByLogin(authentication_result[0])[0] user = getUserByLogin(self.getPortalObject(), login)[0]
# Activate password to have the real tid # Activate password to have the real tid
user.password._p_activate() user.password._p_activate()
...@@ -221,17 +219,24 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager): ...@@ -221,17 +219,24 @@ class ERP5JSONWebTokenPlugin(ERP5UserManager):
cookie = self.same_site_cookie cookie = self.same_site_cookie
cookie_parameters["same_site"] = "Lax" cookie_parameters["same_site"] = "Lax"
request.response.setCookie( response.setCookie(
cookie, cookie,
jwt.encode(data, self._secret), jwt.encode(data, self._secret),
**cookie_parameters **cookie_parameters
) )
# Expire default cookie set by default ################################
# (even with plugin deactivated) # ICredentialsResetPlugin #
# request.response.expireCookie('__ac') ################################
security.declarePrivate( 'resetCredentials' )
return authentication_result def resetCredentials( self, request, response ):
""" Logout
"""
for cookie in (self.same_site_cookie,
self.cors_cookie):
if request.cookies.get(cookie) is not None:
response.expireCookie(cookie, path="/")
################################ ################################
# Properties for ZMI managment # # Properties for ZMI managment #
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment