Commit 312417f0 authored by Rafael Monnerat's avatar Rafael Monnerat

slapos_web: Replace google/facebook authentication

  This authentication code will be provided by generic ERP5 code now.
  Add google and facebook oauth2 dependencies.
  Use correct name for RSS style on skin later.
parent 9b0a60d7
......@@ -2,14 +2,10 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
......@@ -24,18 +20,6 @@
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
......@@ -49,12 +33,24 @@
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebSection_viewFacebookLogin</string> </value>
<value> <string>login_form</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
......
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title tal:content="python: here.getPortalObject().getTitle()"></title>
<link rel="stylesheet" href="gadget_erp5_nojqm.css">
<link rel="stylesheet" href="zocial.min.css">
</head>
<body tal:define="form_action string:WebSite_login;">
<div data-gadget-scope='header'>
<div class="ui-header">
<h1><span i18n:domain="ui" i18n:translate="">Connect</span></h1>
</div>
</div>
<div data-gadget-scope='panel'>
<div data-role="header">
<div class="panel_img">
<img alt="ERP5" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAAA/CAMAAADaDqrIAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowMEM5NUE4MzQ5NjQxMUUzOUZEQUU2NUY1RTI1RjdCQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowMEM5NUE4NDQ5NjQxMUUzOUZEQUU2NUY1RTI1RjdCQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAwQzk1QTgxNDk2NDExRTM5RkRBRTY1RjVFMjVGN0JCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAwQzk1QTgyNDk2NDExRTM5RkRBRTY1RjVFMjVGN0JCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+J9MJsAAAAwBQTFRF///////M//+Z//9m//8z//8A/8z//8zM/8yZ/8xm/8wz/8wA/5n//5nM/5mZ/5lm/5kz/5kA/2b//2bM/2aZ/2Zm/2Yz/2YA/zP//zPM/zOZ/zNm/zMz/zMA/wD//wDM/wCZ/wBm/wAz/wAAzP//zP/MzP+ZzP9mzP8zzP8AzMz/zMzMzMyZzMxmzMwzzMwAzJn/zJnMzJmZzJlmzJkzzJkAzGb/zGbMzGaZzGZmzGYzzGYAzDP/zDPMzDOZzDNmzDMzzDMAzAD/zADMzACZzABmzAAzzAAAmf//mf/Mmf+Zmf9mmf8zmf8Amcz/mczMmcyZmcxmmcwzmcwAmZn/mZnMmZmZmZlmmZkzmZkAmWb/mWbMmWaZmWZmmWYzmWYAmTP/mTPMmTOZmTNmmTMzmTMAmQD/mQDMmQCZmQBmmQAzmQAAZv//Zv/MZv+ZZv9mZv8zZv8AZsz/ZszMZsyZZsxmZswzZswAZpn/ZpnMZpmZZplmZpkzZpkAZmb/ZmbMZmaZZmZmZmYzZmYAZjP/ZjPMZjOZZjNmZjMzZjMAZgD/ZgDMZgCZZgBmZgAzZgAAM///M//MM/+ZM/9mM/8zM/8AM8z/M8zMM8yZM8xmM8wzM8wAM5n/M5nMM5mZM5lmM5kzM5kAM2b/M2bMM2aZM2ZmM2YzM2YAMzP/MzPMMzOZMzNmMzMzMzMAMwD/MwDMMwCZMwBmMwAzMwAAAP//AP/MAP+ZAP9mAP8zAP8AAMz/AMzMAMyZAMxmAMwzAMwAAJn/AJnMAJmZAJlmAJkzAJkAAGb/AGbMAGaZAGZmAGYzAGYAADP/ADPMADOZADNmADMzADMAAAD/AADMAACZAABmAAAzAAAAHHa7K3/AOojESZHJWZvNaKTSd63Whrbblb/fpMjks9Howtrt4e320uTx8Pb6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdfKHSQAAAOh0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ALItoLoAAAJkSURBVHja7NlZsqsgEABQ979C5kGGddxo4os0Q8BAynoFn0nUU23TNGRB9xzLdE3XdE3XdP2fLnIzF2VSrsYvC72HizAh9eZ5DVuOl15S4/jWwC+kDC8HPzCSJVHcRY8QXV2PwQuujUYTsYrvYlBv1yKLrsXHsPghnvR3Lazoip/JKuwpF8sm6/bY01Ow9CBlTXi53PNoRcUktEZT1NV1PIPnXAjtMJIMlzerFIzU1dVWF7aPj3Tetae4gjdxktG2et/qQjJ4kZFrv6ED4bK4eR1qduHzvEi4+PbGwD0EGu9CQQLFrv1F4jBc6BcuW3YhkPiPX1hOfxQvnHfh0HVMRqc4HuqiQf5k3mO6dq1s8Hx0H/Le5kq9YaNcxAcrZLpOvAv+ClcgNcZFtnrucd5Fwx4mXj0drnbBYXMuJmFDAV3Uhm4qlbHh3ddOrmholHNxFTVC+xTl2mf6pI4uVe4LwSp0DPGOmh/hAnM94bKZjkHlWuiLeV+cTab4luMKk82wy/Px2fLJskvKUk03hRf9RZ0wiYiZ4uVwVwTb9E71y0XboTYXGuTaa33Y47W5zBjXM3P11y7TfR2ycFdxyaW7uwSc5m0un9ix91m3TWJfW+1ijfW+wcVAfjS5dOP62LyvZZdcLHnA1MkFAtbgwq+Vm47po8OA1buwyy/a1ec5urzveAes2sV8kfW963mFaHNhlT0b6+Yi5+StchH1CtaK0ThXELDPLiyOPZHldzm/J1z9O/B14g7/K+DtkP7U8ivSeD4xaPCTSQvy+YIfubZWzRgtRe1iMP+3mq7pmq7p6jv+BBgAPrgi/TzwWzkAAAAASUVORK5CYII="/>
</div>
</div>
</div>
<div class="gadget-content">
<article>
<section>
<tal:block tal:condition="exists: request/portal_status_message">
<span tal:attributes="data-i18n request/portal_status_message"><span tal:content="request/portal_status_message"></span></span>
</tal:block>
</section>
<section>
<form method="post" tal:attributes="action python: '%s/' % context.absolute_url()">
<div class="ui-field-contain">
<label i18n:domain="ui" i18n:translate="" >Login</label>
<div><input autofocus type="text" name="__ac_name" value="" required=""></div>
</div>
<div class="ui-field-contain">
<label i18n:domain="ui" i18n:translate="" >Password</label>
<div><input type="password" name="__ac_password" value="" required=""></div>
</div>
<div class="ui-field-contain">
<label></label>
<div tal:define="absolute_url python:context.absolute_url()">
<a i18n:domain="ui" i18n:translate="" tal:attributes="href python: '%s/WebSite_viewRecoverAccount?came_from=%s' % (absolute_url, absolute_url)">I forgot my password!</a>
</div>
</div><br/>
<div class="ui-field-contain">
<label></label>
<div><input type="submit" value='Login' i18n:attributes="value" i18n:domain="ui" tal:attributes="name python: '%s:method' % (form_action, )"/></div>
</div>
<tal:block tal:define="available_oauth_login_list python: context.getPortalObject().ERP5Site_getAvailableOAuthLoginList();">
<tal:block tal:condition="python: 'google' in available_oauth_login_list">
<div class="field">
<label>&nbsp;</label>
<div class="input">
<a tal:attributes="href string:${here/portal_url}/ERP5Site_redirectToGoogleLoginPage"
i18n:translate="" i18n:domain="ui" class="zocial google">Login with Google</a>
  • Wouldn't it be possible to have google login in generic ERP5 ?

    /cc @gabriel @romain

  • ( I meant in generic version of ERP5JS's login_form )

  • I guess it depends on how it is implemented currently.

    Better ask @rafael

  • Well it is possible to move (of course), however I'm don't remember how much effort it will require to clean up, adjust to cleaner approach, add more tests (to complete coverage), etc etc.

    In general it was here because I wasn't confortable to merge everything into generic as it was.

    Edited by Rafael Monnerat
  • Thanks for feedback. I think this google/facebook login in slapos has at least two aspects:

    • creating an new account from google/facebook
    • logging in, once the account was created.

    What we are interested in now is the later, only the ability to log in once the account is created. This works out of the box in erp5_xhtml_style and I guess to add support in ERP5JS, we only need to apply similar changes to the login page, ie adding block like:

                <tal:block tal:condition="enable_google_login">
                  <div class="field">
                    <label>&nbsp;</label>
                    <div class="input">
                      <a tal:attributes="href string:${here/portal_url}/ERP5Site_redirectToGoogleLoginPage"
                         i18n:translate="" i18n:domain="ui" class="zocial google">Login with Google</a>
                    </div>
                  </div>
                </tal:block>

    and probably nothing more, after ERP5Site_redirectToGoogleLoginPage I guess it's same. Maybe just changing a bit where we redirect, but at least there should be no difference in the approach here.

    @gabriel (or me) will work on extracting the changes to ERP5JS login page and submit a merge request to ERP5 master.

  • Thank you for the feedback.

    @jerome, If we can wait, I can start work on it next week.

  • In our use cases, we have multiple renderjs web sites but we only want to enable google login on one of them. Should "enable google login" be a web site layout property ?

  • mmh thinking more about this, maybe it's fine to always show google login like we do on back office.

    PS: logout does not seem to work with google login in renderjs, it's a different cookie that does not seem to be cleared.

Please register or sign in to reply
</div>
</div>
</tal:block>
<tal:block tal:condition="python: 'facebook' in available_oauth_login_list">
<div class="field">
<label>&nbsp;</label>
<div class="input">
<a tal:attributes="href string:${here/portal_url}/ERP5Site_redirectToFacebookLoginPage"
i18n:translate="" i18n:domain="ui" class="zocial facebook">Login with Facebook</a>
</div>
</div>
</tal:block>
</tal:block>
<input type="hidden" name="url" tal:attributes="value python: context.absolute_url()" />
<input tal:condition="exists: request/came_from"
type="hidden" name="came_from"
tal:attributes="value request/came_from" />
</form>
</section>
</article>
</div>
</body>
</html>
\ No newline at end of file
......@@ -2,25 +2,25 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>Google_getUserId</string> </value>
<key> <string>__name__</string> </key>
<value> <string>zocial.min.css</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>ViFiBWeb</string> </value>
<key> <string>content_type</string> </key>
<value> <string>text/css</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Google_getUserId</string> </value>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
<value> <string>zocial.min.css</string> </value>
</item>
</dictionary>
</pickle>
......
##############################################################################
#
# Copyright (c) 2002-2010 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 adviced 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.
#
##############################################################################
import facebook
from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE
import httplib
import urllib
import urlparse
import json
import apiclient.discovery
import httplib2
import oauth2client.client
import socket
# common methods
def _getCacheFactory(self, cache_factory_name):
portal = self.getPortalObject()
cache_tool = portal.portal_caches
cache_factory = cache_tool.getRamCacheRoot().get(cache_factory_name)
#XXX This conditional statement should be remove as soon as
#Broadcasting will be enable among all zeo clients.
#Interaction which update portal_caches should interact with all nodes.
if cache_factory is None \
and getattr(cache_tool, cache_factory_name, None) is not None:
#ram_cache_root is not up to date for current node
cache_tool.updateCache()
return cache_tool.getRamCacheRoot().get(cache_factory_name)
def setServerToken(self, key, body, cache_factory_name):
cache_factory = _getCacheFactory(self, cache_factory_name)
cache_duration = cache_factory.cache_duration
for cache_plugin in cache_factory.getCachePluginList():
cache_plugin.set(key, DEFAULT_CACHE_SCOPE,
body, cache_duration=cache_duration)
def getServerToken(self, key, cache_factory_name):
cache_factory = _getCacheFactory(self, cache_factory_name)
for cache_plugin in cache_factory.getCachePluginList():
cache_entry = cache_plugin.get(key, DEFAULT_CACHE_SCOPE)
if cache_entry is not None:
return cache_entry.getValue()
raise KeyError('Key %r not found' % key)
# Facebook AS
def Facebook_setServerToken(self, key, body):
setServerToken(self, key, body, 'facebook_server_auth_token_cache_factory')
def Facebook_getServerToken(self, key):
return getServerToken(self, key, 'facebook_server_auth_token_cache_factory')
def Facebook_getAccessTokenFromCode(self, code, redirect_uri):
return facebook.GraphAPI(version="2.7").get_access_token_from_code(code=code,
redirect_uri=redirect_uri,
app_id=self.portal_preferences.getPreferredVifibFacebookApplicationId(),
app_secret=self.portal_preferences.getPreferredVifibFacebookApplicationSecret())
# Google AS
def Google_setServerToken(self, key, body):
setServerToken(self, key, body, 'google_server_auth_token_cache_factory')
def Google_getServerToken(self, key):
return getServerToken(self, key, 'google_server_auth_token_cache_factory')
def Google_getAccessTokenFromCode(self, code, redirect_uri):
connection_kw = {'host': 'accounts.google.com', 'timeout': 30}
connection = httplib.HTTPSConnection(**connection_kw)
data = {
'client_id': self.portal_preferences.getPreferredVifibGoogleApplicationId(),
'client_secret': self.portal_preferences.getPreferredVifibGoogleApplicationSecret(),
'grant_type': 'authorization_code',
'redirect_uri': redirect_uri,
'code': code
}
data = urllib.urlencode(data)
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "*/*"
}
connection.request('POST', '/o/oauth2/token', data, headers)
response = connection.getresponse()
if response.status != 200:
return None
try:
body = json.loads(response.read())
except Exception:
return None
try:
return body
except Exception:
return None
def Facebook_getUserId(access_token):
facebook_entry = facebook.GraphAPI(access_token).get_object("me")
return facebook_entry['id'].encode('utf-8')
def Google_getUserId(access_token):
timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(10)
http = oauth2client.client.AccessTokenCredentials(access_token, 'Vifib'
).authorize(httplib2.Http())
service = apiclient.discovery.build("oauth2", "v1", http=http)
google_entry = service.userinfo().get().execute()
except Exception:
google_entry = None
finally:
socket.setdefaulttimeout(timeout)
if google_entry is not None:
return google_entry['id'].encode('utf-8')
return None
def Facebook_checkUserExistence(self):
hash = self.REQUEST.get('__ac_facebook_hash')
try:
access_token_dict = Facebook_getServerToken(self, hash)
except KeyError:
return False
access_token = access_token_dict.get('access_token')
url = urlparse.urlsplit(self.portal_preferences.getPreferredVifibRestApiLoginCheck())
connection_kw = {'host': url.netloc, 'timeout': 30}
if url.scheme == 'http':
connection = httplib.HTTPConnection(**connection_kw)
else:
connection = httplib.HTTPSConnection(**connection_kw)
connection.request('GET', url.path, headers = {
'Authorization' : 'Facebook %s' % access_token,
'Accept': 'application/json'})
response = connection.getresponse()
# user exist if server gave some correct response without waiting for user
return response.status in (200, 204)
def Google_checkUserExistence(self):
hash = self.REQUEST.get('__ac_google_hash')
try:
access_token_dict = Google_getServerToken(self, hash)
except KeyError:
return False
access_token = access_token_dict.get('access_token')
url = urlparse.urlsplit(self.portal_preferences.getPreferredVifibRestApiLoginCheck())
connection_kw = {'host': url.netloc, 'timeout': 30}
if url.scheme == 'http':
connection = httplib.HTTPConnection(**connection_kw)
else:
connection = httplib.HTTPSConnection(**connection_kw)
connection.request('GET', url.path, headers = {
'Authorization' : 'Google %s' % access_token,
'Accept': 'application/json'})
response = connection.getresponse()
# user exist if server gave some correct response without waiting for user
return response.status in (200, 204)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Extension Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>ViFiBWeb</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>extension.erp5.ViFiBWeb</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Extension Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple>
<string>W:142, 2: Redefining built-in \'hash\' (redefined-builtin)</string>
<string>W:163, 2: Redefining built-in \'hash\' (redefined-builtin)</string>
</tuple>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5FacebookExtractionPlugin" module="Products.ERP5Security.ERP5ExternalOauth2ExtractionPlugin"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_facebook_extraction</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5GoogleExtractionPlugin" module="Products.ERP5Security.ERP5ExternalOauth2ExtractionPlugin"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_google_extraction</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VifibBrowserIDExtractionPlugin" module="Products.Vifib.VifibCookieHashExtractionPlugin"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_browser_id_authentication</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VifibFacebookServerExtractionPlugin" module="Products.Vifib.VifibCookieHashExtractionPlugin"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_facebook_authentication</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VifibGoogleServerExtractionPlugin" module="Products.Vifib.VifibCookieHashExtractionPlugin"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>slapos_google_authentication</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Cache Factory" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>