Commit 6f2014a7 authored by Titouan Soulard's avatar Titouan Soulard

erp5_api_style: rewrite most of `jIOWebSection`

parent b815419f
# -*- 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,86 +27,100 @@
##############################################################################
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 OFS.Traversable 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 jIOError(object):
"""
Describes a JSON error, to avoid ERP5 default error pages.
Publishable but non-Persistent (since errors are logged in a separate module) and without Acquisition.
"""
def __init__(self, context, portal, request):
self.context = context
self.portal = portal
self.request = request
def __init__(self, underlyingError):
HTTPClientError.__init__(self)
self.underlyingError = underlyingError
# Method not expected to be called without context, return Bad Request
if not self.context:
self.context = {
"error_code": 400,
"error_name": "BadRequest"
}
self.context["text_content"] = request.get("BODY")
# Used for debugging, especially in tests
def __str__(self):
return str(self.underlyingError)
return str(self.context)
def __bytes__(self):
return bytes(self.underlyingError)
def __call__(self):
portal = self.portal
response = self.request.response
def convertTojIOAPICall(function):
"""
Wrap the method to create a log entry for each invocation to the zope logger
"""
def wrapper(self, *args, **kwd):
response.setHeader("Content-Type", "application/json")
response.setStatus(self.context["error_code"], lock=True)
# Skin used to allow replacement and because Manager proxy role is needed
error_log = portal.ERP5Site_logApiErrorAndReturn(**self.context)
response.setBody(error_log, lock=True)
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"
)
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"
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
return '%s' % retval
wrapper.__doc__ = function.__doc__
return wrapper
except Exception as e:
error_context = {
"error_code": 500,
"error_message": str(e),
"error_name": "InternalError"
}
# This NotFound catches instance of objects not found inside API call
if isinstance(e, NotFound):
error_context["error_code"] = 404
error_context["error_name"] = "NotFound"
elif isinstance(e, Unauthorized):
error_context["error_code"] = 403
error_context["error_name"] = "Unauthorized"
# Avoid informations leak when Unauthorized
del error_context["error_message"]
return jIOError(error_context, self.web_section.getPortalObject(), self.web_section.REQUEST)
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
......@@ -122,46 +135,23 @@ class jIOWebSection(WebSection):
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, '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, '__bobo_traverse__')
def __bobo_traverse__(self, request, name):
if name in ALLOWED_MODES:
return jIOMethod(name, self)
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"
}
document = jIOError(error_context, self.getPortalObject(), self.REQUEST)
return document
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