예제 #1
0
def _split_resource(context: Context) -> Tuple[str, str, str]:
    """Splits a background event's resource into a CloudEvent service, resource, and subject."""
    service = ""
    resource = ""
    if isinstance(context.resource, dict):
        service = context.resource.get("service", "")
        resource = context.resource["name"]
    else:
        resource = context.resource

    # If there's no service we'll choose an appropriate one based on the event type.
    if not service:
        for b_service, ce_service in _SERVICE_BACKGROUND_TO_CE.items():
            if context.event_type.startswith(b_service):
                service = ce_service
                break
        if not service:
            raise EventConversionException(
                "Unable to find CloudEvent equivalent service "
                f"for {context.event_type}")

    # If we don't need to split the resource string then we're done.
    if service not in _CE_SERVICE_TO_RESOURCE_RE:
        return service, resource, ""

    # Split resource into resource and subject.
    match = _CE_SERVICE_TO_RESOURCE_RE[service].fullmatch(resource)
    if not match:
        raise EventConversionException("Resource regex did not match")

    return service, match.group(1), match.group(2)
예제 #2
0
def cloud_event_to_background_event(request) -> Tuple[Any, Context]:
    """Converts a background event represented by the given HTTP request into a CloudEvent."""
    try:
        event = from_http(request.headers, request.get_data())
        data = event.data
        service, name = _split_ce_source(event["source"])

        if event["type"] not in _CE_TO_BACKGROUND_TYPE:
            raise EventConversionException(
                f'Unable to find background event equivalent type for "{event["type"]}"'
            )

        if service == _PUBSUB_CE_SERVICE:
            resource = {
                "service": service,
                "name": name,
                "type": _PUBSUB_MESSAGE_TYPE
            }
            if "message" in data:
                data = data["message"]
            if "messageId" in data:
                del data["messageId"]
            if "publishTime" in data:
                del data["publishTime"]
        elif service == _FIREBASE_AUTH_CE_SERVICE:
            resource = name
            if "metadata" in data:
                for old, new in _FIREBASE_AUTH_METADATA_FIELDS_CE_TO_BACKGROUND.items(
                ):
                    if old in data["metadata"]:
                        data["metadata"][new] = data["metadata"][old]
                        del data["metadata"][old]
        elif service == _STORAGE_CE_SERVICE:
            resource = {
                "name": f"{name}/{event['subject']}",
                "service": service,
                "type": data["kind"],
            }
        elif service == _FIREBASE_DB_CE_SERVICE:
            name = re.sub("/locations/[^/]+", "", name)
            resource = f"{name}/{event['subject']}"
        else:
            resource = f"{name}/{event['subject']}"

        context = Context(
            eventId=event["id"],
            timestamp=event["time"],
            eventType=_CE_TO_BACKGROUND_TYPE[event["type"]],
            resource=resource,
        )
        return (data, context)
    except (AttributeError, KeyError, TypeError, MissingRequiredFields):
        raise EventConversionException(
            "Failed to convert CloudEvent to BackgroundEvent.")
예제 #3
0
def marshal_background_event_data(request):
    """Marshal the request body of a raw Pub/Sub HTTP request into the schema that is expected of
    a background event"""
    try:
        request_data = request.get_json()
        if not _is_raw_pubsub_payload(request_data):
            # If this in not a raw Pub/Sub request, return the unaltered request data.
            return request_data
        return {
            "context": {
                "eventId":
                request_data["message"]["messageId"],
                "timestamp":
                request_data["message"].get(
                    "publishTime",
                    datetime.utcnow().isoformat() + "Z"),
                "eventType":
                _PUBSUB_EVENT_TYPE,
                "resource": {
                    "service": _PUBSUB_CE_SERVICE,
                    "type": _PUBSUB_MESSAGE_TYPE,
                    "name": _parse_pubsub_topic(request.path),
                },
            },
            "data": {
                "@type": _PUBSUB_MESSAGE_TYPE,
                "data": request_data["message"]["data"],
                "attributes": request_data["message"]["attributes"],
            },
        }
    except (AttributeError, KeyError, TypeError):
        raise EventConversionException(
            "Failed to convert Pub/Sub payload to event")
