From 1a0feb1405f20b8df4cec6dcad68d4a8622d478d Mon Sep 17 00:00:00 2001 From: Matthias Goerens Date: Thu, 14 Aug 2025 18:24:24 +0200 Subject: [PATCH] Add ability to control if registration is allowed The enableRegistration field is optional and defaults to false (maintaining current behavior). Users can now control Synapse registration by setting: ```yaml spec: homeserver: values: enableRegistration: true # Enable user registration ``` The registration status is reflected in the CR status under status.homeserverConfiguration.registrationEnabled. Fix #32 Assisted-by: claude-4-sonnet Signed-off-by: Matthias Goerens --- api/synapse/v1alpha1/synapse_types.go | 8 + api/synapse/v1alpha1/zz_generated.deepcopy.go | 7 +- .../manifests/synapse.opdev.io_synapses.yaml | 8 + .../crd/bases/synapse.opdev.io_synapses.yaml | 8 + .../synapse/synapse/synapse_controller.go | 7 + .../synapse/synapse_controller_test.go | 225 +++++++++++++----- internal/templates/synapse_configmap.yaml | 2 + internal/templates/templates.go | 4 +- 8 files changed, 204 insertions(+), 65 deletions(-) diff --git a/api/synapse/v1alpha1/synapse_types.go b/api/synapse/v1alpha1/synapse_types.go index 9241b92..a03bf28 100644 --- a/api/synapse/v1alpha1/synapse_types.go +++ b/api/synapse/v1alpha1/synapse_types.go @@ -80,6 +80,11 @@ type SynapseHomeserverValues struct { // Whether or not to report anonymized homeserver usage statistics ReportStats bool `json:"reportStats"` + + // +kubebuilder:default:=false + + // Whether new user registration is allowed. Defaults to false. + EnableRegistration *bool `json:"enableRegistration,omitempty"` } // SynapseStatus defines the observed state of Synapse. @@ -155,6 +160,9 @@ type SynapseStatusHomeserverConfiguration struct { // Whether or not to report anonymized homeserver usage statistics ReportStats bool `json:"reportStats,omitempty"` + + // Whether new user registration is enabled + RegistrationEnabled bool `json:"registrationEnabled,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/synapse/v1alpha1/zz_generated.deepcopy.go b/api/synapse/v1alpha1/zz_generated.deepcopy.go index bfcfc76..25d73ea 100644 --- a/api/synapse/v1alpha1/zz_generated.deepcopy.go +++ b/api/synapse/v1alpha1/zz_generated.deepcopy.go @@ -320,7 +320,7 @@ func (in *SynapseHomeserver) DeepCopyInto(out *SynapseHomeserver) { if in.Values != nil { in, out := &in.Values, &out.Values *out = new(SynapseHomeserverValues) - **out = **in + (*in).DeepCopyInto(*out) } } @@ -352,6 +352,11 @@ func (in *SynapseHomeserverConfigMap) DeepCopy() *SynapseHomeserverConfigMap { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SynapseHomeserverValues) DeepCopyInto(out *SynapseHomeserverValues) { *out = *in + if in.EnableRegistration != nil { + in, out := &in.EnableRegistration, &out.EnableRegistration + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynapseHomeserverValues. diff --git a/bundle/manifests/synapse.opdev.io_synapses.yaml b/bundle/manifests/synapse.opdev.io_synapses.yaml index ce8efe3..49a37b1 100644 --- a/bundle/manifests/synapse.opdev.io_synapses.yaml +++ b/bundle/manifests/synapse.opdev.io_synapses.yaml @@ -79,6 +79,11 @@ spec: Holds the required values for the creation of a homeserver.yaml configuration file by the Synapse Operator properties: + enableRegistration: + default: false + description: Whether new user registration is allowed. Defaults + to false. + type: boolean reportStats: description: Whether or not to report anonymized homeserver usage statistics @@ -150,6 +155,9 @@ spec: homeserverConfiguration: description: Holds configuration information for Synapse properties: + registrationEnabled: + description: Whether new user registration is enabled + type: boolean reportStats: description: Whether or not to report anonymized homeserver usage statistics diff --git a/config/crd/bases/synapse.opdev.io_synapses.yaml b/config/crd/bases/synapse.opdev.io_synapses.yaml index 55d6a7f..aa873f7 100644 --- a/config/crd/bases/synapse.opdev.io_synapses.yaml +++ b/config/crd/bases/synapse.opdev.io_synapses.yaml @@ -74,6 +74,11 @@ spec: Holds the required values for the creation of a homeserver.yaml configuration file by the Synapse Operator properties: + enableRegistration: + default: false + description: Whether new user registration is allowed. Defaults + to false. + type: boolean reportStats: description: Whether or not to report anonymized homeserver usage statistics @@ -145,6 +150,9 @@ spec: homeserverConfiguration: description: Holds configuration information for Synapse properties: + registrationEnabled: + description: Whether new user registration is enabled + type: boolean reportStats: description: Whether or not to report anonymized homeserver usage statistics diff --git a/internal/controller/synapse/synapse/synapse_controller.go b/internal/controller/synapse/synapse/synapse_controller.go index d11a776..6cac8ad 100644 --- a/internal/controller/synapse/synapse/synapse_controller.go +++ b/internal/controller/synapse/synapse/synapse_controller.go @@ -183,6 +183,13 @@ func (r *SynapseReconciler) setStatusHomeserverConfiguration( s.Status.HomeserverConfiguration.ServerName = s.Spec.Homeserver.Values.ServerName s.Status.HomeserverConfiguration.ReportStats = s.Spec.Homeserver.Values.ReportStats + // Set registration enabled status - default to false if not specified + registrationEnabled := false + if s.Spec.Homeserver.Values.EnableRegistration != nil { + registrationEnabled = *s.Spec.Homeserver.Values.EnableRegistration + } + s.Status.HomeserverConfiguration.RegistrationEnabled = registrationEnabled + err := utils.UpdateResourceStatus(ctx, r.Client, s, &synapsev1alpha1.Synapse{}) if err != nil { log.Error(err, "Error updating Synapse Status") diff --git a/internal/controller/synapse/synapse/synapse_controller_test.go b/internal/controller/synapse/synapse/synapse_controller_test.go index 9bcf4c4..aafef94 100644 --- a/internal/controller/synapse/synapse/synapse_controller_test.go +++ b/internal/controller/synapse/synapse/synapse_controller_test.go @@ -315,6 +315,20 @@ var _ = Describe("Integration tests for the Synapse controller", Ordered, Label( }, }, ), + Entry( + "when registration option is provided", + map[string]any{ + "spec": map[string]any{ + "homeserver": map[string]any{ + "values": map[string]any{ + "serverName": ServerName, + "reportStats": ReportStats, + "enableRegistration": true, + }, + }, + }, + }, + ), Entry( "when optional CreateNewPostgreSQL and ConfigMap Namespace are missing", map[string]any{ @@ -408,75 +422,157 @@ var _ = Describe("Integration tests for the Synapse controller", Ordered, Label( } When("Specifying the Synapse configuration via Values", func() { - BeforeAll(func() { - initSynapseVariables() + When("Creating a simple Synapse instance", func() { + BeforeAll(func() { + initSynapseVariables() - synapseSpec = synapsev1alpha1.SynapseSpec{ - Homeserver: synapsev1alpha1.SynapseHomeserver{ - Values: &synapsev1alpha1.SynapseHomeserverValues{ - ServerName: ServerName, - ReportStats: ReportStats, + synapseSpec = synapsev1alpha1.SynapseSpec{ + Homeserver: synapsev1alpha1.SynapseHomeserver{ + Values: &synapsev1alpha1.SynapseHomeserverValues{ + ServerName: ServerName, + ReportStats: ReportStats, + }, }, - }, - IsOpenshift: true, - } + IsOpenshift: true, + } - createSynapseInstance() - }) + createSynapseInstance() + }) - AfterAll(func() { - cleanupSynapseResources() - }) + AfterAll(func() { + cleanupSynapseResources() + }) - It("Should should update the Synapse Status", func() { - expectedStatus := synapsev1alpha1.SynapseStatus{ - State: "RUNNING", - Reason: "", - HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ - ServerName: ServerName, - ReportStats: ReportStats, - }, - } - // Status may need some time to be updated - Eventually(func() synapsev1alpha1.SynapseStatus { - _ = k8sClient.Get(ctx, synapseLookupKey, synapse) - return synapse.Status - }, timeout, interval).Should(Equal(expectedStatus)) - }) + It("Should should update the Synapse Status", func() { + expectedStatus := synapsev1alpha1.SynapseStatus{ + State: "RUNNING", + Reason: "", + HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ + ServerName: ServerName, + ReportStats: ReportStats, + RegistrationEnabled: false, // Default value + }, + } + // Status may need some time to be updated + Eventually(func() synapsev1alpha1.SynapseStatus { + _ = k8sClient.Get(ctx, synapseLookupKey, synapse) + return synapse.Status + }, timeout, interval).Should(Equal(expectedStatus)) + }) - It("Should create a Synapse ConfigMap", func() { - checkResourcePresence(createdConfigMap, synapseLookupKey, expectedOwnerReference) - }) + It("Should create a Synapse ConfigMap", func() { + checkResourcePresence(createdConfigMap, synapseLookupKey, expectedOwnerReference) + }) - It("Should create a Synapse PVC", func() { - checkResourcePresence(createdPVC, synapseLookupKey, expectedOwnerReference) - }) + It("Should create a Synapse PVC", func() { + checkResourcePresence(createdPVC, synapseLookupKey, expectedOwnerReference) + }) - It("Should create a Synapse Deployment", func() { - By("Checking that a Synapse Deployment exists and is correctly configured") - checkResourcePresence(createdDeployment, synapseLookupKey, expectedOwnerReference) - - By("Checking that initContainer for generating config file contains the required environment variables") - envVars := []corev1.EnvVar{{ - Name: "SYNAPSE_SERVER_NAME", - Value: ServerName, - }, { - Name: "SYNAPSE_REPORT_STATS", - Value: utils.BoolToYesNo(ReportStats), - }} - Expect(createdDeployment.Spec.Template.Spec.InitContainers[1].Env).Should(ContainElements(envVars)) - }) + It("Should create a Synapse Deployment", func() { + By("Checking that a Synapse Deployment exists and is correctly configured") + checkResourcePresence(createdDeployment, synapseLookupKey, expectedOwnerReference) - It("Should create a Synapse Service", func() { - checkResourcePresence(createdService, synapseLookupKey, expectedOwnerReference) - }) + By("Checking that initContainer for generating config file contains the required environment variables") + envVars := []corev1.EnvVar{{ + Name: "SYNAPSE_SERVER_NAME", + Value: ServerName, + }, { + Name: "SYNAPSE_REPORT_STATS", + Value: utils.BoolToYesNo(ReportStats), + }} + Expect(createdDeployment.Spec.Template.Spec.InitContainers[1].Env).Should(ContainElements(envVars)) + }) - It("Should create a Synapse ServiceAccount", func() { - checkResourcePresence(createdServiceAccount, synapseLookupKey, expectedOwnerReference) + It("Should create a Synapse Service", func() { + checkResourcePresence(createdService, synapseLookupKey, expectedOwnerReference) + }) + + It("Should create a Synapse ServiceAccount", func() { + checkResourcePresence(createdServiceAccount, synapseLookupKey, expectedOwnerReference) + }) + + It("Should create a Synapse RoleBinding", func() { + checkResourcePresence(createdRoleBinding, synapseLookupKey, expectedOwnerReference) + }) }) - It("Should create a Synapse RoleBinding", func() { - checkResourcePresence(createdRoleBinding, synapseLookupKey, expectedOwnerReference) + When("Specifying a registration option", func() { + DescribeTable("Should create a ConfigMap with the correct enable_registration setting", + func(enableRegistration *bool, expectedValue bool) { + initSynapseVariables() + + synapseSpec = synapsev1alpha1.SynapseSpec{ + Homeserver: synapsev1alpha1.SynapseHomeserver{ + Values: &synapsev1alpha1.SynapseHomeserverValues{ + ServerName: ServerName, + ReportStats: ReportStats, + }, + }, + IsOpenshift: true, + } + + // Only set enableRegistration if provided + if enableRegistration != nil { + synapseSpec.Homeserver.Values.EnableRegistration = enableRegistration + } + + createSynapseInstance() + + By("Checking that the Synapse Status is correctly updated") + expectedStatus := synapsev1alpha1.SynapseStatus{ + State: "RUNNING", + Reason: "", + HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ + ServerName: ServerName, + ReportStats: ReportStats, + RegistrationEnabled: expectedValue, + }, + } + // Status may need some time to be updated + Eventually(func() synapsev1alpha1.SynapseStatus { + _ = k8sClient.Get(ctx, synapseLookupKey, synapse) + return synapse.Status + }, timeout, interval).Should(Equal(expectedStatus)) + + // Verify ConfigMap content + Eventually(func(g Gomega) { + By("Checking that the Synapse ConfigMap exists") + checkResourcePresence(createdConfigMap, synapseLookupKey, expectedOwnerReference) + + By("Checking that the ConfigMap contains the enable_registration setting") + ConfigMapData, ok := createdConfigMap.Data["homeserver.yaml"] + g.Expect(ok).Should(BeTrue()) + + homeserver := make(map[string]any) + g.Expect(yaml.Unmarshal([]byte(ConfigMapData), homeserver)).Should(Succeed()) + + // Check enable_registration setting + enableRegistrationValue, exists := homeserver["enable_registration"] + if exists { + g.Expect(enableRegistrationValue).Should(Equal(expectedValue)) + } else { + // If not present, Synapse defaults to false + g.Expect(expectedValue).Should(BeFalse()) + } + + // Check enable_registration_without_verification setting + enableRegistrationWithoutVerificationValue, withoutVerificationExists := homeserver["enable_registration_without_verification"] + if expectedValue { + // When registration is enabled, enable_registration_without_verification should also be true + g.Expect(withoutVerificationExists).Should(BeTrue()) + g.Expect(enableRegistrationWithoutVerificationValue).Should(BeTrue()) + } else { + // When registration is disabled, enable_registration_without_verification should not be present + g.Expect(withoutVerificationExists).Should(BeFalse()) + } + }, timeout, interval).Should(Succeed()) + + cleanupSynapseResources() + }, + Entry("when enableRegistration is nil (default)", nil, false), + Entry("when enableRegistration is false", utils.BoolAddr(false), false), + Entry("when enableRegistration is true", utils.BoolAddr(true), true), + ) }) }) @@ -534,8 +630,9 @@ var _ = Describe("Integration tests for the Synapse controller", Ordered, Label( State: "RUNNING", Reason: "", HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ - ServerName: ServerName, - ReportStats: ReportStats, + ServerName: ServerName, + ReportStats: ReportStats, + RegistrationEnabled: false, // Default value }, } // Status may need some time to be updated @@ -764,8 +861,9 @@ var _ = Describe("Integration tests for the Synapse controller", Ordered, Label( State: "RUNNING", Reason: "", HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ - ServerName: ServerName, - ReportStats: ReportStats, + ServerName: ServerName, + ReportStats: ReportStats, + RegistrationEnabled: false, // Default value }, } // Status may need some time to be updated @@ -912,8 +1010,9 @@ var _ = Describe("Integration tests for the Synapse controller", Ordered, Label( State: "RUNNING", Reason: "", HomeserverConfiguration: synapsev1alpha1.SynapseStatusHomeserverConfiguration{ - ServerName: ServerName, - ReportStats: ReportStats, + ServerName: ServerName, + ReportStats: ReportStats, + RegistrationEnabled: false, // Default value }, } // Status may need some time to be updated diff --git a/internal/templates/synapse_configmap.yaml b/internal/templates/synapse_configmap.yaml index f79ddc8..f6d0e31 100644 --- a/internal/templates/synapse_configmap.yaml +++ b/internal/templates/synapse_configmap.yaml @@ -34,6 +34,8 @@ data: media_store_path: "/data/media_store" registration_shared_secret: "{{ .Values.RegistrationSharedSecret }}" report_stats: {{ if .Values.Spec.Homeserver.Values.ReportStats }}yes{{ else }}no{{ end }} + enable_registration: {{ if eq (Deref .Values.Spec.Homeserver.Values.EnableRegistration) true }}true{{ else }}false{{ end }} + {{ if eq (Deref .Values.Spec.Homeserver.Values.EnableRegistration) true }}enable_registration_without_verification: true{{ end }} macaroon_secret_key: "{{ .Values.MacaroonSecretKey }}" form_secret: "{{ .Values.FormSecret }}" signing_key_path: "data/{{ .Values.Spec.Homeserver.Values.ServerName }}.signing.key" diff --git a/internal/templates/templates.go b/internal/templates/templates.go index a3cc855..7af8b66 100644 --- a/internal/templates/templates.go +++ b/internal/templates/templates.go @@ -17,7 +17,9 @@ func ResourceFromTemplate[T any, R any](t *T, name string) (*R, error) { if err != nil { return nil, fmt.Errorf("could not read %s template: %v", name, err) } - tmpl, err := template.New(name).Parse(string(resYaml)) + tmpl, err := template.New(name).Funcs(template.FuncMap{ + "Deref": func(i *bool) bool { return *i }, + }).Parse(string(resYaml)) if err != nil { return nil, fmt.Errorf("could not parse %s template: %v", name, err) }