Commit 9c4d0edd authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

Introduce OpenId Connect

erp5_openid_connect_client_login: Add dedicated cache factory to work with open id connect

erp5_openid_connect_client_login: Add Scope, Client Metadata and state

* Add configurable scope to connector
* Client Metadata is a JSON defined on connector
* Add state parameter to make redirect non repeatable

erp5_openid_connect_client_login: Update OpenId Connect to be be fully functionnal

erp5_openid_connect_client_login: Add tests and some minor fixup

erp5_openid_connect_client_login: Don't call create user on each call to callback

erp5_openid_connect_client_login: Test create connector in portal web services

erp5_openid_connect_client_login: Fix test on open Id connector creation in afterSetUp

OpenIDConnect: Add Extraction Plugin

openidconnect: Have functionnal Extractor

erp5_core: Add Case of OpenId Connect Client

erp5_xhtml_style: Add OpenId Connect to Login Form

erp5_web: Add OpenId Connect Logout

erp5_web_renderjs_ui: Add OpenId Connect to Login Form and Logout

erp5_credential: Add OpenId Connect to login form
parent 9fbfeaac
......@@ -6,6 +6,7 @@
available_oauth_login_list python: context.getPortalObject().ERP5Site_getAvailableOAuthLoginList();
enable_google_login python: 'google' in available_oauth_login_list;
enable_facebook_login python: 'facebook' in available_oauth_login_list;
enable_openidconnect_login python: 'openidconnect' 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">
......@@ -71,6 +72,15 @@
</div>
</div>
</tal:block>
<tal:block tal:condition="enable_openidconnect_login">
<div class="field">
<label>&nbsp;</label>
<div class="input">
<a tal:attributes="href string:${here/portal_url}/ERP5Site_redirectToOpenIdLoginPage"
i18n:translate="" i18n:domain="ui" class="zocial openid">Login with OpenId Connect</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}/OpenIdConnectConnector_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_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}/ExternalLogin_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# -*- coding:utf-8 -*-
##############################################################################
#
# Copyright (C) 2021 Nexedi SA and Contributors.
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
#
##############################################################################
import json
from oic import rndstr
from oic.oauth2.grant import Token
from oic.oic import Client
from oic.oic.message import AuthorizationResponse
from oic.oic.message import RegistrationResponse
from zExceptions import Unauthorized
openid_connect_cache_factory = "openid_connect_server_auth_token_cache_factory"
def _getOpenOpenIdConnector(portal, reference="default"):
"""Returns google client id and secret key.
Internal function.
"""
result_list = portal.portal_catalog.unrestrictedSearchResults(
portal_type="OpenId Connect Connector",
reference=reference,
validation_state="validated",
limit=2,
)
assert result_list, "OpenId Connector not found"
if len(result_list) == 2:
raise ValueError("Impossible to select one OpenId Connector Please contact support")
openid_connector = result_list[0].getObject()
return openid_connector
def unrestrictedSearchOpenIdConnectLogin(self, login, REQUEST=None):
if REQUEST is not None:
raise Unauthorized
return self.getPortalObject().portal_catalog.unrestrictedSearchResults(
portal_type="OpenId Connect Login",
reference=login,
validation_state="validated", limit=1)
def _getOpenOpenIdClientIdAndSecretKey(portal, reference="default"):
"""Returns client id and secret key.
Internal function.
"""
openid_connector = _getOpenOpenIdConnector(portal, reference)
return openid_connector.getUserId(), openid_connector.getPassword()
def _prepareAndReturnClient(portal, openid_connector, reference="default"):
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
client = Client(client_authn_method=CLIENT_AUTHN_METHOD)
issuer = openid_connector.getUrlString()
client.provider_config(issuer)
client_metadata = json.loads(openid_connector.getDescription())
client_metadata["client_id"] = openid_connector.getUserId()
client_metadata["client_secret"] = openid_connector.getPassword()
client_reg = RegistrationResponse(**client_metadata)
client.store_registration_info(client_reg)
return client
def redirectToOpenIdConnectLoginPage(self, reference="default"):
portal = self.getPortalObject()
openid_connector = _getOpenOpenIdConnector(portal, reference)
client = _prepareAndReturnClient(portal, openid_connector, reference)
session = {}
session["state"] = rndstr()
session["nonce"] = rndstr()
portal.Base_setBearerToken(session["state"], session, openid_connect_cache_factory)
args = {
"client_id": client.client_id,
"response_type": "code",
"scope": openid_connector.getScopeList(),
"nonce": session["nonce"],
"redirect_uri": client.registration_response["redirect_uris"][0],
"state": session["state"]
}
auth_req = client.construct_AuthorizationRequest(request_args=args)
login_url = auth_req.request(client.authorization_endpoint)
return self.REQUEST.RESPONSE.redirect(login_url)
def getAccessTokenFromCode(self, query_string, redirect_uri, reference="default"):
portal = self.getPortalObject()
openid_connector = _getOpenOpenIdConnector(portal, reference)
client = _prepareAndReturnClient(portal, openid_connector, reference)
aresp = client.parse_response(
AuthorizationResponse,
info=query_string,
sformat="urlencoded"
)
args = {
"redirect_uri": client.registration_response["redirect_uris"][0],
"grant_type": "authorization_code",
}
response = client.do_access_token_request(
state=aresp["state"],
request_args=args,
authn_method="client_secret_basic",
)
response = dict(response)
if 'id_token' in response:
assert response['id_token'].verify()
response.pop('id_token')
return response
def getAccessTokenFromRefreshToken(self, response_dict, reference="default"):
portal = self.getPortalObject()
openid_connector = _getOpenOpenIdConnector(portal, reference)
client = _prepareAndReturnClient(portal, openid_connector, reference)
args = {
"redirect_uri": client.registration_response["redirect_uris"][0],
}
token = Token(response_dict)
response = client.do_access_token_refresh(
token=token,
request_args=args,
authn_method="client_secret_basic",
)
if 'id_token' in response:
assert response['id_token'].verify()
return dict(response)
def getUserEntry(self, token="", reference="default"):
portal = self.getPortalObject()
openid_connector = _getOpenOpenIdConnector(portal, reference)
client = _prepareAndReturnClient(portal, openid_connector, reference)
result = client.do_user_info_request(token=token, method="GET")
return dict(result)
<?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>OpenIdConnectLoginUtility</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>extension.erp5.OpenIdConnectLoginUtility</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.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<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>
</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>
</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>86400</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>openid_connect_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>openid_connect_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="Distributed Ram Cache" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>specialise/portal_memcached/persistent_memcached_plugin</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>persistent_cache_plugin</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Distributed Ram Cache</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>persistent_cache</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<allowed_content_type_list>
<portal_type id="Person">
<item>OpenId Connect Login</item>
</portal_type>
<portal_type id="Web Service Tool">
<item>OpenId Connect Connector</item>
</portal_type>
</allowed_content_type_list>
\ No newline at end of file
<property_sheet_list>
<portal_type id="OpenId Connect Connector">
<item>Login</item>
<item>OpenIdConnectConnector</item>
<item>Reference</item>
<item>Url</item>
</portal_type>
<portal_type id="Template Tool">
<item>TemplateToolERP5OpenIdConnectExtractionPluginConstraint</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>OpenId Connect 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>
<?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>OpenId Connect 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>OpenId Connect Connector</type>
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain>
<type>OpenId Connect Login</type>
<workflow>edit_workflow, login_interaction_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>OpenIdConnectConnector</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/lines</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>scope_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: ()</string> </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_openid_connect_client</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>getAccessTokenFromRefreshToken</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>OpenIdConnectLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getAccessTokenFromRefreshToken</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>getAccessTokenFromCode</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>OpenIdConnectLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getOpenIdConnectAccessTokenFromCode</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>unrestrictedSearchOpenIdConnectLogin</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>OpenIdConnectLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getOpenIdConnectLogin</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>getUserEntry</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>OpenIdConnectLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getOpenIdUserEntry</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from zExceptions import Unauthorized
if REQUEST is not None:
raise Unauthorized
login = context.ERP5Site_getOpenIdConnectLogin(login)
if login is None or not len(login):
return None
if len(login) > 1:
raise ValueError("Duplicated User")
return login[0].getParentValue().getRelativeUrl()
<?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>login, REQUEST=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getPersonFromOpenIdLogin</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
import time
request = container.REQUEST
response = request.RESPONSE
cache_factory = "openid_connect_server_auth_token_cache_factory"
def handleError(error, error_description="", state=None):
if state:
context.Base_setBearerToken(state, None, cache_factory)
return context.getWebSiteValue().Base_redirect(
'login_form',
keep_items={"portal_status_message":
context.Base_translateString(
"There was problem with your login: ${error} ${error_description}. Please contact your administrator.",
mapping={"error": error, "error_description": error_description})
})
state = state or request.form["state"]
if not state:
raise ValueError("Missing state value")
session = context.Base_getBearerToken(state, cache_factory)
if not session:
raise ValueError("Unknown state Value")
error = error or request.form.get("error", None)
error_description = error_description or request.form.get("error_description", None)
code = code or request.form.get("code", None)
if error is not None:
return handleError(error, error_description, state)
elif code is not None:
response_dict = context.ERP5Site_getOpenIdConnectAccessTokenFromCode(
context.REQUEST.environ['QUERY_STRING'],
"{0}/hateoas/connection/oid_auth".format(context.absolute_url())
)
if response_dict is not None:
"""
Here is an example of correct response dict:
{
'access_token': u'XXXX0koYI5hASXZaExeYsGlqz1bSIcGyEg',
'token_type': u'Bearer',
'expires_in': 3599,
'refresh_token': u'XXXXXXQocR4zlxQUdrit5sZ1FutZcece9'
}
"""
if "error" in response_dict:
return handleError(response_dict.get('error'), response_dict.get('error_description'), state)
access_token = response_dict['access_token'].encode('utf-8')
hash_str = context.Base_getHMAC(access_token, access_token)
context.setAuthCookie(response, '__ac_openidconnect_hash', hash_str)
# store timestamp in second since the epoch in UTC is enough
response_dict["response_timestamp"] = time.time()
context.Base_setBearerToken(hash_str,
response_dict,
"openid_connect_server_auth_token_cache_factory")
user_dict = context.ERP5Site_getOpenIdUserEntry(token=access_token)
user_reference = user_dict["sub"].encode('utf-8')
context.Base_setBearerToken(access_token,
{"reference": user_reference},
"openid_connect_server_auth_token_cache_factory")
# XXX for ERP5JS web sites without a rewrite rule, we make sure there's a trailing /
web_site_value = context.getWebSiteValue() or context
person_relative_url = context.ERP5Site_getPersonFromOpenIdLogin(user_reference)
if not person_relative_url:
method = getattr(context, "ERP5Site_createOpenIdConnectUserToOAuth", None)
if method is not None:
method(user_reference, user_dict)
# XXX CLN Hackish redirect
return web_site_value.Base_redirect('hateoas/connection/login_form', keep_items={
"portal_status_message": "Your user is being created, please retry clicking on 'Login with OpenId Connect' in 1 minute."
})
came_from = web_site_value.absolute_url() + "/#!login?n.me=%s" % person_relative_url
response.setHeader('Location', came_from)
response.setStatus(303)
else:
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, error_description=None, state=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_receiveOpenIdCallback</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>redirectToOpenIdConnectLoginPage</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>OpenIdConnectLoginUtility</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_redirectToOpenIdLoginPage</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="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>action_title</string> </key>
<value> <string></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>
<string>my_scope_list</string>
<string>my_description</string>
</list>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_reference</string>
<string>my_url_string</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list>
<string>my_user_id</string>
<string>my_password</string>
<string>my_translated_validation_state_title</string>
</list>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>OpenIdConnectConnector_view</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>OpenIdConnectConnector_view</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>OpenId Connect Connector</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_description</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>
<item>
<key> <string>target</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>
<item>
<key> <string>target</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_text_area_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Client JSON Metadata</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>id</string> </key>
<value> <string>my_reference</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_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</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/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_translated_validation_state_title</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_translated_workflow_state_title</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</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_url_string</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>
<item>
<key> <string>target</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>
<item>
<key> <string>target</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>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Provider Metadata URL / issuer</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_user_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>
<item>
<key> <string>target</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>
<item>
<key> <string>target</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>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Client Id</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
acl_users = context.getPortalObject().acl_users
plugin_id = 'erp5_openid_connect_extraction'
error_list = []
if plugin_id not in acl_users.objectIds():
error_list.append(
'OpenId Connect Extraction Plugin does not exist as %s/%s' % (acl_users.getPath(), plugin_id))
if fixit:
acl_users.manage_addProduct['ERP5Security'].addERP5OpenIdConnectExtractionPlugin(plugin_id)
getattr(acl_users, plugin_id).manage_activateInterfaces([
'IExtractionPlugin',
])
return error_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>fixit=False</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>TemplateTool_checkOpenIdConnectExtractionPluginExistenceConsistency</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?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>testOpenIdConnectLogin</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testOpenIdConnectLogin</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>
<string>W:298, 4: Unreachable code (unreachable)</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.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<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>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
erp5_bearer_token
erp5_web_service
erp5_oauth
\ No newline at end of file
OpenId Connect Connector | view
OpenId Connect Login | view
\ No newline at end of file
extension.erp5.OpenIdConnectLoginUtility
\ No newline at end of file
portal_caches/openid_connect_server_auth_token_cache_factory
portal_caches/openid_connect_server_auth_token_cache_factory/**
\ No newline at end of file
Person | OpenId Connect Login
Web Service Tool | OpenId Connect Connector
\ No newline at end of file
OpenId Connect Connector
OpenId Connect Login
\ No newline at end of file
OpenId Connect Connector | Login
OpenId Connect Connector | OpenIdConnectConnector
OpenId Connect Connector | Reference
OpenId Connect Connector | Url
Template Tool | TemplateToolERP5OpenIdConnectExtractionPluginConstraint
\ No newline at end of file
OpenId Connect Connector | edit_workflow
OpenId Connect Connector | validation_workflow
OpenId Connect Login | edit_workflow
OpenId Connect Login | login_interaction_workflow
OpenId Connect Login | validation_workflow
\ No newline at end of file
OpenIdConnectConnector
\ No newline at end of file
erp5_openid_connect_client
\ No newline at end of file
test.erp5.testOpenIdConnectLogin
\ No newline at end of file
erp5_full_text_myisam_catalog
erp5_credential
erp5_web_renderjs_ui
\ No newline at end of file
erp5_openid_connect_client_login
\ No newline at end of file
......@@ -22,6 +22,9 @@ if getattr(portal.portal_skins, "erp5_oauth_google_login", None):
if getattr(portal.portal_skins, "erp5_oauth_facebook_login", None):
REQUEST.RESPONSE.expireCookie('__ac_facebook_hash', path='/')
if getattr(portal.portal_skins, "erp5_openid_connect_client", None):
REQUEST.RESPONSE.expireCookie('__ac_openidconnect_hash', path='/')
# PAS logout, if user is from a PAS user folder (which is the acquisition parent of the user)
getattr(
user,
......
......@@ -22,6 +22,9 @@ if getattr(portal.portal_skins, "erp5_oauth_google_login", None):
if getattr(portal.portal_skins, "erp5_oauth_facebook_login", None):
REQUEST.RESPONSE.expireCookie('__ac_facebook_hash', path='/')
if getattr(portal.portal_skins, "erp5_openid_connect_client", None):
REQUEST.RESPONSE.expireCookie('__ac_openidconnect_hash', path='/')
# PAS logout, if user is from a PAS user folder (which is the acquisition parent of the user)
getattr(
user,
......
......@@ -19,6 +19,9 @@ if getattr(portal.portal_skins, "erp5_oauth_google_login", None):
if getattr(portal.portal_skins, "erp5_oauth_facebook_login", None):
REQUEST.RESPONSE.expireCookie('__ac_facebook_hash', path='/')
if getattr(portal.portal_skins, "erp5_openid_connect_client", None):
REQUEST.RESPONSE.expireCookie('__ac_openidconnect_hash', path='/')
# PAS logout, if user is from a PAS user folder (which is the acquisition parent of the user)
getattr(
user,
......
......@@ -8,5 +8,7 @@ if getattr(portal_skin, "erp5_oauth_google_login", None) is not None:
if getattr(portal_skin, "erp5_oauth_facebook_login", None) is not None:
oauth_login_list.append("facebook")
if getattr(portal_skin, "erp5_openid_connect_client", None) is not None:
oauth_login_list.append("openidconnect")
return oauth_login_list
......@@ -6,7 +6,8 @@
available_oauth_login_list python: context.getPortalObject().ERP5Site_getAvailableOAuthLoginList();
enable_google_login python: 'google' in available_oauth_login_list;
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 [];
enable_openidconnect_login python: 'openidconnect' in available_oauth_login_list;
css_list python: (enable_google_login or enable_facebook_login or enable_openidconnect_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">
......@@ -70,6 +71,15 @@
</div>
</div>
</tal:block>
<tal:block tal:condition="enable_openidconnect_login">
<div class="field">
<label>&nbsp;</label>
<div class="input">
<a tal:attributes="href string:${here/portal_url}/ERP5Site_redirectToOpenIdLoginPage"
i18n:translate="" i18n:domain="ui" class="zocial openid">Login with OpenId Connect</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>
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from Products.ERP5Type.Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PluggableAuthService.interfaces import plugins
from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.ERP5Security import _setUserNameForAccessLog
from Products.ERP5Security.ERP5ExternalOauth2ExtractionPlugin import ERP5ExternalOauth2ExtractionPlugin
from AccessControl.SecurityManagement import getSecurityManager, \
setSecurityManager, newSecurityManager
from Products.ERP5Type.Cache import DEFAULT_CACHE_SCOPE
import time
import socket
import httplib
import urllib
import json
from zLOG import LOG, ERROR, INFO
#Form for new plugin in ZMI
manage_addERP5OpenIdConnectExtractionPluginForm = PageTemplateFile(
'www/ERP5Security_addERP5OpenIdConnectExtractionPlugin', globals(),
__name__='manage_addERP5OpenIdConnectExtractionPluginForm')
def addERP5OpenIdConnectExtractionPlugin(dispatcher, id, title=None, REQUEST=None):
""" Add a ERP5OpenIdConnectExtractionPlugin to a Pluggable Auth Service. """
plugin = ERP5OpenIdConnectExtractionPlugin(id, title)
dispatcher._setObject(plugin.getId(), plugin)
if REQUEST is not None:
REQUEST['RESPONSE'].redirect(
'%s/manage_workspace'
'?manage_tabs_message='
'ERP5OpenIdConnectExtractionPlugin+added.'
% dispatcher.absolute_url())
class ERP5OpenIdConnectExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugin):
"""
Plugin to authenticate with OpenId Connect.
"""
meta_type = "ERP5 OpenId Connect Extraction Plugin"
login_portal_type = "OpenId Connect Login"
cookie_name = "__ac_openidconnect_hash"
cache_factory_name = "openid_connect_server_auth_token_cache_factory"
def refreshTokenIfExpired(self, key, cache_value):
expires_in = cache_value.get("token_response", {}).get("expires_in")
refresh_token = cache_value.get("refresh_token")
if expires_in and refresh_token:
if (time.time() - cache_value["response_timestamp"]) >= float(expires_in):
credential = self.portal.ERP5Site_getAccessTokenFromRefreshToken(
response_dict={
'access_token': cache_value["access_token"],
'token_type': cache_value["token_type"],
'expires_in': cache_value["expires_in"],
'refresh_token': cache_value["refresh_token"],
}
)
cache_value = credential
cache_value["response_timestamp"] = time.time()
self.setToken(key, cache_value)
return cache_value
def getUserEntry(self, token):
return self.getPortalObject().ERP5Site_getOpenIdUserEntry(token)
#List implementation of class
classImplements( ERP5OpenIdConnectExtractionPlugin,
plugins.ILoginPasswordHostExtractionPlugin
)
InitializeClass(ERP5OpenIdConnectExtractionPlugin)
\ No newline at end of file
......@@ -85,6 +85,7 @@ def initialize(context):
ERP5ExternalOauth2ExtractionPlugin,
ERP5AccessTokenExtractionPlugin,
ERP5DumbHTTPExtractionPlugin,
ERP5ExternalOpenIdConnectExtractionPlugin,
)
registerMultiPlugin(ERP5UserManager.ERP5UserManager.meta_type)
......@@ -99,6 +100,7 @@ def initialize(context):
registerMultiPlugin(ERP5ExternalOauth2ExtractionPlugin.ERP5GoogleExtractionPlugin.meta_type)
registerMultiPlugin(ERP5AccessTokenExtractionPlugin.ERP5AccessTokenExtractionPlugin.meta_type)
registerMultiPlugin(ERP5DumbHTTPExtractionPlugin.ERP5DumbHTTPExtractionPlugin.meta_type)
registerMultiPlugin(ERP5ExternalOpenIdConnectExtractionPlugin.ERP5OpenIdConnectExtractionPlugin.meta_type)
context.registerClass( ERP5UserManager.ERP5UserManager
......@@ -209,6 +211,15 @@ def initialize(context):
, icon='www/portal.gif'
)
context.registerClass( ERP5ExternalOpenIdConnectExtractionPlugin.ERP5OpenIdConnectExtractionPlugin
, permission=ManageUsers
, constructors=(
ERP5ExternalOpenIdConnectExtractionPlugin.manage_addERP5OpenIdConnectExtractionPluginForm,
ERP5ExternalOpenIdConnectExtractionPlugin.addERP5OpenIdConnectExtractionPlugin, )
, visibility=None
, icon='www/portal.gif'
)
from AccessControl.SecurityInfo import ModuleSecurityInfo
ModuleSecurityInfo('Products.ERP5Security.ERP5UserManager').declarePublic(
'getUserByLogin')
......@@ -781,6 +781,14 @@ class TestPASAPI(UserManagementTestCase):
ERP5BearerExtractionPlugin
verifyClass(ILoginPasswordHostExtractionPlugin, ERP5BearerExtractionPlugin)
def test_ERP5OpenIdConnectExtractionPluginInterfaces(self):
"""Tests openid connect extraction plugin respects interfaces."""
from Products.PluggableAuthService.interfaces.plugins import\
ILoginPasswordHostExtractionPlugin
from Products.ERP5Security.ERP5ExternalOpenIdConnectExtractionPlugin import\
ERP5OpenIdConnectExtractionPlugin
verifyClass(ILoginPasswordHostExtractionPlugin, ERP5OpenIdConnectExtractionPlugin)
def test_ERP5DumbHTTPExtractionPluginInterfaces(self):
"""Tests dumb HTTP extraction plugin respects interfaces."""
from Products.PluggableAuthService.interfaces.plugins import\
......
<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
<h2 tal:define="form_title string:Add ERP5 OpenId Connect Extraction Plugin"
tal:replace="structure context/manage_form_title">FORM TITLE</h2>
<p class="form-help">Please input the configuration</p>
<form action="addERP5OpenIdConnectExtractionPlugin" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td colspan="2"> <input type="submit" value="add plugin"/>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
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