def validate(self): try: if self.type == 'SECONDARY' and self.masters is None: errors = ValidationErrorList() e = ValidationError() e.path = ['type'] e.validator = 'required' e.validator_value = ['masters'] e.message = "'masters' is a required property" errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) super(Domain, self).validate() except exceptions.RelationNotLoaded as ex: errors = ValidationErrorList() e = ValidationError() e.path = ['type'] e.validator = 'required' e.validator_value = [ex.relation] e.message = "'%s' is a required property" % ex.relation errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self)
def parse(cls, format_, values, output_object, *args, **kwargs): LOG.debug("Creating %s object with values %r", output_object.obj_name(), values) LOG.debug(output_object) try: if isinstance(output_object, objects.ListObjectMixin): # type_ = 'list' return cls.get_object_adapter(format_, output_object)._parse_list( values, output_object, *args, **kwargs) else: # type_ = 'object' return cls.get_object_adapter(format_, output_object)._parse_object( values, output_object, *args, **kwargs) except TypeError as e: LOG.exception("TypeError creating %(name)s with values %(values)r", { "name": output_object.obj_name(), "values": values }) error_message = (u'Provided object is not valid. ' u'Got a TypeError with message {}'.format( six.text_type(e))) raise exceptions.InvalidObject(error_message) except AttributeError as e: LOG.exception( "AttributeError creating %(name)s with values %(values)r", { "name": output_object.obj_name(), "values": values }) error_message = (u'Provided object is not valid. ' u'Got an AttributeError with message {}'.format( six.text_type(e))) raise exceptions.InvalidObject(error_message) except exceptions.InvalidObject: LOG.info("InvalidObject creating %(name)s with values %(values)r", { "name": output_object.obj_name(), "values": values }) raise except Exception as e: LOG.exception("Exception creating %(name)s with values %(values)r", { "name": output_object.obj_name(), "values": values }) error_message = (u'Provided object is not valid. ' u'Got a {} error with message {}'.format( type(e).__name__, six.text_type(e))) raise exceptions.InvalidObject(error_message)
def validate(self): # NOTE(kiall, daidv): We make use of the Object registry here # in order to avoid an impossible circular import. ValidationErrorList = self.obj_cls_from_name('ValidationErrorList') ValidationError = self.obj_cls_from_name('ValidationError') self.fields = self.FIELDS for name in self.fields: field = self.fields[name] if self.obj_attr_is_set(name): value = getattr(self, name) # Check relation if isinstance(value, ListObjectMixin): for obj in value.objects: obj.validate() elif isinstance(value, DesignateObject): value.validate() else: try: field.coerce(self, name, value) # Check value except Exception: raise exceptions.InvalidObject( "{} is invalid".format(name)) elif not field.nullable: # Check required is True ~ nullable is False errors = ValidationErrorList() e = ValidationError() e.path = ['records', 0] e.validator = 'required' e.validator_value = [name] e.message = "'%s' is a required property" % name errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self)
def _raise(self, errors): if len(errors) != 0: raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self)
def validate(self): make_class_validator(self) # NOTE(kiall): We make use of the Object registry here in order to # avoid an impossible circular import. ValidationErrorList = self.obj_cls_from_name('ValidationErrorList') ValidationError = self.obj_cls_from_name('ValidationError') values = self.to_dict() errors = ValidationErrorList() LOG.debug("Validating '%(name)s' object with values: %(values)r", { 'name': self.obj_name(), 'values': values, }) for error in self._obj_validator.iter_errors(values): errors.append(ValidationError.from_js_error(error)) if len(errors) > 0: raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self)
def patch_one(self, zone_id): """Update Zone""" # TODO(kiall): This needs cleanup to say the least.. request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response # TODO(kiall): Validate we have a sane UUID for zone_id # Fetch the existing zone zone = self.central_api.get_zone(context, zone_id) # Don't allow updates to zones that are being deleted if zone.action == "DELETE": raise exceptions.BadRequest('Can not update a deleting zone') if request.content_type == 'application/json-patch+json': # Possible pattern: # # 1) Load existing zone. # 2) Apply patch, maintain list of changes. # 3) Return changes, after passing through the code ^ for plain # JSON. # # Difficulties: # # 1) "Nested" resources? records inside a recordset. # 2) What to do when a zone doesn't exist in the first place? # 3) ...? raise NotImplemented('json-patch not implemented') else: # Update the zone object with the new values zone = DesignateAdapter.parse('API_v2', body, zone) zone.validate() # If masters are specified then we set zone.transferred_at to None # which will cause a new transfer if 'attributes' in zone.obj_what_changed(): zone.transferred_at = None # Update and persist the resource if zone.type == 'SECONDARY' and 'email' in zone.obj_what_changed(): msg = "Changed email is not allowed." raise exceptions.InvalidObject(msg) increment_serial = zone.type == 'PRIMARY' zone = self.central_api.update_zone( context, zone, increment_serial=increment_serial) LOG.info(_LI("Updated %(zone)s"), {'zone': zone}) if zone.status == 'PENDING': response.status_int = 202 else: response.status_int = 200 return DesignateAdapter.render('API_v2', zone, request=request)
def validate(self): make_class_validator(self) # NOTE(kiall): We make use of the Object registry here in order to # avoid an impossible circular import. ValidationErrorList = self.obj_cls_from_name('ValidationErrorList') ValidationError = self.obj_cls_from_name('ValidationError') errors = ValidationErrorList() try: values = self.to_dict() except exceptions.RelationNotLoaded as e: e = ValidationError() e.path = ['type'] e.validator = 'required' e.validator_value = [e.relation] e.message = "'%s' is a required property" % e.relation errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) LOG.debug("Validating '%(name)s' object with values: %(values)r", { 'name': self.obj_name(), 'values': values, }) for error in self._obj_validator.iter_errors(values): errors.append(ValidationError.from_js_error(error)) if len(errors) > 0: LOG.debug( "Error Validating '%(name)s' object with values: " "%(values)r", { 'name': self.obj_name(), 'values': values, }) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self)
def _parse_object(cls, new_recordset, recordset, *args, **kwargs): # TODO(Graham): Remove this when # https://bugs.launchpad.net/designate/+bug/1432842 is fixed try: recordset.records except exceptions.RelationNotLoaded: recordset.records = objects.RecordList() original_records = set() for record in recordset.records: original_records.add(record.data) # Get new list of Records new_records = set() if 'records' in new_recordset: if isinstance(new_recordset['records'], list): for record in new_recordset['records']: new_records.add(record) else: errors = objects.ValidationErrorList() e = objects.ValidationError() e.path = ['records'] e.validator = 'type' e.validator_value = ["list"] e.message = ("'%(data)s' is not a valid list of records" % { 'data': new_recordset['records'] }) # Add it to the list for later errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=cls.ADAPTER_OBJECT()) # Get differences of Records records_to_add = new_records.difference(original_records) records_to_rm = original_records.difference(new_records) # Update all items except records record_update = False if 'records' in new_recordset: record_update = True del new_recordset['records'] # Remove deleted records if we have provided a records array if record_update: recordset.records[:] = [ record for record in recordset.records if record.data not in records_to_rm ] # Add new records for record in records_to_add: recordset.records.append(objects.Record(data=record)) return super(RecordSetAPIv2Adapter, cls)._parse_object(new_recordset, recordset, *args, **kwargs)
def _validate_fail(self, errors, msg): e = ValidationError() e.path = ['recordset', 'type'] e.validator = 'value' e.validator_value = [self.type] e.message = msg # Add it to the list for later errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self)
def validate(self, obj): LOG.debug('Validating values: %r' % obj) errors = [] for error in self.validator.iter_errors(obj): errors.append({ 'path': ".".join([str(x) for x in error.path]), 'message': error.message, 'validator': error.validator }) if len(errors) > 0: LOG.debug('Errors in validation: %r' % errors) raise exceptions.InvalidObject("Provided object does not match " "schema", errors=errors)
def load_values(request, valid_keys): """Load valid attributes from request""" result = {} error_keys = [] values = request.json for k in values: if k in valid_keys: result[k] = values[k] else: error_keys.append(k) if error_keys: error_msg = 'Provided object does not match schema. Keys {0} are not \ valid in the request body', error_keys raise exceptions.InvalidObject(error_msg) return result
def _load(self, context, request, body, valid_keys): """Extract a "central" compatible dict from an API call""" result = {} item = body[self._resource_name] error_keys = [] # Copy keys which need no alterations for k in item: if k in valid_keys: result[k] = item[k] else: error_keys.append(k) if error_keys: error_message = str.format( 'Provided object does not match schema. Keys {0} are not ' 'valid in the request body', error_keys) raise exceptions.InvalidObject(error_message) return result
def validate(self): LOG.debug("Validating '%(name)s' object with values: %(values)r", { 'name': self.obj_name(), 'values': self.to_dict(), }) LOG.debug(list(self.records)) errors = ValidationErrorList() # Get the right classes (e.g. A for Recordsets with type: 'A') try: record_list_cls = self.obj_cls_from_name('%sList' % self.type) record_cls = self.obj_cls_from_name(self.type) except (KeyError, ovo_exc.UnsupportedObjectError) as e: err_msg = ("'%(type)s' is not a valid record type" % { 'type': self.type }) self._validate_fail(errors, err_msg) if self.type not in cfg.CONF.supported_record_type: err_msg = ("'%(type)s' is not a supported record type" % { 'type': self.type }) self._validate_fail(errors, err_msg) # Get any rules that the record type imposes on the record changes = record_cls.get_recordset_schema_changes() old_fields = {} if changes: LOG.debug("Record %s is overriding the RecordSet schema with: %s", record_cls.obj_name(), changes) old_fields = deepcopy(self.FIELDS) self.FIELDS = utils.deep_dict_merge(self.FIELDS, changes) error_indexes = [] # Copy these for safekeeping old_records = deepcopy(self.records) # Blank the records for this object with the right list type self.records = record_list_cls() i = 0 for record in old_records: record_obj = record_cls() try: record_obj._from_string(record.data) # The _from_string() method will throw a ValueError if there is not # enough data blobs except ValueError as e: # Something broke in the _from_string() method # Fake a correct looking ValidationError() object e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except TypeError as e: e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except AttributeError as e: e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except Exception as e: error_message = ('Provided object is not valid. Got a %s error' ' with message %s' % (type(e).__name__, six.text_type(e))) raise exceptions.InvalidObject(error_message) else: # Seems to have loaded right - add it to be validated by # JSONSchema self.records.append(record_obj) i += 1 try: # Run the actual validate code super(RecordSet, self).validate() except exceptions.InvalidObject as e: raise e else: # If JSONSchema passes, but we found parsing errors, # raise an exception if len(errors) > 0: LOG.debug( "Error Validating '%(name)s' object with values: " "%(values)r", { 'name': self.obj_name(), 'values': self.to_dict(), }) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) finally: if old_fields: self.FIELDS = old_fields # Send in the traditional Record objects to central / storage self.records = old_records
def validate(self): LOG.debug("Validating '%(name)s' object with values: %(values)r", { 'name': self.obj_name(), 'values': self.to_dict(), }) errors = ValidationErrorList() # Get the right classes (e.g. A for Recordsets with type: 'A') try: record_list_cls = self.obj_cls_from_name('%sList' % self.type) record_cls = self.obj_cls_from_name(self.type) except KeyError as e: e = ValidationError() e.path = ['recordset', 'type'] e.validator = 'value' e.validator_value = [self.type] e.message = ("'%(type)s' is not a supported Record type" % { 'type': self.type }) # Add it to the list for later errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) # Get any rules that the record type imposes on the record changes = record_cls.get_recordset_schema_changes() old_fields = {} if changes: LOG.debug("Record %s is overriding the RecordSet schema with: %s" % (record_cls.obj_name(), changes)) old_fields = deepcopy(self.FIELDS) self.FIELDS = utils.deep_dict_merge(self.FIELDS, changes) error_indexes = [] # Copy these for safekeeping old_records = deepcopy(self.records) # Blank the records for this object with the right list type self.records = record_list_cls() i = 0 for record in old_records: record_obj = record_cls() try: record_obj._from_string(record.data) # The _from_string() method will throw a ValueError if there is not # enough data blobs except ValueError as e: # Something broke in the _from_string() method # Fake a correct looking ValidationError() object e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except TypeError as e: e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except AttributeError as e: e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except Exception as e: error_message = str.format( 'Provided object is not valid. ' 'Got a %s error with message %s' % (type(e).__name__, six.text_type(e))) raise exceptions.InvalidObject(error_message) else: # Seems to have loaded right - add it to be validated by # JSONSchema self.records.append(record_obj) i += 1 try: # Run the actual validate code super(RecordSet, self).validate() except exceptions.InvalidObject as e: # Something is wrong according to JSONSchema - append our errors increment = 0 # This code below is to make sure we have the index for the record # list correct. JSONSchema may be missing some of the objects due # to validation above, so this re - inserts them, and makes sure # the index is right for error in e.errors: if len(error.path) > 1 and isinstance(error.path[1], int): error.path[1] += increment while error.path[1] in error_indexes: increment += 1 error.path[1] += 1 # Add the list from above e.errors.extend(errors) # Raise the exception raise e else: # If JSONSchema passes, but we found parsing errors, # raise an exception if len(errors) > 0: LOG.debug( "Error Validating '%(name)s' object with values: " "%(values)r", { 'name': self.obj_name(), 'values': self.to_dict(), }) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) finally: if old_fields: self.FIELDS = old_fields # Send in the traditional Record objects to central / storage self.records = old_records
def _parse_object(cls, values, output_object, *args, **kwargs): error_keys = [] for key, value in values.items(): if key in cls.MODIFICATIONS['fields']: # No rename needed obj_key = key # This item may need to be translated if cls.MODIFICATIONS['fields'][key].get('rename', False): obj_key = cls.MODIFICATIONS['fields'][key].get('rename') ############################################################## # TODO(graham): Remove this section of code when validation # # is moved into DesignateObjects properly # ############################################################## # Check if the field should be allowed change after it is # initially set (eg zone name) if cls.MODIFICATIONS['fields'][key].get('immutable', False): if getattr(output_object, obj_key, False) and \ getattr(output_object, obj_key) != value: error_keys.append(key) break # Is this field a read only field elif cls.MODIFICATIONS['fields'][key].get('read_only', True) \ and getattr(output_object, obj_key) != value: error_keys.append(key) break # Check if the key is a nested object if output_object.FIELDS.get(obj_key, {}).get('relation', False): # Get the right class name obj_class_name = output_object.FIELDS.get( obj_key, {}).get('relation_cls') # Get the an instance of it obj_class = \ objects.DesignateObject.obj_cls_from_name( obj_class_name) # Get the adapted object obj = \ cls.get_object_adapter( cls.ADAPTER_FORMAT, obj_class_name).parse( value, obj_class()) # Set the object on the main object setattr(output_object, obj_key, obj) else: # No nested objects here, just set the value setattr(output_object, obj_key, value) else: # We got an extra key error_keys.append(key) if error_keys: error_message = str.format( 'Provided object does not match schema. Keys {0} are not ' 'valid for {1}', error_keys, cls.MODIFICATIONS['options']['resource_name']) raise exceptions.InvalidObject(error_message) return output_object
def patch_one(self, zone_id): """Update Zone""" # TODO(kiall): This needs cleanup to say the least.. request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response # TODO(kiall): Validate we have a sane UUID for zone_id # Fetch the existing zone zone = self.central_api.get_domain(context, zone_id) # Convert to APIv2 Format zone_data = self._view.show(context, request, zone) if request.content_type == 'application/json-patch+json': # Possible pattern: # # 1) Load existing zone. # 2) Apply patch, maintain list of changes. # 3) Return changes, after passing through the code ^ for plain # JSON. # # Difficulties: # # 1) "Nested" resources? records inside a recordset. # 2) What to do when a zone doesn't exist in the first place? # 3) ...? raise NotImplemented('json-patch not implemented') else: zone_data = utils.deep_dict_merge(zone_data, body) # Validate the new set of data self._resource_schema.validate(zone_data) # Unpack the values values = self._view.load(context, request, body) zone.set_masters(values.pop('masters', [])) # If masters are specified then we set zone.transferred_at to None # which will cause a new transfer if 'attributes' in zone.obj_what_changed(): zone.transferred_at = None # Update and persist the resource zone.update(values) if zone.type == 'SECONDARY' and 'email' in zone.obj_what_changed(): msg = "Changed email is not allowed." raise exceptions.InvalidObject(msg) increment_serial = zone.type == 'PRIMARY' zone = self.central_api.update_domain( context, zone, increment_serial=increment_serial) if zone.status == 'PENDING': response.status_int = 202 else: response.status_int = 200 return self._view.show(context, request, zone)