Add basic file content schema validation

Using jsonschema we can validate (at a basic level)
that the files created and the contents written were
not altered to be of a different format so add some
basic format validation to help in detecting format
issues/changes.

This also avoids having to use type errors to try to
detect this same kind of tampering/data format change.

Change-Id: I80b4e560f24162e079cf4adab06d35ec75f4b70c
This commit is contained in:
Joshua Harlow 2015-12-17 14:39:39 -08:00
parent c08caaef07
commit 815c363de9
2 changed files with 49 additions and 19 deletions

@ -7,6 +7,7 @@ stevedore>=1.5.0 # Apache-2.0
six>=1.9.0
iso8601>=0.1.9
zake>=0.1.6 # Apache-2.0
jsonschema>=2.0.0,<3.0.0,!=2.5.0
msgpack-python>=0.4.0
fasteners>=0.7 # Apache-2.0
retrying!=1.3.0,>=1.2.3 # Apache-2.0

@ -26,6 +26,7 @@ import threading
from concurrent import futures
import fasteners
import jsonschema
from oslo_utils import timeutils
import six
@ -41,12 +42,53 @@ LOG = logging.getLogger(__name__)
def _translate_failures():
try:
yield
except EnvironmentError as e:
except (EnvironmentError, jsonschema.ValidationError) as e:
coordination.raise_with_cause(coordination.ToozError,
utils.exception_message(e),
cause=e)
_SCHEMA_TYPES = {
'array': (list, tuple),
'string': six.string_types + (six.binary_type,),
}
_SCHEMAS = {
'group': {
"type": "object",
"properties": {
"group_id": {
"type": "string"
},
},
"required": [
"group_id",
],
"additionalProperties": True,
},
'member': {
"type": "object",
"properties": {
"member_id": {
"type": "string"
},
# For now joined_on and capabilities are left out as
# capabilities can be nearly arbitrary and joined_on is a date
# type (that is a python custom type).
},
"required": [
"member_id",
],
"additionalProperties": True,
},
}
def _validate(data, schema_key):
schema = _SCHEMAS[schema_key]
jsonschema.validate(data, schema, types=_SCHEMA_TYPES)
return data
def _lock_me(lock):
def wrapper(func):
@ -248,13 +290,8 @@ class FileDriver(coordination._RunWatchersMixin,
def _read_member_id(path):
with open(path, 'rb') as fh:
contents = fh.read()
details = utils.loads(contents)
if isinstance(details, (dict)):
return details['member_id']
else:
raise TypeError(
"Expected dict encoded in '%s'"
" but got %s instead" % (path, type(details)))
details = _validate(utils.loads(contents), 'member')
return details['member_id']
@_lock_me(self._driver_lock)
def _do_get_members():
@ -305,12 +342,8 @@ class FileDriver(coordination._RunWatchersMixin,
else:
raise
else:
details = utils.loads(contents)
if not isinstance(details, (dict)):
raise TypeError("Expected dict encoded in '%s'"
" but got %s instead" % (member_path,
type(details)))
return details["capabilities"]
details = _validate(utils.loads(contents), 'member')
return details.get("capabilities")
fut = self._executor.submit(_do_get_member_capabilities)
return FileFutureResult(fut)
@ -346,11 +379,7 @@ class FileDriver(coordination._RunWatchersMixin,
def _read_group_id(path):
with open(path, 'rb') as fh:
contents = fh.read()
details = utils.loads(contents)
if not isinstance(details, (dict)):
raise TypeError("Expected dict encoded in '%s'"
" but got %s instead" % (path,
type(details)))
details = _validate(utils.loads(contents), 'group')
return details['group_id']
@_lock_me(self._driver_lock)