def background_event_to_cloudevent(request) -> CloudEvent:
    """Converts a background event represented by the given HTTP request into a CloudEvent."""
    event_data = marshal_background_event_data(request)
    if not event_data:
        raise EventConversionException("Failed to parse JSON")

    event_object = BackgroundEvent(**event_data)
    data = event_object.data
    context = Context(**event_object.context)

    if context.event_type not in _BACKGROUND_TO_CE_TYPE:
        raise EventConversionException(
            f'Unable to find CloudEvent equivalent type for "{context.event_type}"'
        )
    new_type = _BACKGROUND_TO_CE_TYPE[context.event_type]

    service, resource, subject = _split_resource(context)

    # Handle Pub/Sub events.
    if service == _PUBSUB_CE_SERVICE:
        data = {"message": data}

    # Handle Firebase Auth events.
    if service == _FIREBASE_AUTH_CE_SERVICE:
        if "metadata" in data:
            for old, new in _FIREBASE_AUTH_METADATA_FIELDS_BACKGROUND_TO_CE.items(
            ):
                if old in data["metadata"]:
                    data["metadata"][new] = data["metadata"][old]
                    del data["metadata"][old]
        if "uid" in data:
            uid = data["uid"]
            subject = f"users/{uid}"

    metadata = {
        "id": context.event_id,
        "time": context.timestamp,
        "specversion": _CLOUDEVENT_SPEC_VERSION,
        "datacontenttype": "application/json",
        "type": new_type,
        "source": f"//{service}/{resource}",
    }

    if subject:
        metadata["subject"] = subject

    return CloudEvent(metadata, data)
예제 #5
0
def _split_ce_source(source) -> Tuple[str, str]:
    """Splits a CloudEvent source string into resource and subject components."""
    regex = re.compile(r"\/\/([^/]+)\/(.+)")
    match = regex.fullmatch(source)
    if not match:
        raise EventConversionException("Unexpected CloudEvent source.")

    return match.group(1), match.group(2)
예제 #6
0
def background_event_to_cloud_event(request) -> CloudEvent:
    """Converts a background event represented by the given HTTP request into a CloudEvent."""
    event_data = marshal_background_event_data(request)
    if not event_data:
        raise EventConversionException("Failed to parse JSON")

    event_object = BackgroundEvent(**event_data)
    data = event_object.data
    context = Context(**event_object.context)

    if context.event_type not in _BACKGROUND_TO_CE_TYPE:
        raise EventConversionException(
            f'Unable to find CloudEvent equivalent type for "{context.event_type}"'
        )
    new_type = _BACKGROUND_TO_CE_TYPE[context.event_type]

    service, resource, subject = _split_resource(context)
    source = f"//{service}/{resource}"

    # Handle Pub/Sub events.
    if service == _PUBSUB_CE_SERVICE:
        if "messageId" not in data:
            data["messageId"] = context.event_id
        if "publishTime" not in data:
            data["publishTime"] = context.timestamp
        data = {"message": data}

    # Handle Firebase Auth events.
    if service == _FIREBASE_AUTH_CE_SERVICE:
        if "metadata" in data:
            for old, new in _FIREBASE_AUTH_METADATA_FIELDS_BACKGROUND_TO_CE.items(
            ):
                if old in data["metadata"]:
                    data["metadata"][new] = data["metadata"][old]
                    del data["metadata"][old]
        if "uid" in data:
            uid = data["uid"]
            subject = f"users/{uid}"

    # Handle Firebase DB events.
    if service == _FIREBASE_DB_CE_SERVICE:
        # The CE source of firebasedatabase CloudEvents includes location information
        # that is inferred from the 'domain' field of legacy events.
        if "domain" not in event_data:
            raise EventConversionException(
                "Invalid FirebaseDB event payload: missing 'domain'")

        domain = event_data["domain"]
        location = "us-central1"
        if domain != "firebaseio.com":
            location = domain.split(".")[0]

        resource = f"projects/_/locations/{location}/{resource}"
        source = f"//{service}/{resource}"

    metadata = {
        "id": context.event_id,
        "time": context.timestamp,
        "specversion": _CLOUD_EVENT_SPEC_VERSION,
        "datacontenttype": "application/json",
        "type": new_type,
        "source": source,
    }

    if subject:
        metadata["subject"] = subject

    return CloudEvent(metadata, data)