diff --git a/docs/production/authentication-methods.md b/docs/production/authentication-methods.md index 3686717a9f..0cb0a71aa6 100644 --- a/docs/production/authentication-methods.md +++ b/docs/production/authentication-methods.md @@ -400,6 +400,8 @@ it as follows: user ID) and name for the user. 5. The `display_name` and `display_icon` fields are used to display the login/registration buttons for the IdP. + 6. The `auto_signup` field determines how Zulip should handle + login attempts by users who don't have an account yet. 1. Install the certificate(s) required for SAML authentication. You will definitely need the public certificate of your IdP. Some IdP diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 84c3184f7c..1f7fca6e07 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -1945,6 +1945,39 @@ class SAMLAuthBackendTest(SocialAuthBase): result, ) + @override_settings(TERMS_OF_SERVICE=None) + def test_social_auth_registration_auto_signup(self) -> None: + """ + Verify that with SAML auto signup enabled, a user coming from the /login page + (so without the is_signup param) will be taken straight to registration, without + having to go through the step of having to confirm that they do want to sign up. + """ + email = "newuser@zulip.com" + name = "Full Name" + subdomain = "zulip" + realm = get_realm("zulip") + account_data_dict = self.get_account_data_dict(email=email, name=name) + idps_dict = copy.deepcopy(settings.SOCIAL_AUTH_SAML_ENABLED_IDPS) + idps_dict["test_idp"]["auto_signup"] = True + + with self.settings(SOCIAL_AUTH_SAML_ENABLED_IDPS=idps_dict): + result = self.social_auth_test( + account_data_dict, + expect_choose_email_screen=True, + subdomain=subdomain, + is_signup=False, + ) + self.stage_two_of_registration( + result, + realm, + subdomain, + email, + name, + name, + self.BACKEND_CLASS.full_name_validated, + expect_confirm_registration_page=False, + ) + def test_social_auth_complete(self) -> None: with mock.patch.object(OneLogin_Saml2_Response, "is_valid", return_value=True): with mock.patch.object( diff --git a/zproject/backends.py b/zproject/backends.py index c312714845..8451ff27b6 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -1560,7 +1560,7 @@ def social_auth_finish( validate_otp_params(mobile_flow_otp, desktop_flow_otp) if user_profile is None or user_profile.is_mirror_dummy: - is_signup = strategy.session_get("is_signup") == "1" + is_signup = strategy.session_get("is_signup") == "1" or backend.should_auto_signup() else: is_signup = False @@ -1675,6 +1675,9 @@ class SocialAuthMixin(ZulipAuthMixin, ExternalAuthMethod, BaseAuth): self.logger.warning(str(e)) return None + def should_auto_signup(self) -> bool: + return False + @classmethod def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: return [ @@ -2271,6 +2274,9 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): if param in self.standard_relay_params: self.strategy.session_set(param, value) + # We want the IdP name to be accessible from the social pipeline. + self.strategy.session_set("saml_idp_name", idp_name) + # super().auth_complete expects to have RelayState set to the idp_name, # so we need to replace this param. post_params = self.strategy.request.POST.copy() @@ -2339,6 +2345,15 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth): return result + def should_auto_signup(self) -> bool: + """ + This function is meant to be called in the social pipeline or later, + as it requires (validated) information about the IdP name to have + already been store in the session. + """ + idp_name = self.strategy.session_get("saml_idp_name") + return settings.SOCIAL_AUTH_SAML_ENABLED_IDPS[idp_name].get("auto_signup", False) + @external_auth_method class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth): diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py index c1b423ece9..d102c612d1 100644 --- a/zproject/prod_settings_template.py +++ b/zproject/prod_settings_template.py @@ -438,6 +438,12 @@ SOCIAL_AUTH_SAML_ENABLED_IDPS: Dict[str, Any] = { ## You can also limit subdomains by setting "attr_org_membership" ## to be a SAML attribute containing the allowed subdomains for a user. # "attr_org_membership": "member", + ## + ## Determines whether "Log in with SAML" will automatically + ## register a new account if one does not already exist. By + ## default, Zulip asks the user whether they want to create an + ## account or try to log in again using another method. + # "auto_signup": False, }, }