From da3ae5e7b5902a11d5ba49f5511e2da440d96aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Ninivin?= <cedric.leninivin@tiolive.com> Date: Wed, 27 Feb 2013 13:04:51 +0000 Subject: [PATCH] Cache Cookie Extractor For Oauth 2 Authentication --- ...P5CacheCookieCredentialExtractionPlugin.py | 125 ++++++++++++++++++ product/ERP5Security/__init__.py | 11 ++ product/ERP5Security/utils.py | 33 +++++ ...5CacheCookieCredentialExtractionPlugin.zpt | 40 ++++++ 4 files changed, 209 insertions(+) create mode 100644 product/ERP5Security/ERP5CacheCookieCredentialExtractionPlugin.py create mode 100644 product/ERP5Security/utils.py create mode 100644 product/ERP5Security/www/ERP5Security_addERP5CacheCookieCredentialExtractionPlugin.zpt diff --git a/product/ERP5Security/ERP5CacheCookieCredentialExtractionPlugin.py b/product/ERP5Security/ERP5CacheCookieCredentialExtractionPlugin.py new file mode 100644 index 0000000000..0ed0d20a60 --- /dev/null +++ b/product/ERP5Security/ERP5CacheCookieCredentialExtractionPlugin.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly advised to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +############################################################################## +from zLOG import LOG, WARNING +from Products.ERP5Type.Globals import InitializeClass +from AccessControl import ClassSecurityInfo +from Products.PageTemplates.PageTemplateFile import PageTemplateFile +from Products.PluggableAuthService.interfaces import plugins +from Products.PluggableAuthService.utils import classImplements +from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin +from .utils import getHostFromRequest + +class ERP5CacheCookieCredentialExtractionPlugin(BasePlugin): + """ + Extracts credentials from a ram cache, based on a cookie value received + from request. + """ + security = ClassSecurityInfo() + meta_type = 'ERP5 Cache Cookie Credential Extraction Plugin' + + _properties=( + {'id': 'cookie_id', 'type': 'string', 'mode': 'w'}, + {'id': 'cache_factory_id', 'type': 'string', 'mode': 'w'}, + {'id': 'cache_scope', 'type': 'string', 'mode': 'w'}, + ) + + def __init__(self, id, cookie_id, cache_factory_id, title=None, + cache_scope=None): + self._setId(id) + self.title = title + self.cookie_id = cookie_id + self.cache_factory_id = cache_factory_id + self.cache_scope = cache_scope + + security.declarePrivate('extractCredentials') + def extractCredentials(self, request): + try: + cookie = request.cookies[self.cookie_id] + except KeyError: + pass + else: + portal_caches = self.getPortalObject().portal_caches + try: + # TODO: When portal_cache allows accessing factories from persistent + # objects, catch AttributeError instead of KeyError and replace the + # following line with this: + #cache_factory = getattr(portal_caches, self.cache_factory_id) + # XXX: getRamCacheRoot is misnamed: its return value contains all kinds + # of caches, actually. + cache_factory = portal_caches.getRamCacheRoot()[self.cache_factory_id] + except KeyError: + LOG(self.id, WARNING, 'Cache factory not found: %r' % ( + self.cache_factory_id, )) + else: + for cache_plugin in cache_factory.getCachePluginList(): + cache_entry = cache_plugin.get(cookie, self.cache_scope or + self.cookie_id, None) + if cache_entry is not None: + entry_value = cache_entry.getValue() + try: + login = entry_value['login'] + except KeyError: + pass + else: + remote_host, remote_address = getHostFromRequest(request) + return { + 'external_login': login, + 'remote_host': remote_host, + 'remote_address': remote_address, + } + return {} + +classImplements(ERP5CacheCookieCredentialExtractionPlugin, + plugins.IExtractionPlugin, +) +InitializeClass(ERP5CacheCookieCredentialExtractionPlugin) + +manage_addERP5CacheCookieCredentialExtractionPluginForm = PageTemplateFile( + 'www/ERP5Security_addERP5CacheCookieCredentialExtractionPlugin', globals(), + __name__='manage_addERP5CacheCookieCredentialExtractionPluginForm') + +def addERP5CacheCookieCredentialExtractionPlugin(dispatcher, id, cookie_id, + cache_scope, cache_factory_id, title=None, REQUEST=None): + """ bla bla, mandatory docstring """ + if not cookie_id: + raise ValueError('cookie_id is mandatory') + if not cache_factory_id: + raise ValueError('cache_factory_id is mandatory') + plugin = ERP5CacheCookieCredentialExtractionPlugin( + id=id, + title=title, + cookie_id=cookie_id, + cache_scope=cache_scope, + cache_factory_id=cache_factory_id, + ) + dispatcher._setObject(plugin.getId(), plugin) + if REQUEST is not None: + REQUEST['RESPONSE'].redirect( + dispatcher.absolute_url() + '/manage_workspace?manage_tabs_message=' + 'Add+ERP5+Cache+Cookie+Credential+Extraction+Plugin+added.', + ) diff --git a/product/ERP5Security/__init__.py b/product/ERP5Security/__init__.py index 6ea4229dea..13e2ceb091 100644 --- a/product/ERP5Security/__init__.py +++ b/product/ERP5Security/__init__.py @@ -29,6 +29,7 @@ import ERP5KeyAuthPlugin import ERP5ExternalAuthenticationPlugin import ERP5BearerExtractionPlugin import ERP5ExternalOauth2ExtractionPlugin +import ERP5CacheCookieCredentialExtractionPlugin def mergedLocalRoles(object): """Returns a merging of object and its ancestors' @@ -67,6 +68,7 @@ registerMultiPlugin(ERP5ExternalAuthenticationPlugin.ERP5ExternalAuthenticationP registerMultiPlugin(ERP5BearerExtractionPlugin.ERP5BearerExtractionPlugin.meta_type) registerMultiPlugin(ERP5ExternalOauth2ExtractionPlugin.ERP5FacebookExtractionPlugin.meta_type) registerMultiPlugin(ERP5ExternalOauth2ExtractionPlugin.ERP5GoogleExtractionPlugin.meta_type) +registerMultiPlugin(ERP5CacheCookieCredentialExtractionPlugin.ERP5CacheCookieCredentialExtractionPlugin.meta_type) def initialize(context): @@ -151,6 +153,15 @@ def initialize(context): , icon='www/portal.gif' ) + context.registerClass( ERP5CacheCookieCredentialExtractionPlugin.ERP5CacheCookieCredentialExtractionPlugin + , permission=ManageUsers + , constructors=( + ERP5CacheCookieCredentialExtractionPlugin.manage_addERP5CacheCookieCredentialExtractionPluginForm, + ERP5CacheCookieCredentialExtractionPlugin.addERP5CacheCookieCredentialExtractionPlugin, ) + , visibility=None + , icon='www/portal.gif' + ) + from AccessControl.SecurityInfo import ModuleSecurityInfo ModuleSecurityInfo('Products.ERP5Security.ERP5UserManager').declarePublic( 'getUserByLogin') diff --git a/product/ERP5Security/utils.py b/product/ERP5Security/utils.py new file mode 100644 index 0000000000..6a4cb03a0c --- /dev/null +++ b/product/ERP5Security/utils.py @@ -0,0 +1,33 @@ +############################################################################## +# +# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly advised to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +############################################################################## + +def getHostFromRequest(request): + try: + remote_address = request.getClientAddr() + except AttributeError: + remote_address = request.get('REMOTE_ADDR', '') + return (request.get('REMOTE_HOST', ''), remote_address) diff --git a/product/ERP5Security/www/ERP5Security_addERP5CacheCookieCredentialExtractionPlugin.zpt b/product/ERP5Security/www/ERP5Security_addERP5CacheCookieCredentialExtractionPlugin.zpt new file mode 100644 index 0000000000..74bd5f6788 --- /dev/null +++ b/product/ERP5Security/www/ERP5Security_addERP5CacheCookieCredentialExtractionPlugin.zpt @@ -0,0 +1,40 @@ +<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1> +<h2 tal:define="form_title string:Add Cache Cookie Credential Extraction Plugin" + tal:replace="structure context/manage_form_title">FORM TITLE</h2> +<p class="form-help">Please input the configuration</p> +<form action="addERP5CacheCookieCredentialExtractionPlugin" method="POST"> +<table cellspacing="0" cellpadding="2" border="0"> + <tr> + <td align="left" valign="top"><div class="form-label">Id</div></td> + <td align="left" valign="top"> + <input type="text" name="id" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"><div class="form-label">Title</div></td> + <td align="left" valign="top"> + <input type="text" name="title" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"><div class="form-label">Cookie ID</div></td> + <td align="left" valign="top"> + <input type="text" name="cookie_id" size="40" value="__ac_CHANGE_ME" /> + </td> + </tr> + <tr> + <td align="left" valign="top"><div class="form-label">Cache scope (if left empty, becomes Cookie ID)</div></td> + <td align="left" valign="top"> + <input type="text" name="cache_scope" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"><div class="form-label">Cache factory</div></td> + <td align="left" valign="top"> + <input type="text" name="cache_factory_id" size="40" value="credential_cache_factory" /> + </td> + </tr> + <tr><td colspan="2"><input type="submit" value="add plugin"/></td></tr> +</table> +</form> +<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1> -- 2.30.9