package api import ( "context" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1" "encoding/json" "fmt" "github.com/google/uuid" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" "net/http" "net/url" "strings" "testing" "time" ) type mockSpan struct { spanContext trace.SpanContext } func (s *mockSpan) SpanContext() trace.SpanContext { return s.spanContext } func Test_TraceParentFromSpan(t *testing.T) { tracer := otel.Tracer("test") verifyTraceParent := func(traceParent string, span trace.Span, isSampled bool) { parts := strings.Split(traceParent, "-") assert.Equal(t, 4, len(parts)) // trace version assert.Equal(t, "00", parts[0]) assert.Equal(t, span.SpanContext().TraceID().String(), parts[1]) assert.Equal(t, span.SpanContext().SpanID().String(), parts[2]) var traceFlags = "00" if isSampled { traceFlags = "01" } assert.Equal(t, traceFlags, parts[3]) } t.Run("sampled", func(t *testing.T) { _, span := tracer.Start(context.Background(), "test") updatedFlags := span.SpanContext().TraceFlags().WithSampled(true) updatedContext := span.SpanContext().WithTraceFlags(updatedFlags) mockedSpan := mockSpan{ spanContext: updatedContext, } traceParent := TraceParentFromSpan(&mockedSpan) verifyTraceParent(traceParent, span, true) }) t.Run("non-sampled", func(t *testing.T) { _, span := tracer.Start(context.Background(), "test") traceParent := TraceParentFromSpan(span) verifyTraceParent(traceParent, span, false) }) } func Test_GetCalledServiceNameFromRequest(t *testing.T) { t.Run("localhost", func(t *testing.T) { request := ApiRequest{Host: "localhost:8080"} serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager") assert.Equal(t, "resource-manager", serviceName) }) t.Run("cf", func(t *testing.T) { request := ApiRequest{Host: "stackit-resource-manager-go-dev.apps.01.cf.eu01.stackit.cloud"} serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager") assert.Equal(t, "stackit-resource-manager-go-dev", serviceName) }) t.Run("cf invalid host", func(t *testing.T) { request := ApiRequest{Host: ""} serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager") assert.Equal(t, "resource-manager", serviceName) }) t.Run( "ip", func(t *testing.T) { request := ApiRequest{Host: "127.0.0.1"} serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager") assert.Equal(t, "resource-manager", serviceName) }, ) t.Run( "ip short", func(t *testing.T) { request := ApiRequest{Host: "::1"} serviceName := GetCalledServiceNameFromRequest(&request, "resource-manager") assert.Equal(t, "resource-manager", serviceName) }, ) } func Test_NewPbInt64Value(t *testing.T) { t.Run("nil", func(t *testing.T) { value := NewPbInt64Value(nil) assert.Nil(t, value) }) t.Run("value", func(t *testing.T) { var input int64 = 1 value := NewPbInt64Value(&input) assert.Equal(t, wrapperspb.Int64Value{Value: 1}.Value, value.Value) }) } func Test_NewResponseMetadata(t *testing.T) { headers := make(map[string]string) headers["Content-Type"] = "application/json" responseTime := time.Now().UTC() responseItems := int64(10) responseSize := int64(100) t.Run("no error", func(t *testing.T) { for code := 1; code < 400; code++ { metadata := NewResponseMetadata(code, &responseItems, &responseSize, headers, responseTime) assert.Equal(t, wrapperspb.Int32(int32(code)).Value, metadata.StatusCode.Value) assert.Nil(t, metadata.ErrorMessage) assert.Nil(t, metadata.ErrorDetails) assert.Equal(t, wrapperspb.Int64(responseItems), metadata.ResponseAttributes.NumResponseItems) assert.Equal(t, wrapperspb.Int64(responseSize), metadata.ResponseAttributes.Size) assert.Equal(t, timestamppb.New(responseTime), metadata.ResponseAttributes.Time) } }) t.Run("client error", func(t *testing.T) { for code := 400; code < 500; code++ { metadata := NewResponseMetadata(code, nil, nil, headers, responseTime) assert.Equal(t, wrapperspb.Int32(int32(code)).Value, metadata.StatusCode.Value) assert.Equal(t, "Client error", *metadata.ErrorMessage) assert.Nil(t, metadata.ErrorDetails) assert.Nil(t, metadata.ResponseAttributes.NumResponseItems) assert.Nil(t, metadata.ResponseAttributes.Size) assert.Equal(t, timestamppb.New(responseTime), metadata.ResponseAttributes.Time) } }) t.Run("server error", func(t *testing.T) { for code := 500; code < 600; code++ { metadata := NewResponseMetadata(code, nil, nil, headers, responseTime) assert.Equal(t, wrapperspb.Int32(int32(code)).Value, metadata.StatusCode.Value) assert.Equal(t, "Server error", *metadata.ErrorMessage) assert.Nil(t, metadata.ErrorDetails) assert.Nil(t, metadata.ResponseAttributes.NumResponseItems) assert.Nil(t, metadata.ResponseAttributes.Size) assert.Equal(t, timestamppb.New(responseTime), metadata.ResponseAttributes.Time) } }) } func Test_NewRequestMetadata(t *testing.T) { userAgent := "userAgent" requestHeaders := make(map[string][]string) requestHeaders["User-Agent"] = []string{userAgent} requestHeaders["Custom"] = []string{"customHeader"} queryString := "topic=project" request := ApiRequest{ Method: "GET", URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, Host: "localhost:8080", Proto: "HTTP/1.1", Scheme: "http", Header: requestHeaders, } requestId := "requestId" requestScheme := "requestScheme" requestTime := time.Now().UTC() audiences := []string{"audience"} authenticationPrincipal := "authenticationPrincipal" claimMap := make(map[string]interface{}) auditClaims, _ := structpb.NewStruct(claimMap) clientIp := "clientIp" filteredHeaders := make(map[string]string) filteredHeaders["Custom"] = "customHeader" filteredHeaders["User-Agent"] = userAgent verifyRequestMetadata := func(requestMetadata *auditV1.RequestMetadata, requestId *string) { assert.Equal(t, clientIp, requestMetadata.CallerIp) assert.Equal(t, userAgent, requestMetadata.CallerSuppliedUserAgent) assert.NotNil(t, requestMetadata.RequestAttributes) attributes := requestMetadata.RequestAttributes assert.Equal(t, requestId, attributes.Id) assert.Equal(t, filteredHeaders, attributes.Headers) assert.Equal(t, request.URL.Path, attributes.Path) assert.Equal(t, request.Host, attributes.Host) assert.Equal(t, requestScheme, attributes.Scheme) assert.Equal(t, timestamppb.New(requestTime), attributes.Time) assert.Equal(t, request.Proto, attributes.Protocol) assert.NotNil(t, attributes.Auth) auth := attributes.Auth assert.Equal(t, authenticationPrincipal, auth.Principal) assert.Equal(t, audiences, auth.Audiences) assert.Equal(t, auditClaims, auth.Claims) } t.Run("with query parameters", func(t *testing.T) { requestMetadata := NewRequestMetadata( &request, filteredHeaders, &requestId, requestScheme, requestTime, clientIp, authenticationPrincipal, audiences, auditClaims, ) verifyRequestMetadata(requestMetadata, &requestId) assert.Equal(t, "topic%3Dproject", *requestMetadata.RequestAttributes.Query) }) t.Run("without query parameters", func(t *testing.T) { request := ApiRequest{ Method: "GET", URL: RequestUrl{Path: "/audit/new"}, Host: "localhost:8080", Proto: "HTTP/1.1", Header: requestHeaders, } requestMetadata := NewRequestMetadata( &request, filteredHeaders, &requestId, requestScheme, requestTime, clientIp, authenticationPrincipal, audiences, auditClaims, ) verifyRequestMetadata(requestMetadata, &requestId) assert.Nil(t, requestMetadata.RequestAttributes.Query) }) t.Run("with empty query parameters", func(t *testing.T) { emptyQuery := "" request := ApiRequest{ Method: "GET", URL: RequestUrl{Path: "/audit/new", RawQuery: &emptyQuery}, Host: "localhost:8080", Proto: "HTTP/1.1", Header: requestHeaders, } requestMetadata := NewRequestMetadata( &request, filteredHeaders, &requestId, requestScheme, requestTime, clientIp, authenticationPrincipal, audiences, auditClaims, ) verifyRequestMetadata(requestMetadata, &requestId) assert.Nil(t, requestMetadata.RequestAttributes.Query) }) t.Run("without request id", func(t *testing.T) { request := ApiRequest{ Method: "GET", URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, Host: "localhost:8080", Proto: "HTTP/1.1", Header: requestHeaders, } requestMetadata := NewRequestMetadata( &request, filteredHeaders, nil, requestScheme, requestTime, clientIp, authenticationPrincipal, audiences, auditClaims, ) verifyRequestMetadata(requestMetadata, nil) }) t.Run("various default http methods", func(t *testing.T) { httpMethods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} for _, httpMethod := range httpMethods { request := ApiRequest{ Method: httpMethod, URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, Host: "localhost:8080", Proto: "HTTP/1.1", Header: requestHeaders, } requestMetadata := NewRequestMetadata( &request, filteredHeaders, &requestId, requestScheme, requestTime, clientIp, authenticationPrincipal, audiences, auditClaims, ) verifyRequestMetadata(requestMetadata, &requestId) expectedMethod := fmt.Sprintf("HTTP_METHOD_%s", httpMethod) assert.Equal(t, expectedMethod, requestMetadata.RequestAttributes.Method.String()) } }) t.Run("unknown http method", func(t *testing.T) { request := ApiRequest{ Method: "", URL: RequestUrl{Path: "/audit/new", RawQuery: &queryString}, Host: "localhost:8080", Proto: "HTTP/1.1", Header: requestHeaders, } requestMetadata := NewRequestMetadata( &request, filteredHeaders, &requestId, requestScheme, requestTime, clientIp, authenticationPrincipal, audiences, auditClaims, ) verifyRequestMetadata(requestMetadata, &requestId) assert.Equal(t, auditV1.AttributeContext_HTTP_METHOD_UNSPECIFIED.String(), requestMetadata.RequestAttributes.Method.String()) }) } func Test_FilterAndMergeRequestHeaders(t *testing.T) { t.Run("skip headers", func(t *testing.T) { headers := make(map[string][]string) headers["Authorization"] = []string{"ey..."} headers["B3"] = []string{"80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90"} headers[":authority"] = []string{"localhost:9090"} filteredHeaders := FilterAndMergeHeaders(headers) assert.Equal(t, 0, len(filteredHeaders)) }) t.Run("skip headers by prefix", func(t *testing.T) { headers := make(map[string][]string) headers["X-Forwarded-Proto"] = []string{"https"} headers["Stackit-test"] = []string{"test"} filteredHeaders := FilterAndMergeHeaders(headers) assert.Equal(t, 0, len(filteredHeaders)) }) t.Run("merge headers", func(t *testing.T) { headers := make(map[string][]string) headers["Custom1"] = []string{"value1", "value2"} headers["Custom2"] = []string{"value3", "value4"} filteredHeaders := FilterAndMergeHeaders(headers) assert.Equal(t, 2, len(filteredHeaders)) assert.Equal(t, "value1,value2", filteredHeaders["Custom1"]) assert.Equal(t, "value3,value4", filteredHeaders["Custom2"]) }) } func Test_AuditAttributesFromAuthorizationHeader(t *testing.T) { t.Run("basic token", func(t *testing.T) { headerValue := "Basic username:password" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) assert.ErrorIs(t, err, ErrTokenIsNotBearerToken) }) t.Run("invalid header value", func(t *testing.T) { headerValue := "a b c" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) assert.ErrorIs(t, err, ErrInvalidAuthorizationHeaderValue) }) t.Run("invalid token too many parts", func(t *testing.T) { headerValue := "Bearer a.b.c.d" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) assert.ErrorIs(t, err, ErrInvalidBearerToken) }) t.Run("invalid bearer token", func(t *testing.T) { headerValue := "Bearer a.b.c" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} _, _, _, _, err := AuditAttributesFromAuthorizationHeader(&request) assert.ErrorIs(t, err, ErrInvalidBearerToken) }) t.Run("client credentials token", func(t *testing.T) { headerValue := "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1yZXNvdXJjZS1tYW5hZ2VyLWRldiJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2IiwiZXhwIjoxNzI0NDA1MzI2LCJpYXQiOjE3MjQ0MDQ0MjYsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZGV2LnN0YWNraXQuY2xvdWQiLCJqdGkiOiJlNDZlYmEzOC1kZWRiLTQ1NDEtOTRmMy00OWY5N2E5MzRkNTgiLCJuYmYiOjE3MjQ0MDQ0MjYsInNjb3BlIjoidWFhLm5vbmUiLCJzdWIiOiJzdGFja2l0LXJlc291cmNlLW1hbmFnZXItZGV2In0.JP5Uy7AMdK4ukzQ6aOYzbVwEmq0Tp2ppQGRqGOhuVQgbqs6yJ33GKXo7RPsJVLw3FR7XAxENIVqNvzGotbDXr0NjBGdzyxIHzrOaUqM4w1iLzD1KF51dXFwkoigqDdD7Ze9eI_Uo3tSn8FwGLTSoO-ONQYpnceCiGut2Gc6VIL8HOLdh8dzlRENGQtgYd-3Y5zqpoLrsR2Bd-0sv15sF-5aI0CqcC8gE70JPImKf2u_IYI-TYMDNk86YSCtaYO5-alOrHXXWwgzSoH-r2s5qoOhPbei9myV_P4fdcKXxMqfap9hImXPUooVhpdUr1AabZw3MtW7rION8tJAiauhMQA" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := AuditAttributesFromAuthorizationHeader(&request) auditClaimsMap := auditClaims.AsMap() assert.Nil(t, err) assert.Equal(t, "https://accounts.dev.stackit.cloud", auditClaimsMap["iss"]) assert.Equal(t, "stackit-resource-manager-dev", auditClaimsMap["sub"]) assert.Equal(t, []interface{}{"stackit-resource-manager-dev"}, auditClaimsMap["aud"].([]interface{})) assert.Equal(t, "e46eba38-dedb-4541-94f3-49f97a934d58", auditClaimsMap["jti"]) principal := fmt.Sprintf("%s/%s", url.QueryEscape("stackit-resource-manager-dev"), url.QueryEscape("https://accounts.dev.stackit.cloud")) assert.Equal(t, principal, authenticationPrincipal) assert.Equal(t, []string{"stackit-resource-manager-dev"}, audiences) assert.Equal(t, "stackit-resource-manager-dev", authenticationInfo.PrincipalId) assert.Equal(t, "do-not-reply@stackit.cloud", authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) }) t.Run("service account access token", func(t *testing.T) { headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxMGYzOGIwMS01MzRiLTQ3YmItYTAzYS1lMjk0Y2EyYmU0ZGUiLCJhdWQiOlsic3RhY2tpdCIsImFwaSJdLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3Rva2VuLnNvdXJjZSI6ImxlZ2FjeSIsInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiJjZDk0ZjAxYS1kZjJlLTQ0NTYtOTAyZS00OGY1ZTU3ZjBiNjMiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50Iiwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMTBmMzhiMDEtNTM0Yi00N2JiLWEwM2EtZTI5NGNhMmJlNGRlIiwiZXhwIjoxNzIyNjY5MzQzLCJpYXQiOjE3MjI1ODI5NDMsImVtYWlsIjoibXktc2VydmljZS15aWZjOWUxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiI4NGMzMGE0Ni0xMDAxLTQzNmYtODU5Zi04OWMwYmExOWJlMWUifQ.hb8X9VKc9xViHgNMyFHT9ePj_lyEwTV1D2es8E278WtoCJ9-4GPPQGjhcLGGrigjnvpRYV2LKzNqpQslerT5lFT_pHACsryaAE0ImYjmoe-nutA7BBpYuM_JN6pk5VIjVFLTqRKeIvFexPacqS2Vo3YoK1GvxPB8WPWBbGIsBtMl-PTm8OTwwzooBOoCRhhMR-E1lFbAymLsc1JI4yDQKLLomvhEopgmocCnQ-P1QkiKMqdkNxiD_YYLLYTOApg6d62BhqpH66ziqx493AStdZ8d5Kjvf3e1knDhaxVwNCghQj7lSo2kNAqZe__g2tiXpiZNTXBFJ_5HgQMLh67wng" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := AuditAttributesFromAuthorizationHeader(&request) auditClaimsMap := auditClaims.AsMap() assert.Nil(t, err) assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"]) assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", auditClaimsMap["sub"]) assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{})) assert.Equal(t, "84c30a46-1001-436f-859f-89c0ba19be1e", auditClaimsMap["jti"]) principal := fmt.Sprintf("%s/%s", url.QueryEscape("10f38b01-534b-47bb-a03a-e294ca2be4de"), url.QueryEscape("stackit/serviceaccount")) assert.Equal(t, principal, authenticationPrincipal) assert.Equal(t, []string{"stackit", "api"}, audiences) assert.Equal(t, "10f38b01-534b-47bb-a03a-e294ca2be4de", authenticationInfo.PrincipalId) assert.Equal(t, "my-service-yifc9e1@sa.stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/10f38b01-534b-47bb-a03a-e294ca2be4de", *authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) }) t.Run("impersonated token of access token", func(t *testing.T) { headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiMDJhZWY1MTYtMzE3Zi00ZWMxLWExZGYtMWFjYmQ0ZDQ5ZmUzIn0sInN0YWNraXQvc2VydmljZWFjY291bnQvbmFtZXNwYWNlIjoiYXBpIiwic3RhY2tpdC9wcm9qZWN0L3Byb2plY3QuaWQiOiJkYWNjNzgzMC04NDNlLTRjNWUtODZmZi1hYTBmYjUxZDYzNmYiLCJhenAiOiIwMmFlZjUxNi0zMTdmLTRlYzEtYTFkZi0xYWNiZDRkNDlmZTMiLCJzdGFja2l0L3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmNDUwMDliMi02NDMzLTQzYzEtYjZjNy02MThjNDQzNTllNzEiLCJleHAiOjE3MjQwNjI5MDcsImlhdCI6MTcyNDA1OTMwNywiZW1haWwiOiJzZXJ2aWNlLWFjY291bnQtMi10ajlzcnQxQHNhLnN0YWNraXQuY2xvdWQiLCJqdGkiOiIzNzU1NTE4My0wMWI5LTQyNzAtYmRjMS02OWI0ZmNmZDVlZTkifQ.auBvvsIesFMAlWOCPCPC77DrrHF7gSKZwKs_Zry5KFvu2bpZZC1BcSXOc8b9eh0SzANI9M9aGJBhOzOm39-ZZ5XOQ-6_y1aWuEenYQ6kT5D3GzCUTMDzSi1lcZ4IG5nFMa_AAlVEN_7AMv7LHGtz49bWLJnAgeTo1cvof-OgP4mCQ5O6E0iyAq-5u8V8NJL7HIZy7BDe4J1mjfYhwKagrN7QFWu4fhN4TNS7d922X_6V489BhjRFRYjLW_qDnv912JorbGRz_XwNy_dPA81EkdMyKE0BJUezguJUEKEG2_JEi9O64Flcoi6x8cFHYhaDuMMSLipzePaHdyk2lQtH7Q" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := AuditAttributesFromAuthorizationHeader(&request) auditClaimsMap := auditClaims.AsMap() assert.Nil(t, err) assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"]) assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", auditClaimsMap["sub"]) assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{})) assert.Equal(t, "37555183-01b9-4270-bdc1-69b4fcfd5ee9", auditClaimsMap["jti"]) principal := fmt.Sprintf("%s/%s", url.QueryEscape("f45009b2-6433-43c1-b6c7-618c44359e71"), url.QueryEscape("stackit/serviceaccount")) assert.Equal(t, principal, authenticationPrincipal) assert.Equal(t, []string{"stackit", "api"}, audiences) assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", authenticationInfo.PrincipalId) assert.Equal(t, "service-account-2-tj9srt1@sa.stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/f45009b2-6433-43c1-b6c7-618c44359e71", *authenticationInfo.ServiceAccountName) assert.NotNil(t, authenticationInfo.ServiceAccountDelegationInfo) serviceAccountDelegationInfo := authenticationInfo.ServiceAccountDelegationInfo assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalId) assert.Equal(t, "do-not-reply@stackit.cloud", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalEmail) }) t.Run("impersonated token of impersonated access token", func(t *testing.T) { headerValue := "Bearer eyJraWQiOiJaVFJqWlRNek5tSmlNRGt3TldJMU5USTRZVGxpT1RjMllUWXlZVE16WldNIiwiYWxnIjoiUlM1MTIifQ.eyJzdWIiOiIxNzM0YjRiNi0xZDVlLTQ4MTktOWI1MC0yOTkxN2ExYjlhZDUiLCJpc3MiOiJzdGFja2l0L3NlcnZpY2VhY2NvdW50IiwiYXVkIjpbInN0YWNraXQiLCJhcGkiXSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC90b2tlbi5zb3VyY2UiOiJvYXV0aDIiLCJhY3QiOnsic3ViIjoiZjQ1MDA5YjItNjQzMy00M2MxLWI2YzctNjE4YzQ0MzU5ZTcxIiwiYWN0Ijp7InN1YiI6IjAyYWVmNTE2LTMxN2YtNGVjMS1hMWRmLTFhY2JkNGQ0OWZlMyJ9fSwic3RhY2tpdC9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGkiLCJzdGFja2l0L3Byb2plY3QvcHJvamVjdC5pZCI6ImRhY2M3ODMwLTg0M2UtNGM1ZS04NmZmLWFhMGZiNTFkNjM2ZiIsImF6cCI6ImY0NTAwOWIyLTY0MzMtNDNjMS1iNmM3LTYxOGM0NDM1OWU3MSIsInN0YWNraXQvc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE3MzRiNGI2LTFkNWUtNDgxOS05YjUwLTI5OTE3YTFiOWFkNSIsImV4cCI6MTcyNDA2Mjk2MywiaWF0IjoxNzI0MDU5MzYzLCJlbWFpbCI6InNlcnZpY2UtYWNjb3VudC0zLWZnaHN4dzFAc2Euc3RhY2tpdC5jbG91ZCIsImp0aSI6IjFmN2YxZWZjLTMzNDktNDExYS1hNWQ3LTIyNTVlMGE1YThhZSJ9.c1ae17bAtyOdmwXQbK37W-NTyOxo7iER5aHS_C0fU1qKl2BjOz708GLjH-_vxx9eKPeYznfI21_xlTaAvuG4Aco9f5YDK7fooTVHnDaOSSggqcEaDzDPrNXhhKEDxotJeq9zRMVCEStcbirjTounnLbuULRbO5GSY5jo-8n2UKxSZ2j5G_SjFHajdJwmzwvOttp08tdL8ck1uDdgVNBfcm0VIdb6WmgrCIUq5rmoa-cRPkdEurNtIEgEB_9U0Xh-SpmmsvFsWWeNIKz0e_5RCIyJonm_wMkGmblGegemkYL76ypeMNXTQsly1RozDIePfzHuZOWbySHSCd-vKQa2kw" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := AuditAttributesFromAuthorizationHeader(&request) auditClaimsMap := auditClaims.AsMap() assert.Nil(t, err) assert.Equal(t, "stackit/serviceaccount", auditClaimsMap["iss"]) assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", auditClaimsMap["sub"]) assert.Equal(t, []interface{}{"stackit", "api"}, auditClaimsMap["aud"].([]interface{})) assert.Equal(t, "1f7f1efc-3349-411a-a5d7-2255e0a5a8ae", auditClaimsMap["jti"]) principal := fmt.Sprintf("%s/%s", url.QueryEscape("1734b4b6-1d5e-4819-9b50-29917a1b9ad5"), url.QueryEscape("stackit/serviceaccount")) assert.Equal(t, principal, authenticationPrincipal) assert.Equal(t, []string{"stackit", "api"}, audiences) assert.Equal(t, "1734b4b6-1d5e-4819-9b50-29917a1b9ad5", authenticationInfo.PrincipalId) assert.Equal(t, "service-account-3-fghsxw1@sa.stackit.cloud", authenticationInfo.PrincipalEmail) assert.Equal(t, "projects/dacc7830-843e-4c5e-86ff-aa0fb51d636f/service-accounts/1734b4b6-1d5e-4819-9b50-29917a1b9ad5", *authenticationInfo.ServiceAccountName) assert.NotNil(t, authenticationInfo.ServiceAccountDelegationInfo) serviceAccountDelegationInfo := authenticationInfo.ServiceAccountDelegationInfo assert.Equal(t, "f45009b2-6433-43c1-b6c7-618c44359e71", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalId) assert.Equal(t, "do-not-reply@stackit.cloud", serviceAccountDelegationInfo[0].GetIdpPrincipal().PrincipalEmail) assert.Equal(t, "02aef516-317f-4ec1-a1df-1acbd4d49fe3", serviceAccountDelegationInfo[1].GetIdpPrincipal().PrincipalId) assert.Equal(t, "do-not-reply@stackit.cloud", serviceAccountDelegationInfo[1].GetIdpPrincipal().PrincipalEmail) }) t.Run("user token", func(t *testing.T) { headerValue := "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg" headers := make(map[string][]string) headers["Authorization"] = []string{headerValue} request := ApiRequest{Header: headers} auditClaims, authenticationPrincipal, audiences, authenticationInfo, err := AuditAttributesFromAuthorizationHeader(&request) auditClaimsMap := auditClaims.AsMap() assert.Nil(t, err) assert.Equal(t, "https://accounts.dev.stackit.cloud", auditClaimsMap["iss"]) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", auditClaimsMap["sub"]) assert.Equal(t, []interface{}{"stackit-portal-login-dev-client-id"}, auditClaimsMap["aud"].([]interface{})) assert.Equal(t, "d73a67ac-d1ec-4b55-99d4-e953275f022a", auditClaimsMap["jti"]) principal := fmt.Sprintf("%s/%s", url.QueryEscape("cd94f01a-df2e-4456-902e-48f5e57f0b63"), url.QueryEscape("https://accounts.dev.stackit.cloud")) assert.Equal(t, principal, authenticationPrincipal) assert.Equal(t, []string{"stackit-portal-login-dev-client-id"}, audiences) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) }) } func Test_NewAuditLogEntry(t *testing.T) { t.Run("minimum attributes set", func(t *testing.T) { userAgent := "userAgent" requestHeaders := make(map[string][]string) requestHeaders["Authorization"] = []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"} requestHeaders["User-Agent"] = []string{userAgent} requestHeaders["Custom"] = []string{"customHeader"} request := ApiRequest{ Method: "GET", URL: RequestUrl{Path: "/audit/new"}, Host: "localhost:8080", Proto: "HTTP/1.1", Scheme: "http", Header: requestHeaders, } clientIp := "127.0.0.1" correlationId := uuid.NewString() auditRequest := AuditRequest{ Request: &request, RequestClientIP: clientIp, RequestCorrelationId: &correlationId, RequestId: nil, RequestTime: nil, } statusCode := 200 auditResponse := AuditResponse{ ResponseBodyBytes: nil, ResponseStatusCode: statusCode, ResponseHeaders: nil, ResponseNumItems: nil, ResponseTime: nil, } objectId := uuid.NewString() logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity) serviceName := "resource-manager" operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName) resourceName := fmt.Sprintf("projects/%s", objectId) auditTime := time.Now().UTC() insertId := fmt.Sprintf("%d/%s/%s/%d", auditTime.UnixNano(), "eu01", "1", 1) severity := auditV1.LogSeverity_LOG_SEVERITY_DEFAULT auditMetadata := AuditMetadata{ AuditInsertId: insertId, AuditLabels: nil, AuditLogName: logName, AuditLogSeverity: severity, AuditOperationName: operationName, AuditPermission: nil, AuditPermissionGranted: nil, AuditResourceName: resourceName, AuditServiceName: serviceName, AuditTime: nil, } logEntry, _ := NewAuditLogEntry( auditRequest, auditResponse, nil, auditMetadata, nil, nil) assert.Equal(t, logName, logEntry.LogName) assert.Equal(t, insertId, logEntry.InsertId) assert.Equal(t, &correlationId, logEntry.CorrelationId) assert.Equal(t, severity, logEntry.Severity) assert.NoError(t, logEntry.Timestamp.CheckValid()) assert.Nil(t, logEntry.Labels) assert.Nil(t, logEntry.TraceParent) assert.Nil(t, logEntry.TraceState) payload := logEntry.ProtoPayload assert.NotNil(t, payload) assert.Equal(t, serviceName, payload.ServiceName) assert.Equal(t, operationName, payload.OperationName) assert.Equal(t, resourceName, payload.ResourceName) assert.Equal(t, int32(statusCode), payload.ResponseMetadata.StatusCode.Value) assert.Nil(t, payload.ResponseMetadata.ErrorMessage) assert.Nil(t, payload.ResponseMetadata.ErrorDetails) assert.Nil(t, payload.ResponseMetadata.ResponseAttributes.Size) assert.NotNil(t, payload.ResponseMetadata.ResponseAttributes.Time) assert.Nil(t, payload.ResponseMetadata.ResponseAttributes.NumResponseItems) assert.Nil(t, payload.ResponseMetadata.ResponseAttributes.Headers) assert.Nil(t, payload.Request) assert.Nil(t, payload.Response) assert.Nil(t, payload.Metadata) authenticationInfo := payload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) assert.Nil(t, payload.AuthorizationInfo) requestMetadata := payload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, clientIp, requestMetadata.CallerIp) assert.Equal(t, userAgent, requestMetadata.CallerSuppliedUserAgent) // Don't verify explicitly - trust on other unit test assert.NotNil(t, userAgent, requestMetadata.RequestAttributes) }) t.Run("all attributes set", func(t *testing.T) { userAgent := "userAgent" requestHeaders := make(map[string][]string) requestHeaders["Authorization"] = []string{"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOGJlZjc1LWRmY2QtNGE3My1hMzkxLTU0YTdhZjU3YTdkNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3RhY2tpdC1wb3J0YWwtbG9naW4tZGV2LWNsaWVudC1pZCJdLCJjbGllbnRfaWQiOiJzdGFja2l0LXBvcnRhbC1sb2dpbi1kZXYtY2xpZW50LWlkIiwiZW1haWwiOiJDaHJpc3RpYW4uU2NoYWlibGVAbm92YXRlYy1nbWJoLmRlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTcyMjU5MDM2NywiaWF0IjoxNzIyNTg2NzY3LCJpc3MiOiJodHRwczovL2FjY291bnRzLmRldi5zdGFja2l0LmNsb3VkIiwianRpIjoiZDczYTY3YWMtZDFlYy00YjU1LTk5ZDQtZTk1MzI3NWYwMjJhIiwibmJmIjoxNzIyNTg2NzY3LCJzY29wZSI6Im9wZW5pZCBlbWFpbCIsInN1YiI6ImNkOTRmMDFhLWRmMmUtNDQ1Ni05MDJlLTQ4ZjVlNTdmMGI2MyJ9.ajhjYbC5l5g7un9NSheoAwBT83YcZM91rH4DJxPTDsB78HzIVrmaKTPrK3AI_E1THlD2Z3_ot9nFr_eX7XcwWp_ZBlataKmakdXlAmeb4xSMGNYefIfzV_3w9ZZAZ66yoeTrtn8dUx5ezquenCYpctB1NcccmK4U09V0kNcq9dFcfF3Sg9YilF3orUCR0ql1d9RnOs3EiFZuUpdBEkyoVsAdSh2P-PRbNViR_FgCcAJem97TsN5CQc9RlvKYe4sYKgqQoqa2GDVi9Niiw3fe1V8SCnROYcpkOzBBWdvuzFMBUjln3uOogYVOz93xkmImV6jidgyQ70fLt-eDUmZZfg"} requestHeaders["User-Agent"] = []string{userAgent} requestHeaders["Custom"] = []string{"customHeader"} requestBody := make(map[string]interface{}) requestBody["key"] = "request" requestBodyBytes, _ := json.Marshal(requestBody) query := "topic=project" request := ApiRequest{ Method: "GET", URL: RequestUrl{Path: "/audit/new", RawQuery: &query}, Host: "localhost:8080", Proto: "HTTP/1.1", Scheme: "http", Header: requestHeaders, Body: &requestBodyBytes, } clientIp := "127.0.0.1" correlationId := uuid.NewString() requestId := uuid.NewString() requestTime := time.Now().UTC() auditRequest := AuditRequest{ Request: &request, RequestClientIP: clientIp, RequestCorrelationId: &correlationId, RequestId: &requestId, RequestTime: &requestTime, } response := make(map[string]interface{}) response["key"] = "value" responseBody, _ := json.Marshal(response) responseHeader := http.Header{} responseHeader.Set("Content-Type", "application/json") responseHeaderMap := make(map[string]string) responseHeaderMap["Content-Type"] = "application/json" responseNumItems := int64(1) responseStatusCode := 400 responseTime := time.Now().UTC() auditResponse := AuditResponse{ ResponseBodyBytes: &responseBody, ResponseStatusCode: responseStatusCode, ResponseHeaders: responseHeader, ResponseNumItems: &responseNumItems, ResponseTime: &responseTime, } auditTime := time.Now().UTC() objectId := uuid.NewString() logName := fmt.Sprintf("projects/%s/logs/%s", objectId, EventTypeAdminActivity) serviceName := "resource-manager" operationName := fmt.Sprintf("stackit.%s.v2.projects.updated", serviceName) resourceName := fmt.Sprintf("projects/%s", objectId) insertId := fmt.Sprintf("%d/%s/%s/%d", auditTime.UnixNano(), "eu01", "1", 1) permission := "resource-manager.project.edit" permissionGranted := true labels := make(map[string]string) labels["label"] = "value" severity := auditV1.LogSeverity_LOG_SEVERITY_DEFAULT auditMetadata := AuditMetadata{ AuditInsertId: insertId, AuditLabels: &labels, AuditLogName: logName, AuditLogSeverity: severity, AuditOperationName: operationName, AuditPermission: &permission, AuditPermissionGranted: &permissionGranted, AuditResourceName: resourceName, AuditServiceName: serviceName, AuditTime: &auditTime, } eventMetadata := map[string]interface{}{"key": "value"} traceParent := "traceParent" traceState := "traceState" logEntry, _ := NewAuditLogEntry( auditRequest, auditResponse, &eventMetadata, auditMetadata, &traceParent, &traceState) assert.Equal(t, logName, logEntry.LogName) assert.Equal(t, insertId, logEntry.InsertId) assert.Equal(t, labels, logEntry.Labels) assert.Equal(t, correlationId, *logEntry.CorrelationId) assert.Equal(t, timestamppb.New(auditTime), logEntry.Timestamp) assert.Equal(t, severity, logEntry.Severity) assert.Equal(t, &traceParent, logEntry.TraceParent) assert.Equal(t, &traceState, logEntry.TraceState) assert.NotNil(t, logEntry.ProtoPayload) payload := logEntry.ProtoPayload assert.NotNil(t, payload) assert.Equal(t, serviceName, payload.ServiceName) assert.Equal(t, operationName, payload.OperationName) assert.Equal(t, resourceName, payload.ResourceName) assert.Equal(t, int32(responseStatusCode), payload.ResponseMetadata.StatusCode.Value) assert.Equal(t, "Client error", *payload.ResponseMetadata.ErrorMessage) assert.Nil(t, payload.ResponseMetadata.ErrorDetails) assert.Equal(t, wrapperspb.Int64(int64(len(responseBody))), payload.ResponseMetadata.ResponseAttributes.Size) assert.Equal(t, timestamppb.New(responseTime), payload.ResponseMetadata.ResponseAttributes.Time) assert.Equal(t, wrapperspb.Int64(responseNumItems), payload.ResponseMetadata.ResponseAttributes.NumResponseItems) assert.Equal(t, responseHeaderMap, payload.ResponseMetadata.ResponseAttributes.Headers) expectedRequestBody, _ := structpb.NewStruct(requestBody) assert.Equal(t, expectedRequestBody, payload.Request) expectedResponseBody, _ := structpb.NewStruct(response) assert.Equal(t, expectedResponseBody, payload.Response) expectedEventMetadata, _ := structpb.NewStruct(eventMetadata) assert.Equal(t, expectedEventMetadata, payload.Metadata) authenticationInfo := payload.AuthenticationInfo assert.NotNil(t, authenticationInfo) assert.Equal(t, "cd94f01a-df2e-4456-902e-48f5e57f0b63", authenticationInfo.PrincipalId) assert.Equal(t, "Christian.Schaible@novatec-gmbh.de", authenticationInfo.PrincipalEmail) assert.Nil(t, authenticationInfo.ServiceAccountName) assert.Nil(t, authenticationInfo.ServiceAccountDelegationInfo) authorizationInfo := payload.AuthorizationInfo assert.NotNil(t, authorizationInfo) assert.Equal(t, 1, len(authorizationInfo)) assert.Equal(t, permission, *authorizationInfo[0].Permission) assert.Equal(t, permissionGranted, *authorizationInfo[0].Granted) assert.Equal(t, resourceName, authorizationInfo[0].Resource) requestMetadata := payload.RequestMetadata assert.NotNil(t, requestMetadata) assert.Equal(t, clientIp, requestMetadata.CallerIp) assert.Equal(t, userAgent, requestMetadata.CallerSuppliedUserAgent) // Don't verify explicitly - trust on other unit test assert.NotNil(t, userAgent, requestMetadata.RequestAttributes) }) } func Test_NewInsertId(t *testing.T) { insertTime := time.Now().UTC() location := "eu01" workerId := uuid.NewString() var eventSequenceNumber uint64 = 1 insertId := NewInsertId(insertTime, location, workerId, eventSequenceNumber) expectedId := fmt.Sprintf("%d/%s/%s/%d", insertTime.UnixNano(), location, workerId, eventSequenceNumber) assert.Equal(t, expectedId, insertId) } func Test_NewNewAuditRoutingIdentifier(t *testing.T) { objectId := uuid.NewString() objectType := ObjectTypeProject routingIdentifier := NewAuditRoutingIdentifier(objectId, objectType) assert.Equal(t, objectId, routingIdentifier.Identifier) assert.Equal(t, objectType, routingIdentifier.Type) } func Test_OperationNameFromUrlPath(t *testing.T) { t.Run("empty path", func(t *testing.T) { operationName := OperationNameFromUrlPath("", "GET") assert.Equal(t, "", operationName) }) t.Run("root path", func(t *testing.T) { operationName := OperationNameFromUrlPath("/", "GET") assert.Equal(t, "", operationName) }) t.Run("path without version", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects", "GET") assert.Equal(t, "projects.read", operationName) }) t.Run("path with uuid without version", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET") assert.Equal(t, "projects.read", operationName) }) t.Run("path with uuid and version", func(t *testing.T) { operationName := OperationNameFromUrlPath("/v2/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa", "GET") assert.Equal(t, "v2.projects.read", operationName) }) t.Run("concatenated path", func(t *testing.T) { operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/folders/167fc176-9d8e-477b-a56c-b50d7b26adcf/projects/0a2a4f9b-4e67-4562-ad02-c2d200e05aa6/audit/policy", "GET") assert.Equal(t, "v2.organizations.folders.projects.audit.policy.read", operationName) }) t.Run("path with query params", func(t *testing.T) { operationName := OperationNameFromUrlPath("/v2/organizations/ac51bbd2-cb23-441b-a2ee-5393189695aa/audit/policy?since=2024-08-27", "GET") assert.Equal(t, "v2.organizations.audit.policy.read", operationName) }) t.Run("path trailing slash", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/", "GET") assert.Equal(t, "projects.read", operationName) }) t.Run("path trailing slash and query params", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects/ac51bbd2-cb23-441b-a2ee-5393189695aa/?changeDate=2024-10-13", "GET") assert.Equal(t, "projects.read", operationName) }) t.Run("http method post", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects", "POST") assert.Equal(t, "projects.create", operationName) }) t.Run("http method put", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects", "PUT") assert.Equal(t, "projects.update", operationName) }) t.Run("http method patch", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects", "PATCH") assert.Equal(t, "projects.update", operationName) }) t.Run("http method delete", func(t *testing.T) { operationName := OperationNameFromUrlPath("/projects", "DELETE") assert.Equal(t, "projects.delete", operationName) }) } func Test_OperationNameFromGrpcMethod(t *testing.T) { t.Run("empty path", func(t *testing.T) { operationName := OperationNameFromGrpcMethod("") assert.Equal(t, "", operationName) }) t.Run("root path", func(t *testing.T) { operationName := OperationNameFromGrpcMethod("/") assert.Equal(t, "", operationName) }) t.Run("path without version", func(t *testing.T) { operationName := OperationNameFromGrpcMethod("/example.ExampleService/ManualAuditEvent") assert.Equal(t, "example.exampleservice.manualauditevent", operationName) }) t.Run("path with version", func(t *testing.T) { operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent") assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName) }) t.Run("path trailing slash", func(t *testing.T) { operationName := OperationNameFromGrpcMethod("/example.v1.ExampleService/ManualAuditEvent/") assert.Equal(t, "example.v1.exampleservice.manualauditevent", operationName) }) } func Test_GetObjectIdAndTypeFromUrlPath(t *testing.T) { t.Run("object id and type not in url", func(t *testing.T) { objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/audit") assert.NoError(t, err) assert.Equal(t, "", objectId) assert.Nil(t, objectType) }) t.Run("object id and type in url", func(t *testing.T) { objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/projects/f17d4064-9b65-4334-b6a7-8fed96340124") assert.NoError(t, err) assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId) assert.Equal(t, ObjectTypeProject, *objectType) }) t.Run("multiple object ids and types in url", func(t *testing.T) { objectId, objectType, err := GetObjectIdAndTypeFromUrlPath("/v2/organization/8ee58bec-d496-4bb9-af8d-72fda4d78b6b/projects/f17d4064-9b65-4334-b6a7-8fed96340124") assert.NoError(t, err) assert.Equal(t, "f17d4064-9b65-4334-b6a7-8fed96340124", objectId) assert.Equal(t, ObjectTypeProject, *objectType) }) } func Test_ToArrayMap(t *testing.T) { t.Run("empty map", func(t *testing.T) { result := ToArrayMap(map[string]string{}) assert.Equal(t, map[string][]string{}, result) }) t.Run("empty map", func(t *testing.T) { result := ToArrayMap(map[string]string{"key1": "value1", "key2": "value2"}) assert.Equal(t, map[string][]string{ "key1": {"value1"}, "key2": {"value2"}, }, result) }) } func Test_StringAttributeFromMetadata(t *testing.T) { metadata := map[string][]string{"key1": {"value1"}, "key2": {"value2"}} t.Run("not found", func(t *testing.T) { attribute := StringAttributeFromMetadata(metadata, "key3") assert.Equal(t, "", attribute) }) t.Run("found", func(t *testing.T) { attribute := StringAttributeFromMetadata(metadata, "key2") assert.Equal(t, "value2", attribute) }) } func Test_ResponseBodyToBytes(t *testing.T) { t.Run( "nil response body", func(t *testing.T) { bytes, err := ResponseBodyToBytes(nil) assert.Nil(t, bytes) assert.Nil(t, err) }, ) t.Run( "bytes", func(t *testing.T) { responseBody := []byte("data") bytes, err := ResponseBodyToBytes(responseBody) assert.Nil(t, err) assert.Equal(t, &responseBody, bytes) }, ) t.Run( "Protobuf message", func(t *testing.T) { protobufMessage := auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(ObjectTypeProject)} bytes, err := ResponseBodyToBytes(&protobufMessage) assert.Nil(t, err) expected, err := protojson.Marshal(&protobufMessage) assert.Nil(t, err) assert.Equal(t, &expected, bytes) }, ) t.Run( "struct", func(t *testing.T) { type CustomObject struct { Value string } responseBody := CustomObject{Value: "data"} bytes, err := ResponseBodyToBytes(responseBody) assert.Nil(t, err) expected, err := json.Marshal(responseBody) assert.Nil(t, err) assert.Equal(t, &expected, bytes) }, ) t.Run( "map", func(t *testing.T) { responseBody := map[string]interface{}{"value": "data"} bytes, err := ResponseBodyToBytes(responseBody) assert.Nil(t, err) expected, err := json.Marshal(responseBody) assert.Nil(t, err) assert.Equal(t, &expected, bytes) }, ) }