Commit 8eb191f7 authored by Jérome Perrin's avatar Jérome Perrin

Merge remote-tracking branch 'gabriel/support_google_login' into...

Merge remote-tracking branch 'gabriel/support_google_login' into master_calendar_wip_patches_extend_security
parents cb98a9a3 2bf29d0d
import hmac import hmac
from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE
CACHE_FACTORY_NAME = 'bearer_token_cache_factory'
def getHMAC(self, key, body): def getHMAC(self, key, body):
digest = hmac.new(key, body) digest = hmac.new(key, body)
return digest.hexdigest() return digest.hexdigest()
def _getCacheFactory(self): def _getCacheFactory(self, cache_factory_name):
portal = self.getPortalObject() portal = self.getPortalObject()
cache_tool = portal.portal_caches cache_tool = portal.portal_caches
cache_factory_name = 'bearer_token_cache_factory'
cache_factory = cache_tool.getRamCacheRoot().get(cache_factory_name) cache_factory = cache_tool.getRamCacheRoot().get(cache_factory_name)
#XXX This conditional statement should be remove as soon as #XXX This conditional statement should be remove as soon as
#Broadcasting will be enable among all zeo clients. #Broadcasting will be enable among all zeo clients.
...@@ -19,15 +20,15 @@ def _getCacheFactory(self): ...@@ -19,15 +20,15 @@ def _getCacheFactory(self):
cache_tool.updateCache() cache_tool.updateCache()
return cache_tool.getRamCacheRoot().get(cache_factory_name) return cache_tool.getRamCacheRoot().get(cache_factory_name)
def setBearerToken(self, key, body): def setBearerToken(self, key, body, cache_factory_name=CACHE_FACTORY_NAME):
cache_factory = _getCacheFactory(self) cache_factory = _getCacheFactory(self, cache_factory_name)
cache_duration = cache_factory.cache_duration cache_duration = cache_factory.cache_duration
for cache_plugin in cache_factory.getCachePluginList(): for cache_plugin in cache_factory.getCachePluginList():
cache_plugin.set(key, DEFAULT_CACHE_SCOPE, cache_plugin.set(key, DEFAULT_CACHE_SCOPE,
body, cache_duration=cache_duration) body, cache_duration=cache_duration)
def getBearerToken(self, key): def getBearerToken(self, key, cache_factory_name=CACHE_FACTORY_NAME):
cache_factory = _getCacheFactory(self) cache_factory = _getCacheFactory(self, cache_factory_name)
for cache_plugin in cache_factory.getCachePluginList(): for cache_plugin in cache_factory.getCachePluginList():
cache_entry = cache_plugin.get(key, DEFAULT_CACHE_SCOPE) cache_entry = cache_plugin.get(key, DEFAULT_CACHE_SCOPE)
if cache_entry is not None: if cache_entry is not None:
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
xmlns:i18n="http://xml.zope.org/namespaces/i18n"> xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<tal:block tal:define="form_action string:logged_in; <tal:block tal:define="form_action string:logged_in;
global form_id string:login_form; global form_id string:login_form;
available_oauth_login_list python: context.getPortalObject().ERP5Site_getAvailableOAuthLoginList();
enable_google_login python: 'google' in available_oauth_login_list;
css_list python: enable_google_login and ['%s/zocial.min.css' % here.portal_url()] or [];
js_list python: ['%s/login_form.js' % (here.portal_url(), ), '%s/erp5.js' % (here.portal_url(), )]"> js_list python: ['%s/login_form.js' % (here.portal_url(), ), '%s/erp5.js' % (here.portal_url(), )]">
<tal:block metal:use-macro="here/main_template/macros/master"> <tal:block metal:use-macro="here/main_template/macros/master">
<tal:block metal:fill-slot="main"> <tal:block metal:fill-slot="main">
...@@ -45,7 +48,17 @@ ...@@ -45,7 +48,17 @@
<a tal:attributes="href string:${here/portal_url}/ERP5Site_viewCredentialRecoveryLoginDialog" <a tal:attributes="href string:${here/portal_url}/ERP5Site_viewCredentialRecoveryLoginDialog"
i18n:translate="" i18n:domain="ui">Can't access your account ?</a> i18n:translate="" i18n:domain="ui">Can't access your account ?</a>
</div> </div>
<p class="clear"></p>
</div> </div>
<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>
</fieldset> </fieldset>
<script type="text/javascript">setFocus()</script> <script type="text/javascript">setFocus()</script>
<p i18n:translate="" i18n:domain="ui">Having trouble logging in? Make sure to enable cookies in your web browser.</p> <p i18n:translate="" i18n:domain="ui">Having trouble logging in? Make sure to enable cookies in your web browser.</p>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Login_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>add_google_login</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Set own password</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>0.5</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Add Google Login</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/ERP5Site_redirectToGoogleLoginPage</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: context.getPortalObject().portal_membership.getAuthenticatedMember().getUserValue() is not None</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>google_preference_view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>20.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Google OAuth</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/SystemPreference_viewGoogleOAuthPreference</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import httplib
import urllib
import json
import httplib2
import apiclient.discovery
import oauth2client.client
import socket
from zLOG import LOG, ERROR
def getAccessTokenFromCode(self, code, redirect_uri):
connection_kw = {'host': 'accounts.google.com', 'timeout': 30}
connection = httplib.HTTPSConnection(**connection_kw)
data = {
'client_id': self.portal_preferences.getPreferredGoogleClientId(),
'client_secret': self.portal_preferences.getPreferredGoogleSecretKey(),
'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()
status = response.status
if status != 200:
return status, None
try:
body = json.loads(response.read())
except Exception, error_str:
return status, {"error": error_str}
try:
return status, body
except Exception:
return status, None
def getUserId(access_token):
timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(10)
http = oauth2client.client.AccessTokenCredentials(access_token, 'ERP5'
).authorize(httplib2.Http())
service = apiclient.discovery.build("oauth2", "v1", http=http)
google_entry = service.userinfo().get().execute()
except Exception, error_str:
google_entry = None
LOG("GoogleLoginUtility", ERROR, error_str)
finally:
socket.setdefaulttimeout(timeout)
if google_entry is not None:
return google_entry['id'].encode('utf-8')
return None
\ No newline at end of file
<?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>GoogleLoginUtility</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>extension.erp5.GoogleLoginUtility</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/>
</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="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>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>cache_duration</string> </key>
<value> <int>3600</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>google_server_auth_token_cache_factory</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Cache Factory</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>google_server_auth_token_cache_factory</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Ram Cache" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>volatile_cache_plugin</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Ram Cache</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<allowed_content_type_list>
<portal_type id="Person">
<item>Google Login</item>
</portal_type>
</allowed_content_type_list>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Base Type" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>content_icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<tuple>
<string>login</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Google Login</string> </value>
</item>
<item>
<key> <string>init_script</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>permission</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Base Type</string> </value>
</item>
<item>
<key> <string>searchable_text_property_id</string> </key>
<value>
<tuple>
<string>reference</string>
</tuple>
</value>
</item>
<item>
<key> <string>type_class</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<workflow_chain>
<chain>
<type>Google Login</type>
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
</workflow_chain>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" 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>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>GoogleOAuthPreference</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_google_client_id_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/string</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>preferred_google_secret_key_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>preference</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Folder" module="OFS.Folder"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>erp5_oauth_google_login</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from DateTime import DateTime
if context.REQUEST.get('Base_createOauth2User') is not None:
return
context.REQUEST.set('Base_createOauth2User', 1)
portal = context.getPortalObject()
if portal.portal_activities.countMessageWithTag(tag) > 0:
# If activity already exists, it means that the user reloaded the page and
# searchUsers() from ERP5ExternalOauth2ExtractionPlugin:186 did not find the user yet
return
person = portal.Base_getUserValueByUserId(reference)
current_user = portal.portal_membership.getAuthenticatedMember()
if person is not None or (current_user and current_user is None):
# Script should here stop if person exists or the user logged in is a Zope user
return
activate_kw = {'tag': tag}
# In future we can move this script to another, because this script is generic enough
# to support Facebook login, for example.
assert login_portal_type in ("Google Login",), "Impossible to select a portal type"
if user_id in ("Anonymous User", None):
person = portal.person_module.newContent(portal_type='Person',
user_id=reference,
first_name=first_name,
last_name=last_name,
default_email_coordinate_text=email,
activate_kw=activate_kw)
duration = getattr(portal.portal_preferences,
"getPreferredCredentialAssignmentDuration",
lambda: 0)() or 365
today = DateTime()
delay = today + duration
# Support erp5_credential
getAssignmentCategoryList = getattr(portal.portal_preferences,
"getPreferredSubscriptionAssignmentCategoryList",
None)
category_list = getAssignmentCategoryList and getAssignmentCategoryList() or []
assignment = person.newContent(
portal_type='Assignment',
category_list=category_list,
start_date=today,
stop_date=delay,
activate_kw=activate_kw)
assignment.open(activate_kw=activate_kw)
person.setDefaultCareerRoleList(assignment.getRoleList())
else:
person = context.Base_getUserValueByUserId(user_id)
login = person.newContent(portal_type=login_portal_type,
reference=reference)
login.validate(activate_kw=activate_kw)
if person.getValidationState() != "validated":
person.validate(activate_kw=activate_kw)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<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>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>tag, first_name, last_name, reference, email, login_portal_type, user_id=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_createOauth2User</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>getAccessTokenFromCode</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>GoogleLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getAccessTokenFromCode</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="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>getUserId</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>GoogleLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getGoogleUserId</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
def handleError(error):
context.Base_redirect(
'login_form',
keep_items={"portal_status_message":
context.Base_translateString(
"There was problem with Google login: ${error}. Please try again later.",
mapping={"error": error})
})
if error is not None:
return handleError(error)
elif code is not None:
portal = context.getPortalObject()
status, response_dict = context.ERP5Site_getAccessTokenFromCode(
code,
"{0}/ERP5Site_receiveGoogleCallback".format(portal.absolute_url()))
if status != 200 and response_dict is not None:
return handleError(
" ".join(["%s : %s" % (k,v) for k,v in response_dict.iteritems()]))
if response_dict is not None:
access_token = response_dict['access_token'].encode('utf-8')
response_dict['login'] = context.ERP5Site_getGoogleUserId(access_token)
response_dict['user_id'] = portal.portal_membership.getAuthenticatedMember().getUserId()
hash_str = context.Base_getHMAC(access_token, access_token)
context.REQUEST.RESPONSE.setCookie('__ac_google_hash', hash_str, path='/')
context.Base_setBearerToken(hash_str,
response_dict,
"google_server_auth_token_cache_factory")
return context.REQUEST.RESPONSE.redirect(
context.REQUEST.get("came_from") or portal.absolute_url())
return handleError('')
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<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>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>code=None, error=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_receiveGoogleCallback</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from ZTUtils import make_query
portal = context.getPortalObject()
query = make_query({
'response_type': 'code',
'client_id': portal.portal_preferences.getPreferredGoogleClientId(),
'redirect_uri': "{0}/ERP5Site_receiveGoogleCallback".format(portal.absolute_url()),
'scope': 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'
})
context.REQUEST.RESPONSE.redirect("https://accounts.google.com/o/oauth2/auth?" + query)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<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>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_redirectToGoogleLoginPage</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_preferred_google_client_id</string>
<string>my_preferred_google_secret_key</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SystemPreference_viewGoogleOAuthPreference</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Preference_viewGoogleOAuthPreference</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Google OAuth</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_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="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_google_client_id</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Client ID</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_preferred_google_secret_key</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Secret Key</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
##############################################################################
#
# Copyright (c) 2002-2016 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 json
import uuid
import httplib
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from erp5.component.extension import GoogleLoginUtility
from Products.ERP5.Document.Person import UserExistsError
CLIENT_ID = "a1b2c3"
SECRET_KEY = "3c2ba1"
ACCESS_TOKEN = "T1234"
CODE = "1234"
class MockHTTPSConnectionResponse(object):
def __init__(self):
self.status = 200
def read(self):
return json.dumps({"access_token": ACCESS_TOKEN})
class MockHTTPSConnection:
def __init__(self, host, timeout):
assert host == 'accounts.google.com'
assert timeout == 30
def request(self, method, url, body, headers):
assert method == "POST"
assert url == '/o/oauth2/token'
assert "client_id=%s" % CLIENT_ID in body, "CLIENT_ID not found %s" % body
assert "client_secret=%s" % SECRET_KEY in body, "SECRET_KEY not found %s" % body
assert "code=%s" % CODE in body, "CODE not found %s" % body
def getresponse(self):
return MockHTTPSConnectionResponse()
def getUserId(access_token):
return "1234"
httplib.HTTPSConnection = MockHTTPSConnection
GoogleLoginUtility.getUserId = getUserId
class TestGoogleLogin(ERP5TypeTestCase):
def getTitle(self):
return "Test Google Login"
def afterSetUp(self):
"""
This is ran before anything, used to set the environment
"""
self.dummy_user_id = "dummy"
person_module = self.portal.person_module
if getattr(person_module, self.dummy_user_id, None) is None:
person = person_module.newContent(first_name="Dummy",
id=self.dummy_user_id,
reference=self.dummy_user_id,
user_id=self.dummy_user_id
)
assignment = person.newContent(portal_type="Assignment")
assignment.open()
login = person.newContent(portal_type="ERP5 Login", reference=self.dummy_user_id)
login.validate()
person.validate()
self.tic()
for obj in self.portal.portal_catalog(portal_type=["Google Login", "Person"],
reference=getUserId(None),
validation_state="validated"):
obj.getObject().invalidate()
uuid_str = uuid.uuid4().hex
obj.setReference(uuid_str)
obj.setUserId(uuid_str)
system_preference = self.portal.portal_preferences.getActiveSystemPreference()
if system_preference is None:
system_preference = self.portal.portal_preferences.newContent(
title="Global System Preference",
portal_type="System Preference")
system_preference.enable()
system_preference.edit(
preferred_google_client_id=CLIENT_ID,
preferred_google_secret_key=SECRET_KEY,
)
self.tic()
def test_redirect(self):
"""
Check URL generate to redirect to Google
"""
self.logout()
self.portal.ERP5Site_redirectToGoogleLoginPage()
location = self.portal.REQUEST.RESPONSE.getHeader("Location")
self.assertTrue(location.startswith("https://accounts.google.com/o/oauth2/auth"), location)
self.assertIn("response_type=code", location)
self.assertIn("client_id=%s" % CLIENT_ID, location)
self.assertNotIn("secret_key=", location)
self.assertIn("/ERP5Site_receiveGoogleCallback", location)
def test_receive_google_callback(self):
"""
Check if ERP5 set cookie properly after receive code from external service
"""
self.logout()
response = self.portal.ERP5Site_receiveGoogleCallback(code=CODE)
self.assertEqual(self.portal.absolute_url(), response)
def create_user_that_already_exists(self):
self.portal.person_module.newContent(portal_type="Person", user_id=CODE)
def test_create_google_login_under_pre_existing_person(self):
user_id = getUserId(None)
user_entry = {"tag": '123_user_creation_in_progress',
"first_name": "User",
"last_name": "Last Name",
"reference": user_id,
"email": 'example@email.com',
"login_portal_type": "Google Login",
"user_id": self.dummy_user_id
}
# We are using superuser to avoid Unauthorized error
# The goal of this test to check if Google Login is created
# in the right place
self.login()
self.portal.Base_createOauth2User(**user_entry)
self.tic()
dummy_user = getattr(self.portal.person_module, self.dummy_user_id)
google_login, = [g for g in dummy_user.objectValues(
portal_type="Google Login") if g.getReference() == user_id]
self.assertNotEqual(None, google_login)
self.assertEqual("validated", google_login.getValidationState())
def test_create_user_with_google_id(self):
user_id = getUserId(None)
user_entry = {"tag": '123_user_creation_in_progress',
"first_name": "User",
"last_name": "Last Name",
"reference": user_id,
"email": 'example@email.com',
"login_portal_type": "Google Login",
"user_id": 'Anonymous User'
}
self.portal.Base_createOauth2User(**user_entry)
self.tic()
google_login = self.portal.portal_catalog(portal_type="Google Login",
reference=user_id,
validation_state="validated")
self.assertNotEqual(None, google_login)
self.login(user_id)
person = self.portal.Base_getUserValueByUserId(user_id)
self.assertEqual(user_id, person.getReference())
self.assertEqual(user_entry["first_name"], person.getFirstName())
self.assertEqual(user_entry["last_name"], person.getLastName())
self.login()
self.assertRaises(UserExistsError, self.create_user_that_already_exists)
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test 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>testGoogleLogin</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testGoogleLogin</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test 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/>
</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>
erp5_bearer_token
\ No newline at end of file
Google Login | view
Preference | add_google_login
System Preference | google_preference_view
\ No newline at end of file
extension.erp5.GoogleLoginUtility
\ No newline at end of file
portal_caches/google_server_auth_token_cache_factory
portal_caches/google_server_auth_token_cache_factory/**
\ No newline at end of file
Google Login | edit_workflow
Google Login | validation_workflow
\ No newline at end of file
GoogleOAuthPreference
\ No newline at end of file
erp5_oauth_google_login
\ No newline at end of file
test.erp5.testGoogleLogin
\ No newline at end of file
erp5_full_text_myisam_catalog
\ No newline at end of file
erp5_oauth_google_login
\ No newline at end of file
...@@ -11,4 +11,8 @@ REQUEST = portal.REQUEST ...@@ -11,4 +11,8 @@ REQUEST = portal.REQUEST
if REQUEST.has_key('portal_skin'): if REQUEST.has_key('portal_skin'):
portal.portal_skins.clearSkinCookie() portal.portal_skins.clearSkinCookie()
REQUEST.RESPONSE.expireCookie('__ac', path='/') REQUEST.RESPONSE.expireCookie('__ac', path='/')
if getattr(portal.portal_skins, "erp5_oauth_google_login", None):
REQUEST.RESPONSE.expireCookie('__ac_google_hash', path='/')
return REQUEST.RESPONSE.redirect(REQUEST.URL1 + '/logged_out') return REQUEST.RESPONSE.redirect(REQUEST.URL1 + '/logged_out')
oauth_login_list = []
portal_skin = context.getPortalObject().portal_skins
if getattr(portal_skin, "erp5_oauth_google_login", None) is not None:
oauth_login_list.append("google")
return oauth_login_list
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<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>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getAvailableOAuthLoginList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -1103,3 +1103,7 @@ div.pdf-preview-navigation img.last{ ...@@ -1103,3 +1103,7 @@ div.pdf-preview-navigation img.last{
fieldset > div.large-gadget { fieldset > div.large-gadget {
height: 85vh; height: 85vh;
} }
a.zocial {
margin-top: 10px;
}
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
xmlns:i18n="http://xml.zope.org/namespaces/i18n"> xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<tal:block tal:define="form_action string:logged_in; <tal:block tal:define="form_action string:logged_in;
global form_id string:login_form; global form_id string:login_form;
available_oauth_login_list python: context.getPortalObject().ERP5Site_getAvailableOAuthLoginList();
enable_google_login python: 'google' in available_oauth_login_list;
css_list python: enable_google_login and ['%s/zocial.min.css' % here.portal_url()] or [];
js_list python: ['%s/login_form.js' % (here.portal_url(), ), '%s/erp5.js' % (here.portal_url(), )]"> js_list python: ['%s/login_form.js' % (here.portal_url(), ), '%s/erp5.js' % (here.portal_url(), )]">
<tal:block metal:use-macro="here/main_template/macros/master"> <tal:block metal:use-macro="here/main_template/macros/master">
<tal:block metal:fill-slot="main"> <tal:block metal:fill-slot="main">
...@@ -46,6 +49,15 @@ ...@@ -46,6 +49,15 @@
i18n:translate="" i18n:domain="ui">I forgot my password!</a> i18n:translate="" i18n:domain="ui">I forgot my password!</a>
</div> </div>
</div> </div>
<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>
</fieldset> </fieldset>
<script type="text/javascript">setFocus()</script> <script type="text/javascript">setFocus()</script>
<p i18n:translate="" i18n:domain="ui">Having trouble logging in? Make sure to enable cookies in your web browser.</p> <p i18n:translate="" i18n:domain="ui">Having trouble logging in? Make sure to enable cookies in your web browser.</p>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>zocial.min.css</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/css</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>zocial.min.css</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -91,7 +91,7 @@ def addERP5GoogleExtractionPlugin(dispatcher, id, title=None, REQUEST=None): ...@@ -91,7 +91,7 @@ def addERP5GoogleExtractionPlugin(dispatcher, id, title=None, REQUEST=None):
class ERP5ExternalOauth2ExtractionPlugin: class ERP5ExternalOauth2ExtractionPlugin:
cache_factory_name = 'extrenal_oauth2_token_cache_factory' cache_factory_name = 'external_oauth2_token_cache_factory'
security = ClassSecurityInfo() security = ClassSecurityInfo()
def __init__(self, id, title=None): def __init__(self, id, title=None):
...@@ -146,15 +146,18 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -146,15 +146,18 @@ class ERP5ExternalOauth2ExtractionPlugin:
'No Base_createOauth2User script available, install ' 'No Base_createOauth2User script available, install '
'erp5_credential_oauth2, disabled authentication.') 'erp5_credential_oauth2, disabled authentication.')
return DumbHTTPExtractor().extractCredentials(request) return DumbHTTPExtractor().extractCredentials(request)
creds, user_dict = {"login_portal_type": self.login_portal_type}, None
cookie_hash = request.get(self.cookie_name)
if cookie_hash is not None:
try:
user_dict = self.getToken(cookie_hash)
except KeyError:
LOG(self.getId(), INFO, 'Hash %s not found' % cookie_hash)
return DumbHTTPExtractor().extractCredentials(request)
creds = {}
token = None token = None
if request._auth is not None: if "access_token" in user_dict:
# 1st - try to fetch from Authorization header token = user_dict["access_token"]
if self.header_string.lower() in request._auth.lower():
l = request._auth.split()
if len(l) == 2:
token = l[1]
if token is None: if token is None:
# no token # no token
...@@ -168,28 +171,29 @@ class ERP5ExternalOauth2ExtractionPlugin: ...@@ -168,28 +171,29 @@ class ERP5ExternalOauth2ExtractionPlugin:
except KeyError: except KeyError:
user_entry = self.getUserEntry(token) user_entry = self.getUserEntry(token)
if user_entry is not None: if user_entry is not None:
user = user_entry['reference'] user = user_entry["reference"] = user_dict["login"]
if user is None: if user is None:
# fallback to default way # fallback to default way
return DumbHTTPExtractor().extractCredentials(request) return DumbHTTPExtractor().extractCredentials(request)
tag = '%s_user_creation_in_progress' % user.encode('hex') tag = '%s_user_creation_in_progress' % cookie_hash.encode('hex')
if self.getPortalObject().portal_activities.countMessageWithTag(tag) > 0: if self.getPortalObject().portal_activities.countMessageWithTag(tag) > 0:
self.REQUEST['USER_CREATION_IN_PROGRESS'] = user self.REQUEST['USER_CREATION_IN_PROGRESS'] = user_dict
else: else:
# create the user if not found # create the user if not found
if not self.searchUsers(id=user, exact_match=True): if not self.searchUsers(login=user, exact_match=True):
sm = getSecurityManager() sm = getSecurityManager()
if sm.getUser().getId() != ERP5Security.SUPER_USER: if sm.getUser().getId() != ERP5Security.SUPER_USER:
newSecurityManager(self, self.getUser(ERP5Security.SUPER_USER)) newSecurityManager(self, self.getUser(ERP5Security.SUPER_USER))
try: try:
self.REQUEST['USER_CREATION_IN_PROGRESS'] = user self.REQUEST['USER_CREATION_IN_PROGRESS'] = user_dict
if user_entry is None: user_entry["login_portal_type"] = creds["login_portal_type"]
user_entry = self.getUserEntry(token) # user_id is optional.
# It is only used to create Google Login under a pre-existing person
user_entry["user_id"] = user_dict.get("user_id")
try: try:
self.Base_createOauth2User(tag, **user_entry) Base_createOauth2User(tag, **user_entry)
except Exception: except Exception:
LOG('ERP5ExternalOauth2ExtractionPlugin', ERROR, LOG('ERP5ExternalOauth2ExtractionPlugin', ERROR,
'Issue while calling creation script:', error=True) 'Issue while calling creation script:', error=True)
...@@ -240,8 +244,7 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi ...@@ -240,8 +244,7 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi
try: try:
for k in ('first_name', 'last_name', 'id', 'email'): for k in ('first_name', 'last_name', 'id', 'email'):
if k == 'id': if k == 'id':
user_entry['reference'] = self.prefix + facebook_entry[k].encode( user_entry['reference'] = facebook_entry[k].encode('utf-8')
'utf-8')
else: else:
user_entry[k] = facebook_entry[k].encode('utf-8') user_entry[k] = facebook_entry[k].encode('utf-8')
except KeyError: except KeyError:
...@@ -256,6 +259,9 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin) ...@@ -256,6 +259,9 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin)
meta_type = "ERP5 Google Extraction Plugin" meta_type = "ERP5 Google Extraction Plugin"
prefix = 'go_' prefix = 'go_'
header_string = 'google' header_string = 'google'
login_portal_type = "Google Login"
cookie_name = "__ac_google_hash"
cache_factory_name = "google_server_auth_token_cache_factory"
def getUserEntry(self, token): def getUserEntry(self, token):
if httplib2 is None: if httplib2 is None:
...@@ -267,7 +273,8 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin) ...@@ -267,7 +273,8 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin)
try: try:
# require really fast interaction # require really fast interaction
socket.setdefaulttimeout(5) socket.setdefaulttimeout(5)
http = oauth2client.client.AccessTokenCredentials(token, 'ERP5 Client' http = oauth2client.client.AccessTokenCredentials(token,
'ERP5 Client'
).authorize(httplib2.Http()) ).authorize(httplib2.Http())
service = apiclient.discovery.build("oauth2", "v1", http=http) service = apiclient.discovery.build("oauth2", "v1", http=http)
google_entry = service.userinfo().get().execute() google_entry = service.userinfo().get().execute()
...@@ -282,11 +289,8 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin) ...@@ -282,11 +289,8 @@ class ERP5GoogleExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin)
try: try:
for k in (('first_name', 'given_name'), for k in (('first_name', 'given_name'),
('last_name', 'family_name'), ('last_name', 'family_name'),
('reference', 'id'),
('email', 'email')): ('email', 'email')):
value = google_entry[k[1]].encode('utf-8') value = google_entry[k[1]].encode('utf-8')
if k[0] == 'reference':
value = self.prefix + value
user_entry[k[0]] = value user_entry[k[0]] = value
except KeyError: except KeyError:
user_entry = None user_entry = None
......
...@@ -267,7 +267,7 @@ class ERP5LoginUserManager(BasePlugin): ...@@ -267,7 +267,7 @@ class ERP5LoginUserManager(BasePlugin):
# users so code checking if a user login exists before allowing it to be # users so code checking if a user login exists before allowing it to be
# reused, preventing misleading logins from being misused. # reused, preventing misleading logins from being misused.
result.append({ result.append({
'id': None, 'id': special_user_name,
'login': special_user_name, 'login': special_user_name,
'pluginid': plugin_id, 'pluginid': plugin_id,
......
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