diff --git a/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook.go b/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook.go index 38823d44..0faf296d 100644 --- a/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook.go +++ b/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook.go @@ -109,6 +109,19 @@ func (v *ContactGroupMembershipValidator) ValidateCreate(ctx context.Context, ob errs = append(errs, field.Invalid(field.NewPath("spec"), cgm.Spec, fmt.Sprintf("cannot create membership as a ContactGroupMembershipRemoval %s already exists", existingRemovals.Items[0].Name))) } + // Check that cgm namespace is the same as the contact namespace for contacts that are related + // to the resourcemanager.miloapis.com API group + if contact.Spec.SubjectRef != nil { + switch contact.Spec.SubjectRef.APIGroup { + case "resourcemanager.miloapis.com": + if cgm.Namespace != contact.Namespace { + errs = append(errs, field.Invalid(field.NewPath("spec"), cgm.Spec, "namespace must be the same as the contact namespace for contacts that are related to the resourcemanager.miloapis.com API group")) + } + default: + return nil, errors.NewInternalError(fmt.Errorf("server does not handle SubjectRef APIGroup %s", contact.Spec.SubjectRef.APIGroup)) + } + } + if len(errs) > 0 { return nil, errors.NewInvalid(notificationv1alpha1.SchemeGroupVersion.WithKind("ContactGroupMembership").GroupKind(), cgm.Name, errs) } diff --git a/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook_test.go b/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook_test.go index 2841cc3d..d8f6371e 100644 --- a/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook_test.go +++ b/internal/webhooks/notification/v1alpha1/contactgroupmembership_webhook_test.go @@ -104,6 +104,45 @@ func TestContactGroupMembershipValidator(t *testing.T) { expectError: true, errorContains: "not found", }, + "subjectref same namespace": { + newObj: ¬ificationv1alpha1.ContactGroupMembership{ + ObjectMeta: metav1.ObjectMeta{Name: "m6", Namespace: "default"}, + Spec: notificationv1alpha1.ContactGroupMembershipSpec{ + ContactRef: notificationv1alpha1.ContactReference{Name: "c-sub", Namespace: "default"}, + ContactGroupRef: notificationv1alpha1.ContactGroupReference{Name: "g-sub", Namespace: "default"}, + }, + }, + seedObjects: []client.Object{ + ¬ificationv1alpha1.Contact{ + ObjectMeta: metav1.ObjectMeta{Name: "c-sub", Namespace: "default"}, + Spec: notificationv1alpha1.ContactSpec{ + SubjectRef: ¬ificationv1alpha1.SubjectReference{APIGroup: "resourcemanager.miloapis.com", Kind: "Project", Name: "proj1"}, + }, + }, + ¬ificationv1alpha1.ContactGroup{ObjectMeta: metav1.ObjectMeta{Name: "g-sub", Namespace: "default"}}, + }, + expectError: false, + }, + "subjectref namespace mismatch": { + newObj: ¬ificationv1alpha1.ContactGroupMembership{ + ObjectMeta: metav1.ObjectMeta{Name: "m7", Namespace: "other"}, + Spec: notificationv1alpha1.ContactGroupMembershipSpec{ + ContactRef: notificationv1alpha1.ContactReference{Name: "c-sub", Namespace: "default"}, + ContactGroupRef: notificationv1alpha1.ContactGroupReference{Name: "g-sub", Namespace: "other"}, + }, + }, + seedObjects: []client.Object{ + ¬ificationv1alpha1.Contact{ + ObjectMeta: metav1.ObjectMeta{Name: "c-sub", Namespace: "default"}, + Spec: notificationv1alpha1.ContactSpec{ + SubjectRef: ¬ificationv1alpha1.SubjectReference{APIGroup: "resourcemanager.miloapis.com", Kind: "Project", Name: "proj1"}, + }, + }, + ¬ificationv1alpha1.ContactGroup{ObjectMeta: metav1.ObjectMeta{Name: "g-sub", Namespace: "other"}}, + }, + expectError: true, + errorContains: "namespace must be the same", + }, } for name, tt := range tests { diff --git a/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook.go b/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook.go index 7f86650f..3d318879 100644 --- a/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook.go +++ b/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook.go @@ -50,7 +50,8 @@ func (v *ContactGroupMembershipRemovalValidator) ValidateCreate(ctx context.Cont var errs field.ErrorList // Ensure Contact exists - if err := v.Client.Get(ctx, client.ObjectKey{Namespace: removal.Spec.ContactRef.Namespace, Name: removal.Spec.ContactRef.Name}, ¬ificationv1alpha1.Contact{}); err != nil { + contact := ¬ificationv1alpha1.Contact{} + if err := v.Client.Get(ctx, client.ObjectKey{Namespace: removal.Spec.ContactRef.Namespace, Name: removal.Spec.ContactRef.Name}, contact); err != nil { if errors.IsNotFound(err) { errs = append(errs, field.NotFound(field.NewPath("spec", "contactRef", "name"), removal.Spec.ContactRef.Name)) } else { @@ -79,6 +80,19 @@ func (v *ContactGroupMembershipRemovalValidator) ValidateCreate(ctx context.Cont errs = append(errs, field.Duplicate(field.NewPath("spec"), fmt.Sprintf("membership removal already exists in ContactGroupMembershipRemoval %s", existing.Items[0].Name))) } + // Check that cgmr namespace is the same as the contact namespace for contacts that are related + // to the resourcemanager.miloapis.com API group + if contact.Spec.SubjectRef != nil { + switch contact.Spec.SubjectRef.APIGroup { + case "resourcemanager.miloapis.com": + if removal.Namespace != contact.Namespace { + errs = append(errs, field.Invalid(field.NewPath("spec"), removal.Spec, "namespace must be the same as the contact namespace for contacts that are related to the resourcemanager.miloapis.com API group")) + } + default: + return nil, errors.NewInternalError(fmt.Errorf("server does not handle SubjectRef APIGroup %s", contact.Spec.SubjectRef.APIGroup)) + } + } + if len(errs) > 0 { return nil, errors.NewInvalid(notificationv1alpha1.SchemeGroupVersion.WithKind("ContactGroupMembershipRemoval").GroupKind(), removal.Name, errs) } diff --git a/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook_test.go b/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook_test.go index 9482b6c8..56004d48 100644 --- a/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook_test.go +++ b/internal/webhooks/notification/v1alpha1/contactgroupmembershipremoval_webhook_test.go @@ -37,6 +37,24 @@ func TestContactGroupMembershipRemovalValidator(t *testing.T) { return ¬ificationv1alpha1.ContactGroup{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default"}} } + // makeContactWithSubject creates a Contact that includes a SubjectRef with the given apiGroup and namespace. + makeContactWithSubject := func(name, ns, apiGroup string) *notificationv1alpha1.Contact { + return ¬ificationv1alpha1.Contact{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}, + Spec: notificationv1alpha1.ContactSpec{ + SubjectRef: ¬ificationv1alpha1.SubjectReference{ + APIGroup: apiGroup, + Kind: "User", + Name: "demo", // value not relevant for validator logic + Namespace: ns, + }, + FamilyName: "Doe", + GivenName: "John", + Email: "john.doe@example.com", + }, + } + } + tests := map[string]struct { newObj *notificationv1alpha1.ContactGroupMembershipRemoval seedObjects []client.Object @@ -81,6 +99,29 @@ func TestContactGroupMembershipRemovalValidator(t *testing.T) { }, expectError: false, }, + "contact with resourcemanager apiGroup same namespace": { + newObj: makeRemoval("rm7", "c-res", "g1"), + seedObjects: []client.Object{makeContactWithSubject("c-res", "default", "resourcemanager.miloapis.com"), makeGroup("g1")}, + expectError: false, + }, + "contact with resourcemanager apiGroup different namespace": { + newObj: ¬ificationv1alpha1.ContactGroupMembershipRemoval{ + ObjectMeta: metav1.ObjectMeta{Name: "rm8", Namespace: "other-ns"}, + Spec: notificationv1alpha1.ContactGroupMembershipRemovalSpec{ + ContactRef: notificationv1alpha1.ContactReference{Name: "c-res", Namespace: "default"}, + ContactGroupRef: notificationv1alpha1.ContactGroupReference{Name: "g1", Namespace: "default"}, + }, + }, + seedObjects: []client.Object{makeContactWithSubject("c-res", "default", "resourcemanager.miloapis.com"), makeGroup("g1")}, + expectError: true, + errorContains: "namespace must be the same", + }, + "contact with unsupported apiGroup": { + newObj: makeRemoval("rm9", "c-oth", "g1"), + seedObjects: []client.Object{makeContactWithSubject("c-oth", "default", "unsupported.group"), makeGroup("g1")}, + expectError: true, + errorContains: "server does not handle subjectref apigroup", + }, } for name, tt := range tests {