Source code for privacyidea.lib.eventhandler.responsemangler
# 2019-09-14 Cornelius Kölbel <cornelius.koelbel@netknights.it>
# Initial writup
#
# License: AGPLv3
# (c) 2019. Cornelius Kölbel
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
# License as published by the Free Software Foundation; either
# version 3 of the License, or any later version.
#
# This code 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 AFFERO GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#
__doc__ = """This is the event handler module that can mangle the JSON response.
We can add or delete key or even subtrees in the JSON response of a request.
The key is identified by a JSON Pointer
(see https://tools.ietf.org/html/rfc6901)
"""
from privacyidea.lib.eventhandler.base import BaseEventHandler
from privacyidea.lib.utils import is_true
from privacyidea.lib import _
import json
import logging
log = logging.getLogger(__name__)
[docs]class ACTION_TYPE(object):
"""
Allowed actions
"""
SET = "set"
DELETE = "delete"
[docs]class ResponseManglerEventHandler(BaseEventHandler):
"""
An Eventhandler needs to return a list of actions, which it can handle.
It also returns a list of allowed action and conditions
It returns an identifier, which can be used in the eventhandlig definitions
"""
identifier = "ResponseMangler"
description = "This event handler can mangle the JSON response."
@property
def allowed_positions(cls):
"""
This returns the allowed positions of the event handler definition.
The ResponseMangler can only be located at the "post" position
:return: list of allowed positions
"""
return ["post"]
@property
def actions(cls):
"""
This method returns a dictionary of allowed actions and possible
options in this handler module.
:return: dict with actions
"""
actions = {ACTION_TYPE.DELETE:
{"JSON pointer":
{"type": "str",
"required": True,
"description": _("The JSON pointer (key) that should be deleted. Please "
"specify in the format '/detail/message'.")}
},
ACTION_TYPE.SET:
{"JSON pointer":
{"type": "str",
"required": True,
"description": _("The JSON pointer (key) that should be set. "
"Please specify in the format '/detail/message'.")
},
"type":
{"type": "str",
"required": True,
"description": _("The type of the value."),
"value": ["string", "integer", "bool"]
},
"value":
{"type": "str",
"required": True,
"description": _("The value of the JSON key that should be set.")
}
}
}
return actions
[docs] def do(self, action, options=None):
"""
This method executes the defined action in the given event.
:param action:
:param options: Contains the flask parameters g, request, response
and the handler_def configuration
:type options: dict
:return:
"""
ret = True
response = options.get("response")
try:
content = self._get_response_content(response)
except Exception:
# The body could not be decoded to JSON. Actually this would be
# a BadRequest, but we refrain from importing from werkzeug, here.
log.info("The body could not be decoded to JSON.")
# Early exist
return ret
handler_def = options.get("handler_def")
handler_options = handler_def.get("options", {})
json_pointer = handler_options.get("JSON pointer", "")
value = handler_options.get("value")
type = handler_options.get("type")
comp = json_pointer.split("/")
comp = [x for x in comp if x]
if action.lower() == ACTION_TYPE.DELETE:
try:
if len(comp) == 1:
del(content[comp[0]])
elif len(comp) == 2:
del(content[comp[0]][comp[1]])
elif len(comp) == 3:
del (content[comp[0]][comp[1]][comp[2]])
else:
log.warning("JSON pointer length of {0!s} not supported.".format(len(comp)))
options.get("response").data = json.dumps(content)
except KeyError:
log.warning("Can not delete response JSON Pointer {0!s}.".format(json_pointer))
elif action.lower() == ACTION_TYPE.SET:
try:
if type == "integer":
value = int(value)
elif type == "bool":
value = is_true(value)
except ValueError:
log.warning("Failed to convert value")
if len(comp) == 1:
content[comp[0]] = value
elif len(comp) == 2:
content[comp[0]] = content.get(comp[0]) or {}
content[comp[0]][comp[1]] = value
elif len(comp) == 3:
content[comp[0]] = content.get(comp[0]) or {}
content[comp[0]][comp[1]] = content[comp[0]].get(comp[1]) or {}
content[comp[0]][comp[1]][comp[2]] = value
else:
log.warning("JSON pointer of length {0!s} not supported.".format(len(comp)))
options.get("response").data = json.dumps(content)
return ret