Exemplo n.º 1
0
 def __init__(self, *args, **kwargs):
     super(Secrets, self).__init__(*args, **kwargs)
     self.allowed_keytypes = ['simple']
     if self.config and 'allowed_keytypes' in self.config:
         kt = self.config['allowed_keytypes'].split()
         self.allowed_keytypes = kt
     self._validator = Validator(self.allowed_keytypes)
Exemplo n.º 2
0
class Secrets(HTTPConsumer):
    def __init__(self, *args, **kwargs):
        super(Secrets, self).__init__(*args, **kwargs)
        self.allowed_keytypes = ['simple']
        if self.config and 'allowed_keytypes' in self.config:
            kt = self.config['allowed_keytypes'].split()
            self.allowed_keytypes = kt
        self._validator = Validator(self.allowed_keytypes)
        self._auditlog = log.audit_log(self.config)

    def _db_key(self, trail):
        if len(trail) < 2:
            raise HTTPError(403)
        return os.path.join('keys', *trail)

    def _db_container_key(self, default, trail):
        f = None
        if len(trail) > 1:
            f = self._db_key(trail)
        elif len(trail) == 1 and trail[0] != '':
            raise HTTPError(403)
        elif default is None:
            # No dfault namespace, fail
            raise HTTPError(403)
        else:
            # Use the default namespace
            f = self._db_key([default, ''])
        return f

    def _parse(self, request, value, name):
        return self._validator.parse(request, value, name)

    def _parent_exists(self, default, trail):
        # check that the containers exist
        basename = self._db_container_key(trail[0], '')
        try:
            keylist = self.root.store.list(basename)
        except CSStoreError:
            raise HTTPError(500)

        # create default namespace if it is the only missing piece
        if keylist is None and len(trail) == 2 and default == trail[0]:
            container = self._db_container_key(default, '')
            self.root.store.set(container, '')
            return True

        # check if any parent is missing
        for n in range(1, len(trail)):
            c = self._db_key(trail[:n] + [''])
            if c not in keylist:
                return False

        return True

    def GET(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            self._list(trail, request, response)
        else:
            self._get_key(trail, request, response)

    def PUT(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            raise HTTPError(405)
        else:
            self._set_key(trail, request, response)

    def DELETE(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0:
            raise HTTPError(405)
        if trail[-1] == '':
            self._destroy(trail, request, response)
        else:
            self._del_key(trail, request, response)

    def POST(self, request, response):
        trail = request.get('trail', [])
        if len(trail) > 0 and trail[-1] == '':
            self._create(trail, request, response)
        else:
            raise HTTPError(405)

    def _list(self, trail, request, response):
        default = request.get('default_namespace', None)
        basename = self._db_container_key(default, trail)
        userfilter = request.get('query', dict()).get('filter', '')
        try:
            keylist = self.root.store.list(basename + userfilter)
            if keylist is None:
                raise HTTPError(404)
            # remove the base container itself
            output = list()
            for k in keylist:
                if k == basename:
                    continue
                # strip away the internal prefix for storing keys
                name = k[len('keys/'):]
                output.append(name)
            response['output'] = json.dumps(output)
        except CSStoreError:
            raise HTTPError(500)

    def _create(self, trail, request, response):
        default = request.get('default_namespace', None)
        basename = self._db_container_key(None, trail)
        try:
            ok = self._parent_exists(default, trail[:-1])
            if not ok:
                raise HTTPError(404)

            self.root.store.set(basename, '')
        except CSStoreExists:
            raise HTTPError(409)
        except CSStoreError:
            raise HTTPError(500)

        response['code'] = 201

    def _destroy(self, trail, request, response):
        basename = self._db_container_key(None, trail)
        try:
            keylist = self.root.store.list(basename)
            if keylist is None:
                raise HTTPError(404)
            if basename not in keylist:
                # uh ?
                raise HTTPError(409)
            if len(keylist) != 1:
                raise HTTPError(409)
            ret = self.root.store.cut(basename)
        except CSStoreError:
            raise HTTPError(500)

        if ret is False:
            raise HTTPError(404)

        response['code'] = 204

    def _client_name(self, request):
        if 'remote_user' in request:
            return request['remote_user']
        elif 'creds' in request:
            creds = request['creds']
            return '<pid={pid:d} uid={uid:d} gid={gid:d}>'.format(**creds)
        else:
            return 'Unknown'

    def _audit(self, ok, fail, fn, trail, request, response):
        action = fail
        client = self._client_name(request)
        key = '/'.join(trail)
        try:
            fn(trail, request, response)
            action = ok
        finally:
            self._auditlog.key_access(action, client, key)

    def _get_key(self, trail, request, response):
        self._audit(log.AUDIT_GET_ALLOWED, log.AUDIT_GET_DENIED,
                    self._int_get_key, trail, request, response)

    def _int_get_key(self, trail, request, response):
        # default to simple
        query = request.get('query', '')
        if len(query) == 0:
            query = {'type': 'simple', 'value': ''}
        try:
            name = '/'.join(trail)
            msg = self._parse(request, query, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        key = self._db_key(trail)
        try:
            output = self.root.store.get(key)
            if output is None:
                raise HTTPError(404)
            response['output'] = msg.reply(output)
        except CSStoreError:
            raise HTTPError(500)

    def _set_key(self, trail, request, response):
        self._audit(log.AUDIT_SET_ALLOWED, log.AUDIT_SET_DENIED,
                    self._int_set_key, trail, request, response)

    def _int_set_key(self, trail, request, response):
        content_type = request.get('headers', dict()).get('Content-Type', '')
        if content_type.split(';')[0].strip() != 'application/json':
            raise HTTPError(400, 'Invalid Content-Type')
        body = request.get('body')
        if body is None:
            raise HTTPError(400)
        value = bytes(body).decode('utf-8')
        try:
            name = '/'.join(trail)
            msg = self._parse(request, json.loads(value), name)
        except UnknownMessageType as e:
            raise HTTPError(406, str(e))
        except UnallowedMessage as e:
            raise HTTPError(406, str(e))
        except Exception as e:
            raise HTTPError(400, str(e))

        # must _db_key first as access control is done here for now
        # otherwise users would e able to probe containers in namespaces
        # they do not have access to.
        key = self._db_key(trail)

        try:
            default = request.get('default_namespace', None)
            ok = self._parent_exists(default, trail)
            if not ok:
                raise HTTPError(404)

            ok = self.root.store.set(key, msg.payload)
        except CSStoreExists:
            raise HTTPError(409)
        except CSStoreError:
            raise HTTPError(500)

        response['code'] = 201

    def _del_key(self, trail, request, response):
        self._audit(log.AUDIT_DEL_ALLOWED, log.AUDIT_DEL_DENIED,
                    self._int_del_key, trail, request, response)

    def _int_del_key(self, trail, request, response):
        key = self._db_key(trail)
        try:
            ret = self.root.store.cut(key)
        except CSStoreError:
            raise HTTPError(500)

        if ret is False:
            raise HTTPError(404)

        response['code'] = 204
Exemplo n.º 3
0
class Secrets(HTTPConsumer):

    def __init__(self, *args, **kwargs):
        super(Secrets, self).__init__(*args, **kwargs)
        self.allowed_keytypes = ['simple']
        if self.config and 'allowed_keytypes' in self.config:
            kt = self.config['allowed_keytypes'].split()
            self.allowed_keytypes = kt
        self._validator = Validator(self.allowed_keytypes)
        self._auditlog = log.audit_log(self.config)

    def _db_key(self, trail):
        if len(trail) < 2:
            raise HTTPError(403)
        return os.path.join('keys', *trail)

    def _db_container_key(self, default, trail):
        f = None
        if len(trail) > 1:
            f = self._db_key(trail)
        elif len(trail) == 1 and trail[0] != '':
            raise HTTPError(403)
        elif default is None:
            # No dfault namespace, fail
            raise HTTPError(403)
        else:
            # Use the default namespace
            f = self._db_key([default, ''])
        return f

    def _parse(self, request, value, name):
        return self._validator.parse(request, value, name)

    def _parent_exists(self, default, trail):
        # check that the containers exist
        basename = self._db_container_key(trail[0], '')
        try:
            keylist = self.root.store.list(basename)
        except CSStoreError:
            raise HTTPError(500)

        # create default namespace if it is the only missing piece
        if keylist is None and len(trail) == 2 and default == trail[0]:
            container = self._db_container_key(default, '')
            self.root.store.set(container, '')
            return True

        # check if any parent is missing
        for n in range(1, len(trail)):
            c = self._db_key(trail[:n] + [''])
            if c not in keylist:
                return False

        return True

    def GET(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            self._list(trail, request, response)
        else:
            self._get_key(trail, request, response)

    def PUT(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            raise HTTPError(405)
        else:
            self._set_key(trail, request, response)

    def DELETE(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0:
            raise HTTPError(405)
        if trail[-1] == '':
            self._destroy(trail, request, response)
        else:
            self._del_key(trail, request, response)

    def POST(self, request, response):
        trail = request.get('trail', [])
        if len(trail) > 0 and trail[-1] == '':
            self._create(trail, request, response)
        else:
            raise HTTPError(405)

    def _list(self, trail, request, response):
        default = request.get('default_namespace', None)
        basename = self._db_container_key(default, trail)
        userfilter = request.get('query', dict()).get('filter', '')
        try:
            keylist = self.root.store.list(basename + userfilter)
            if keylist is None:
                raise HTTPError(404)
            # remove the base container itself
            output = list()
            for k in keylist:
                if k == basename:
                    continue
                # strip away the internal prefix for storing keys
                name = k[len('keys/'):]
                output.append(name)
            response['output'] = json.dumps(output)
        except CSStoreError:
            raise HTTPError(500)

    def _create(self, trail, request, response):
        default = request.get('default_namespace', None)
        basename = self._db_container_key(None, trail)
        try:
            ok = self._parent_exists(default, trail[:-1])
            if not ok:
                raise HTTPError(404)

            self.root.store.set(basename, '')
        except CSStoreExists:
            raise HTTPError(409)
        except CSStoreError:
            raise HTTPError(500)

        response['code'] = 201

    def _destroy(self, trail, request, response):
        basename = self._db_container_key(None, trail)
        try:
            keylist = self.root.store.list(basename)
            if keylist is None:
                raise HTTPError(404)
            if basename not in keylist:
                # uh ?
                raise HTTPError(409)
            if len(keylist) != 1:
                raise HTTPError(409)
            ret = self.root.store.cut(basename)
        except CSStoreError:
            raise HTTPError(500)

        if ret is False:
            raise HTTPError(404)

        response['code'] = 204

    def _client_name(self, request):
        if 'remote_user' in request:
            return request['remote_user']
        elif 'creds' in request:
            creds = request['creds']
            return '<pid={pid:d} uid={uid:d} gid={gid:d}>'.format(**creds)
        else:
            return 'Unknown'

    def _audit(self, ok, fail, fn, trail, request, response):
        action = fail
        client = self._client_name(request)
        key = '/'.join(trail)
        try:
            fn(trail, request, response)
            action = ok
        finally:
            self._auditlog.key_access(action, client, key)

    def _get_key(self, trail, request, response):
        self._audit(log.AUDIT_GET_ALLOWED, log.AUDIT_GET_DENIED,
                    self._int_get_key, trail, request, response)

    def _int_get_key(self, trail, request, response):
        # default to simple
        query = request.get('query', '')
        if len(query) == 0:
            query = {'type': 'simple', 'value': ''}
        try:
            name = '/'.join(trail)
            msg = self._parse(request, query, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        key = self._db_key(trail)
        try:
            output = self.root.store.get(key)
            if output is None:
                raise HTTPError(404)
            response['output'] = msg.reply(output)
        except CSStoreError:
            raise HTTPError(500)

    def _set_key(self, trail, request, response):
        self._audit(log.AUDIT_SET_ALLOWED, log.AUDIT_SET_DENIED,
                    self._int_set_key, trail, request, response)

    def _int_set_key(self, trail, request, response):
        content_type = request.get('headers',
                                   dict()).get('Content-Type', '')
        if content_type.split(';')[0].strip() != 'application/json':
            raise HTTPError(400, 'Invalid Content-Type')
        body = request.get('body')
        if body is None:
            raise HTTPError(400)
        value = bytes(body).decode('utf-8')
        try:
            name = '/'.join(trail)
            msg = self._parse(request, json.loads(value), name)
        except UnknownMessageType as e:
            raise HTTPError(406, str(e))
        except UnallowedMessage as e:
            raise HTTPError(406, str(e))
        except Exception as e:
            raise HTTPError(400, str(e))

        # must _db_key first as access control is done here for now
        # otherwise users would e able to probe containers in namespaces
        # they do not have access to.
        key = self._db_key(trail)

        try:
            default = request.get('default_namespace', None)
            ok = self._parent_exists(default, trail)
            if not ok:
                raise HTTPError(404)

            ok = self.root.store.set(key, msg.payload)
        except CSStoreExists:
            raise HTTPError(409)
        except CSStoreError:
            raise HTTPError(500)

        response['code'] = 201

    def _del_key(self, trail, request, response):
        self._audit(log.AUDIT_DEL_ALLOWED, log.AUDIT_DEL_DENIED,
                    self._int_del_key, trail, request, response)

    def _int_del_key(self, trail, request, response):
        key = self._db_key(trail)
        try:
            ret = self.root.store.cut(key)
        except CSStoreError:
            raise HTTPError(500)

        if ret is False:
            raise HTTPError(404)

        response['code'] = 204
Exemplo n.º 4
0
 def __init__(self, config, section):
     super(Secrets, self).__init__(config, section)
     self._validator = Validator(self.allowed_keytypes)
Exemplo n.º 5
0
class Secrets(HTTPConsumer):
    allowed_keytypes = PluginOption('str_set', 'simple', None)
    store = PluginOption('store', None, None)

    def __init__(self, config, section):
        super(Secrets, self).__init__(config, section)
        self._validator = Validator(self.allowed_keytypes)

    def _db_key(self, trail):
        if len(trail) < 2:
            self.logger.debug(
                "Forbidden action: Operation only permitted within a "
                "container")
            raise HTTPError(403)
        return os.path.join('keys', *trail)

    def _db_container_key(self, default, trail):
        f = None
        if len(trail) > 1:
            f = self._db_key(trail)
        elif len(trail) == 1 and trail[0] != '':
            self.logger.debug(
                "Forbidden action: Wrong container path. Container names must "
                "end with '/'")
            raise HTTPError(403)
        elif default is None:
            self.logger.debug("Forbidden action: No default namespace")
            raise HTTPError(403)
        else:
            # Use the default namespace
            f = self._db_key([default, ''])
        return f

    def _parse(self, request, query, name):
        return self._validator.parse(request, query, name)

    def _parse_query(self, request, name):
        # default to simple
        query = request.get('query', '')
        if len(query) == 0:
            query = {'type': 'simple', 'value': ''}
        return self._parse(request, query, name)

    def _parse_bin_body(self, request, name):
        body = request.get('body')
        if body is None:
            raise HTTPError(400)
        value = b64encode(bytes(body)).decode('utf-8')
        payload = {'type': 'simple', 'value': value}
        return self._parse(request, payload, name)

    def _parse_body(self, request, name):
        body = request.get('body')
        if body is None:
            raise HTTPError(400)
        value = json.loads(bytes(body).decode('utf-8'))
        return self._parse(request, value, name)

    def _parse_maybe_body(self, request, name):
        body = request.get('body')
        if body is None:
            value = {'type': 'simple', 'value': ''}
        else:
            value = json.loads(bytes(body).decode('utf-8'))
        return self._parse(request, value, name)

    def _parent_exists(self, default, trail):
        # check that the containers exist
        basename = self._db_container_key(trail[0], trail[:-1] + [''])
        try:
            keylist = self.root.store.list(basename)
        except CSStoreError:
            raise HTTPError(500)

        self.logger.debug('parent_exists: %s (%s, %r) -> %r', basename,
                          default, trail, keylist)

        if keylist is not None:
            return True

        # create default namespace if it is the only missing piece
        if len(trail) == 2 and default == trail[0]:
            container = self._db_container_key(default, '')
            self.root.store.span(container)
            return True

        return False

    def _format_reply(self, request, response, handler, output):
        reply = handler.reply(output)
        # special case to allow *very* simple clients
        if handler.msg_type == 'simple':
            binary = False
            accept = request.get('headers', {}).get('Accept', None)
            if accept is not None:
                types = accept.split(',')
                for t in types:
                    if t.strip() == 'application/json':
                        binary = False
                        break
                    elif t.strip() == 'application/octet-stream':
                        binary = True
            if binary is True:
                response['headers'][
                    'Content-Type'] = 'application/octet-stream'
                response['output'] = b64decode(reply['value'])
                return

        if reply is not None:
            response['headers'][
                'Content-Type'] = 'application/json; charset=utf-8'
            response['output'] = reply

    def GET(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            self._list(trail, request, response)
        else:
            self._get_key(trail, request, response)

    def PUT(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            raise HTTPError(405)
        else:
            self._set_key(trail, request, response)

    def DELETE(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0:
            raise HTTPError(405)
        if trail[-1] == '':
            self._destroy(trail, request, response)
        else:
            self._del_key(trail, request, response)

    def POST(self, request, response):
        trail = request.get('trail', [])
        if len(trail) > 0 and trail[-1] == '':
            self._create(trail, request, response)
        else:
            raise HTTPError(405)

    def _list(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_query(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        default = request.get('default_namespace', None)
        basename = self._db_container_key(default, trail)
        try:
            keylist = self.root.store.list(basename)
            self.logger.debug('list %s returned %r', basename, keylist)
            if keylist is None:
                raise HTTPError(404)
            response['headers'][
                'Content-Type'] = 'application/json; charset=utf-8'
            response['output'] = msg.reply(keylist)
        except CSStoreDenied:
            self.logger.exception(
                "List: Permission to perform this operation was denied")
            raise HTTPError(403)
        except CSStoreError:
            self.logger.exception('List: Internal server error')
            raise HTTPError(500)
        except CSStoreUnsupported:
            self.logger.exception('List: Unsupported operation')
            raise HTTPError(501)

    def _create(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_maybe_body(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        default = request.get('default_namespace', None)
        basename = self._db_container_key(None, trail)
        try:
            if len(trail) > 2:
                ok = self._parent_exists(default, trail[:-1])
                if not ok:
                    raise HTTPError(404)

            self.root.store.span(basename)
        except CSStoreDenied:
            self.logger.exception(
                "Create: Permission to perform this operation was denied")
            raise HTTPError(403)
        except CSStoreExists:
            self.logger.exception('Create: Key already exists')
            raise HTTPError(409)
        except CSStoreError:
            self.logger.exception('Create: Internal server error')
            raise HTTPError(500)
        except CSStoreUnsupported:
            self.logger.exception('Create: Unsupported operation')
            raise HTTPError(501)

        output = msg.reply(None)
        if output is not None:
            response['headers'][
                'Content-Type'] = 'application/json; charset=utf-8'
            response['output'] = output
        response['code'] = 201

    def _destroy(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_maybe_body(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        basename = self._db_container_key(None, trail)
        try:
            keylist = self.root.store.list(basename)
            if keylist is None:
                raise HTTPError(404)
            if len(keylist) != 0:
                raise HTTPError(409)
            ret = self.root.store.cut(basename.rstrip('/'))
        except CSStoreDenied:
            self.logger.exception(
                "Delete: Permission to perform this operation was denied")
            raise HTTPError(403)
        except CSStoreError:
            self.logger.exception('Delete: Internal server error')
            raise HTTPError(500)
        except CSStoreUnsupported:
            self.logger.exception('Delete: Unsupported operation')
            raise HTTPError(501)

        if ret is False:
            raise HTTPError(404)

        output = msg.reply(None)
        if output is None:
            response['code'] = 204
        else:
            response['headers'][
                'Content-Type'] = 'application/json; charset=utf-8'
            response['output'] = output
            response['code'] = 200

    def _client_name(self, request):
        if 'remote_user' in request:
            return request['remote_user']
        elif 'creds' in request:
            creds = request['creds']
            return '<pid={pid:d} uid={uid:d} gid={gid:d}>'.format(**creds)
        else:
            return 'Unknown'

    def _audit(self, ok, fail, fn, trail, request, response):
        action = fail
        client = self._client_name(request)
        key = '/'.join(trail)
        try:
            fn(trail, request, response)
            action = ok
        finally:
            self.audit_key_access(action, client, key)

    def _get_key(self, trail, request, response):
        self._audit(log.AUDIT_GET_ALLOWED, log.AUDIT_GET_DENIED,
                    self._int_get_key, trail, request, response)

    def _int_get_key(self, trail, request, response):
        try:
            name = '/'.join(trail)
            handler = self._parse_query(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        key = self._db_key(trail)
        try:
            output = self.root.store.get(key)
            if output is None:
                raise HTTPError(404)
            elif len(output) == 0:
                raise HTTPError(406)
            self._format_reply(request, response, handler, output)
        except CSStoreDenied:
            self.logger.exception(
                "Get: Permission to perform this operation was denied")
            raise HTTPError(403)
        except CSStoreError:
            self.logger.exception('Get: Internal server error')
            raise HTTPError(500)
        except CSStoreUnsupported:
            self.logger.exception('Get: Unsupported operation')
            raise HTTPError(501)

    def _set_key(self, trail, request, response):
        self._audit(log.AUDIT_SET_ALLOWED, log.AUDIT_SET_DENIED,
                    self._int_set_key, trail, request, response)

    def _int_set_key(self, trail, request, response):
        try:
            name = '/'.join(trail)

            content_type = request.get('headers', {}).get('Content-Type', '')
            content_type_value = content_type.split(';')[0].strip()
            if content_type_value == 'application/octet-stream':
                msg = self._parse_bin_body(request, name)
            elif content_type_value == 'application/json':
                msg = self._parse_body(request, name)
            else:
                raise ValueError('Invalid Content-Type')
        except UnknownMessageType as e:
            raise HTTPError(406, str(e))
        except UnallowedMessage as e:
            raise HTTPError(406, str(e))
        except Exception as e:
            raise HTTPError(400, str(e))

        # must _db_key first as access control is done here for now
        # otherwise users would e able to probe containers in namespaces
        # they do not have access to.
        key = self._db_key(trail)

        try:
            default = request.get('default_namespace', None)
            ok = self._parent_exists(default, trail)
            if not ok:
                raise HTTPError(404)

            ok = self.root.store.set(key, msg.payload)
        except CSStoreDenied:
            self.logger.exception(
                "Set: Permission to perform this operation was denied")
            raise HTTPError(403)
        except CSStoreExists:
            self.logger.exception('Set: Key already exist')
            raise HTTPError(409)
        except CSStoreError:
            self.logger.exception('Set: Internal Server Error')
            raise HTTPError(500)
        except CSStoreUnsupported:
            self.logger.exception('Set: Unsupported operation')
            raise HTTPError(501)

        output = msg.reply(None)
        if output is not None:
            response['headers'][
                'Content-Type'] = 'application/json; charset=utf-8'
            response['output'] = output
        response['code'] = 201

    def _del_key(self, trail, request, response):
        self._audit(log.AUDIT_DEL_ALLOWED, log.AUDIT_DEL_DENIED,
                    self._int_del_key, trail, request, response)

    def _int_del_key(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_maybe_body(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        key = self._db_key(trail)
        try:
            ret = self.root.store.cut(key)
        except CSStoreDenied:
            self.logger.exception(
                "Delete: Permission to perform this operation was denied")
            raise HTTPError(403)
        except CSStoreError:
            self.logger.exception('Delete: Internal Server Error')
            raise HTTPError(500)
        except CSStoreUnsupported:
            self.logger.exception('Delete: Unsupported operation')
            raise HTTPError(501)

        if ret is False:
            raise HTTPError(404)

        output = msg.reply(None)
        if output is None:
            response['code'] = 204
        else:
            response['headers'][
                'Content-Type'] = 'application/json; charset=utf-8'
            response['output'] = output
            response['code'] = 200
Exemplo n.º 6
0
class Secrets(HTTPConsumer):

    def __init__(self, *args, **kwargs):
        super(Secrets, self).__init__(*args, **kwargs)
        self.allowed_keytypes = ['simple']
        if self.config and 'allowed_keytypes' in self.config:
            kt = self.config['allowed_keytypes'].split()
            self.allowed_keytypes = kt
        self._validator = Validator(self.allowed_keytypes)

    def _db_key(self, trail):
        if len(trail) < 2:
            raise HTTPError(403)
        return os.path.join('keys', *trail)

    def _db_container_key(self, default, trail):
        f = None
        if len(trail) > 1:
            f = self._db_key(trail)
        elif len(trail) == 1 and trail[0] != '':
            raise HTTPError(403)
        elif default is None:
            # No dfault namespace, fail
            raise HTTPError(403)
        else:
            # Use the default namespace
            f = self._db_key([default, ''])
        return f

    def _parse(self, request, query, name):
        return self._validator.parse(request, query, name)

    def _parse_query(self, request, name):
        # default to simple
        query = request.get('query', '')
        if len(query) == 0:
            query = {'type': 'simple', 'value': ''}
        return self._parse(request, query, name)

    def _parse_bin_body(self, request, name):
        body = request.get('body')
        if body is None:
            raise HTTPError(400)
        value = b64encode(bytes(body)).decode('utf-8')
        payload = {'type': 'simple', 'value': value}
        return self._parse(request, payload, name)

    def _parse_body(self, request, name):
        body = request.get('body')
        if body is None:
            raise HTTPError(400)
        value = json.loads(bytes(body).decode('utf-8'))
        return self._parse(request, value, name)

    def _parse_maybe_body(self, request, name):
        body = request.get('body')
        if body is None:
            value = {'type': 'simple', 'value': ''}
        else:
            value = json.loads(bytes(body).decode('utf-8'))
        return self._parse(request, value, name)

    def _parent_exists(self, default, trail):
        # check that the containers exist
        basename = self._db_container_key(trail[0], trail[:-1] + [''])
        try:
            keylist = self.root.store.list(basename)
        except CSStoreError:
            raise HTTPError(500)

        self.logger.debug('parent_exists: %s (%s, %r) -> %r',
                          basename, default, trail, keylist)

        if keylist is not None:
            return True

        # create default namespace if it is the only missing piece
        if len(trail) == 2 and default == trail[0]:
            container = self._db_container_key(default, '')
            self.root.store.span(container)
            return True

        return False

    def _format_reply(self, request, response, handler, output):
        reply = handler.reply(output)
        # special case to allow *very* simple clients
        if handler.msg_type == 'simple':
            binary = False
            accept = request.get('headers', {}).get('Accept', None)
            if accept is not None:
                types = accept.split(',')
                for t in types:
                    if t.strip() == 'application/json':
                        binary = False
                        break
                    elif t.strip() == 'application/octet-stream':
                        binary = True
            if binary is True:
                unwind = json.loads(reply)
                response['headers'][
                    'Content-type'] = 'application/octet-stream'
                response['output'] = b64decode(unwind['value'])
                return

        response['output'] = reply

    def GET(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            self._list(trail, request, response)
        else:
            self._get_key(trail, request, response)

    def PUT(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0 or trail[-1] == '':
            raise HTTPError(405)
        else:
            self._set_key(trail, request, response)

    def DELETE(self, request, response):
        trail = request.get('trail', [])
        if len(trail) == 0:
            raise HTTPError(405)
        if trail[-1] == '':
            self._destroy(trail, request, response)
        else:
            self._del_key(trail, request, response)

    def POST(self, request, response):
        trail = request.get('trail', [])
        if len(trail) > 0 and trail[-1] == '':
            self._create(trail, request, response)
        else:
            raise HTTPError(405)

    def _list(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_query(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        default = request.get('default_namespace', None)
        basename = self._db_container_key(default, trail)
        try:
            keylist = self.root.store.list(basename)
            self.logger.debug('list %s returned %r', basename, keylist)
            if keylist is None:
                raise HTTPError(404)
            response['output'] = msg.reply(json.dumps(keylist))
        except CSStoreError:
            raise HTTPError(500)

    def _create(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_maybe_body(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        default = request.get('default_namespace', None)
        basename = self._db_container_key(None, trail)
        try:
            if len(trail) > 2:
                ok = self._parent_exists(default, trail[:-1])
                if not ok:
                    raise HTTPError(404)

            self.root.store.span(basename)
        except CSStoreExists:
            raise HTTPError(409)
        except CSStoreError:
            raise HTTPError(500)

        output = msg.reply(None)
        if output is not None:
            response['output'] = output
        response['code'] = 201

    def _destroy(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_maybe_body(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        basename = self._db_container_key(None, trail)
        try:
            keylist = self.root.store.list(basename)
            if keylist is None:
                raise HTTPError(404)
            if len(keylist) != 0:
                raise HTTPError(409)
            ret = self.root.store.cut(basename.rstrip('/'))
        except CSStoreError:
            raise HTTPError(500)

        if ret is False:
            raise HTTPError(404)

        output = msg.reply(None)
        if output is None:
            response['code'] = 204
        else:
            response['output'] = output
            response['code'] = 200

    def _client_name(self, request):
        if 'remote_user' in request:
            return request['remote_user']
        elif 'creds' in request:
            creds = request['creds']
            return '<pid={pid:d} uid={uid:d} gid={gid:d}>'.format(**creds)
        else:
            return 'Unknown'

    def _audit(self, ok, fail, fn, trail, request, response):
        action = fail
        client = self._client_name(request)
        key = '/'.join(trail)
        try:
            fn(trail, request, response)
            action = ok
        finally:
            self.audit_key_access(action, client, key)

    def _get_key(self, trail, request, response):
        self._audit(log.AUDIT_GET_ALLOWED, log.AUDIT_GET_DENIED,
                    self._int_get_key, trail, request, response)

    def _int_get_key(self, trail, request, response):
        try:
            name = '/'.join(trail)
            handler = self._parse_query(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        key = self._db_key(trail)
        try:
            output = self.root.store.get(key)
            if output is None:
                raise HTTPError(404)
            self._format_reply(request, response, handler, output)
        except CSStoreError:
            raise HTTPError(500)

    def _set_key(self, trail, request, response):
        self._audit(log.AUDIT_SET_ALLOWED, log.AUDIT_SET_DENIED,
                    self._int_set_key, trail, request, response)

    def _int_set_key(self, trail, request, response):
        try:
            name = '/'.join(trail)

            content_type = request.get('headers', {}).get('Content-Type', '')
            content_type_value = content_type.split(';')[0].strip()
            if content_type_value == 'application/octet-stream':
                msg = self._parse_bin_body(request, name)
            elif content_type_value == 'application/json':
                msg = self._parse_body(request, name)
            else:
                raise ValueError('Invalid Content-Type')
        except UnknownMessageType as e:
            raise HTTPError(406, str(e))
        except UnallowedMessage as e:
            raise HTTPError(406, str(e))
        except Exception as e:
            raise HTTPError(400, str(e))

        # must _db_key first as access control is done here for now
        # otherwise users would e able to probe containers in namespaces
        # they do not have access to.
        key = self._db_key(trail)

        try:
            default = request.get('default_namespace', None)
            ok = self._parent_exists(default, trail)
            if not ok:
                raise HTTPError(404)

            ok = self.root.store.set(key, msg.payload)
        except CSStoreExists:
            raise HTTPError(409)
        except CSStoreError:
            raise HTTPError(500)

        output = msg.reply(None)
        if output is not None:
            response['output'] = output
        response['code'] = 201

    def _del_key(self, trail, request, response):
        self._audit(log.AUDIT_DEL_ALLOWED, log.AUDIT_DEL_DENIED,
                    self._int_del_key, trail, request, response)

    def _int_del_key(self, trail, request, response):
        try:
            name = '/'.join(trail)
            msg = self._parse_maybe_body(request, name)
        except Exception as e:
            raise HTTPError(406, str(e))
        key = self._db_key(trail)
        try:
            ret = self.root.store.cut(key)
        except CSStoreError:
            raise HTTPError(500)

        if ret is False:
            raise HTTPError(404)

        output = msg.reply(None)
        if output is None:
            response['code'] = 204
        else:
            response['output'] = output
            response['code'] = 200