package api import ( "context" "errors" "fmt" "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/audit/messaging" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1" "google.golang.org/protobuf/proto" ) const ContentTypeProtobuf = "application/x-protobuf" // ErrEventNil indicates that the event was nil var ErrEventNil = errors.New("event is nil") // ErrObjectIdentifierVisibilityMismatch indicates that a reference mismatch was detected. // // Valid combinations are: // * Visibility: Public, ObjectIdentifier: // * Visibility: Private, ObjectIdentifier: -> If ObjectIdentifier is nil, // the ObjectReference in the message will be ObjectName_OBJECT_NAME_SYSTEM var ErrObjectIdentifierVisibilityMismatch = errors.New("object reference visibility mismatch") // ErrRoutableIdentifierMissing indicates that a routable identifier is expected for the constellation of data. var ErrRoutableIdentifierMissing = errors.New("routable identifier expected") // ErrRoutableIdentifierMismatch indicates that a routable identifier (uuid) does not match. var ErrRoutableIdentifierMismatch = errors.New("routable identifier type does not match") // ErrRoutableIdentifierTypeMismatch indicates that a routable identifier type does not match the expected type. var ErrRoutableIdentifierTypeMismatch = errors.New("routable identifier type does not match") // ErrUnsupportedObjectIdentifierType indicates that an unsupported object identifier type has been provided. var ErrUnsupportedObjectIdentifierType = errors.New("unsupported object identifier type") // ErrUnsupportedResourceReferenceType indicates that an unsupported reference type has been provided. var ErrUnsupportedResourceReferenceType = errors.New("unsupported resource reference type") // ErrTopicNameResolverNil states that the topic name resolve is nil var ErrTopicNameResolverNil = errors.New("topic name resolver nil") // ErrMessagingApiNil states that the messaging api is nil var ErrMessagingApiNil = errors.New("messaging api nil") // ErrSerializedPayloadNil states that the give serialized payload is nil var ErrSerializedPayloadNil = errors.New("serialized payload nil") func validateAndSerializePartially( validator *ProtobufValidator, event *auditV1.AuditEvent, visibility auditV1.Visibility, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier, ) (*auditV1.RoutableAuditEvent, error) { // Return error if the given event is nil if event == nil { return nil, ErrEventNil } // Validate the actual event err := (*validator).Validate(event) if err != nil { return nil, err } // Ensure that an object identifier is set if the event is public if objectIdentifier == nil && visibility == auditV1.Visibility_VISIBILITY_PUBLIC { return nil, ErrObjectIdentifierVisibilityMismatch } // Routing identifier not set but object identifier with routable identifier if routingIdentifier == nil && objectIdentifier != nil && visibility != auditV1.Visibility_VISIBILITY_PRIVATE { return nil, ErrRoutableIdentifierMissing } // Routing identifier type and object identifier types are not compatible if routingIdentifier != nil && objectIdentifier != nil && objectIdentifier.Type == auditV1.ObjectType_OBJECT_TYPE_FOLDER && routingIdentifier.Type != RoutingIdentifierTypeOrganization { return nil, ErrRoutableIdentifierTypeMismatch } // Routing identifier type and object identifier do not match if routingIdentifier != nil && objectIdentifier != nil && objectIdentifier.Type != auditV1.ObjectType_OBJECT_TYPE_FOLDER && routingIdentifier.Identifier.String() != objectIdentifier.Identifier { return nil, ErrRoutableIdentifierMismatch } // Test serialization even if the data is dropped later while logging to the legacy solution auditEventBytes, err := proto.Marshal(event) if err != nil { return nil, err } payload := auditV1.UnencryptedData{ Data: auditEventBytes, ProtobufType: fmt.Sprintf("%v", event.ProtoReflect().Descriptor().FullName()), } routableEvent := auditV1.RoutableAuditEvent{ EventName: event.EventName, Visibility: visibility, Data: &auditV1.RoutableAuditEvent_UnencryptedData{UnencryptedData: &payload}, } // Set oneof protobuf fields after creation of the object if objectIdentifier == nil { routableEvent.ResourceReference = &auditV1.RoutableAuditEvent_ObjectName{ ObjectName: auditV1.ObjectName_OBJECT_NAME_SYSTEM} } else { routableEvent.ResourceReference = &auditV1.RoutableAuditEvent_ObjectIdentifier{ ObjectIdentifier: objectIdentifier} } err = (*validator).Validate(&routableEvent) if err != nil { return nil, err } return &routableEvent, nil } // Send implements AuditApi.Send func send( topicNameResolver *TopicNameResolver, messagingApi *messaging.MessagingApi, ctx context.Context, routingIdentifier *RoutingIdentifier, cloudEvent *CloudEvent, ) error { if topicNameResolver == nil { return ErrTopicNameResolverNil } if messagingApi == nil { return ErrMessagingApiNil } if cloudEvent == nil { return ErrSerializedPayloadNil } topic, err := (*topicNameResolver).Resolve(routingIdentifier) if err != nil { return err } applicationAttributes := make(map[string]any) applicationAttributes["cloudEvents:specversion"] = cloudEvent.specVersion applicationAttributes["cloudEvents:source"] = cloudEvent.source applicationAttributes["cloudEvents:id"] = cloudEvent.id applicationAttributes["cloudEvents:time"] = cloudEvent.time.UnixMilli() applicationAttributes["cloudEvents:datacontenttype"] = cloudEvent.dataContentType applicationAttributes["cloudEvents:type"] = cloudEvent.dataType return (*messagingApi).Send( ctx, topic, (*cloudEvent).data, (*cloudEvent).dataContentType, applicationAttributes) }