From bfe428f6084b32132111d4f5aabd5908069bedda Mon Sep 17 00:00:00 2001 From: Mateusz Mandera Date: Sun, 13 Jun 2021 14:18:28 +0200 Subject: [PATCH] saml: Add setting to skip the "continue to registration" page. It's a smoother Just-In-Time provisioning process to allow creating the account and getting signed in on the first login by the user. --- docs/production/authentication-methods.md | 2 ++ zerver/tests/test_auth_backends.py | 33 +++++++++++++++++++++++ zproject/backends.py | 17 +++++++++++- zproject/prod_settings_template.py | 6 +++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/production/authentication-methods.md b/docs/production/authentication-methods.md index 3a127e338e..dc79ec57b6 100644 --- a/docs/production/authentication-methods.md +++ b/docs/production/authentication-methods.md @@ -406,6 +406,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. 3. 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 a745872a2d..6fda7795ab 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -1881,6 +1881,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 fb09a12ce5..509180ba3f 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -1529,7 +1529,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 @@ -1622,6 +1622,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 [ @@ -2194,6 +2197,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() @@ -2262,6 +2268,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) + def validate_otp_params( mobile_flow_otp: Optional[str] = None, desktop_flow_otp: Optional[str] = None diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py index a6d4efab5b..b02d51bdaa 100644 --- a/zproject/prod_settings_template.py +++ b/zproject/prod_settings_template.py @@ -397,6 +397,12 @@ SOCIAL_AUTH_SAML_ENABLED_IDPS = { ## 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, }, }