def _validate_formats(data, schema): """Go through schema checking the formats date-time, time and base64""" if 'format' in schema: format_name = schema['format'] if format_name == "date-time" and not validate_rfc3339(data): raise ForbiddenError("A date-time was not in the required format.") elif format_name == "time" and not _validate_timestr(data): raise ForbiddenError("A time was not in the required format.") elif format_name == "base64" and not _validate_base64(data): raise ForbiddenError("A string was not valid base64.") elif format_name == "timezone" and not _validate_timezone(data): raise ForbiddenError("A string was not a valid timezone.") if 'properties' in schema and isinstance(schema['properties'], dict): for key, value in data.items(): try: _validate_formats(value, schema['properties'][key]) except (TypeError, KeyError): pass if 'additionalProperties' in schema: if isinstance(schema['additionalProperties'], dict): for value in data.values(): try: _validate_formats(value, schema['additionalProperties']) except TypeError: pass if 'items' in schema and isinstance(schema['items'], dict): for item in data: try: _validate_formats(item, schema['items']) except TypeError: pass
def add_listener_update(doc, req): """ Update function: ``payload_telemetry/_update/add_listener`` Given a prototype payload_telemetry JSON document in the request body, containing just the _raw telemetry string and one entry in receivers, create the document or merge this listener into it as appropriate. Used by listeners when a new payload telemetry string has been received. Usage:: PUT /habitat/_design/payload_telemetry/_update/add_listener/<doc ID> { "data": { "_raw": "<base64 raw telemetry data>" }, "receivers": { "<receiver callsign>": { "time_created": "<RFC3339 timestamp>", "time_uploaded": "<RFC3339 timestamp>", <other keys as desired, for instance latest_listener_telemetry, latest_listener_info, etc> } } } The document ID should be sha256(doc["data"]["_raw"]) in hexadecimal. Returns "OK" if everything was fine, otherwise CouchDB will raise an error. Errors might occur in validation (in which case the validation error is returned) or because of a save conflict. In the event of a save conflict, uploaders should retry the same request until the conflict is resolved. """ protodoc = json.loads(req["body"]) if "data" not in protodoc or "_raw" not in protodoc["data"]: raise ForbiddenError("doc.data._raw is required") if "receivers" not in protodoc or len(protodoc["receivers"]) != 1: raise ForbiddenError("doc.receivers must exist and have one receiver") callsign = protodoc["receivers"].keys()[0] protodoc["receivers"][callsign]["time_server"] = now_to_rfc3339_utcoffset() if not doc: doc = { "_id": req["id"], "type": "payload_telemetry", "data": { "_raw": protodoc["data"]["_raw"] }, "receivers": {} } doc["receivers"][callsign] = protodoc["receivers"][callsign] return doc, "OK"
def _check_only_new(new, old): """ Raise an error if any items in old are not present unchanged in new. """ for k in old: if k == u'_rev': continue if k not in new: raise ForbiddenError("You may not remove objects.") if isinstance(old[k], dict): _check_only_new(new[k], old[k]) else: if new[k] != old[k]: raise ForbiddenError("You may not edit existing items.")
def _validate_ukhas(sentence): """ For UKHAS sentences, check that the checksum is an allowable type and that fields with structural requirements are valid. """ checksums = ["xor", "crc16-ccitt", "fletcher-16", "fletcher-16-256", "none"] if 'checksum' in sentence: if sentence['checksum'] not in checksums: raise ForbiddenError("Invalid checksum algorithm.") else: raise ForbiddenError("UKHAS sentences must have a checksum.") if 'fields' in sentence: if len(sentence['fields']) < 1: raise ForbiddenError( "UKHAS sentences must have at least one field.") field_names = [] for field in sentence['fields']: if field['name'][0] == '_': raise ForbiddenError("Field names may not start with _") if field['name'] == 'payload': raise ForbiddenError("Field name may not be 'payload'") field_names.append(field['name']) if field['sensor'] == "stdtelem.coordinate": if 'format' not in field: raise ForbiddenError( "Coordinate fields must have formats.") if len(field_names) != len(set(field_names)): raise ForbiddenError("Duplicate field names") else: raise ForbiddenError("UKHAS sentences must have fields.")
def wrapped(new, old, userctx, secobj): new_type = new.get("type", None) new_deleted = new.get("_deleted", False) if old: old_type = old.get("type", None) else: old_type = None # sanity checks if old_type is None: assert old == {} or old is None if new_deleted: assert new_type is None if new_type == doc_type and old_type in [None, doc_type]: # new doc, or modified doc of correct type. validate: return func(new, old, userctx, secobj) elif new_deleted and old_type == doc_type: # deletion is managed by habitat.validate return elif new_type == doc_type or old_type == doc_type: # one or the other types match but not both, and not a new or deleted doc. raise ForbiddenError("You cannot change the type of a doc") else: # other type: not our business return
def validate_doc(data, schema): """Validate *data* against *schema*, raising descriptive errors""" v = Validator() errors = list(v.iter_errors(data, schema)) if errors: errors = ', '.join((str(error) for error in errors)) raise ForbiddenError("Validation errors: {0}".format(errors)) _validate_formats(data, schema)
def my_validate_func(new, old, userctx, secobj): """my docstring""" assert userctx == {'roles': ['test role']} assert secobj['secobj'] == True assert new['type'] == "a_document_type" if "check_old" in new: assert old == {"type": "a_document_type", "raise": False} elif new["raise"]: raise ForbiddenError("raising")
def _validate_filter(f): """ Check that filters have the required keys according to their type. """ required_keys = { 'normal': ['filter'], 'hotfix': ['code', 'signature', 'certificate']} for k in required_keys[f['type']]: if k not in f: raise ForbiddenError( "{0} filters must include '{1}'.".format(f['type'], k))
def validate(new, old, userctx, secobj): """ Validate this payload_telemetry document against the schema, then perform some specific checks: * Admins may perform any further editing * If edited * Only the parser may add new fields to data * The receivers list may only get new receivers * If created * Must have one receiver * Must have _raw and nothing but _raw in data """ global schema if not schema: schema = read_json_schema("payload_telemetry.json") validate_doc(new, schema) if '_admin' in userctx['roles']: return expect_id = hashlib.sha256(new['data']['_raw']).hexdigest() if '_id' not in new or new['_id'] != expect_id: raise ForbiddenError("Document ID must be sha256(base64 _raw data)") if old: if 'parser' not in userctx['roles'] and \ not _is_equal_relaxed_floats(new['data'], old['data']): raise UnauthorizedError("Only the parser may add data to an" " existing document.") for receiver in old['receivers']: if (receiver not in new['receivers'] or new['receivers'][receiver] != old['receivers'][receiver]): raise ForbiddenError("May not edit or remove receivers.") else: if len(new['receivers']) != 1: raise ForbiddenError("New documents must have exactly one" "receiver.") if set(new['data'].keys()) - set(('_raw', '_fallbacks')): raise ForbiddenError("New documents may only have _raw and/or " "_fallbacks in data.")
def validate(new, old, userctx, secobj): """ Core habitat validation function. * Prevent deletion by anyone except administrators. * Prevent documents without a type. * Prevent documents whose type is invalid. * Prevent changing document type. """ if '_deleted' in new: must_be_admin(userctx, "Only administrators may delete documents.") return if 'type' not in new: raise ForbiddenError("All documents must have a type.") if new['type'] not in allowed_types: raise ForbiddenError("Invalid document type.") if old and new['type'] != old['type']: raise ForbiddenError("Cannot change document type.")
def f(new, old, user, secobj): if new == 1: assert old == 2 assert user == 3 assert secobj == 4 elif new == "bad": from couch_named_python import log, ForbiddenError log("Some sort of log") raise ForbiddenError("Some sort of error.") elif new == "what": from couch_named_python import UnauthorizedError raise UnauthorizedError("You shall not pass") elif new == "meh": {"a dict": True}["nonexistant key"]
def validate(new, old, userctx, secobj): """ Validate this flight document against the schema, then check that only managers are approving documents and approved documents are only edited by managers. """ global schema if not schema: schema = read_json_schema("flight.json") validate_doc(new, schema) if '_admin' in userctx['roles']: return if new['approved'] and 'manager' not in userctx['roles']: raise UnauthorizedError("Only managers may approve documents.") if old and 'manager' not in userctx['roles']: raise UnauthorizedError("Only managers may edit documents.") start = rfc3339_to_timestamp(new['start']) end = rfc3339_to_timestamp(new['end']) launch = rfc3339_to_timestamp(new['launch']['time']) if start > end: raise ForbiddenError("Launch window may not end before it starts.") if end - start > 7 * 24 * 3600: raise ForbiddenError( "Launch window may not be greater than one week" " (speak to an admin if you have a special requirement).") if not start <= launch < end: raise ForbiddenError("Launch time must be within launch window.") if 'payloads' in new: payloads = new['payloads'] if len(payloads) != len(set(payloads)): raise ForbiddenError("Duplicate entries in payloads list")
def _validate_modulation_settings(transmission): """ Check that required keys for each modulation type are present. """ required_keys = {'RTTY': ['shift', 'encoding', 'baud', 'parity', 'stop'], 'DominoEX': ['speed'], 'Hellschreiber': ['variant']} modulation = transmission['modulation'] if modulation not in required_keys: return for k in required_keys[modulation]: if k not in transmission: raise ForbiddenError( "{0} transmissions must include '{1}'.".format(modulation, k))