Commit bc17aafd authored by Titouan Soulard's avatar Titouan Soulard

erp5_api_style: rewrite most of `jIOWebSection`

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