diff options
Diffstat (limited to 'smartproxy/ipa-smartproxy.py')
-rw-r--r-- | smartproxy/ipa-smartproxy.py | 399 |
1 files changed, 0 insertions, 399 deletions
diff --git a/smartproxy/ipa-smartproxy.py b/smartproxy/ipa-smartproxy.py deleted file mode 100644 index 1e5418636..000000000 --- a/smartproxy/ipa-smartproxy.py +++ /dev/null @@ -1,399 +0,0 @@ -# Authors: -# Rob Crittenden <rcritten@redhat.com> -# -# Copyright (C) 2014 Red Hat -# see file 'COPYING' for use and warranty information -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import sys -sys.stdout = sys.stderr - -import cherrypy -import os -import json -from functools import wraps -import traceback as tb_internal -from cherrypy import response -from ipalib import api -from ipalib import errors -from ipaplatform.paths import paths -from ipalib.request import context -from ipalib.rpc import json_encode_binary -from ipapython.version import VERSION, API_VERSION -from ipapython.ipa_log_manager import root_logger - - -def jsonout(func): - '''JSON output decorator''' - @wraps(func) - def wrapper(*args, **kw): - value = func(*args, **kw) - response.headers["Content-Type"] = "application/json;charset=utf-8" - data = json_encode_binary(value, version=API_VERSION) - return json.dumps(data, sort_keys=True, indent=2) - - return wrapper - - -def handle_error(status, message, traceback, version): - """ - Return basic messages to user and log backtrace in case of 500 - error. - """ - if status.startswith('500'): - root_logger.error(message) - root_logger.error(tb_internal.format_exc()) - - resp = cherrypy.response - resp.headers['Content-Type'] = 'application/json' - return json.dumps({'status': status, 'message': message}) - - -def convert_unicode(value): - """ - IPA requires all incoming values to be unicode. Recursively - convert the values. - """ - if not isinstance(value, basestring): - return value - - if value is not None: - return unicode(value) - else: - return None - - -def raise_rest_exception(e): - """ - Raise a REST-specific exception. - """ - try: - raise e - except (errors.DuplicateEntry, errors.DNSNotARecordError, - errors.ValidationError, errors.ConversionError,) as e: - raise IPAError( - status=400, - message=e - ) - except errors.ACIError as e: - raise IPAError( - status=401, - message=e - ) - except errors.NotFound as e: - raise IPAError( - status=404, - message=e - ) - except Exception as e: - raise IPAError( - status=500, - message=e - ) - - -def popifnone(params, option): - """ - If option, a string, exists in params, a dict, and is None then - remove it from the dict. - - No return value. The dict is updated in-place if necessary. - """ - if params.get(option) is None: - params.pop(option, None) - - -def Command(command, *args, **options): - """ - Execute an IPA command with the given arguments and options. - - This doesn't care what the options are, it passes them along to - the IPA API. The exceptions are: - - :param nomaskexceptions: boolean to decide if we raise the real IPA - exception or a REST-specific exceptioon. - """ - if (cherrypy.request.config.get('local_only', False) and - cherrypy.request.remote.ip not in ['::1', '127.0.0.1']): - raise IPAError( - status=401, - message="Not a local request" - ) - - try: - if not api.Backend.rpcclient.isconnected(): - api.Backend.rpcclient.connect() - except errors.CCacheError as e: - root_logger.info('Connection failed: %s', e) - raise IPAError( - status=401, - message=e - ) - - # IPA wants all its strings as unicode - args = map(lambda v: convert_unicode(v), args) - options = dict(zip(options, map(convert_unicode, options.values()))) - - nomaskexception = options.pop('nomaskexception', False) - - api.Command[command].args_options_2_params(*args, **options) - try: - return api.Command[command](*args, **options)['result'] - except Exception as e: - if not nomaskexception: - raise_rest_exception(e) - else: - # The caller needs to be able to handle IPA-specific - # exceptions. - raise e - - -@jsonout -def GET(command, *args, **options): - return Command(command, *args, **options) - - -@jsonout -def POST(status, command, *args, **options): - cherrypy.response.status = status - return Command(command, *args, **options) - - -@jsonout -def DELETE(command, *args, **options): - return Command(command, *args, **options) - - -class IPAError(cherrypy.HTTPError): - """ - Return errors in IPA-style json. - - Local errors are treated as strings so do not include the code and - name attributes within the error dict. - - This is not padded for IE. - """ - - def set_response(self): - response = cherrypy.serving.response - - cherrypy._cperror.clean_headers(self.code) - - # In all cases, finalize will be called after this method, - # so don't bother cleaning up response values here. - response.status = self.status - - if isinstance(self._message, Exception): - try: - code = self._message.errno - except AttributeError: - code = 0 - error = {'code': code, - 'message': self._message.message, - 'name': self._message.__class__.__name__} - elif isinstance(self._message, basestring): - error = {'message': self._message} - else: - error = {'message': - 'Unable to handle error message type %s' % - type(self._message)} - - principal = getattr(context, 'principal', None) - response.headers["Content-Type"] = "application/json;charset=utf-8" - response.body = json.dumps({'error': error, - 'id': 0, - 'principal': principal, - 'result': None, - 'version': VERSION}, - sort_keys=True, indent=2) - - -class Host(object): - """ - Manage IPA host objects - """ - - exposed = True - - def GET(self, fqdn=None): - - if fqdn is None: - command = 'host_find' - else: - command = 'host_show' - - return GET(command, fqdn) - - def POST(self, hostname, description=None, - macaddress=None, userclass=None, ip_address=None, - password=None, rebuild=None): - cmd = 'host_add' - - if password is None: - random = True - else: - random = False - - params = {'description' : description, - 'random' : random, - 'macaddress' : macaddress, - 'userclass' : userclass, - 'userpassword' : password} - - # If the host is being rebuilt, disable it in order to revoke - # existing certs, keytabs, etc. - try: - Command('host_show', hostname, nomaskexception=True) - except errors.NotFound: - # Adding a new host - status = 201 - params['ip_address'] = ip_address - params['force'] = True - except Exception as e: - raise_rest_exception(e) - else: - if ip_address is not None: - raise IPAError( - status=400, - message='IP address must be changed in DNS' - ) - cmd = 'host_mod' - - # Foreman doesn't pass these in on update so drop them otherwise - # IPA will consider these as being set to None which deletes them. - popifnone(params, 'description') - popifnone(params, 'macaddress') - popifnone(params, 'userclass') - popifnone(params, 'userpassword') - status = 200 - if rebuild: - root_logger.info("Attempting to disable %s", hostname) - try: - Command('host_disable', hostname, nomaskexception=True) - except errors.AlreadyInactive as e: - pass - else: - raise e - return POST(status, cmd, hostname, **params) - - def DELETE(self, fqdn): - # The host-del behavior is a bit off due to - # https://fedorahosted.org/freeipa/ticket/4329 - # A NotFound is returned if the user can't read DNS. - # Do a GET to see if the host exists, then we can more blindly - # try the delete. - - # If the GET is ok then we know there is a host, though this is a - # bit racy. - GET('host_show', fqdn) - - remove_dns = cherrypy.request.config.get('remove_dns', False) - - return DELETE('host_del', fqdn, updatedns=remove_dns) - - -class Hostgroup(object): - """ - Manage IPA hostgroup objects - """ - - exposed = True - - def GET(self, name=None): - - if name is None: - command = 'hostgroup_find' - else: - command = 'hostgroup_show' - - return GET(command, name) - - def POST(self, name=None, description=None): - cherrypy.response.status = 201 - return POST(201, 'hostgroup_add', name, - description=description,) - - def DELETE(self, name): - return DELETE('hostgroup_del', name) - - -class Features(object): - exposed = True - - def GET(self): - return '["realm"]' - - -def start(config=None): - # Set the umask so only the owner can read the log files - old_umask = os.umask(077) - - cherrypy.tree.mount( - Features(), '/features', - {'/': - {'request.dispatch': cherrypy.dispatch.MethodDispatcher()} - } - ) - cherrypy.tree.mount( - Host(), '/ipa/smartproxy/host', - {'/': - {'request.dispatch': cherrypy.dispatch.MethodDispatcher()} - } - ) - cherrypy.tree.mount( - Hostgroup(), '/ipa/smartproxy/hostgroup', - {'/': - {'request.dispatch': cherrypy.dispatch.MethodDispatcher()} - } - ) - - # Register the realm for requests from Foreman - root_logger.info("Mounting /realm/%s", api.env.realm) - cherrypy.tree.mount( - Host(), '/realm/%s' % api.env.realm, - {'/': - {'request.dispatch': cherrypy.dispatch.MethodDispatcher()} - } - ) - - for c in config or []: - try: - cherrypy.config.update(c) - except (IOError, OSError) as e: - root_logger.error("Exception trying to load %s: %s", c, e) - return 1 - - # Log files are created, reset umask - os.umask(old_umask) - - cherrypy.config.update({'error_page.500': handle_error}) - - return 0 - -def application(environ, start_response): - root_logger.info("IPA smartproxy WSGI start") - return cherrypy.tree(environ, start_response) - -wsgi_config = {'environment': 'embedded', - 'log.screen': False, - 'show_tracebacks': False, - 'engine.autoreload_on': False -} - -api.bootstrap(context='ipasmartproxy', log=paths.DEV_NULL) -api.finalize() - -cherrypy.config.update(wsgi_config) -start([paths.IPA_SMARTPROXY_CONF]) |