From 7957f8d19d6693de52c758cad76cd61480ec336f Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Thu, 26 Feb 2015 15:50:37 -0500 Subject: [PATCH] Add base REST provider framework classes These classes handle mounting the REST plugins. The starting mount point is: /idp/rest/providers https://fedorahosted.org/ipsilon/ticket/26 Signed-off-by: Rob Crittenden Reviewed-by: Simo Sorce --- ipsilon/providers/common.py | 41 +++++++++++++++++ ipsilon/rest/__init__.py | 0 ipsilon/rest/common.py | 89 +++++++++++++++++++++++++++++++++++++ ipsilon/rest/providers.py | 11 +++++ 4 files changed, 141 insertions(+) create mode 100644 ipsilon/rest/__init__.py create mode 100644 ipsilon/rest/common.py create mode 100644 ipsilon/rest/providers.py diff --git a/ipsilon/providers/common.py b/ipsilon/providers/common.py index 4206387..dff302d 100644 --- a/ipsilon/providers/common.py +++ b/ipsilon/providers/common.py @@ -19,6 +19,7 @@ from ipsilon.util.log import Log from ipsilon.util.plugin import PluginInstaller, PluginLoader from ipsilon.util.plugin import PluginObject, PluginConfig from ipsilon.util.page import Page +from ipsilon.rest.common import RestPage import cherrypy @@ -153,3 +154,43 @@ class ProvidersInstall(object): def __init__(self): pi = PluginInstaller(ProvidersInstall, FACILITY) self.plugins = pi.get_plugins() + + +class RestProviderBase(RestPage): + + def __init__(self, site, config): + super(RestProviderBase, self).__init__(site) + self.plugin_name = config.name + self.cfg = config + + def GET(self, *args, **kwargs): + raise cherrypy.HTTPError(501) + + def POST(self, *args, **kwargs): + raise cherrypy.HTTPError(501) + + def DELETE(self, *args, **kwargs): + raise cherrypy.HTTPError(501) + + def PUT(self, *args, **kwargs): + raise cherrypy.HTTPError(501) + + def root(self, *args, **kwargs): + method = cherrypy.request.method + + preop = getattr(self, 'pre_%s' % method, None) + if preop and callable(preop): + preop(*args, **kwargs) + + op = getattr(self, method, self.GET) + if callable(op): + return op(*args, **kwargs) + else: + raise cherrypy.HTTPError(405) + + def _debug(self, fact): + superfact = '%s: %s' % (self.plugin_name, fact) + super(RestProviderBase, self)._debug(superfact) + + def _audit(self, fact): + cherrypy.log('%s: %s' % (self.plugin_name, fact)) diff --git a/ipsilon/rest/__init__.py b/ipsilon/rest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ipsilon/rest/common.py b/ipsilon/rest/common.py new file mode 100644 index 0000000..4103e8e --- /dev/null +++ b/ipsilon/rest/common.py @@ -0,0 +1,89 @@ +# Copyright (C) 2015 Ipsilon Contributors see COPYING for license + +import cherrypy +import json +from functools import wraps +from ipsilon.util.endpoint import Endpoint + + +def jsonout(func): + """ + JSON output decorator. Does not handle binary data. + """ + @wraps(func) + def wrapper(*args, **kw): + value = func(*args, **kw) + cherrypy.response.headers["Content-Type"] = \ + "application/json;charset=utf-8" + return json.dumps(value, sort_keys=True, indent=2) + + return wrapper + + +def rest_error(status=500, message=''): + """ + Create a REST error response. + + The assumption is that the jsonout wrapper will handle converting + the response to JSON. + """ + cherrypy.response.status = status + cherrypy.response.headers['Content-Type'] = 'application/json' + return {'status': status, 'message': message} + + +class RestPage(Endpoint): + + def __init__(self, *args, **kwargs): + super(RestPage, self).__init__(*args, **kwargs) + self.default_headers.update({ + 'Cache-Control': 'no-cache, must-revalidate', + 'Pragma': 'no-cache', + 'Expires': 'Thu, 01 Dec 1994 16:00:00 GMT', + }) + self.auth_protect = True + + +class RestPlugins(RestPage): + def __init__(self, name, site, parent, facility, ordered=True): + super(RestPlugins, self).__init__(site) + self._master = parent + self.name = name + self.title = '%s plugins' % name + self.url = '%s/%s' % (parent.url, name) + self.facility = facility + self.template = None + self.order = None + parent.add_subtree(name, self) + + for plugin in self._site[facility].available: + obj = self._site[facility].available[plugin] + if hasattr(obj, 'rest'): + cherrypy.log.error('Rest plugin: %s' % plugin) + obj.rest.mount(self) + + def root_with_msg(self, message=None, message_type=None, changed=None): + return None + + def root(self, *args, **kwargs): + return self.root_with_msg() + + +class Rest(RestPage): + + def __init__(self, site, mount): + super(Rest, self).__init__(site) + self.title = None + self.mount = mount + self.url = '%s/%s' % (self.basepath, mount) + self.menu = [self] + + @jsonout + def root(self, *args, **kwargs): + return rest_error(404, 'Not Found') + + def add_subtree(self, name, page): + self.__dict__[name] = page + + def del_subtree(self, name): + del self.__dict__[name] diff --git a/ipsilon/rest/providers.py b/ipsilon/rest/providers.py new file mode 100644 index 0000000..b32efc5 --- /dev/null +++ b/ipsilon/rest/providers.py @@ -0,0 +1,11 @@ +# Copyright (C) 2015 Ipsilon Contributors see COPYING for license + +from ipsilon.rest.common import RestPlugins +from ipsilon.providers.common import FACILITY + + +class RestProviderPlugins(RestPlugins): + def __init__(self, site, parent): + super(RestProviderPlugins, self).__init__('providers', site, parent, + FACILITY, ordered=False) + self.title = 'Identity Providers' -- 2.20.1