async def patch(self): """ --- summary: Initiate administrative actions description: | The body of the request needs to contain a set of instructions detailing the operations to perform. Currently the supported operations are `rescan`: ```JSON [ { "operation": "rescan" } ] ``` * Will remove from the registry and database any currently stopped plugins who's directory has been removed. * Will add and start any new plugin directories. And reloading the plugin logging configuration: ```JSON [ { "operation": "reload", "path": "/config/logging/plugin" } ] ``` parameters: - name: patch in: body required: true description: Instructions for operations schema: $ref: '#/definitions/Patch' responses: 204: description: Operation successfully initiated 50x: $ref: '#/definitions/50xError' tags: - Admin """ self.verify_user_permission_for_object(GARDEN_UPDATE, local_garden()) operations = SchemaParser.parse_patch( self.request.decoded_body, many=True, from_string=True ) for op in operations: if op.operation == "rescan": await self.client(Operation(operation_type="RUNNER_RESCAN")) elif op.operation == "reload": if op.path == "/config/logging/plugin": await self.client(Operation(operation_type="PLUGIN_LOG_RELOAD")) else: raise ModelValidationError(f"Unsupported path '{op.path}'") else: raise ModelValidationError(f"Unsupported operation '{op.operation}'") self.set_status(204)
def get_and_validate_system(self, request): """Ensure there is a system in the DB that corresponds to this Request. :param request: The request to validate :return: The system corresponding to this Request :raises ModelValidationError: There is no system that corresponds to this Request """ system = db.query_unique( System, namespace=request.namespace, name=request.system, version=request.system_version, ) if system is None: raise ModelValidationError( "Could not find System named '%s' matching version '%s'" % (request.system, request.system_version)) if request.instance_name not in system.instance_names: raise ModelValidationError( "Could not find instance with name '%s' in system '%s'" % (request.instance_name, system.name)) self.logger.debug("Found System %s-%s" % (request.system, request.instance_name)) return system
def clean(self): """Validate before saving to the database""" if self.status not in BrewtilsRequest.STATUS_LIST: raise ModelValidationError( f"Can not save Request {self}: Invalid status '{self.status}'") if (self.command_type is not None and self.command_type not in BrewtilsRequest.COMMAND_TYPES): raise ModelValidationError( f"Can not save Request {self}: Invalid command type" f" '{self.command_type}'") if (self.output_type is not None and self.output_type not in BrewtilsRequest.OUTPUT_TYPES): raise ModelValidationError( f"Can not save Request {self}: Invalid output type '{self.output_type}'" ) # Deal with has_parent if self.has_parent is None: self.has_parent = bool(self.parent) elif self.has_parent != bool(self.parent): raise ModelValidationError( f"Cannot save Request {self}: parent value of {self.parent!r} is not " f"consistent with has_parent value of {self.has_parent}") if (self.namespace == config.get("garden.name")) and ( "status" in self.changed_fields or self.created): self.status_updated_at = datetime.datetime.utcnow()
def clean(self): if self.type == "static" and not isinstance(self.value, (list, dict)): raise ModelValidationError( f"Can not save choices '{self}': type is 'static' but the value is " "not a list or dictionary") elif self.type == "url" and not isinstance(self.value, six.string_types): raise ModelValidationError( f"Can not save choices '{self}': type is 'url' but the value is " "not a string") elif self.type == "command" and not isinstance( self.value, (six.string_types, dict)): raise ModelValidationError( f"Can not save choices '{self}': type is 'command' but the value is " "not a string or dict") if self.type == "command" and isinstance(self.value, dict): value_keys = self.value.keys() for required_key in ("command", "system", "version"): if required_key not in value_keys: raise ModelValidationError( f"Can not save choices '{self}': specifying value as a " f"dictionary requires a '{required_key}' item") try: if self.details == {}: if isinstance(self.value, six.string_types): self.details = parse(self.value) elif isinstance(self.value, dict): self.details = parse(self.value["command"]) except (LarkError, ParseError): raise ModelValidationError( f"Can not save choices '{self}': Unable to parse")
def clean(self): if self.type == "static" and not isinstance(self.value, (list, dict)): raise ModelValidationError( "Error saving choices '%s' - type was 'static' " "but the value was not a list or dictionary" % self.value) elif self.type == "url" and not isinstance(self.value, six.string_types): raise ModelValidationError( "Error saving choices '%s' - type was 'url' but " "the value was not a string" % self.value) elif self.type == "command" and not isinstance( self.value, (six.string_types, dict)): raise ModelValidationError( "Error saving choices '%s' - type was 'command' " "but the value was not a string or dict" % self.value) if self.type == "command" and isinstance(self.value, dict): value_keys = self.value.keys() for required_key in ("command", "system", "version"): if required_key not in value_keys: raise ModelValidationError( "Error saving choices '%s' - specifying " "value as a dictionary requires a '%s' " "item" % (self.value, required_key)) try: if self.details == {}: if isinstance(self.value, six.string_types): self.details = parse(self.value) elif isinstance(self.value, dict): self.details = parse(self.value["command"]) except (LarkError, ParseError): raise ModelValidationError( "Error saving choices '%s' - unable to parse" % self.value)
def patch(self): """ --- summary: Refresh an auth token. description: | The body of the request needs to contain a set of instructions. Currently the only operation supported is `refresh`, with path `/payload`: ```JSON { "operations": [ { "operation": "refresh", "path": "/payload", "value": "REFRESH_ID" } ] } ``` If you do not know your REFRESH_ID, it should be set in a cookie by the server. If you leave `value` as `null` and include this cookie, then we will automatically refresh. Also, if you are using a cookie, you should really consider just using a GET on /api/v1/tokens as it has the same effect. parameters: - name: patch in: body required: true description: Instructions for what to do schema: $ref: '#/definitions/Patch' responses: 200: description: New Auth token schema: $ref: '#/definitions/RefreshToken' 400: $ref: '#/definitions/400Error' 404: $ref: '#/definitions/404Error' 50x: $ref: '#/definitions/50xError' tags: - Tokens """ operations = self.parser.parse_patch(self.request.decoded_body, many=True, from_string=True) token = None for op in operations: if op.operation == "refresh": if op.path == "/payload": token = self._refresh_token(op.value) else: raise ModelValidationError("Unsupported path '%s'" % op.path) else: raise ModelValidationError("Unsupported operation '%s'" % op.operation) self.write(json.dumps(token))
def _validate_parameter_based_on_type(self, value, parameter, command, request): """Validates the value passed in, ensures the type matches. Recursive calls for dictionaries which also have nested parameters""" try: if value is None and not parameter.nullable: raise ModelValidationError( "There is no value for parameter '%s' " "and this field is not nullable." % parameter.key) elif parameter.type.upper() == "STRING": if isinstance(value, six.string_types): return str(value) else: raise TypeError("Invalid value for string (%s)" % value) elif parameter.type.upper() == "INTEGER": if int(value) != float(value): raise TypeError("Invalid value for integer (%s)" % value) return int(value) elif parameter.type.upper() == "FLOAT": return float(value) elif parameter.type.upper() == "ANY": return value elif parameter.type.upper() == "BOOLEAN": if value in [True, False]: return value else: raise TypeError("Invalid value for boolean (%s)" % value) elif parameter.type.upper() == "DICTIONARY": dict_value = dict(value) if parameter.parameters: self.logger.debug("Found Nested Parameters.") return self.get_and_validate_parameters( request, command, parameter.parameters, dict_value) return dict_value elif parameter.type.upper() == "DATE": return int(value) elif parameter.type.upper() == "DATETIME": return int(value) elif parameter.type.upper() == "BASE64": return value else: raise ModelValidationError( "Unknown type for parameter. Please contact a system administrator." ) except TypeError as ex: self.logger.exception(ex) raise ModelValidationError( "Value for key: %s is not the correct type. Should be: %s" % (parameter.key, parameter.type)) except ValueError as ex: self.logger.exception(ex) raise ModelValidationError( "Value for key: %s is not the correct type. Should be: %s" % (parameter.key, parameter.type))
def clean(self): """Validate before saving to the database""" if not list(filter(lambda i: i != "", self.pattern)): raise ModelValidationError( "Cannot save FileTrigger. Must have at least one non-empty pattern." ) if True not in self.callbacks.values(): raise ModelValidationError( "Cannot save FileTrigger. Must have at least one callback selected." )
def post(self): """ --- summary: Create a new Role parameters: - name: role in: body description: The Role definition schema: $ref: '#/definitions/Role' consumes: - application/json responses: 201: description: A new Role has been created schema: $ref: '#/definitions/Role' 400: $ref: '#/definitions/400Error' 50x: $ref: '#/definitions/50xError' tags: - Roles """ role = BeerGardenSchemaParser.parse_role(self.request.decoded_body, from_string=True) # Make sure all new permissions are real if not set(role.permissions).issubset(Permissions.values): invalid = set(role.permissions).difference(Permissions.values) raise ModelValidationError("Permissions %s do not exist" % invalid) # And the same for nested roles nested_roles = [] for nested_role in role.roles: try: db_role = Role.objects.get(name=nested_role.name) # There shouldn't be any way to construct a cycle with a new # role, but check just to be sure ensure_no_cycles(role, db_role) nested_roles.append(db_role) except DoesNotExist: raise ModelValidationError("Role '%s' does not exist" % nested_role.name) role.roles = nested_roles role.save() self.set_status(201) self.write(BeerGardenSchemaParser.serialize_role(role, to_string=False))
def clean(self): """Validate before saving to the database""" if self.trigger_type not in self.TRIGGER_MODEL_MAPPING: raise ModelValidationError( "Cannot save job. No matching model for trigger type: %s" % self.trigger_type) if not isinstance(self.trigger, self.TRIGGER_MODEL_MAPPING[self.trigger_type]): raise ModelValidationError( "Cannot save job. Trigger type: %s but got trigger: %s" % (self.trigger_type, type(self.trigger)))
def clean(self): """Validate before saving to the database""" if len(self.instances) > self.max_instances > -1: raise ModelValidationError( "Can not save System %s: Number of instances (%s) " "exceeds system limit (%s)" % (str(self), len(self.instances), self.max_instances)) if len(self.instances) != len( set(instance.name for instance in self.instances)): raise ModelValidationError( "Can not save System %s: Duplicate instance names" % str(self))
def clean(self): """Validate before saving to the database""" if self.trigger_type not in self.TRIGGER_MODEL_MAPPING: raise ModelValidationError( f"Cannot save job. No mongo model for trigger type {self.trigger_type}" ) trigger_class = self.TRIGGER_MODEL_MAPPING.get(self.trigger_type) if not isinstance(self.trigger, trigger_class): raise ModelValidationError( f"Cannot save job. Expected trigger type {self.trigger_type} but " f"actual type was {type(self.trigger)}")
def clean(self): """Validate before saving to the database""" if not self.nullable and self.optional and self.default is None: raise ModelValidationError( "Can not save Parameter %s: For this Parameter " "nulls are not allowed, but the parameter is " "optional with no default defined." % self.key) if len(self.parameters) != len( set(parameter.key for parameter in self.parameters)): raise ModelValidationError( "Can not save Parameter %s: Contains Parameters " "with duplicate keys" % self.key)
def clean(self): """Validate before saving to the database""" if not self.nullable and self.optional and self.default is None: raise ModelValidationError( f"Can not save Parameter {self}: For this Parameter nulls are not " "allowed, but the parameter is optional with no default defined." ) if len(self.parameters) != len( set(parameter.key for parameter in self.parameters)): raise ModelValidationError( f"Can not save Parameter {self}: Contains Parameters with duplicate" " keys")
def get_and_validate_command_for_system(self, request, system=None): """Ensure the System has a command with a name that matches this request. :param request: The request to validate :param system: Specifies a System to use. If None a system lookup will be attempted. :return: The database command :raises ValidationError: if the request or command is invalid """ self.logger.debug("Getting and Validating Command for System") if system is None: self.logger.debug("No System was passed in") system = self.get_and_validate_system(request) if request.command is None: raise ModelValidationError( "Could not validate command because it was None.") self.logger.debug( "Looking through Command Names to find the Command Specified.") command_names = [] for command in system.commands: if command.name == request.command: self.logger.debug("Found Command with name: %s" % request.command) if request.command_type is None: request.command_type = command.command_type elif command.command_type != request.command_type: raise ModelValidationError( "Command Type for Request was %s but the command specified " "the type as %s" % (request.command_type, command.command_type)) if request.output_type is None: request.output_type = command.output_type elif command.output_type != request.output_type: raise ModelValidationError( "Output Type for Request was %s but the command specified " "the type as %s" % (request.output_type, command.output_type)) request.hidden = command.hidden return command command_names.append(command.name) raise ModelValidationError( "No Command with name: %s could be found. Valid Commands for %s are: %s" % (request.command, system.name, command_names))
def check_file(file_id: str, upsert: bool = False) -> File: """Checks that the file with file_id exists in the DB Args: file_id: The id for the requested file. upsert: If the file doesn't exist create a placeholder file Returns: The file object Raises: NotFoundError: File with the requested ID doesn't exist and is expected to ModelValidationError: Incorrectly formatted ID is given """ try: ObjectId(file_id) except (InvalidId, TypeError): raise ModelValidationError( f"Cannot create a file id with the string {file_id}. " "Requires 24-character hex string.") res = db.query_unique(File, id=file_id) if res is None: if upsert: create_file("BG_placeholder", 0, 0, file_id) res = db.query_unique(File, id=file_id) else: raise NotFoundError(f"Tried to fetch an unsaved file {file_id}") db.modify(res, updated_at=datetime.utcnow()) return res
def clean(self): """Validate before saving to the database""" if self.status not in BrewtilsInstance.INSTANCE_STATUSES: raise ModelValidationError( "Can not save Instance %s: Invalid status '%s' " "provided." % (self.name, self.status))
def clean(self): """Validate before saving to the database""" if self.status not in BrewtilsInstance.INSTANCE_STATUSES: raise ModelValidationError( f"Can not save Instance {self}: Invalid status '{self.status}'" )
def prepare(self): """Called before each verb handler""" # Used for recording prometheus metrics. # We keep this time in seconds, also time-zone does not matter # because we are just calculating a duration. self.request.created_time = time.time() # This is used for sending event notifications self.request.event = Event() self.request.event_extras = {} content_type = self.request.headers.get('content-type', '') if self.request.method.upper() in ['POST', 'PATCH'] and content_type: content_type = content_type.split(';') self.request.mime_type = content_type[0] if self.request.mime_type not in [ 'application/json', 'application/x-www-form-urlencoded' ]: raise ModelValidationError( 'Unsupported or missing content-type header') # Attempt to parse out the charset and decode the body, default to utf-8 charset = 'utf-8' if len(content_type) > 1: search_result = self.charset_re.search(content_type[1]) if search_result: charset = search_result.group(1) self.request.charset = charset self.request.decoded_body = self.request.body.decode(charset)
def delete(self, role_id): """ --- summary: Delete a specific Role parameters: - name: role_id in: path required: true description: The ID of the Role type: string responses: 204: description: Role has been successfully deleted 404: $ref: '#/definitions/404Error' 50x: $ref: '#/definitions/50xError' tags: - Roles """ role = Role.objects.get(id=str(role_id)) if role.name in ("bg-admin", "bg-anonymous", "bg-plugin"): raise ModelValidationError("Unable to remove '%s' role" % role.name) role.delete() self.set_status(204)
def _extract_parameter_value_from_request(self, request, command_parameter, request_parameters, command): """Extracts the expected value based on the parameter in the database, uses the default and validates the type of the request parameter""" request_value = request_parameters.get(command_parameter.key, command_parameter.default) if request_value is None and command_parameter.nullable: return None if command_parameter.multi: request_values = request_value if not isinstance(request_values, list): raise ModelValidationError( "%s was specified as a list, but was not provided as such" % command_parameter.key) value_to_return = [] for value in request_values: value_to_return.append( self._validate_parameter_based_on_type( value, command_parameter, command, request)) else: value_to_return = self._validate_parameter_based_on_type( request_value, command_parameter, command, request) return value_to_return
def prepare(self): """Called before each verb handler""" # Used for calculating request handling duration self.request.created_time = datetime.datetime.utcnow() content_type = self.request.headers.get("content-type", "") if self.request.method.upper() in ["POST", "PATCH"] and content_type: content_type = content_type.split(";") self.request.mime_type = content_type[0] if self.request.mime_type not in [ "application/json", "application/x-www-form-urlencoded", ]: raise ModelValidationError( "Unsupported or missing content-type header") # Attempt to parse out the charset and decode the body, default to utf-8 charset = "utf-8" if len(content_type) > 1: search_result = self.charset_re.search(content_type[1]) if search_result: charset = search_result.group(1) self.request.charset = charset self.request.decoded_body = self.request.body.decode(charset)
async def patch(self): """ --- summary: Partially update a Garden description: | The body of the request needs to contain a set of instructions detailing the updates to apply. Currently the only operations are: * sync ```JSON [ { "operation": "" } ] ``` parameters: - name: garden_name in: path required: true description: Garden to use type: string - name: patch in: body required: true description: Instructions for how to update the Garden schema: $ref: '#/definitions/Patch' responses: 200: description: Execute Patch action against Gardens schema: $ref: '#/definitions/Garden' 400: $ref: '#/definitions/400Error' 404: $ref: '#/definitions/404Error' 50x: $ref: '#/definitions/50xError' tags: - Garden """ patch = SchemaParser.parse_patch(self.request.decoded_body, from_string=True) for op in patch: operation = op.operation.lower() if operation == "sync": response = await self.client( Operation( operation_type="GARDEN_SYNC", ) ) else: raise ModelValidationError(f"Unsupported operation '{op.operation}'") self.set_header("Content-Type", "application/json; charset=UTF-8") self.write(response)
def test_type_match(self): e = ModelValidationError('error message') self.objects_mock.get.side_effect = e response = self.fetch('/api/v1/commands/id') self.assertEqual(400, response.code) self.assertEqual(json.dumps({'message': str(e)}), response.body.decode('utf-8'))
def test_type_match(self): e = ModelValidationError("error message") self.objects_mock.get.side_effect = e response = self.fetch("/api/v1/commands/id") self.assertEqual(400, response.code) self.assertEqual(json.dumps({"message": str(e)}), response.body.decode("utf-8"))
def _validate_regex(self, value, command_parameter): """Validate that the value matches the regex""" if value is not None and not command_parameter.optional: if command_parameter.regex: if not re.match(command_parameter.regex, value): raise ModelValidationError( "Value %s does not match regular expression %s" % (value, command_parameter.regex))
def clean(self): """Validate before saving to the database""" if self.status not in BrewtilsRequest.STATUS_LIST: raise ModelValidationError( 'Can not save Request %s: Invalid status "%s"' % (str(self), self.status)) if (self.command_type is not None and self.command_type not in BrewtilsRequest.COMMAND_TYPES): raise ModelValidationError("Can not save Request %s: Invalid " 'command type "%s"' % (str(self), self.command_type)) if (self.output_type is not None and self.output_type not in BrewtilsRequest.OUTPUT_TYPES): raise ModelValidationError( "Can not save Request %s: Invalid output " 'type "%s"' % (str(self), self.output_type))
def _validate_minimum(self, value, command_parameter): """Validate that the value(s) are above the specified minimum""" if value is not None and not command_parameter.optional: if command_parameter.minimum: if isinstance(value, Sequence): if len(value) < command_parameter.minimum: raise ModelValidationError( "Length %s is less than the minimum allowed length (%s) " "for parameter %s" % ( len(value), command_parameter.minimum, command_parameter.key, )) else: if value < command_parameter.minimum: raise ModelValidationError( "Value %s is less than the minimum allowed value (%s) " "for parameter %s" % (value, command_parameter.minimum, command_parameter.key))
def _validate_no_extra_request_parameter_keys(self, request_parameters, command_parameters): """Validate that all the parameters passed in were valid keys. If there is a key specified that is not noted in the database, then a validation error is thrown""" self.logger.debug("Validating Keys") valid_keys = [cp.key for cp in command_parameters] self.logger.debug("Valid Keys are : %s" % valid_keys) for key in request_parameters: if key not in valid_keys: raise ModelValidationError( "Unknown key '%s' provided in the parameters. Valid Keys are: %s" % (key, valid_keys))
def _validate_required_parameter_is_included_in_request( self, request, command_parameter, request_parameters): """If the parameter is required but was not provided in the request_parameters and does not have a default, then raise a ValidationError""" self.logger.debug( "Validating that Required Parameters are included in the request.") if not command_parameter.optional: if (command_parameter.key not in request_parameters and command_parameter.default is None): raise ModelValidationError( "Required key '%s' not provided in request. Parameters are: %s" % (command_parameter.key, request.parameters))