Commit ceb9befc authored by Jérome Perrin's avatar Jérome Perrin Committed by Arnaud Fontaine

oauth2_authorisation: py3

parent 0eddd761
...@@ -70,7 +70,7 @@ from DateTime import DateTime ...@@ -70,7 +70,7 @@ from DateTime import DateTime
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type.Message import translateString from Products.ERP5Type.Message import translateString
from Products.ERP5Type.UnrestrictedMethod import super_user from Products.ERP5Type.UnrestrictedMethod import super_user
from Products.ERP5Type.Utils import bytes2str, str2bytes, unicode2str from Products.ERP5Type.Utils import bytes2str, str2bytes, unicode2str, str2unicode
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Security.ERP5GroupManager import ( from Products.ERP5Security.ERP5GroupManager import (
disableCache as ERP5GroupManager_disableCache, disableCache as ERP5GroupManager_disableCache,
...@@ -448,7 +448,7 @@ class _ERP5AuthorisationEndpoint(AuthorizationEndpoint): ...@@ -448,7 +448,7 @@ class _ERP5AuthorisationEndpoint(AuthorizationEndpoint):
} }
for x in ( for x in (
portal.portal_categories.resolveCategory( portal.portal_categories.resolveCategory(
'oauth2_scope/' + y.encode('utf-8'), 'oauth2_scope/' + unicode2str(y),
) )
for y in scope_list for y in scope_list
) )
...@@ -553,7 +553,7 @@ class _ERP5RequestValidator(RequestValidator): ...@@ -553,7 +553,7 @@ class _ERP5RequestValidator(RequestValidator):
return token_callable(**kw) return token_callable(**kw)
except jwt.InvalidTokenError: except jwt.InvalidTokenError:
pass pass
raise raise # pylint:disable=misplaced-bare-raise
def client_authentication_required(self, request, *args, **kwargs): def client_authentication_required(self, request, *args, **kwargs):
# Use this method, which is called early on most endpoints, to setup request.client . # Use this method, which is called early on most endpoints, to setup request.client .
...@@ -699,7 +699,7 @@ class _ERP5RequestValidator(RequestValidator): ...@@ -699,7 +699,7 @@ class _ERP5RequestValidator(RequestValidator):
client_value=request.client.erp5_client_value, client_value=request.client.erp5_client_value,
redirect_uri=request.redirect_uri, redirect_uri=request.redirect_uri,
scope_list=[ scope_list=[
x.encode('utf-8') unicode2str(x)
for x in request.scopes for x in request.scopes
], ],
code_challenge=request.code_challenge, code_challenge=request.code_challenge,
...@@ -859,7 +859,7 @@ def _callEndpoint(endpoint, self, REQUEST): ...@@ -859,7 +859,7 @@ def _callEndpoint(endpoint, self, REQUEST):
# not have to care about intermediate proxies). # not have to care about intermediate proxies).
request_header_dict['X_FORWARDED_FOR'] = REQUEST.getClientAddr() request_header_dict['X_FORWARDED_FOR'] = REQUEST.getClientAddr()
request_body = REQUEST.get('BODY') request_body = REQUEST.get('BODY')
if request_body is None and content_type == 'application/x-www-form-urlencoded': if (not request_body) and content_type == 'application/x-www-form-urlencoded':
# XXX: very imperfect, but should be good enough for OAuth2 usage: # XXX: very imperfect, but should be good enough for OAuth2 usage:
# no standard OAuth2 POST field should be marshalled by Zope. # no standard OAuth2 POST field should be marshalled by Zope.
request_body = urlencode([ request_body = urlencode([
...@@ -1287,7 +1287,7 @@ class OAuth2AuthorisationServerConnector(XMLObject): ...@@ -1287,7 +1287,7 @@ class OAuth2AuthorisationServerConnector(XMLObject):
ensure_ascii(token_dict[JWT_PAYLOAD_KEY]), ensure_ascii(token_dict[JWT_PAYLOAD_KEY]),
) )
return token_dict return token_dict
raise raise # pylint:disable=misplaced-bare-raise
def _getRefreshTokenDict(self, value, request): def _getRefreshTokenDict(self, value, request):
for _, algorithm, symetric_key in self.__getRefreshTokenKeyList(): for _, algorithm, symetric_key in self.__getRefreshTokenKeyList():
...@@ -1309,14 +1309,14 @@ class OAuth2AuthorisationServerConnector(XMLObject): ...@@ -1309,14 +1309,14 @@ class OAuth2AuthorisationServerConnector(XMLObject):
continue continue
else: else:
return token_dict return token_dict
raise raise # pylint:disable=misplaced-bare-raise
def _checkCustomTokenPolicy(self, token, request): def _checkCustomTokenPolicy(self, token, request):
""" """
Validate non-standard jwt claims against request. Validate non-standard jwt claims against request.
""" """
if not isAddressInNetworkList( if not isAddressInNetworkList(
address=request.headers['X_FORWARDED_FOR'].decode('utf-8'), address=str2unicode(request.headers['X_FORWARDED_FOR']),
network_list=token[JWT_CLAIM_NETWORK_LIST_KEY], network_list=token[JWT_CLAIM_NETWORK_LIST_KEY],
): ):
raise jwt.InvalidTokenError raise jwt.InvalidTokenError
...@@ -1369,7 +1369,7 @@ class OAuth2AuthorisationServerConnector(XMLObject): ...@@ -1369,7 +1369,7 @@ class OAuth2AuthorisationServerConnector(XMLObject):
continue continue
else: else:
return token_dict['iss'] return token_dict['iss']
raise raise # pylint:disable=misplaced-bare-raise
security.declarePrivate('getRefreshTokenClientId') security.declarePrivate('getRefreshTokenClientId')
def getRefreshTokenClientId(self, value, request): def getRefreshTokenClientId(self, value, request):
...@@ -1395,13 +1395,13 @@ class OAuth2AuthorisationServerConnector(XMLObject): ...@@ -1395,13 +1395,13 @@ class OAuth2AuthorisationServerConnector(XMLObject):
continue continue
else: else:
return token_dict['iss'] return token_dict['iss']
raise raise # pylint:disable=misplaced-bare-raise
def _getSessionValueFromTokenDict(self, token_dict): def _getSessionValueFromTokenDict(self, token_dict):
session_value = self._getSessionValue( session_value = self._getSessionValue(
token_dict[JWT_PAYLOAD_KEY][ unicode2str(token_dict[JWT_PAYLOAD_KEY][
JWT_PAYLOAD_AUTHORISATION_SESSION_ID_KEY JWT_PAYLOAD_AUTHORISATION_SESSION_ID_KEY
].encode('utf-8'), ]),
'validated', 'validated',
) )
if session_value is not None: if session_value is not None:
......
...@@ -5,12 +5,13 @@ Mutate REQUEST to call standard OAuth2 /authorize endpoint from an ERP5 Form in ...@@ -5,12 +5,13 @@ Mutate REQUEST to call standard OAuth2 /authorize endpoint from an ERP5 Form in
import json import json
import six import six
from erp5.component.document.OAuth2AuthorisationServerConnector import substituteRequest from erp5.component.document.OAuth2AuthorisationServerConnector import substituteRequest
from Products.ERP5Type.Utils import unicode2str
# XXX: Accessing REQUEST from acquisition is bad. But Base_callDialogMethod # XXX: Accessing REQUEST from acquisition is bad. But Base_callDialogMethod
# does not propagate the request cleanly, so no other way so far. # does not propagate the request cleanly, so no other way so far.
REQUEST = context.REQUEST REQUEST = context.REQUEST
form = { form = {
key.encode('utf-8'): value.encode('utf-8') unicode2str(key): unicode2str(value)
for key, value in six.iteritems(json.loads(request_info_json)) for key, value in six.iteritems(json.loads(request_info_json))
} }
if scope_list: if scope_list:
......
...@@ -51,7 +51,7 @@ from OFS.Traversable import NotFound ...@@ -51,7 +51,7 @@ from OFS.Traversable import NotFound
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.Timeout import getTimeLeft from Products.ERP5Type.Timeout import getTimeLeft
from Products.ERP5Type.Utils import bytes2str, str2bytes, str2unicode from Products.ERP5Type.Utils import bytes2str, unicode2str, str2bytes, str2unicode
from Products.ERP5Security.ERP5OAuth2ResourceServerPlugin import ( from Products.ERP5Security.ERP5OAuth2ResourceServerPlugin import (
OAuth2AuthorisationClientConnectorMixIn, OAuth2AuthorisationClientConnectorMixIn,
ERP5OAuth2ResourceServerPlugin, ERP5OAuth2ResourceServerPlugin,
...@@ -227,13 +227,16 @@ class _OAuth2AuthorisationServerProxy(object): ...@@ -227,13 +227,16 @@ class _OAuth2AuthorisationServerProxy(object):
) )
else: else:
Connection = HTTPConnection Connection = HTTPConnection
if six.PY2:
# Changed in version 3.4: The strict parameter was removed.
# HTTP 0.9-style "Simple Responses" are no longer supported.
Connection = functools.partial(Connection, strict=True)
timeout = getTimeLeft() timeout = getTimeLeft()
if timeout is None or timeout > self._timeout: if timeout is None or timeout > self._timeout:
timeout = self._timeout timeout = self._timeout
http_connection = Connection( http_connection = Connection(
host=parsed_url.hostname, host=parsed_url.hostname,
port=parsed_url.port, port=parsed_url.port,
strict=True,
timeout=timeout, timeout=timeout,
source_address=self._bind_address, source_address=self._bind_address,
) )
...@@ -274,7 +277,7 @@ class _OAuth2AuthorisationServerProxy(object): ...@@ -274,7 +277,7 @@ class _OAuth2AuthorisationServerProxy(object):
def _queryOAuth2(self, method, REQUEST, RESPONSE): def _queryOAuth2(self, method, REQUEST, RESPONSE):
header_dict, body, status = self._query( header_dict, body, status = self._query(
method, method,
body=urlencode(REQUEST.form.items()), body=urlencode(REQUEST.form),
header_dict={ header_dict={
'CONTENT_TYPE': REQUEST.environ['CONTENT_TYPE'], 'CONTENT_TYPE': REQUEST.environ['CONTENT_TYPE'],
}, },
...@@ -313,7 +316,7 @@ class _OAuth2AuthorisationServerProxy(object): ...@@ -313,7 +316,7 @@ class _OAuth2AuthorisationServerProxy(object):
def getAccessTokenSignatureAlgorithmAndPublicKeyList(self): def getAccessTokenSignatureAlgorithmAndPublicKeyList(self):
return tuple( return tuple(
(signature_algorithm.encode('ascii'), public_key.encode('ascii')) (unicode2str(signature_algorithm), unicode2str(public_key))
for signature_algorithm, public_key in self._queryERP5( for signature_algorithm, public_key in self._queryERP5(
'getAccessTokenSignatureAlgorithmAndPublicKeyList', 'getAccessTokenSignatureAlgorithmAndPublicKeyList',
) )
...@@ -864,7 +867,7 @@ class OAuth2AuthorisationClientConnector( ...@@ -864,7 +867,7 @@ class OAuth2AuthorisationClientConnector(
try: try:
state_dict = json.loads( state_dict = json.loads(
self.__getMultiFernet().decrypt( self.__getMultiFernet().decrypt(
state, str2bytes(state),
ttl=self._SESSION_STATE_VALIDITY, ttl=self._SESSION_STATE_VALIDITY,
), ),
) )
...@@ -882,7 +885,7 @@ class OAuth2AuthorisationClientConnector( ...@@ -882,7 +885,7 @@ class OAuth2AuthorisationClientConnector(
came_from = state_dict.get(_STATE_CAME_FROM_NAME) came_from = state_dict.get(_STATE_CAME_FROM_NAME)
if came_from: if came_from:
context = self # whatever context = self # whatever
kw['redirect_url'] = came_from.encode('utf-8') kw['redirect_url'] = unicode2str(came_from)
else: else:
context = self._getNeutralContextValue() context = self._getNeutralContextValue()
context.Base_redirect(**kw) context.Base_redirect(**kw)
...@@ -930,7 +933,7 @@ class OAuth2AuthorisationClientConnector( ...@@ -930,7 +933,7 @@ class OAuth2AuthorisationClientConnector(
REQUEST=REQUEST, REQUEST=REQUEST,
RESPONSE=RESPONSE, RESPONSE=RESPONSE,
) )
identifier_from_state = state_dict[_STATE_IDENTIFIER_NAME].encode('ascii') identifier_from_state = unicode2str(state_dict[_STATE_IDENTIFIER_NAME])
for ( for (
state_cookie_name, state_cookie_name,
identifier_from_cookie, identifier_from_cookie,
...@@ -965,7 +968,7 @@ class OAuth2AuthorisationClientConnector( ...@@ -965,7 +968,7 @@ class OAuth2AuthorisationClientConnector(
'code': code, 'code': code,
'redirect_uri': self.getRedirectUri(), 'redirect_uri': self.getRedirectUri(),
'client_id': self.getReference(), 'client_id': self.getReference(),
'code_verifier': state_dict[_STATE_CODE_VERIFIER_NAME].encode('ascii'), 'code_verifier': unicode2str(state_dict[_STATE_CODE_VERIFIER_NAME])
}, },
) )
access_token, _, error_message = self._setCookieFromTokenResponse( access_token, _, error_message = self._setCookieFromTokenResponse(
......
...@@ -46,6 +46,7 @@ from Products.ERP5Type.TransactionalVariable import \ ...@@ -46,6 +46,7 @@ from Products.ERP5Type.TransactionalVariable import \
getTransactionalVariable, TransactionalResource getTransactionalVariable, TransactionalResource
from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules
from Products.ERP5Type.mixin.response_header_generator import ResponseHeaderGenerator from Products.ERP5Type.mixin.response_header_generator import ResponseHeaderGenerator
from Products.ERP5Type.Utils import str2bytes, bytes2str
from zLOG import LOG, INFO, WARNING, ERROR from zLOG import LOG, INFO, WARNING, ERROR
from zExceptions import BadRequest from zExceptions import BadRequest
...@@ -248,10 +249,10 @@ class AutorisationExtractorBeforeTraverseHook(object): ...@@ -248,10 +249,10 @@ class AutorisationExtractorBeforeTraverseHook(object):
ERP5_AUTHORISATION_EXTRACTOR_PASSWORD_NAME in form_dict ERP5_AUTHORISATION_EXTRACTOR_PASSWORD_NAME in form_dict
): ):
username = form_dict[ERP5_AUTHORISATION_EXTRACTOR_USERNAME_NAME] username = form_dict[ERP5_AUTHORISATION_EXTRACTOR_USERNAME_NAME]
request._auth = 'Basic ' + base64.b64encode('%s:%s' % ( request._auth = 'Basic ' + bytes2str(base64.b64encode(str2bytes('%s:%s' % (
username, username,
form_dict[ERP5_AUTHORISATION_EXTRACTOR_PASSWORD_NAME], form_dict[ERP5_AUTHORISATION_EXTRACTOR_PASSWORD_NAME],
)) ))))
request.response._auth = 1 request.response._auth = 1
_setUserNameForAccessLog(username, request) _setUserNameForAccessLog(username, request)
......
...@@ -48,6 +48,7 @@ from Products.PluggableAuthService.interfaces.plugins import ( ...@@ -48,6 +48,7 @@ from Products.PluggableAuthService.interfaces.plugins import (
) )
from Products.ERP5Security import _setUserNameForAccessLog from Products.ERP5Security import _setUserNameForAccessLog
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Utils import bytes2str, str2bytes, str2unicode, unicode2str
# Public constants. Must not change once deployed. # Public constants. Must not change once deployed.
...@@ -109,11 +110,11 @@ def encodeAccessTokenPayload(payload): ...@@ -109,11 +110,11 @@ def encodeAccessTokenPayload(payload):
Encode given json-safe value into a format suitable for Encode given json-safe value into a format suitable for
decodeAccessTokenPayload. decodeAccessTokenPayload.
""" """
return base64.urlsafe_b64encode( return bytes2str(base64.urlsafe_b64encode(
zlib.compress( zlib.compress(
json.dumps(payload), str2bytes(json.dumps(payload)),
), ),
) ))
def decodeAccessTokenPayload(encoded_payload): def decodeAccessTokenPayload(encoded_payload):
""" """
...@@ -353,7 +354,7 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin): ...@@ -353,7 +354,7 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin):
if client_id is None: if client_id is None:
# Peek into token (without checking its signature) to guess the client_id # Peek into token (without checking its signature) to guess the client_id
# to look for. # to look for.
client_id = jwt.decode( client_id = unicode2str(jwt.decode(
raw_token, raw_token,
# no key. # no key.
# any algorithm is fine. # any algorithm is fine.
...@@ -361,7 +362,7 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin): ...@@ -361,7 +362,7 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin):
'verify_signature': False, 'verify_signature': False,
'verify_exp': False, 'verify_exp': False,
}, },
)['iss'].encode('utf-8') )['iss'])
assert client_id is not None assert client_id is not None
web_service_value_list = list(self.__iterClientConnectorValue( web_service_value_list = list(self.__iterClientConnectorValue(
client_id=client_id, client_id=client_id,
...@@ -425,7 +426,7 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin): ...@@ -425,7 +426,7 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin):
The schema of this dictionary is purely an internal implementation detail The schema of this dictionary is purely an internal implementation detail
of this plugin. of this plugin.
""" """
client_address = request.getClientAddr().decode('utf-8') client_address = str2unicode(request.getClientAddr())
token = self.__checkTokenSignature(access_token) token = self.__checkTokenSignature(access_token)
if token is None and can_update_key: if token is None and can_update_key:
self.__updateAccessTokenSignatureKeyList(request=request) self.__updateAccessTokenSignatureKeyList(request=request)
...@@ -440,9 +441,9 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin): ...@@ -440,9 +441,9 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin):
return return
# JWT is known valid. Access its content. # JWT is known valid. Access its content.
token_payload = decodeAccessTokenPayload( token_payload = decodeAccessTokenPayload(
token[JWT_PAYLOAD_KEY].encode('ascii'), bytes2str(token[JWT_PAYLOAD_KEY].encode('ascii')),
) )
client_id = token['iss'].encode('utf-8') client_id = unicode2str(token['iss'])
if self.__getWebServiceValue( if self.__getWebServiceValue(
client_id=client_id, client_id=client_id,
).getSessionVersion( ).getSessionVersion(
...@@ -452,8 +453,8 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin): ...@@ -452,8 +453,8 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin):
return return
return { return {
_PRIVATE_EXTRACTED_KEY: ( _PRIVATE_EXTRACTED_KEY: (
token_payload[JWT_PAYLOAD_USER_ID_KEY].encode('utf-8'), unicode2str(token_payload[JWT_PAYLOAD_USER_ID_KEY]),
token_payload[JWT_PAYLOAD_USER_CAPTION_KEY].encode('utf-8'), unicode2str(token_payload[JWT_PAYLOAD_USER_CAPTION_KEY]),
), ),
_PRIVATE_TOKEN_KEY: (access_token, refresh_token), _PRIVATE_TOKEN_KEY: (access_token, refresh_token),
_PRIVATE_CLIENT_ID: client_id, _PRIVATE_CLIENT_ID: client_id,
...@@ -467,10 +468,10 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin): ...@@ -467,10 +468,10 @@ class ERP5OAuth2ResourceServerPlugin(BasePlugin):
] or '', ] or '',
}, },
_PRIVATE_GROUP_LIST_KEY: tuple( _PRIVATE_GROUP_LIST_KEY: tuple(
x.encode('utf-8') for x in token_payload[JWT_PAYLOAD_GROUP_LIST_KEY] unicode2str(x) for x in token_payload[JWT_PAYLOAD_GROUP_LIST_KEY]
), ),
_PRIVATE_ROLE_LIST_KEY: tuple( _PRIVATE_ROLE_LIST_KEY: tuple(
x.encode('utf-8') for x in token_payload[JWT_PAYLOAD_ROLE_LIST_KEY] unicode2str(x) for x in token_payload[JWT_PAYLOAD_ROLE_LIST_KEY]
), ),
} }
......
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