def validate_manifest_uris(manifest: dict): """ Fetch & validate manifest's remote objects """ request_type = manifest.get('request_type', '') entry_validators = { "groundtruth_uri": lambda k, v: validate_groundtruth_entry(k, v, request_type), "taskdata_uri": lambda _, v: validate_taskdata_entry(v) } for uri_key, validate_entry in entry_validators.items(): uri = manifest.get(uri_key) if uri is None: continue try: response = requests.get(uri) response.raise_for_status() entries_count = traverse_json_entries(response.json(), validate_entry) except (BaseError, RequestException) as e: raise ValidationError(f"{uri_key} validation failed: {e}") from e if entries_count == 0: raise ValidationError(f"fetched {uri_key} is empty")
def keys_uuid_check(self, value): for key in self.keys_iterator(value): if key != key.lower(): raise ValidationError("use lowercase") try: UUID(key) except: raise ValidationError("invalid uuid")
def validate_requester_question_example(self, data, value): # validation runs before other params, so need to handle missing case if not data.get('request_type'): raise ValidationError("request_type missing") # based on https://github.com/hCaptcha/hmt-basemodels/issues/27#issuecomment-590706643 supports_lists = ['image_label_area_select', 'image_label_binary'] if isinstance(value, list) and not data['request_type'] in supports_lists: raise ValidationError( "Lists are not allowed in this challenge type") return value
def validate_taskdata_uri(self, data, value): if data.get('taskdata') and len( data.get('taskdata')) > 0 and data.get('taskdata_uri'): raise ValidationError( u'Specify only one of taskdata {} or taskdata_uri {}'.format( data.get('taskdata'), data.get('taskdata_uri'))) return value
def validate_score(self, value): if value is None: return value if not isinstance(value, dict): raise ValidationError("value must be a dict") if len(value) != 1: raise ValidationError("invalid number of keys") score = value.get("score") if score is None: raise ValidationError("score missing") if not isinstance(score, (float, int)): raise ValidationError("invalid score value type") if score > 1 or score < 0: raise ValidationError("invalid score value")
def validate_requester_restricted_answer_set(self, data, value): """image_label_area_select should always have a single RAS set""" # validation runs before other params, so need to handle missing case if not data.get('request_type'): raise ValidationError("request_type missing") if data['request_type'] == 'image_label_area_select': if not value or len(value.keys()) == 0: value = {'label': {}} data['requester_restricted_answer_set'] = value return value
def validate_request_type(self, data, value): """ validate request types for all types of challenges multi_challenge should always have multi_challenge_manifests """ # validation runs before other params, so need to handle missing case if not data.get('request_type'): raise ValidationError("request_type missing") if data.get('request_type') == 'multi_challenge': if not data.get('multi_challenge_manifests'): raise ValidationError( "multi_challenge requires multi_challenge_manifests.") elif data.get('request_type') in [ 'image_label_multiple_choice', 'image_label_area_select' ]: if data.get('multiple_choice_min_choices', 1) > data.get( 'multiple_choice_max_choices', 1): raise ValidationError( "multiple_choice_min_choices cannot be greater than multiple_choice_max_choices" ) return value
def validate_taskdata_entry(value: dict): """ Validate taskdata entry """ if not isinstance(value, dict): raise ValidationError("taskdata entry should be dict") TaskDataEntry(value).validate()
def validate_metadata(self, data, value): if len(str(value)) > 1024: raise ValidationError("metadata should be < 1024") return value
def keys_choices_check(self, value, choices): for key in self.keys_iterator(value): if key not in choices: raise ValidationError(f"{key} not allowed. Use {choices}")
def keys_lowercase_check(self, value): for key in self.keys_iterator(value): if key != key.lower(): raise ValidationError("use lowercase")
def keys_iterator(self, value): if isinstance(value, list): for restriction in value: if len(restriction) != 1: raise ValidationError("exactly 1 element is required") yield next(iter(restriction))
def validate_groundtruth(self, data, value): if data.get('groundtruth_uri') and data.get('groundtruth'): raise ValidationError( "Specify only groundtruth_uri or groundtruth, not both.") return value