From 58b8c1a010747a9a78ee7222ffdab513abcb3f90 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Tue, 3 Feb 2026 15:31:04 +0530 Subject: [PATCH] [fix] Added setting to disable cross-organization login #675 Fixes #675 --- docs/images/organization_cross_login.png | Bin 0 -> 21065 bytes docs/user/rest-api.rst | 17 +++++-- docs/user/settings.rst | 31 ++++++++++++ openwisp_radius/admin.py | 1 + openwisp_radius/api/views.py | 2 + openwisp_radius/base/models.py | 9 ++++ .../migrations/0038_clean_fallbackfields.py | 6 +-- ...cross_organization_registration_enabled.py | 27 ++++++++++ openwisp_radius/settings.py | 3 ++ .../tests/test_api/test_rest_token.py | 22 ++++++++ openwisp_radius/tests/test_api/test_utils.py | 47 ++++++++++++++++++ 11 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 docs/images/organization_cross_login.png create mode 100644 openwisp_radius/migrations/0043_organizationradiussettings_cross_organization_registration_enabled.py diff --git a/docs/images/organization_cross_login.png b/docs/images/organization_cross_login.png new file mode 100644 index 0000000000000000000000000000000000000000..88ee40706fcfe2cc2ab66d34f4557d3b61605fd8 GIT binary patch literal 21065 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. +.. note:: + + This behavior can be disabled globally by setting + :ref:`OPENWISP_RADIUS_CROSS_ORGANIZATION_LOGIN_ENABLED + ` to ``False`` in + your Django settings. When disabled, users cannot register to multiple + organizations via the login endpoint. + .. _radius_reset_password: Reset password @@ -622,10 +630,11 @@ recognize these users and trigger the appropriate response needed (e.g.: reject them or initiate account verification). If an existing user account tries to authenticate to an organization of -which they're not member of, then they would be automatically added as -members (if registration is enabled for that org). Please refer to -:ref:`"Registering to Multiple Organizations" -`. +which they're not a member, they will be automatically added as members +only if the organization has both registration and cross-organization +login enabled. Please refer to :ref:`"Registering to Multiple +Organizations" ` for more +information. This endpoint updates the user language preference field according to the ``Accept-Language`` HTTP header. diff --git a/docs/user/settings.rst b/docs/user/settings.rst index 4df3d1c2..d76f7b04 100644 --- a/docs/user/settings.rst +++ b/docs/user/settings.rst @@ -628,6 +628,37 @@ screenshot below. otherwise, if all the organization use the same configuration, we recommend changing the global setting. +.. _openwisp_radius_cross_organization_login_enabled: + +``OPENWISP_RADIUS_CROSS_ORGANIZATION_LOGIN_ENABLED`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Default**: ``True`` + +This setting controls whether a user who is registered in one organization +can also access other organizations. + +When enabled, a user who already has an account in one organization can +log in to another organization using the :ref:`login endpoint +` without completing an additional +registration. During the login process, the user is automatically added to +the new organization **only if registration is enabled** for that organization. + +When disabled (``False``), users can log in **only** to organizations they +are already registered with. Logging in to a different organization is not +allowed, even if that organization permits new user registrations. + +**This setting can be overridden in individual organizations via the admin +interface**, by going to *Organizations* then edit a specific organization +and scroll down to *"Organization RADIUS settings"*, as shown in the +screenshot below. + +.. image:: ../images/organization_cross_login.png + :alt: cross-organization login setting + +See :ref:`Registering to Multiple Organizations +` for more information. + .. _openwisp_radius_sms_verification_enabled: ``OPENWISP_RADIUS_SMS_VERIFICATION_ENABLED`` diff --git a/openwisp_radius/admin.py b/openwisp_radius/admin.py index ebd816de..2d2b9023 100644 --- a/openwisp_radius/admin.py +++ b/openwisp_radius/admin.py @@ -579,6 +579,7 @@ class OrganizationRadiusSettingsInline(admin.StackedInline): "freeradius_allowed_hosts", "coa_enabled", "registration_enabled", + "cross_organization_login_enabled", "saml_registration_enabled", "social_registration_enabled", "mac_addr_roaming_enabled", diff --git a/openwisp_radius/api/views.py b/openwisp_radius/api/views.py index d478b180..dc942f63 100644 --- a/openwisp_radius/api/views.py +++ b/openwisp_radius/api/views.py @@ -320,6 +320,8 @@ def get_user(self, serializer, *args, **kwargs): def validate_membership(self, user): if not (user.is_superuser or user.is_member(self.organization)): if get_organization_radius_settings( + self.organization, "cross_organization_login_enabled" + ) and get_organization_radius_settings( self.organization, "registration_enabled" ): if self._needs_identity_verification( diff --git a/openwisp_radius/base/models.py b/openwisp_radius/base/models.py index 11dfc0bb..63caceac 100644 --- a/openwisp_radius/base/models.py +++ b/openwisp_radius/base/models.py @@ -1291,6 +1291,15 @@ class AbstractOrganizationRadiusSettings(UUIDModel): help_text=_REGISTRATION_ENABLED_HELP_TEXT, fallback=app_settings.REGISTRATION_API_ENABLED, ) + cross_organization_login_enabled = FallbackBooleanChoiceField( + help_text=_( + "Allow users registered in a different organization to log in to" + " this organization without performing an additional registration." + ), + verbose_name=_("Cross-organization login enabled"), + fallback=app_settings.CROSS_ORGANIZATION_LOGIN_ENABLED, + ) + saml_registration_enabled = FallbackBooleanChoiceField( help_text=_SAML_REGISTRATION_ENABLED_HELP_TEXT, verbose_name=_("SAML registration enabled"), diff --git a/openwisp_radius/migrations/0038_clean_fallbackfields.py b/openwisp_radius/migrations/0038_clean_fallbackfields.py index bd86d17c..569c3312 100644 --- a/openwisp_radius/migrations/0038_clean_fallbackfields.py +++ b/openwisp_radius/migrations/0038_clean_fallbackfields.py @@ -2,8 +2,6 @@ from openwisp_utils.fields import FallbackMixin -from ..utils import load_model - def clean_fallback_fields(apps, schema_editor): """ @@ -15,7 +13,9 @@ def clean_fallback_fields(apps, schema_editor): is the same as the fallback value, effectively removing the unnecessary data from the database. """ - OrganizationRadiusSettings = load_model("OrganizationRadiusSettings") + OrganizationRadiusSettings = apps.get_model( + "openwisp_radius", "OrganizationRadiusSettings" + ) fallback_fields = [] fallback_field_names = [] diff --git a/openwisp_radius/migrations/0043_organizationradiussettings_cross_organization_registration_enabled.py b/openwisp_radius/migrations/0043_organizationradiussettings_cross_organization_registration_enabled.py new file mode 100644 index 00000000..300d91cc --- /dev/null +++ b/openwisp_radius/migrations/0043_organizationradiussettings_cross_organization_registration_enabled.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.9 on 2026-02-03 08:05 + +from django.db import migrations + +import openwisp_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("openwisp_radius", "0042_set_existing_batches_completed"), + ] + + operations = [ + migrations.AddField( + model_name="organizationradiussettings", + name="cross_organization_login_enabled", + field=openwisp_utils.fields.FallbackBooleanChoiceField( + blank=True, + default=None, + fallback=True, + help_text="Allow users already registered in another organization to log in without registering with this organization.", + null=True, + verbose_name="Cross-organization login enabled", + ), + ), + ] diff --git a/openwisp_radius/settings.py b/openwisp_radius/settings.py index e7f908dd..1f49fb48 100644 --- a/openwisp_radius/settings.py +++ b/openwisp_radius/settings.py @@ -94,6 +94,9 @@ def get_default_password_reset_url(urls): ALLOWED_MOBILE_PREFIXES = get_settings_value("ALLOWED_MOBILE_PREFIXES", []) ALLOW_FIXED_LINE_OR_MOBILE = get_settings_value("ALLOW_FIXED_LINE_OR_MOBILE", False) REGISTRATION_API_ENABLED = get_settings_value("REGISTRATION_API_ENABLED", True) +CROSS_ORGANIZATION_LOGIN_ENABLED = get_settings_value( + "CROSS_ORGANIZATION_LOGIN_ENABLED", True +) NEEDS_IDENTITY_VERIFICATION = get_settings_value("NEEDS_IDENTITY_VERIFICATION", False) SMS_MESSAGE_TEMPLATE = get_settings_value( "SMS_MESSAGE_TEMPLATE", _("{organization} verification code: {code}") diff --git a/openwisp_radius/tests/test_api/test_rest_token.py b/openwisp_radius/tests/test_api/test_rest_token.py index 911d532f..013ceadf 100644 --- a/openwisp_radius/tests/test_api/test_rest_token.py +++ b/openwisp_radius/tests/test_api/test_rest_token.py @@ -251,6 +251,28 @@ def test_unverified_registered_user_different_organization(self): self.assertEqual(response.status_code, 200) self.assertIn("key", response.data) + @capture_any_output() + def test_user_auth_token_cross_organization_login_disabled(self): + self._get_org_user() + org2 = self._create_org(name="org2") + OrganizationRadiusSettings.objects.create( + organization=org2, cross_organization_login_enabled=False + ) + url = reverse("radius:user_auth_token", args=[org2.slug]) + response = self.client.post(url, {"username": "tester", "password": "tester"}) + self.assertEqual(response.status_code, 403) + self.assertEqual( + response.data["detail"], + f"{org2} does not allow self registration of new accounts.", + ) + # Ensure no OrganizationUser was created + self.assertEqual( + OrganizationUser.objects.filter( + organization=org2, user__username="tester" + ).count(), + 0, + ) + def test_user_auth_token_404(self): url = reverse( "radius:user_auth_token", args=["00000000-0000-0000-0000-000000000000"] diff --git a/openwisp_radius/tests/test_api/test_utils.py b/openwisp_radius/tests/test_api/test_utils.py index 4dff58f5..df424c2b 100644 --- a/openwisp_radius/tests/test_api/test_utils.py +++ b/openwisp_radius/tests/test_api/test_utils.py @@ -79,3 +79,50 @@ def test_is_sms_verification_enabled(self): str(context_manager.exception), "Could not complete operation because of an internal misconfiguration", ) + + @capture_any_output() + def test_cross_organization_login_enabled(self): + org = self._create_org() + OrganizationRadiusSettings.objects.create(organization=org) + + with self.subTest("Test cross-organization registration enabled set to True"): + org.radius_settings.cross_organization_login_enabled = True + self.assertEqual( + get_organization_radius_settings( + org, "cross_organization_login_enabled" + ), + True, + ) + + with self.subTest("Test cross-organization registration enabled set to False"): + org.radius_settings.cross_organization_login_enabled = False + self.assertEqual( + get_organization_radius_settings( + org, "cross_organization_login_enabled" + ), + False, + ) + + with self.subTest("Test cross-organization registration enabled set to None"): + org.radius_settings.cross_organization_login_enabled = None + org.radius_settings.save(update_fields=["cross_organization_login_enabled"]) + org.radius_settings.refresh_from_db( + fields=["cross_organization_login_enabled"] + ) + self.assertEqual( + get_organization_radius_settings( + org, "cross_organization_login_enabled" + ), + app_settings.CROSS_ORGANIZATION_LOGIN_ENABLED, + ) + + with self.subTest("Test related radius setting does not exist"): + org.radius_settings = None + with self.assertRaises(APIException) as context_manager: + get_organization_radius_settings( + org, "cross_organization_login_enabled" + ) + self.assertEqual( + str(context_manager.exception), + "Could not complete operation because of an internal misconfiguration", + )