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