Source code for privacyidea.lib.eventhandler.federationhandler
# 2017-08-23 Cornelius Kölbel <cornelius.koelbel@netknights.it>
# Initial federation handler
#
# License: AGPLv3
# (c) 2017. 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 for privacyIDEA federations.
Requests can be forwarded to other privacyIDEA servers.
"""
from privacyidea.lib.eventhandler.base import BaseEventHandler
from privacyidea.lib.privacyideaserver import (get_privacyideaservers,
get_privacyideaserver)
from privacyidea.lib import _
from privacyidea.lib.utils import is_true
import json
import logging
import requests
from flask import current_app
log = logging.getLogger(__name__)
[docs]class ACTION_TYPE(object):
"""
Allowed actions
"""
FORWARD = "forward"
[docs]class FederationEventHandler(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 eventhandling definitions
"""
identifier = "Federation"
description = "This event handler can forward the request to other " \
"privacyIDEA servers"
# TODO: Do we need to change the federation handler this way, that it does only pre-handling?
@property
def actions(cls):
"""
This method returns a dictionary of allowed actions and possible
options in this handler module.
:return: dict with actions
"""
pi_servers = [x.config.identifier for x in get_privacyideaservers()]
actions = {ACTION_TYPE.FORWARD:
{"privacyIDEA":
{"type": "str",
"required": True,
"value": pi_servers,
"description": _("The remote/child privacyIDEA "
"Server.")
},
"realm":
{"type": "str",
"description": _("Change the realm name to a "
"realm on the child privacyIDEA "
"system.")
},
"resolver":
{"type": "str",
"description": _("Change the resolver name to a "
"resolver on the child "
"privacyIDEA system.")
},
"forward_client_ip":
{"type": "bool",
"description": _("Forward the client IP to the "
"child privacyIDEA server. "
"Otherwise this server will "
"be the client.")
},
"forward_authorization_token":
{"type": "bool",
"description": _("Forward the authorization header. "
"This allows to also forward request like "
"token- and system-requests.")
}
}
}
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:
"""
g = options.get("g")
request = options.get("request")
handler_def = options.get("handler_def")
handler_options = handler_def.get("options", {})
if action == ACTION_TYPE.FORWARD:
server_def = handler_options.get("privacyIDEA")
pi_server = get_privacyideaserver(server_def)
# the new url is the configured server url and the original path
url = pi_server.config.url + request.path
# We use the original method
method = request.method
tls = pi_server.config.tls
# We also transfer the original payload
data = request.all_data
if is_true(handler_options.get("forward_client_ip", False)):
data["client"] = g.client_ip
if handler_options.get("realm"):
data["realm"] = handler_options.get("realm")
if handler_options.get("resolver"):
data["resolver"] = handler_options.get("resolver")
log.info("Sending {0} request to {1!r}".format(method, url))
requestor = None
params = None
headers = {}
# We need to pass an authorization header if we forward administrative requests
if is_true(handler_options.get("forward_authorization_token", False)):
auth_token = request.headers.get('PI-Authorization')
if not auth_token:
auth_token = request.headers.get('Authorization')
headers["PI-Authorization"] = auth_token
if method.upper() == "GET":
params = data
data = None
requestor = requests.get
elif method.upper() == "POST":
requestor = requests.post
elif method.upper() == "DELETE":
requestor = requests.delete
if requestor:
r = requestor(url, params=params, data=data,
headers=headers, verify=tls)
# convert requests Response to werkzeug Response
response_dict = json.loads(r.text)
if "detail" in response_dict:
detail = response_dict.setdefault("detail", {})
# In case of exceptions we may not have a detail
detail["origin"] = url
# We will modify the response!
# We can not use flask.jsonify(response_dict) here, since we
# would work outside of application context!
options["response"] = current_app.response_class(json.dumps(response_dict),
mimetype="application/json")
options["response"].status_code = r.status_code
else:
log.warning("Unsupported method: {0!r}".format(method))
return True