Commit fba8d7a8 authored by Rafael Monnerat's avatar Rafael Monnerat

erp5_oauth_facebook_login: Implement Facebook Login Support for OAuth

   Changes on ERP5Security: Define getFacebookUserEntry to reduce code duplication
   Add facebook support for login and logout (optional) on erp5_core, xhtml and credentials.
parent b21bca15
......@@ -5,7 +5,8 @@
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 [];
enable_facebook_login python: 'facebook' in available_oauth_login_list;
css_list python: (enable_google_login or enable_facebook_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(), )]">
<tal:block metal:use-macro="here/main_template/macros/master">
<tal:block metal:fill-slot="main">
......@@ -59,6 +60,15 @@
</div>
</div>
</tal:block>
<tal:block tal:condition="enable_facebook_login">
<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>
</fieldset>
<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>
......
<?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}/FacebookConnector_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -2,10 +2,10 @@ import facebook
from Products.ERP5Security.ERP5ExternalOauth2ExtractionPlugin import getFacebookUserEntry
def getAccessTokenFromCode(self, code, redirect_uri):
client_id, secret_key = self.ERP5Site_getFacebookClientIdAndSecretKey()
return facebook.GraphAPI(version="2.7").get_access_token_from_code(
code=code, redirect_uri=redirect_uri,
app_id=self.getClientId(),
app_secret=self.getSecretKey())
app_id=client_id, app_secret=secret_key)
def getUserEntry(token):
return getFacebookUserEntry(token)
\ No newline at end of file
return getFacebookUserEntry(token)
<allowed_content_type_list>
<portal_type id="OAuth Tool">
<item>Facebook Connector</item>
</portal_type>
<portal_type id="Person">
<item>Facebook Login</item>
</portal_type>
......
<property_sheet_list>
<portal_type id="Facebook Connector">
<item>OAuthClient</item>
</portal_type>
<portal_type id="Template Tool">
<item>TemplateToolERP5FacebookExtractionPluginConstraint</item>
</portal_type>
</property_sheet_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>id</string> </key>
<value> <string>Facebook Connector</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>type_class</string> </key>
<value> <string>XMLObject</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>Facebook Connector</type>
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain>
<type>Facebook Login</type>
<workflow>edit_workflow, validation_workflow</workflow>
......
<?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>TemplateToolERP5FacebookExtractionPluginConstraint</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="Script Constraint" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_identity_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_range_criterion</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>constraint_type/post_upgrade</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5FacebookExtractionPlugin_existence_constraint</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Script Constraint</string> </value>
</item>
<item>
<key> <string>script_id</string> </key>
<value> <string>TemplateTool_checkFacebookExtractionPluginExistenceConsistency</string> </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/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -36,11 +36,11 @@ elif code is not None:
{"reference": user_reference},
"facebook_server_auth_token_cache_factory")
method = getattr(context, "Base_createOAuth2User", None)
method = getattr(context, "ERP5Site_createFacebookUserToOAuth", None)
if method is not None:
pass #method("Facebook Login", user_reference, user_dict)
method(user_reference, user_dict)
return context.REQUEST.RESPONSE.redirect(
context.REQUEST.get("came_from") or portal.absolute_url())
came_from = context.REQUEST.get("came_from", portal.absolute_url() + "#")
return context.REQUEST.RESPONSE.redirect(came_from)
return handleError('')
......@@ -11,7 +11,7 @@ result_list = context.getPortalObject().portal_catalog(
assert result_list, "Facebook Connector not found"
if len(result_list) == 2:
raise ValueError("Impossible to select one Google Connector")
raise ValueError("Impossible to select one Facebook Connector")
facebook_connector = result_list[0]
return facebook_connector.getClientId(), facebook_connector.getSecretKey()
from ZTUtils import make_query
client_id, _ = context.ERP5Site_getFacebookClientIdAndSecretKey()
query = make_query({
# Call at he context of the appropriate web_service.
'client_id': context.getClientId(),
'client_id': client_id,
'redirect_uri': "{0}/ERP5Site_callbackFacebookLogin".format(came_from or context.absolute_url()),
'scope': 'email'
})
......
......@@ -30,16 +30,25 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from erp5.component.extension import FacebookLoginUtility
from Products.ERP5Type.tests.utils import createZODBPythonScript
CLIENT_ID = "a1b2c3"
SECRET_KEY = "3c2ba1"
ACCESS_TOKEN = "EAAF10h0gIiQZDZD"
CODE = "1235"
def getUserId(access_token):
return "1209832093821098102938120938129381"
return "1234567890123456"
def getAccessTokenFromCode(code, redirect_uri):
assert code == CODE, "Invalid code"
# This is an example of a Facebook response
return {}
return {u'access_token': u'EAAF10h0gIiQZDZD',
u'token_type': u'bearer',
u'expires_in': 5138578}
def getUserEntry(access_token):
return {}
return {'name': 'John Doe',
'reference': getUserId(None),
'email': "dummy@example.org"}
FacebookLoginUtility_getAccessTokenFromCode = FacebookLoginUtility.getAccessTokenFromCode
FacebookLoginUtility_getUserEntry = FacebookLoginUtility.getUserEntry
......@@ -97,11 +106,11 @@ class TestFacebookLogin(ERP5TypeTestCase):
self.logout()
self.portal.ERP5Site_redirectToFacebookLoginPage()
location = self.portal.REQUEST.RESPONSE.getHeader("Location")
self.assertIn("XXXX", location)
self.assertIn("response_type=code", location)
self.assertIn("https://www.facebook.com/v2.10/dialog/oauth?", location)
self.assertIn("scope=email&redirect_uri=", location)
self.assertIn("client_id=%s" % CLIENT_ID, location)
self.assertNotIn("secret_key=", location)
self.assertIn("ERP5Site_receiveFacebookCallback", location)
self.assertIn("ERP5Site_callbackFacebookLogin", location)
def test_create_user_in_ERP5Site_createFacebookUserToOAuth(self):
"""
......@@ -143,8 +152,7 @@ return reference, None
module = context.getPortalObject().getDefaultModule(portal_type='Credential Request')
credential_request = module.newContent(
portal_type="Credential Request",
first_name=user_dict["first_name"],
last_name=user_dict["last_name"],
first_name=user_dict["name"],
reference=user_reference,
default_email_text=user_dict["email"],
)
......@@ -153,12 +161,11 @@ context.portal_alarms.accept_submitted_credentials.activeSense()
return credential_request
""")
self.logout()
response = self.portal.ERP5Site_receiveFacebookCallback(code=CODE)
response = self.portal.ERP5Site_callbackFacebookLogin(code=CODE)
facebook_hash = self.portal.REQUEST.RESPONSE.cookies.get("__ac_facebook_hash")["value"]
self.assertEqual("b01533abb684a658dc71c81da4e67546", facebook_hash)
self.assertEqual("8cec04e21e927f1023f4f4980ec11a77", facebook_hash)
self.assertEqual(self.portal.absolute_url(), response)
cache_dict = self.portal.Base_getBearerToken(facebook_hash, "facebook_server_auth_token_cache_factory")
self.assertEqual(CLIENT_ID, cache_dict["client_id"])
self.assertEqual(ACCESS_TOKEN, cache_dict["access_token"])
self.assertEqual({'reference': getUserId(None)},
self.portal.Base_getBearerToken(ACCESS_TOKEN, "facebook_server_auth_token_cache_factory")
......@@ -173,7 +180,8 @@ return credential_request
self.login()
credential_request = self.portal.portal_catalog(portal_type="Credential Request",
reference=getUserId(None))[0].getObject()
credential_request.accept()
if credential_request.getValidationState() != "accepted":
credential_request.accept()
person = credential_request.getDestinationDecisionValue()
facebook_login = person.objectValues(portal_types="Facebook Login")[0]
self.assertEqual(getUserId(None), facebook_login.getReference())
......@@ -45,7 +45,9 @@
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
<tuple>
<string>W: 77, 4: Unused variable \'person_module\' (unused-variable)</string>
</tuple>
</value>
</item>
<item>
......
Facebook Connector | view
Facebook Login | view
\ No newline at end of file
OAuth Tool | Facebook Connector
Person | Facebook Login
\ No newline at end of file
Facebook Connector
Facebook Login
\ No newline at end of file
Facebook Connector | OAuthClient
Template Tool | TemplateToolERP5FacebookExtractionPluginConstraint
\ No newline at end of file
Facebook Connector | edit_workflow
Facebook Connector | validation_workflow
Facebook Login | edit_workflow
Facebook Login | validation_workflow
\ No newline at end of file
TemplateToolERP5FacebookExtractionPluginConstraint
\ No newline at end of file
erp5_full_text_myisam_catalog
\ No newline at end of file
erp5_full_text_myisam_catalog
erp5_credential
......@@ -15,4 +15,7 @@ REQUEST.RESPONSE.expireCookie('__ac', path='/')
if getattr(portal.portal_skins, "erp5_oauth_google_login", None):
REQUEST.RESPONSE.expireCookie('__ac_google_hash', path='/')
if getattr(portal.portal_skins, "erp5_oauth_facebook_login", None):
REQUEST.RESPONSE.expireCookie('__ac_facebook_hash', path='/')
return REQUEST.RESPONSE.redirect(REQUEST.URL1 + '/logged_out')
......@@ -5,4 +5,8 @@ portal_skin = context.getPortalObject().portal_skins
if getattr(portal_skin, "erp5_oauth_google_login", None) is not None:
oauth_login_list.append("google")
if getattr(portal_skin, "erp5_oauth_facebook_login", None) is not None:
oauth_login_list.append("facebook")
return oauth_login_list
......@@ -5,7 +5,8 @@
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 [];
enable_facebook_login python: 'facebook' in available_oauth_login_list;
css_list python: (enable_google_login or enable_facebook_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(), )]">
<tal:block metal:use-macro="here/main_template/macros/master">
<tal:block metal:fill-slot="main">
......@@ -58,6 +59,15 @@
</div>
</div>
</tal:block>
<tal:block tal:condition="enable_facebook_login">
<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>
</fieldset>
<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>
......
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