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.
This commit is contained in:
Mateusz Mandera
2021-06-13 14:18:28 +02:00
committed by Tim Abbott
parent d200e3547f
commit bfe428f608
4 changed files with 57 additions and 1 deletions

View File

@@ -406,6 +406,8 @@ it as follows:
user ID) and name for the user. user ID) and name for the user.
5. The `display_name` and `display_icon` fields are used to 5. The `display_name` and `display_icon` fields are used to
display the login/registration buttons for the IdP. 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 3. Install the certificate(s) required for SAML authentication. You
will definitely need the public certificate of your IdP. Some IdP will definitely need the public certificate of your IdP. Some IdP

View File

@@ -1881,6 +1881,39 @@ class SAMLAuthBackendTest(SocialAuthBase):
result, 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: def test_social_auth_complete(self) -> None:
with mock.patch.object(OneLogin_Saml2_Response, "is_valid", return_value=True): with mock.patch.object(OneLogin_Saml2_Response, "is_valid", return_value=True):
with mock.patch.object( with mock.patch.object(

View File

@@ -1529,7 +1529,7 @@ def social_auth_finish(
validate_otp_params(mobile_flow_otp, desktop_flow_otp) validate_otp_params(mobile_flow_otp, desktop_flow_otp)
if user_profile is None or user_profile.is_mirror_dummy: 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: else:
is_signup = False is_signup = False
@@ -1622,6 +1622,9 @@ class SocialAuthMixin(ZulipAuthMixin, ExternalAuthMethod, BaseAuth):
self.logger.warning(str(e)) self.logger.warning(str(e))
return None return None
def should_auto_signup(self) -> bool:
return False
@classmethod @classmethod
def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]: def dict_representation(cls, realm: Optional[Realm] = None) -> List[ExternalAuthMethodDictT]:
return [ return [
@@ -2194,6 +2197,9 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
if param in self.standard_relay_params: if param in self.standard_relay_params:
self.strategy.session_set(param, value) 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, # super().auth_complete expects to have RelayState set to the idp_name,
# so we need to replace this param. # so we need to replace this param.
post_params = self.strategy.request.POST.copy() post_params = self.strategy.request.POST.copy()
@@ -2262,6 +2268,15 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
return result 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( def validate_otp_params(
mobile_flow_otp: Optional[str] = None, desktop_flow_otp: Optional[str] = None mobile_flow_otp: Optional[str] = None, desktop_flow_otp: Optional[str] = None

View File

@@ -397,6 +397,12 @@ SOCIAL_AUTH_SAML_ENABLED_IDPS = {
## You can also limit subdomains by setting "attr_org_membership" ## You can also limit subdomains by setting "attr_org_membership"
## to be a SAML attribute containing the allowed subdomains for a user. ## to be a SAML attribute containing the allowed subdomains for a user.
# "attr_org_membership": "member", # "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,
}, },
} }