Commit bc17aafd authored by Titouan Soulard's avatar Titouan Soulard

erp5_api_style: rewrite most of `jIOWebSection`

parent 0887cfcf
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2016 Nexedi SA and Contributors. All Rights Reserved. # Copyright (c) 2024 Nexedi SA and Contributors. All Rights Reserved.
# Cédric Le Ninivin <cedric.leninivin@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
...@@ -28,140 +27,118 @@ ...@@ -28,140 +27,118 @@
############################################################################## ##############################################################################
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from AccessControl import Unauthorized
from Acquisition import aq_inner from Acquisition import aq_inner
from OFS.Traversable import NotFound
from erp5.component.document.WebSection import WebSection from zExceptions import NotFound, Unauthorized
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from zExceptions import HTTPClientError
from zLOG import LOG, INFO from erp5.component.mixin.DocumentExtensibleTraversableMixin import DocumentExtensibleTraversableMixin
from erp5.component.document.WebSection import WebSection
MARKER = [] MARKER = []
ALLOWED_MODES = ["put", "get", "post", "allDocs"]
# Redefine an Unauthorized error to avoid Zope redirecting the user to the main ERP5 login form class jIOAPITraverseErrorWrapper(object):
class jIOUnauthorized(HTTPClientError): """
errmsg = 'Unauthorized' JSON error object, to avoid ERP5 default error pages.
status = 401 Publishable but non-Persistent and without Acquisition.
"""
def __init__(self, underlyingError): def __init__(self, error_context, portal):
HTTPClientError.__init__(self) self.error_context = error_context
self.underlyingError = underlyingError self.portal = portal
# Used for debugging, especially in tests
def __str__(self): def __str__(self):
return str(self.underlyingError) return str(self.error_context)
def __call__(self):
portal = self.portal
# Skin used to allow replacement and because Manager proxy role is needed
portal.ERP5Site_logApiErrorAndReturn(**self.error_context)
def __bytes__(self):
return bytes(self.underlyingError)
def convertTojIOAPICall(function): class jIOMethod(object):
""" """
Wrap the method to create a log entry for each invocation to the zope logger Represents one of the four possible jIO methods.
Publishable but non-Persistent and without Acquisition.
XXX: Acquisition might be suitable here
""" """
def wrapper(self, *args, **kwd):
""" def __init__(self, mode_name, web_section):
Log the call, and the result of the call super(jIOMethod, self).__init__()
""" self.mode_name = mode_name
assert(self.REQUEST.REQUEST_METHOD == "POST") self.web_section = web_section
def __call__(self):
self.web_section.REQUEST.response.setHeader("Content-Type", "application/json")
try: try:
self.REQUEST.response.setHeader("Content-Type", "application/json") return self.web_section.ERP5Site_asjIOStyle(
retval = function(self, *args, **kwd) mode=self.mode_name,
except Unauthorized, e: text_content=self.web_section.REQUEST.get("BODY"),
body = self.ERP5Site_logApiErrorAndReturn( data_dict=None,
error_code="401",
error_message=str(e),
error_name="Unauthorized"
)
self.REQUEST.response.setBody(body, lock=True)
raise jIOUnauthorized(e)
except NotFound, e:
LOG('jIOWebSection', INFO, 'Converting NotFound to NotFound error mesage in JSON,',
error=True)
body = self.ERP5Site_logApiErrorAndReturn(
error_code="404",
error_message=str(e),
error_name="NotFound"
)
self.REQUEST.response.setBody(body, lock=True)
raise
except:
LOG('jIOWebSection', INFO, 'Converting Error to InternalError message in JSON,',
error=True)
body = self.ERP5Site_logApiErrorAndReturn(
error_code="500",
error_message="Internal Server Error",
error_name="InternalError"
) )
self.REQUEST.response.setBody(body, lock=True) except Exception as e:
raise error_context = {
"error_code": 500,
return '%s' % retval "error_name": "API-INTERNAL-ERROR",
wrapper.__doc__ = function.__doc__ "error_message": str(e)
return wrapper }
# This NotFound catches instance of objects not found inside API call
if isinstance(e, NotFound):
error_context["error_code"] = 404
error_context["error_name"] = "API-NOT-FOUND"
elif isinstance(e, Unauthorized):
error_context["error_code"] = 403
error_context["error_name"] = "API-UNAUTHORIZED"
# Avoid information leak when Unauthorized
del error_context["error_message"]
# Skin used to allow replacement and because Manager proxy role is needed
self.web_section.ERP5Site_logApiErrorAndReturn(**error_context)
class jIOWebSection(WebSection): class jIOWebSection(WebSection):
""" portal_type = "jIO Web Section"
This Web Section is a wrapper to jIO to pass content in the body
"""
portal_type = 'jIO Web Section' # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
security.declareProtected(Permissions.AccessContentsInformation, 'getLayoutProperty') security.declareProtected(Permissions.AccessContentsInformation, "getLayoutProperty")
def getLayoutProperty(self, key, default=None): def getLayoutProperty(self, key, default=None):
""" """
A simple method to get a property of the current by A simple method to get a property of the current by
acquiring it from the current section or its parents. acquiring it from the current section or its parents.
""" """
section = aq_inner(self) section = aq_inner(self)
while section.getPortalType() in ('Web Section', 'Web Site', 'Static Web Section', 'Static Web Site', while section.getPortalType() in ("Web Section", "Web Site", "Static Web Section", "Static Web Site", "jIO Web Section"):
'jIO Web Section'):
result = section.getProperty(key, MARKER) result = section.getProperty(key, MARKER)
if result not in (MARKER, None): if result not in (MARKER, None):
return result return result
section = section.aq_parent section = section.aq_parent
return default return default
@convertTojIOAPICall security.declareProtected(Permissions.View, "_bobo_traverse__")
def _asjIOStyle(self, mode, text_content="", data_dict=None): def __bobo_traverse__(self, request, name):
return self.ERP5Site_asjIOStyle( if name in ALLOWED_MODES:
mode=mode, return jIOMethod(name, self)
text_content=text_content,
data_dict=data_dict,
)
security.declareProtected(Permissions.View, 'get')
def get(self): #pylint:disable=arguments-differ
"""
Taken from WebSection Bobo Traverse, the difference is that
__bobo_traverse__ from DocumentExtensibleTraversableMixin is not called
"""
# Register current web site physical path for later URL generation
return self._asjIOStyle(mode="get", text_content=self.REQUEST.get('BODY'))
security.declareProtected(Permissions.View, 'post') document = None
def post(self): try:
""" # Inheritance as follows: jIOWebSection <| WebSection <| (Domain, DocumentExtensibleTraversableMixin)
Taken from WebSection Bobo Traverse, the difference is that # Use DocumentExtensibleTraversableMixin traversal to avoid ERP5 HTML 404 page.
__bobo_traverse__ from DocumentExtensibleTraversableMixin is not called document = DocumentExtensibleTraversableMixin.__bobo_traverse__(self, request, name)
""" # This NotFound catches objects not found during traversal
# Register current web site physical path for later URL generation except NotFound as e:
return self._asjIOStyle(mode="post", text_content=self.REQUEST.get('BODY')) error_context = {
"error_code": 404,
security.declareProtected(Permissions.View, 'put') "error_message": str(e),
def put(self): "error_name": "NotFound",
""" "text_content": request.get("BODY")
Taken from WebSection Bobo Traverse, the difference is that }
__bobo_traverse__ from DocumentExtensibleTraversableMixin is not called document = jIOAPITraverseErrorWrapper(error_context, self.getPortalObject())
"""
# Register current web site physical path for later URL generation return document
return self._asjIOStyle(mode="put", text_content=self.REQUEST.get('BODY'))
security.declareProtected(Permissions.View, 'allDocs')
def allDocs(self):
"""
Taken from WebSection Bobo Traverse, the difference is that
__bobo_traverse__ from DocumentExtensibleTraversableMixin is not called
"""
# Register current web site physical path for later URL generation
return self._asjIOStyle(mode="allDocs", text_content=self.REQUEST.get('BODY'))
...@@ -7,7 +7,6 @@ error = context.getPortalObject().error_record_module.newContent( ...@@ -7,7 +7,6 @@ error = context.getPortalObject().error_record_module.newContent(
description=str(error_message), description=str(error_message),
text_content=str(text_content) text_content=str(text_content)
) )
container.REQUEST.RESPONSE.setStatus(error_code, lock=True)
# We follow here Paypal api guideline # We follow here Paypal api guideline
# https://github.com/paypal/api-standards/blob/master/api-style-guide.md#error-schema # https://github.com/paypal/api-standards/blob/master/api-style-guide.md#error-schema
error_dict = { error_dict = {
...@@ -24,4 +23,10 @@ if error_link: ...@@ -24,4 +23,10 @@ if error_link:
if detail_list: if detail_list:
error_dict["details"] = detail_list error_dict["details"] = detail_list
error.setDescription(str(error_message) + "\n".join([str(x) for x in detail_list])) error.setDescription(str(error_message) + "\n".join([str(x) for x in detail_list]))
return json.dumps(error_dict, indent=2)
serialized_error = json.dumps(error_dict, indent=2)
context.REQUEST.response.setHeader("Content-Type", "application/json")
context.REQUEST.response.setStatus(error_code, lock=True)
context.REQUEST.response.setBody(serialized_error, lock=True)
return serialized_error
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