diff --git a/docs/production/authentication-methods.md b/docs/production/authentication-methods.md
index a80cdc404d..91a9b412b1 100644
--- a/docs/production/authentication-methods.md
+++ b/docs/production/authentication-methods.md
@@ -35,161 +35,6 @@ Each of these requires one to a handful of lines of configuration in
`settings.py`, as well as a secret in `zulip-secrets.conf`. Details
are documented in your `settings.py`.
-## SAML
-
-Zulip 2.1 and later supports SAML authentication, used by Okta,
-OneLogin, and many other IdPs (identity providers). You can configure
-it as follows:
-
-1. These instructions assume you have an installed Zulip server; if
- you're using Zulip Cloud, see [this article][saml-help-center],
- which also has IdP-side configuration advice for common IdPs.
-
- You can have created a Zulip organization already using the default
- EmailAuthBackend, or plan to create the organization using SAML
- authentication.
-
-1. Tell your IdP how to find your Zulip server:
-
- * **SP Entity ID**: `https://yourzulipdomain.example.com`.
-
- The `Entity ID` should match the value of
- `SOCIAL_AUTH_SAML_SP_ENTITY_ID` computed in the Zulip settings.
- You can get the correct value by running the following:
- `/home/zulip/deployments/current/scripts/get-django-setting
- SOCIAL_AUTH_SAML_SP_ENTITY_ID`.
-
- * **SSO URL**:
- `https://yourzulipdomain.example.com/complete/saml/`. This is
- the "SAML ACS url" in SAML terminology.
-
- If you're
- [hosting multiple organizations](../production/multiple-organizations.html#authentication),
- you need to use `SOCIAL_AUTH_SUBDOMAIN`. For example,
- if `SOCIAL_AUTH_SUBDOMAIN="auth"` and `EXTERNAL_HOST=zulip.example.com`,
- this should be `https://auth.zulip.example.com/complete/saml/`.
-
-2. Tell Zulip how to connect to your SAML provider(s) by filling
- out the section of `/etc/zulip/settings.py` on your Zulip server
- with the heading "SAML Authentication".
- * You will need to update `SOCIAL_AUTH_SAML_ORG_INFO` with your
- organization name (`displayname` may appear in the IdP's
- authentication flow; `name` won't be displayed to humans).
- * Fill out `SOCIAL_AUTH_SAML_ENABLED_IDPS` with data provided by
- your identity provider. You may find [the python-social-auth
- SAML
- docs](https://python-social-auth.readthedocs.io/en/latest/backends/saml.html)
- helpful. You'll need to obtain several values from your IdP's
- metadata and enter them on the right-hand side of this
- Python dictionary:
- 1. Set the outer `idp_name` key to be an identifier for your IdP,
- e.g. `testshib` or `okta`. This field appears in URLs for
- parts of your Zulip server's SAML authentication flow.
- 2. The IdP should provide the `url` and `entity_id` values.
- 3. Save the `x509cert` value to a file; you'll use it in the
- instructions below.
- 4. The values needed in the `attr_` fields are often configurable
- in your IdP's interface when setting up SAML authentication
- (referred to as "Attribute Statements" with Okta, or
- "Attribute Mapping" with GSuite). You'll want to connect
- these so that Zulip gets the email address (used as a unique
- 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.
-
-3. Install the certificate(s) required for SAML authentication. You
- will definitely need the public certificate of your IdP. Some IdP
- providers also support the Zulip server (Service Provider) having
- a certificate used for encryption and signing. We detail these
- steps as optional below, because they aren't required for basic
- setup, and some IdPs like Okta don't fully support Service
- Provider certificates. You should install them as follows:
-
- 1. On your Zulip server, `mkdir -p /etc/zulip/saml/idps/`
- 2. Put the IDP public certificate in `/etc/zulip/saml/idps/{idp_name}.crt`
- 3. (Optional) Put the Zulip server public certificate in `/etc/zulip/saml/zulip-cert.crt`
- 4. (Optional) Put the Zulip server private key in `/etc/zulip/saml/zulip-private-key.key`
- 5. Set the proper permissions on these files and directories:
-
- ```
- chown -R zulip.zulip /etc/zulip/saml/
- find /etc/zulip/saml/ -type f -exec chmod 644 -- {} +
- chmod 640 /etc/zulip/saml/zulip-private-key.key
- ```
-
-4. (Optional) If you configured the optional public and private server
- certificates above, you can enable the additional setting
- `"authnRequestsSigned": True` in `SOCIAL_AUTH_SAML_SECURITY_CONFIG`
- to have the SAMLRequests the server will be issuing to the IdP
- signed using those certificates. Additionally, if the IdP supports
- it, you can upload the public certificate to enable encryption of
- assertions in the SAMLResponses the IdP will send about
- authenticated users.
-
-5. Enable the `zproject.backends.SAMLAuthBackend` auth backend, in
-`AUTHENTICATION_BACKENDS` in `/etc/zulip/settings.py`.
-
-6. [Restart the Zulip server](../production/settings.md) to ensure
-your settings changes take effect. The Zulip login page should now
-have a button for SAML authentication that you can use to log in or
-create an account (including when creating a new organization).
-
-7. If the configuration was successful, the server's metadata can be
-found at `https://yourzulipdomain.example.com/saml/metadata.xml`. You
-can use this for verifying your configuration or provide it to your
-IdP.
-
-[saml-help-center]: https://zulip.com/help/saml-authentication
-
-### IdP-initiated SSO
-
-The above configuration is sufficient for Service Provider initialized
-SSO, i.e. you can visit the Zulip webapp and click "Sign in with
-{IdP}" and it'll correctly start the authentication flow. If you are
-not hosting multiple organizations, with Zulip 3.0+, the above
-configuration is also sufficient for Identity Provider initiated SSO,
-i.e. clicking a "Sign in to Zulip" button on the IdP's website can
-correctly authenticate the user to Zulip.
-
-If you're hosting multiple organizations and thus using the
-`SOCIAL_AUTH_SUBDOMAIN` setting, you'll need to configure a custom
-`RelayState` in your IdP of the form `{"subdomain":
-"yourzuliporganization"}` to let Zulip know which organization to
-authenticate the user to when they visit your SSO URL from the IdP.
-(If the organization is on the root domain, use the empty string:
-`{"subdomain": ""}`.).
-
-```eval_rst
-.. _ldap:
-```
-
-### Restricting access to specific organizations
-
-If you're hosting multiple Zulip organizations, you can restrict which
-organizations can use a given IdP by setting `limit_to_subdomains`.
-For example, `limit_to_subdomains = ["", "engineering"]` would
-restrict an IdP the root domain and the `engineering` subdomain.
-
-You can achieve the same goal with a SAML attribute; just declare
-which attribute using `attr_org_membership` in the IdP configuration.
-For the root subdomain, `www` in the list will work, or any other of
-`settings.ROOT_SUBDOMAIN_ALIASES`.
-
-For example, with `attr_org_membership` set to `member`, a user with
-the following attribute in their `AttributeStatement` will have access
-to the root and `engineering` subdomains:
-
-```
-
-
- www
-
-
- engineering
-
-
-```
-
## LDAP (including Active Directory)
Zulip supports retrieving information about users via LDAP, and
@@ -459,6 +304,162 @@ the bottom of the problem:
this file (feel free to anonymize any email addresses to
`username@example.com`) in your report.
+## SAML
+
+Zulip 2.1 and later supports SAML authentication, used by Okta,
+OneLogin, and many other IdPs (identity providers). You can configure
+it as follows:
+
+1. These instructions assume you have an installed Zulip server; if
+ you're using Zulip Cloud, see [this article][saml-help-center],
+ which also has IdP-side configuration advice for common IdPs.
+
+ You can have created a Zulip organization already using the default
+ EmailAuthBackend, or plan to create the organization using SAML
+ authentication.
+
+1. Tell your IdP how to find your Zulip server:
+
+ * **SP Entity ID**: `https://yourzulipdomain.example.com`.
+
+ The `Entity ID` should match the value of
+ `SOCIAL_AUTH_SAML_SP_ENTITY_ID` computed in the Zulip settings.
+ You can get the correct value by running the following:
+ `/home/zulip/deployments/current/scripts/get-django-setting
+ SOCIAL_AUTH_SAML_SP_ENTITY_ID`.
+
+ * **SSO URL**:
+ `https://yourzulipdomain.example.com/complete/saml/`. This is
+ the "SAML ACS url" in SAML terminology.
+
+ If you're
+ [hosting multiple organizations](../production/multiple-organizations.html#authentication),
+ you need to use `SOCIAL_AUTH_SUBDOMAIN`. For example,
+ if `SOCIAL_AUTH_SUBDOMAIN="auth"` and `EXTERNAL_HOST=zulip.example.com`,
+ this should be `https://auth.zulip.example.com/complete/saml/`.
+
+2. Tell Zulip how to connect to your SAML provider(s) by filling
+ out the section of `/etc/zulip/settings.py` on your Zulip server
+ with the heading "SAML Authentication".
+ * You will need to update `SOCIAL_AUTH_SAML_ORG_INFO` with your
+ organization name (`displayname` may appear in the IdP's
+ authentication flow; `name` won't be displayed to humans).
+ * Fill out `SOCIAL_AUTH_SAML_ENABLED_IDPS` with data provided by
+ your identity provider. You may find [the python-social-auth
+ SAML
+ docs](https://python-social-auth.readthedocs.io/en/latest/backends/saml.html)
+ helpful. You'll need to obtain several values from your IdP's
+ metadata and enter them on the right-hand side of this
+ Python dictionary:
+ 1. Set the outer `idp_name` key to be an identifier for your IdP,
+ e.g. `testshib` or `okta`. This field appears in URLs for
+ parts of your Zulip server's SAML authentication flow.
+ 2. The IdP should provide the `url` and `entity_id` values.
+ 3. Save the `x509cert` value to a file; you'll use it in the
+ instructions below.
+ 4. The values needed in the `attr_` fields are often configurable
+ in your IdP's interface when setting up SAML authentication
+ (referred to as "Attribute Statements" with Okta, or
+ "Attribute Mapping" with GSuite). You'll want to connect
+ these so that Zulip gets the email address (used as a unique
+ 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.
+
+3. Install the certificate(s) required for SAML authentication. You
+ will definitely need the public certificate of your IdP. Some IdP
+ providers also support the Zulip server (Service Provider) having
+ a certificate used for encryption and signing. We detail these
+ steps as optional below, because they aren't required for basic
+ setup, and some IdPs like Okta don't fully support Service
+ Provider certificates. You should install them as follows:
+
+ 1. On your Zulip server, `mkdir -p /etc/zulip/saml/idps/`
+ 2. Put the IDP public certificate in `/etc/zulip/saml/idps/{idp_name}.crt`
+ 3. (Optional) Put the Zulip server public certificate in `/etc/zulip/saml/zulip-cert.crt`
+ 4. (Optional) Put the Zulip server private key in `/etc/zulip/saml/zulip-private-key.key`
+ 5. Set the proper permissions on these files and directories:
+
+ ```
+ chown -R zulip.zulip /etc/zulip/saml/
+ find /etc/zulip/saml/ -type f -exec chmod 644 -- {} +
+ chmod 640 /etc/zulip/saml/zulip-private-key.key
+ ```
+
+4. (Optional) If you configured the optional public and private server
+ certificates above, you can enable the additional setting
+ `"authnRequestsSigned": True` in `SOCIAL_AUTH_SAML_SECURITY_CONFIG`
+ to have the SAMLRequests the server will be issuing to the IdP
+ signed using those certificates. Additionally, if the IdP supports
+ it, you can upload the public certificate to enable encryption of
+ assertions in the SAMLResponses the IdP will send about
+ authenticated users.
+
+5. Enable the `zproject.backends.SAMLAuthBackend` auth backend, in
+`AUTHENTICATION_BACKENDS` in `/etc/zulip/settings.py`.
+
+6. [Restart the Zulip server](../production/settings.md) to ensure
+your settings changes take effect. The Zulip login page should now
+have a button for SAML authentication that you can use to log in or
+create an account (including when creating a new organization).
+
+7. If the configuration was successful, the server's metadata can be
+found at `https://yourzulipdomain.example.com/saml/metadata.xml`. You
+can use this for verifying your configuration or provide it to your
+IdP.
+
+[saml-help-center]: https://zulip.com/help/saml-authentication
+
+### IdP-initiated SSO
+
+The above configuration is sufficient for Service Provider initialized
+SSO, i.e. you can visit the Zulip webapp and click "Sign in with
+{IdP}" and it'll correctly start the authentication flow. If you are
+not hosting multiple organizations, with Zulip 3.0+, the above
+configuration is also sufficient for Identity Provider initiated SSO,
+i.e. clicking a "Sign in to Zulip" button on the IdP's website can
+correctly authenticate the user to Zulip.
+
+If you're hosting multiple organizations and thus using the
+`SOCIAL_AUTH_SUBDOMAIN` setting, you'll need to configure a custom
+`RelayState` in your IdP of the form `{"subdomain":
+"yourzuliporganization"}` to let Zulip know which organization to
+authenticate the user to when they visit your SSO URL from the IdP.
+(If the organization is on the root domain, use the empty string:
+`{"subdomain": ""}`.).
+
+```eval_rst
+.. _ldap:
+```
+
+### Restricting access to specific organizations
+
+If you're hosting multiple Zulip organizations, you can restrict which
+organizations can use a given IdP by setting `limit_to_subdomains`.
+For example, `limit_to_subdomains = ["", "engineering"]` would
+restrict an IdP the root domain and the `engineering` subdomain.
+
+You can achieve the same goal with a SAML attribute; just declare
+which attribute using `attr_org_membership` in the IdP configuration.
+For the root subdomain, `www` in the list will work, or any other of
+`settings.ROOT_SUBDOMAIN_ALIASES`.
+
+For example, with `attr_org_membership` set to `member`, a user with
+the following attribute in their `AttributeStatement` will have access
+to the root and `engineering` subdomains:
+
+```
+
+
+ www
+
+
+ engineering
+
+
+```
+
+
## Apache-based SSO with `REMOTE_USER`
If you have any existing SSO solution where a preferred way to deploy
diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py
index 6f6340ee46..f7de55bb2d 100644
--- a/zproject/prod_settings_template.py
+++ b/zproject/prod_settings_template.py
@@ -127,6 +127,102 @@ AUTHENTICATION_BACKENDS: Tuple[str, ...] = (
# 'zproject.backends.ZulipRemoteUserBackend', # Local SSO, setup docs on readthedocs
)
+# LDAP integration.
+#
+# Zulip supports retrieving information about users via LDAP, and
+# optionally using LDAP as an authentication mechanism.
+
+import ldap
+from django_auth_ldap.config import LDAPSearch
+
+# Connecting to the LDAP server.
+#
+# For detailed instructions, see the Zulip documentation:
+# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap
+
+# The LDAP server to connect to. Setting this enables Zulip
+# automatically fetching each new user's name from LDAP.
+# Example: "ldaps://ldap.example.com"
+AUTH_LDAP_SERVER_URI = ""
+
+# The DN of the user to bind as (i.e., authenticate as) in order to
+# query LDAP. If unset, Zulip does an anonymous bind.
+AUTH_LDAP_BIND_DN = ""
+
+# Passwords and secrets are not stored in this file. The password
+# corresponding to AUTH_LDAP_BIND_DN goes in `/etc/zulip/zulip-secrets.conf`.
+# In that file, set `auth_ldap_bind_password`. For example:
+# auth_ldap_bind_password = abcd1234
+
+# Mapping user info from LDAP to Zulip.
+#
+# For detailed instructions, see the Zulip documentation:
+# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap
+
+# The LDAP search query to find a given user.
+#
+# The arguments to `LDAPSearch` are (base DN, scope, filter). In the
+# filter, the string `%(user)s` is a Python placeholder. The Zulip
+# server will replace this with the user's Zulip username, i.e. the
+# name they type into the Zulip login form.
+#
+# For more details and alternatives, see the documentation linked above.
+AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
+ ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
+
+# Configuration to lookup a user's LDAP data given their email address
+# (For Zulip reverse mapping). If users log in as e.g. "sam" when
+# their email address is "sam@example.com", set LDAP_APPEND_DOMAIN to
+# "example.com". Otherwise, leave LDAP_APPEND_DOMAIN=None and set
+# AUTH_LDAP_REVERSE_EMAIL_SEARCH and AUTH_LDAP_USERNAME_ATTR below.
+#LDAP_APPEND_DOMAIN = None
+
+# LDAP attribute to find a user's email address.
+#
+# Leave as None if users log in with their email addresses,
+# or if using LDAP_APPEND_DOMAIN.
+#LDAP_EMAIL_ATTR = None
+
+# AUTH_LDAP_REVERSE_EMAIL_SEARCH works like AUTH_LDAP_USER_SEARCH and
+# should query an LDAP user given their email address. It and
+# AUTH_LDAP_USERNAME_ATTR are required when LDAP_APPEND_DOMAIN is None.
+#AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
+# ldap.SCOPE_SUBTREE, "(email=%(email)s)")
+
+# AUTH_LDAP_USERNAME_ATTR should be the Zulip username attribute
+# (defined in AUTH_LDAP_USER_SEARCH).
+#AUTH_LDAP_USERNAME_ATTR = "uid"
+
+# This map defines how to populate attributes of a Zulip user from LDAP.
+#
+# The format is `zulip_name: ldap_name`; each entry maps a Zulip
+# concept (on the left) to the LDAP attribute name (on the right) your
+# LDAP database uses for the same concept.
+AUTH_LDAP_USER_ATTR_MAP = {
+ # full_name is required; common values include "cn" or "displayName".
+ # If names are encoded in your LDAP directory as first and last
+ # name, you can instead specify first_name and last_name, and
+ # Zulip will combine those to construct a full_name automatically.
+ "full_name": "cn",
+ # "first_name": "fn",
+ # "last_name": "ln",
+
+ # Profile pictures can be pulled from the LDAP "thumbnailPhoto"/"jpegPhoto" field.
+ # "avatar": "thumbnailPhoto",
+
+ # This line is for having Zulip to automatically deactivate users
+ # who are disabled in LDAP/Active Directory (and reactivate users who are not).
+ # See docs for usage details and precise semantics.
+ # "userAccountControl": "userAccountControl",
+}
+
+# Whether to automatically deactivate users not found in LDAP. If LDAP
+# is the only authentication method, then this setting defaults to
+# True. If other authentication methods are enabled, it defaults to
+# False.
+#LDAP_DEACTIVATE_NON_MATCHING_USERS = True
+
+
########
# Google OAuth.
#
@@ -543,106 +639,6 @@ EMAIL_GATEWAY_IMAP_PORT = 993
EMAIL_GATEWAY_IMAP_FOLDER = "INBOX"
-################
-# LDAP integration.
-#
-# Zulip supports retrieving information about users via LDAP, and
-# optionally using LDAP as an authentication mechanism.
-
-import ldap
-from django_auth_ldap.config import LDAPSearch
-
-########
-# LDAP integration, part 1: Connecting to the LDAP server.
-#
-# For detailed instructions, see the Zulip documentation:
-# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap
-
-# The LDAP server to connect to. Setting this enables Zulip
-# automatically fetching each new user's name from LDAP.
-# Example: "ldaps://ldap.example.com"
-AUTH_LDAP_SERVER_URI = ""
-
-# The DN of the user to bind as (i.e., authenticate as) in order to
-# query LDAP. If unset, Zulip does an anonymous bind.
-AUTH_LDAP_BIND_DN = ""
-
-# Passwords and secrets are not stored in this file. The password
-# corresponding to AUTH_LDAP_BIND_DN goes in `/etc/zulip/zulip-secrets.conf`.
-# In that file, set `auth_ldap_bind_password`. For example:
-# auth_ldap_bind_password = abcd1234
-
-
-########
-# LDAP integration, part 2: Mapping user info from LDAP to Zulip.
-#
-# For detailed instructions, see the Zulip documentation:
-# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap
-
-# The LDAP search query to find a given user.
-#
-# The arguments to `LDAPSearch` are (base DN, scope, filter). In the
-# filter, the string `%(user)s` is a Python placeholder. The Zulip
-# server will replace this with the user's Zulip username, i.e. the
-# name they type into the Zulip login form.
-#
-# For more details and alternatives, see the documentation linked above.
-AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
- ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
-
-# Configuration to lookup a user's LDAP data given their email address
-# (For Zulip reverse mapping). If users log in as e.g. "sam" when
-# their email address is "sam@example.com", set LDAP_APPEND_DOMAIN to
-# "example.com". Otherwise, leave LDAP_APPEND_DOMAIN=None and set
-# AUTH_LDAP_REVERSE_EMAIL_SEARCH and AUTH_LDAP_USERNAME_ATTR below.
-#LDAP_APPEND_DOMAIN = None
-
-# LDAP attribute to find a user's email address.
-#
-# Leave as None if users log in with their email addresses,
-# or if using LDAP_APPEND_DOMAIN.
-#LDAP_EMAIL_ATTR = None
-
-# AUTH_LDAP_REVERSE_EMAIL_SEARCH works like AUTH_LDAP_USER_SEARCH and
-# should query an LDAP user given their email address. It and
-# AUTH_LDAP_USERNAME_ATTR are required when LDAP_APPEND_DOMAIN is None.
-#AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
-# ldap.SCOPE_SUBTREE, "(email=%(email)s)")
-
-# AUTH_LDAP_USERNAME_ATTR should be the Zulip username attribute
-# (defined in AUTH_LDAP_USER_SEARCH).
-#AUTH_LDAP_USERNAME_ATTR = "uid"
-
-# This map defines how to populate attributes of a Zulip user from LDAP.
-#
-# The format is `zulip_name: ldap_name`; each entry maps a Zulip
-# concept (on the left) to the LDAP attribute name (on the right) your
-# LDAP database uses for the same concept.
-AUTH_LDAP_USER_ATTR_MAP = {
- # full_name is required; common values include "cn" or "displayName".
- # If names are encoded in your LDAP directory as first and last
- # name, you can instead specify first_name and last_name, and
- # Zulip will combine those to construct a full_name automatically.
- "full_name": "cn",
- # "first_name": "fn",
- # "last_name": "ln",
-
- # Profile pictures can be pulled from the LDAP "thumbnailPhoto"/"jpegPhoto" field.
- # "avatar": "thumbnailPhoto",
-
- # This line is for having Zulip to automatically deactivate users
- # who are disabled in LDAP/Active Directory (and reactivate users who are not).
- # See docs for usage details and precise semantics.
- # "userAccountControl": "userAccountControl",
-}
-
-# Whether to automatically deactivate users not found in LDAP. If LDAP
-# is the only authentication method, then this setting defaults to
-# True. If other authentication methods are enabled, it defaults to
-# False.
-#LDAP_DEACTIVATE_NON_MATCHING_USERS = True
-
-
################
# Video call integrations.
#