Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
erp5
Commits
25808ae8
Commit
25808ae8
authored
Jun 11, 2024
by
Jérome Perrin
Browse files
Options
Browse Files
Download
Plain Diff
python3 support google & facebook login
See merge request
nexedi/erp5!1953
parents
92184552
d885e20e
Changes
22
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
631 additions
and
551 deletions
+631
-551
bt5/erp5_oauth_facebook_login/SkinTemplateItem/portal_skins/erp5_oauth_facebook_login/ERP5Site_callbackFacebookLogin.py
...p5_oauth_facebook_login/ERP5Site_callbackFacebookLogin.py
+2
-2
bt5/erp5_oauth_google_login/DocumentTemplateItem/portal_components/document.erp5.GoogleConnector.py
...teItem/portal_components/document.erp5.GoogleConnector.py
+166
-0
bt5/erp5_oauth_google_login/DocumentTemplateItem/portal_components/document.erp5.GoogleConnector.xml
...eItem/portal_components/document.erp5.GoogleConnector.xml
+3
-7
bt5/erp5_oauth_google_login/ExtensionTemplateItem/portal_components/extension.erp5.GoogleLoginUtility.py
...em/portal_components/extension.erp5.GoogleLoginUtility.py
+0
-72
bt5/erp5_oauth_google_login/PortalTypePropertySheetTemplateItem/property_sheet_list.xml
...rtalTypePropertySheetTemplateItem/property_sheet_list.xml
+1
-0
bt5/erp5_oauth_google_login/PortalTypeTemplateItem/portal_types/Google%20Connector.xml
...ortalTypeTemplateItem/portal_types/Google%20Connector.xml
+1
-1
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getAccessTokenFromCode.xml
...p5_oauth_google_login/ERP5Site_getAccessTokenFromCode.xml
+0
-28
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getDefaultGoogleConnector.py
..._oauth_google_login/ERP5Site_getDefaultGoogleConnector.py
+9
-0
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getDefaultGoogleConnector.xml
...oauth_google_login/ERP5Site_getDefaultGoogleConnector.xml
+3
-3
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getGoogleConnector.xml
...s/erp5_oauth_google_login/ERP5Site_getGoogleConnector.xml
+0
-28
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getGoogleLogin.xml
...skins/erp5_oauth_google_login/ERP5Site_getGoogleLogin.xml
+0
-28
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getGoogleUserEntry.xml
...s/erp5_oauth_google_login/ERP5Site_getGoogleUserEntry.xml
+0
-28
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getPersonFromGoogleLogin.py
...5_oauth_google_login/ERP5Site_getPersonFromGoogleLogin.py
+0
-14
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_receiveGoogleCallback.py
...erp5_oauth_google_login/ERP5Site_receiveGoogleCallback.py
+19
-23
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_receiveGoogleCallback.xml
...rp5_oauth_google_login/ERP5Site_receiveGoogleCallback.xml
+1
-1
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_redirectToGoogleLoginPage.py
..._oauth_google_login/ERP5Site_redirectToGoogleLoginPage.py
+7
-0
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_redirectToGoogleLoginPage.xml
...oauth_google_login/ERP5Site_redirectToGoogleLoginPage.xml
+51
-9
bt5/erp5_oauth_google_login/TestTemplateItem/portal_components/test.erp5.testGoogleLogin.py
...mplateItem/portal_components/test.erp5.testGoogleLogin.py
+319
-236
bt5/erp5_oauth_google_login/bt/template_document_id_list
bt5/erp5_oauth_google_login/bt/template_document_id_list
+1
-0
bt5/erp5_oauth_google_login/bt/template_extension_id_list
bt5/erp5_oauth_google_login/bt/template_extension_id_list
+0
-1
bt5/erp5_oauth_google_login/bt/template_portal_type_property_sheet_list
..._google_login/bt/template_portal_type_property_sheet_list
+1
-0
product/ERP5Security/ERP5ExternalOauth2ExtractionPlugin.py
product/ERP5Security/ERP5ExternalOauth2ExtractionPlugin.py
+47
-70
No files found.
bt5/erp5_oauth_facebook_login/SkinTemplateItem/portal_skins/erp5_oauth_facebook_login/ERP5Site_callbackFacebookLogin.py
View file @
25808ae8
...
...
@@ -21,8 +21,8 @@ elif code is not None:
code
,
"{0}/ERP5Site_callbackFacebookLogin"
.
format
(
context
.
absolute_url
()))
if
response_dict
is
not
None
:
access_token
=
response_dict
[
'access_token'
]
.
encode
(
'utf-8'
)
hash_str
=
context
.
Base_getHMAC
(
access_token
,
access_token
)
access_token
=
response_dict
[
'access_token'
]
hash_str
=
context
.
Base_getHMAC
(
access_token
.
encode
(
'utf-8'
),
access_token
.
encode
(
'utf-8'
)
)
context
.
setAuthCookie
(
response
,
'__ac_facebook_hash'
,
hash_str
)
# store timestamp in second since the epoch in UTC is enough
...
...
bt5/erp5_oauth_google_login/DocumentTemplateItem/portal_components/document.erp5.GoogleConnector.py
0 → 100644
View file @
25808ae8
##############################################################################
# Copyright (c) 2024 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability 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
# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from
AccessControl
import
ClassSecurityInfo
import
random
import
string
import
time
import
oauthlib.oauth2
import
requests
from
zExceptions
import
Unauthorized
from
Products.ERP5Type.XMLObject
import
XMLObject
from
Products.ERP5Type
import
Permissions
from
Products.ERP5Type.Utils
import
unicode2str
from
Products.ERP5Type.Timeout
import
getTimeLeft
AUTH_URL
=
'https://accounts.google.com/o/oauth2/auth'
TOKEN_URL
=
'https://accounts.google.com/o/oauth2/token'
USER_INFO_URL
=
'https://www.googleapis.com/oauth2/v1/userinfo'
SCOPE_LIST
=
[
'https://www.googleapis.com/auth/userinfo.profile'
,
'https://www.googleapis.com/auth/userinfo.email'
]
# Default timeout (in seconds) for the HTTP request made to google servers to
# exchange the authorization code for a token.
DEFAULT_HTTP_TIMEOUT
=
10
class
GoogleConnector
(
XMLObject
):
meta_type
=
'ERP5 Google Connector'
portal_type
=
'Google Connector'
security
=
ClassSecurityInfo
()
security
.
declareObjectProtected
(
Permissions
.
AccessContentsInformation
)
@
security
.
public
def
redirectToGoogleLoginPage
(
self
,
redirect_uri
,
RESPONSE
):
"""Redirect to authorization page.
"""
authorization_url
=
self
.
_getOAuthlibClient
().
prepare_request_uri
(
uri
=
AUTH_URL
,
redirect_uri
=
redirect_uri
,
scope
=
SCOPE_LIST
,
access_type
=
"offline"
,
include_granted_scopes
=
'true'
,
prompt
=
"consent"
,
state
=
self
.
_getAuthorizationState
(),
)
return
RESPONSE
.
redirect
(
authorization_url
)
@
security
.
public
# XXX public but not publishable
def
getTokenFromCode
(
self
,
state
,
code
,
redirect_uri
):
self
.
_verifyAuthorizationState
(
state
)
body
=
self
.
_getOAuthlibClient
().
prepare_request_body
(
code
=
code
,
client_secret
=
self
.
getSecretKey
(),
redirect_uri
=
redirect_uri
,
)
resp
=
requests
.
post
(
TOKEN_URL
,
data
=
body
,
headers
=
{
'Content-Type'
:
'application/x-www-form-urlencoded'
},
timeout
=
self
.
_getTimeout
(),
)
__traceback_info__
=
(
resp
.
content
,
resp
.
status_code
)
resp
.
raise_for_status
()
return
self
.
_getGoogleTokenFromJSONResponse
(
resp
.
json
())
@
security
.
private
def
refreshToken
(
self
,
token
):
"""Refresh auth token.
Used by Products.ERP5Security.ERP5ExternalOauth2ExtractionPlugin
"""
body
=
self
.
_getOAuthlibClient
().
prepare_refresh_body
(
client_id
=
self
.
getClientId
(),
client_secret
=
self
.
getSecretKey
(),
access_type
=
"offline"
,
refresh_token
=
token
[
'refresh_token'
],
)
resp
=
requests
.
post
(
TOKEN_URL
,
data
=
body
,
headers
=
{
'Content-Type'
:
'application/x-www-form-urlencoded'
},
timeout
=
self
.
_getTimeout
(),
)
if
not
resp
.
ok
:
return
{}
return
self
.
_getGoogleTokenFromJSONResponse
(
resp
.
json
())
@
security
.
private
def
getUserEntry
(
self
,
access_token
):
resp
=
requests
.
get
(
USER_INFO_URL
,
headers
=
{
'Authorization'
:
'Bearer {}'
.
format
(
access_token
)},
timeout
=
self
.
_getTimeout
(),
)
resp
.
raise_for_status
()
google_entry
=
resp
.
json
()
user_entry
=
{}
# remap user info
for
erp5_key
,
google_key
in
(
(
'first_name'
,
'given_name'
),
(
'last_name'
,
'family_name'
),
(
'email'
,
'email'
),
(
'reference'
,
'email'
),
):
user_entry
[
erp5_key
]
=
unicode2str
(
google_entry
.
get
(
google_key
,
''
))
return
user_entry
def
_getOAuthlibClient
(
self
):
return
oauthlib
.
oauth2
.
WebApplicationClient
(
self
.
getClientId
(),
access_type
=
"offline"
,
)
def
_getGoogleTokenFromJSONResponse
(
self
,
token
):
return
{
'access_token'
:
unicode2str
(
token
[
'access_token'
]),
'refresh_token'
:
unicode2str
(
token
[
'refresh_token'
]),
'expires_in'
:
token
[
'expires_in'
],
'response_timestamp'
:
time
.
time
(),
'connector_relative_url'
:
self
.
getRelativeUrl
(),
}
def
_getAuthorizationState
(
self
):
alphabet
=
string
.
ascii_letters
+
string
.
digits
state
=
''
.
join
(
random
.
SystemRandom
().
choice
(
alphabet
)
for
_
in
range
(
32
))
self
.
getPortalObject
().
portal_sessions
[
'google_login_auth_state'
][
state
]
=
True
return
state
def
_verifyAuthorizationState
(
self
,
state
):
if
not
self
.
getPortalObject
().
portal_sessions
[
'google_login_auth_state'
].
pop
(
state
,
False
):
raise
Unauthorized
def
_getTimeout
(
self
):
"""Compute the time left according to publisher deadline.
"""
time_left
=
getTimeLeft
()
if
time_left
is
None
:
time_left
=
DEFAULT_HTTP_TIMEOUT
return
min
(
self
.
getTimeout
()
or
DEFAULT_HTTP_TIMEOUT
,
time_left
)
bt5/erp5_oauth_google_login/
ExtensionTemplateItem/portal_components/extension.erp5.GoogleLoginUtility
.xml
→
bt5/erp5_oauth_google_login/
DocumentTemplateItem/portal_components/document.erp5.GoogleConnector
.xml
View file @
25808ae8
...
...
@@ -2,13 +2,13 @@
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"
Extension
Component"
module=
"erp5.portal_type"
/>
<global
name=
"
Document
Component"
module=
"erp5.portal_type"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
default_reference
</string>
</key>
<value>
<string>
Google
LoginUtility
</string>
</value>
<value>
<string>
Google
Connector
</string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
...
...
@@ -18,11 +18,7 @@
</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>
<value>
<string>
document.erp5.GoogleConnector
</string>
</value>
</item>
<item>
<key>
<string>
sid
</string>
</key>
...
...
bt5/erp5_oauth_google_login/ExtensionTemplateItem/portal_components/extension.erp5.GoogleLoginUtility.py
deleted
100644 → 0
View file @
92184552
import
json
import
oauth2client.client
import
oauth2client.transport
from
Products.ERP5Security.ERP5ExternalOauth2ExtractionPlugin
import
getGoogleUserEntry
from
zExceptions
import
Unauthorized
SCOPE_LIST
=
[
'https://www.googleapis.com/auth/userinfo.profile'
,
'https://www.googleapis.com/auth/userinfo.email'
]
# Default timeout (in seconds) for the HTTP request made to google servers to
# exchange the authorization code for a token.
DEFAULT_HTTP_TIMEOUT
=
10
def
_getGoogleClientIdAndSecretKey
(
portal
,
reference
=
"default"
):
"""Returns google client id and secret key.
Internal function.
"""
result_list
=
unrestrictedSearchGoogleConnector
(
portal
,
reference
=
reference
)
assert
result_list
,
"Google Connector not found"
if
len
(
result_list
)
==
2
:
raise
ValueError
(
"Impossible to select one Google Connector"
)
google_connector
=
result_list
[
0
].
getObject
()
return
google_connector
.
getClientId
(),
google_connector
.
getSecretKey
()
def
redirectToGoogleLoginPage
(
self
):
client_id
,
secret_key
=
_getGoogleClientIdAndSecretKey
(
self
.
getPortalObject
())
flow
=
oauth2client
.
client
.
OAuth2WebServerFlow
(
client_id
=
client_id
,
client_secret
=
secret_key
,
scope
=
SCOPE_LIST
,
redirect_uri
=
"{0}/ERP5Site_receiveGoogleCallback"
.
format
(
self
.
absolute_url
()),
access_type
=
"offline"
,
prompt
=
"consent"
,
include_granted_scopes
=
"true"
)
self
.
REQUEST
.
RESPONSE
.
redirect
(
flow
.
step1_get_authorize_url
())
def
getAccessTokenFromCode
(
self
,
code
,
redirect_uri
,
timeout
=
DEFAULT_HTTP_TIMEOUT
):
client_id
,
secret_key
=
_getGoogleClientIdAndSecretKey
(
self
.
getPortalObject
())
flow
=
oauth2client
.
client
.
OAuth2WebServerFlow
(
client_id
=
client_id
,
client_secret
=
secret_key
,
scope
=
SCOPE_LIST
,
redirect_uri
=
redirect_uri
,
access_type
=
"offline"
,
include_granted_scopes
=
"true"
)
credential
=
flow
.
step2_exchange
(
code
,
http
=
oauth2client
.
transport
.
get_http_object
(
timeout
=
timeout
))
credential_data
=
json
.
loads
(
credential
.
to_json
())
return
credential_data
def
unrestrictedSearchGoogleConnector
(
self
,
reference
=
"default"
):
return
self
.
getPortalObject
().
portal_catalog
.
unrestrictedSearchResults
(
portal_type
=
"Google Connector"
,
reference
=
reference
,
validation_state
=
"validated"
,
limit
=
2
)
def
unrestrictedSearchGoogleLogin
(
self
,
login
,
REQUEST
=
None
):
if
REQUEST
is
not
None
:
raise
Unauthorized
return
self
.
getPortalObject
().
portal_catalog
.
unrestrictedSearchResults
(
portal_type
=
"Google Login"
,
reference
=
login
,
validation_state
=
"validated"
,
limit
=
1
)
def
getUserEntry
(
access_token
):
return
getGoogleUserEntry
(
access_token
)
\ No newline at end of file
bt5/erp5_oauth_google_login/PortalTypePropertySheetTemplateItem/property_sheet_list.xml
View file @
25808ae8
<property_sheet_list>
<portal_type
id=
"Google Connector"
>
<item>
OAuthClient
</item>
<item>
SocketClient
</item>
</portal_type>
<portal_type
id=
"Template Tool"
>
<item>
TemplateToolERP5GoogleExtractionPluginConstraint
</item>
...
...
bt5/erp5_oauth_google_login/PortalTypeTemplateItem/portal_types/Google%20Connector.xml
View file @
25808ae8
...
...
@@ -40,7 +40,7 @@
</item>
<item>
<key>
<string>
type_class
</string>
</key>
<value>
<string>
XMLObject
</string>
</value>
<value>
<string>
GoogleConnector
</string>
</value>
</item>
<item>
<key>
<string>
type_interface
</string>
</key>
...
...
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getAccessTokenFromCode.xml
deleted
100644 → 0
View file @
92184552
<?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>
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getDefaultGoogleConnector.py
0 → 100644
View file @
25808ae8
portal
=
context
.
getPortalObject
()
result_list
=
portal
.
portal_catalog
(
portal_type
=
"Google Connector"
,
reference
=
reference
,
validation_state
=
"validated"
,
limit
=
2
)
if
len
(
result_list
)
!=
1
:
raise
ValueError
(
"Impossible to select one Google Connector"
)
return
result_list
[
0
].
getObject
()
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_get
PersonFromGoogleLogin
.xml
→
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_get
DefaultGoogleConnector
.xml
View file @
25808ae8
...
...
@@ -50,19 +50,19 @@
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string>
login, REQUEST=None
</string>
</value>
<value>
<string>
reference="default"
</string>
</value>
</item>
<item>
<key>
<string>
_proxy_roles
</string>
</key>
<value>
<tuple>
<string>
Manage
r
</string>
<string>
Audito
r
</string>
</tuple>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_get
PersonFromGoogleLogin
</string>
</value>
<value>
<string>
ERP5Site_get
DefaultGoogleConnector
</string>
</value>
</item>
</dictionary>
</pickle>
...
...
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getGoogleConnector.xml
deleted
100644 → 0
View file @
92184552
<?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>
unrestrictedSearchGoogleConnector
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
GoogleLoginUtility
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_getGoogleConnector
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getGoogleLogin.xml
deleted
100644 → 0
View file @
92184552
<?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>
unrestrictedSearchGoogleConnector
</string>
</value>
</item>
<item>
<key>
<string>
_module
</string>
</key>
<value>
<string>
GoogleLoginUtility
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_getGoogleLogin
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getGoogleUserEntry.xml
deleted
100644 → 0
View file @
92184552
<?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>
GoogleLoginUtility
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_getGoogleUserEntry
</string>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_getPersonFromGoogleLogin.py
deleted
100644 → 0
View file @
92184552
from
zExceptions
import
Unauthorized
if
REQUEST
is
not
None
:
raise
Unauthorized
login
=
context
.
ERP5Site_getGoogleLogin
(
login
)
if
login
is
None
:
return
login
if
len
(
login
)
>
1
:
raise
ValueError
(
"Duplicated User"
)
return
login
[
0
].
getParentValue
().
getRelativeUrl
()
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_receiveGoogleCallback.py
View file @
25808ae8
import
time
from
Products.ERP5Type.Utils
import
str2bytes
portal
=
context
.
getPortalObject
()
request
=
container
.
REQUEST
response
=
request
.
RESPONSE
...
...
@@ -16,27 +17,22 @@ if error is not None:
return
handleError
(
error
)
elif
code
is
not
None
:
response_dict
=
context
.
ERP5Site_getAccessTokenFromCode
(
code
,
"{0}/ERP5Site_receiveGoogleCallback"
.
format
(
context
.
absolute_url
()))
if
response_dict
is
not
None
:
access_token
=
response_dict
[
'access_token'
].
encode
(
'utf-8'
)
hash_str
=
context
.
Base_getHMAC
(
access_token
,
access_token
)
context
.
setAuthCookie
(
response
,
'__ac_google_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
,
"google_server_auth_token_cache_factory"
)
user_dict
=
context
.
ERP5Site_getGoogleUserEntry
(
access_token
)
user_reference
=
user_dict
[
"email"
]
context
.
Base_setBearerToken
(
access_token
,
{
"reference"
:
user_reference
},
"google_server_auth_token_cache_factory"
)
method
=
getattr
(
context
,
"ERP5Site_createGoogleUserToOAuth"
,
None
)
if
method
is
not
None
:
method
(
user_reference
,
user_dict
)
# XXX for ERP5JS web sites without a rewrite rule, we make sure there's a trailing /
return
response
.
redirect
(
request
.
get
(
"came_from"
)
or
context
.
absolute_url
()
+
'/'
)
google_connector
=
portal
.
ERP5Site_getDefaultGoogleConnector
()
response_dict
=
google_connector
.
getTokenFromCode
(
state
=
state
,
code
=
code
,
redirect_uri
=
"{}/ERP5Site_receiveGoogleCallback"
.
format
(
portal
.
absolute_url
()),
)
access_token
=
str2bytes
(
response_dict
[
'access_token'
])
hash_str
=
portal
.
Base_getHMAC
(
access_token
,
access_token
)
portal
.
setAuthCookie
(
response
,
'__ac_google_hash'
,
hash_str
)
portal
.
Base_setBearerToken
(
hash_str
,
response_dict
,
"google_server_auth_token_cache_factory"
)
# XXX for ERP5JS web sites without a rewrite rule, we make sure there's a trailing /
return
response
.
redirect
(
request
.
get
(
"came_from"
)
or
context
.
absolute_url
()
+
'/'
)
return
handleError
(
''
)
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_receiveGoogleCallback.xml
View file @
25808ae8
...
...
@@ -50,7 +50,7 @@
</item>
<item>
<key>
<string>
_params
</string>
</key>
<value>
<string>
code=None, error=None
</string>
</value>
<value>
<string>
code=None,
state=None,
error=None
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
...
...
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_redirectToGoogleLoginPage.py
0 → 100644
View file @
25808ae8
portal
=
context
.
getPortalObject
()
google_connector
=
portal
.
ERP5Site_getDefaultGoogleConnector
()
return
google_connector
.
redirectToGoogleLoginPage
(
"{0}/ERP5Site_receiveGoogleCallback"
.
format
(
portal
.
absolute_url
()),
RESPONSE
=
RESPONSE
,
)
bt5/erp5_oauth_google_login/SkinTemplateItem/portal_skins/erp5_oauth_google_login/ERP5Site_redirectToGoogleLoginPage.xml
View file @
25808ae8
...
...
@@ -2,25 +2,67 @@
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"
ExternalMethod"
module=
"Products.ExternalMethod.ExternalMethod
"
/>
<global
name=
"
PythonScript"
module=
"Products.PythonScripts.PythonScript
"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
_function
</string>
</key>
<value>
<string>
redirectToGoogleLoginPage
</string>
</value>
<key>
<string>
_bind_names
</string>
</key>
<value>
<object>
<klass>
<global
name=
"_reconstructor"
module=
"copy_reg"
/>
</klass>
<tuple>
<global
name=
"NameAssignments"
module=
"Shared.DC.Scripts.Bindings"
/>
<global
name=
"object"
module=
"__builtin__"
/>
<none/>
</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>
_
module
</string>
</key>
<value>
<string>
GoogleLoginUtility
</string>
</value>
<key>
<string>
_
params
</string>
</key>
<value>
<string>
RESPONSE
</string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_redirectToGoogleLoginPage
</string>
</value>
<key>
<string>
_proxy_roles
</string>
</key>
<value>
<tuple>
<string>
Auditor
</string>
</tuple>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string></string>
</value>
<key>
<string>
id
</string>
</key>
<value>
<string>
ERP5Site_redirectToGoogleLoginPage
</string>
</value>
</item>
</dictionary>
</pickle>
...
...
bt5/erp5_oauth_google_login/TestTemplateItem/portal_components/test.erp5.testGoogleLogin.py
View file @
25808ae8
This diff is collapsed.
Click to expand it.
bt5/erp5_oauth_google_login/bt/template_document_id_list
0 → 100644
View file @
25808ae8
document.erp5.GoogleConnector
\ No newline at end of file
bt5/erp5_oauth_google_login/bt/template_extension_id_list
deleted
100644 → 0
View file @
92184552
extension.erp5.GoogleLoginUtility
\ No newline at end of file
bt5/erp5_oauth_google_login/bt/template_portal_type_property_sheet_list
View file @
25808ae8
Google Connector | OAuthClient
Google Connector | SocketClient
Template Tool | TemplateToolERP5GoogleExtractionPluginConstraint
\ No newline at end of file
product/ERP5Security/ERP5ExternalOauth2ExtractionPlugin.py
View file @
25808ae8
...
...
@@ -35,54 +35,22 @@ from Products.PluggableAuthService.utils import classImplements
from
Products.PluggableAuthService.plugins.BasePlugin
import
BasePlugin
from
Products.ERP5Security
import
_setUserNameForAccessLog
from
AccessControl.SecurityManagement
import
getSecurityManager
,
\
setSecurityManager
,
newSecurityManager
from
Products.ERP5Type.Cache
import
DEFAULT_CACHE_SCOPE
import
time
from
six.moves
import
urllib
import
json
from
zLOG
import
LOG
,
ERROR
,
INFO
from
zLOG
import
LOG
,
INFO
try
:
import
facebook
except
ImportError
:
facebook
=
None
try
:
import
apiclient.discovery
import
httplib2
import
oauth2client.client
except
ImportError
:
httplib2
=
None
#Form for new plugin in ZMI
#
Form for new plugin in ZMI
manage_addERP5FacebookExtractionPluginForm
=
PageTemplateFile
(
'www/ERP5Security_addERP5FacebookExtractionPlugin'
,
globals
(),
__name__
=
'manage_addERP5FacebookExtractionPluginForm'
)
def
getGoogleUserEntry
(
token
):
if
httplib2
is
None
:
LOG
(
'ERP5GoogleExtractionPlugin'
,
INFO
,
'No Google modules available, please install google-api-python-client '
'package. Authentication disabled..'
)
return
None
http
=
oauth2client
.
client
.
AccessTokenCredentials
(
token
,
'ERP5 Client'
).
authorize
(
httplib2
.
Http
(
timeout
=
5
))
service
=
apiclient
.
discovery
.
build
(
"oauth2"
,
"v1"
,
http
=
http
)
google_entry
=
service
.
userinfo
().
get
().
execute
()
user_entry
=
{}
if
google_entry
is
not
None
:
# sanitise value
for
k
in
((
'first_name'
,
'given_name'
),
(
'last_name'
,
'family_name'
),
(
'email'
,
'email'
),
(
'reference'
,
'email'
),):
value
=
google_entry
.
get
(
k
[
1
],
''
).
encode
(
'utf-8'
)
user_entry
[
k
[
0
]]
=
value
return
user_entry
def
addERP5FacebookExtractionPlugin
(
dispatcher
,
id
,
title
=
None
,
REQUEST
=
None
):
""" Add a ERP5FacebookExtractionPlugin to a Pluggable Auth Service. """
...
...
@@ -97,7 +65,7 @@ def addERP5FacebookExtractionPlugin(dispatcher, id, title=None, REQUEST=None):
'ERP5FacebookExtractionPlugin+added.'
%
dispatcher
.
absolute_url
())
#Form for new plugin in ZMI
#
Form for new plugin in ZMI
manage_addERP5GoogleExtractionPluginForm
=
PageTemplateFile
(
'www/ERP5Security_addERP5GoogleExtractionPlugin'
,
globals
(),
__name__
=
'manage_addERP5GoogleExtractionPluginForm'
)
...
...
@@ -156,13 +124,13 @@ class ERP5ExternalOauth2ExtractionPlugin:
for
cache_plugin
in
cache_factory
.
getCachePluginList
():
cache_entry
=
cache_plugin
.
get
(
key
,
DEFAULT_CACHE_SCOPE
)
if
cache_entry
is
not
None
:
# Avoid errors if the plugin don't have the funcionality of refresh token
refreshTokenIfExpired
=
getattr
(
self
,
"refreshTokenIfExpired"
,
None
)
cache_value
=
cache_entry
.
getValue
()
if
refreshTokenIfExpired
is
not
None
:
return
refreshTokenIfExpired
(
key
,
cache_value
)
else
:
return
cache_value
# getToken is called for the access_token_dict and for
# the user entry. We try to refresh only for the
# access_token_dict
if
'refresh_token'
in
cache_value
:
return
self
.
refreshTokenIfExpired
(
key
,
cache_value
)
return
cache_value
raise
KeyError
(
'Key %r not found'
%
key
)
####################################
...
...
@@ -170,19 +138,29 @@ class ERP5ExternalOauth2ExtractionPlugin:
####################################
security
.
declarePrivate
(
'extractCredentials'
)
def
extractCredentials
(
self
,
request
):
""" Extract Oauth2 credentials from the request header. """
user_dict
=
{}
""" Extract Oauth2 credentials from cookie.
This plugins uses two level of cache storage:
- cookie_value => access_token_dict
- access_token => user_entry
access_token_dict depends on the concrete plugin classes,
but this is generally access_token and refresh_token.
user_entry must contain "reference", which is the reference
of the corresponding login document in ERP5.
"""
access_token_dict
=
{}
cookie_hash
=
request
.
get
(
self
.
cookie_name
)
if
cookie_hash
is
not
None
:
try
:
user
_dict
=
self
.
getToken
(
cookie_hash
)
access_token
_dict
=
self
.
getToken
(
cookie_hash
)
except
KeyError
:
LOG
(
self
.
getId
(),
INFO
,
'Hash %s not found'
%
cookie_hash
)
return
{}
token
=
None
if
"access_token"
in
user
_dict
:
token
=
user
_dict
[
"access_token"
]
if
"access_token"
in
access_token
_dict
:
token
=
access_token
_dict
[
"access_token"
]
if
token
is
None
:
# no token, then no credentials
...
...
@@ -192,7 +170,7 @@ class ERP5ExternalOauth2ExtractionPlugin:
try
:
user_entry
=
self
.
getToken
(
token
)
except
KeyError
:
user_entry
=
self
.
getUserEntry
(
token
)
user_entry
=
self
.
getUserEntry
(
access_token_dict
)
if
user_entry
is
not
None
:
# Reduce data size because, we don't need more than reference
user_entry
=
{
"reference"
:
user_entry
[
"reference"
]}
...
...
@@ -240,11 +218,14 @@ def getFacebookUserEntry(token):
if
facebook_entry
is
not
None
:
# sanitise value
for
k
in
(
'name'
,
'id'
):
v
=
facebook_entry
[
k
]
if
six
.
PY2
:
v
=
v
.
encode
(
'utf-8'
)
try
:
if
k
==
'id'
:
user_entry
[
'reference'
]
=
facebook_entry
[
k
].
encode
(
'utf-8'
)
user_entry
[
'reference'
]
=
v
else
:
user_entry
[
k
]
=
facebook_entry
[
k
].
encode
(
'utf-8'
)
user_entry
[
k
]
=
v
except
KeyError
:
raise
ValueError
(
facebook_entry
)
return
user_entry
...
...
@@ -259,40 +240,36 @@ class ERP5FacebookExtractionPlugin(ERP5ExternalOauth2ExtractionPlugin, BasePlugi
cookie_name
=
"__ac_facebook_hash"
cache_factory_name
=
"facebook_server_auth_token_cache_factory"
def
refreshTokenIfExpired
(
self
,
key
,
cache_value
):
return
cache_value
def
refreshTokenIfExpired
(
self
,
key
,
access_token_dict
):
return
access_token_dict
def
getUserEntry
(
self
,
token
):
return
getFacebookUserDict
(
token
)
class
ERP5GoogleExtractionPlugin
(
ERP5ExternalOauth2ExtractionPlugin
,
BasePlugin
):
"""
Plugin to authen
icate as machines
.
Plugin to authen
ticate using google OAuth2
.
"""
meta_type
=
"ERP5 Google Extraction Plugin"
login_portal_type
=
"Google Login"
cookie_name
=
"__ac_google_hash"
cache_factory_name
=
"google_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
=
oauth2client
.
client
.
OAuth2Credentials
(
cache_value
[
"access_token"
],
cache_value
[
"client_id"
],
cache_value
[
"client_secret"
],
refresh_token
,
cache_value
[
"token_expiry"
],
cache_value
[
"token_uri"
],
cache_value
[
"user_agent"
])
credential
.
refresh
(
httplib2
.
Http
(
timeout
=
5
))
cache_value
=
json
.
loads
(
credential
.
to_json
())
cache_value
[
"response_timestamp"
]
=
time
.
time
()
self
.
setToken
(
key
,
cache_value
)
return
cache_value
def
refreshTokenIfExpired
(
self
,
key
,
access_token_dict
):
if
(
time
.
time
()
-
access_token_dict
[
"response_timestamp"
])
\
>=
access_token_dict
[
'expires_in'
]:
access_token_dict
=
self
.
getPortalObject
().
unrestrictedTraverse
(
access_token_dict
[
'connector_relative_url'
]
).
refreshToken
(
access_token_dict
)
self
.
setToken
(
key
,
access_token_dict
)
return
access_token_dict
def
getUserEntry
(
self
,
access_token_dict
):
return
self
.
getPortalObject
().
unrestrictedTraverse
(
access_token_dict
[
'connector_relative_url'
],
).
getUserEntry
(
access_token_dict
[
'access_token'
])
def
getUserEntry
(
self
,
token
):
return
getGoogleUserEntry
(
token
)
#List implementation of class
classImplements
(
ERP5FacebookExtractionPlugin
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment