portico: Use /help/ style pages for displaying policies.

This replaces the TERMS_OF_SERVICE and PRIVACY_POLICY settings with
just a POLICIES_DIRECTORY setting, in order to support settings (like
Zulip Cloud) where there's more policies than just those two.

With minor changes by Eeshan Garg.
This commit is contained in:
Tim Abbott 2021-11-03 13:36:54 -07:00 committed by Tim Abbott
parent 95854d9d94
commit ee77c6365a
27 changed files with 204 additions and 189 deletions

View File

@ -34,6 +34,10 @@ log][commit-log] for an up-to-date list of raw changes.
- This release contains a migration, `0009_confirmation_expiry_date_backfill`,
that can take several minutes to run on a server with millions of
messages of history.
- The `TERMS_OF_SERVICE` and `PRIVACY_POLICY` settings have been
removed in favor of a system that supports additional policy
documents, such as a code of conduct. See the [updated
documentation](../production/settings.md) for the new system.
#### Full feature changelog

View File

@ -850,7 +850,7 @@ prefills that value in the new account creation form, but gives the
user the opportunity to edit it before submitting. When `True`, Zulip
assumes the name is correct, and new users will not be presented with
a registration form unless they need to accept Terms of Service for
the server (i.e. `TERMS_OF_SERVICE=True`).
the server (i.e. `TERMS_OF_SERVICE_VERSION` is set).
## Adding more authentication backends

View File

@ -86,12 +86,25 @@ and configure this service.
### Terms of Service and Privacy policy
Zulip allows you to configure your server's Terms of Service and
Privacy Policy pages (`/terms` and `/privacy`, respectively). You can
use the `TERMS_OF_SERVICE` and `PRIVACY_POLICY` settings to configure
the path to your server's policies. The syntax is Markdown (with
support for included HTML). A good approach is to use paths like
`/etc/zulip/terms.md`, so that it's easy to back up your policy
configuration along with your other Zulip server configuration.
Privacy Policy pages (`/terms` and `/privacy`, respectively).
You can configure this using the `POLICIES_DIRECTORY` setting. We
recommend using `/etc/zulip/policies`, so that your policies are
naturally backed up with the server's other configuration. Just place
Markdown files named `terms.md` and `privacy.md` in that directory,
and set `TERMS_OF_SERVICE_VERSION` to `1.0` to enable this feature.
You can place additional files in this directory to document
additional policies; if you do so, you may want to:
- Create a Markdown file `sidebar_index.md` listing the pages in your
policies site; this generates the policies site navigation.
- Create a Markdown file `missing.md` with custom content for 404s in
this directory.
Please make clear in these pages what organization is hosting your
Zulip server, so that nobody could be confused that your policies are
the policies for Zulip Cloud.
### Miscellaneous server settings

View File

@ -0,0 +1,5 @@
# Terms and policies
* [Terms of Service](/policies/terms)
* [Privacy Policy](/policies/privacy)
* [Rules of Use](/policies/rules)

View File

@ -0,0 +1 @@
No such page.

View File

@ -0,0 +1,2 @@
## [Terms of Service](/policies/terms)
## [Privacy Policy](/policies/privacy)

View File

@ -10,10 +10,20 @@
<div class="app help terms-page inline-block{% if page_is_help_center %} help-center{% endif %}{% if page_is_api_center %} api-center{% endif %}">
<div class="sidebar">
<div class="content">
{% if not page_is_policy_center %}
<h1><a href="https://zulip.com" class="no-underline">Zulip homepage</a></h1>
<h1><a href="{{ doc_root }}" class="no-underline">{{ doc_root_title }} home</a></h1>
{% endif %}
{% if page_is_policy_center %}
{{ render_markdown_path(sidebar_index, pure_markdown=True) }}
{% else %}
{{ render_markdown_path(sidebar_index, api_uri_context) }}
{% endif %}
{% if not page_is_policy_center %}
<h1 class="home-link"><a href="/" class="no-underline">Back to Zulip</a></h1>
{% endif %}
</div>
</div>
@ -23,7 +33,11 @@
<div class="markdown">
<div class="content">
{% if page_is_policy_center %}
{{ render_markdown_path(article, pure_markdown=True) }}
{% else %}
{{ render_markdown_path(article, api_uri_context) }}
{% endif %}
<div id="footer" class="documentation-footer">
<hr />

View File

@ -0,0 +1,6 @@
This server is an installation of [Zulip](https://zulip.com), open
source software for team collaboration.
This installation of Zulip has not been configured to display its
policies. You can contact its administrators using the email address
displayed below.

View File

@ -0,0 +1 @@
## No policies configured

View File

@ -17,6 +17,9 @@
{% if page_is_api_center %}
<span class="light"> | <a href="{{ root_domain_uri }}/api/">{{ doc_root_title }}</a></span>
{% endif %}
{% if page_is_policy_center %}
<span class="light"> | <a href="{{ root_domain_uri }}/policies/">{{ doc_root_title }}</a></span>
{% endif %}
</div>
{% endif %}
</div>

View File

@ -1,38 +0,0 @@
{% extends "zerver/portico.html" %}
{% set entrypoint = "landing-page" %}
{% block title %}
<title>Zulip: the best group chat for open source projects</title>
{% endblock %}
{% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %}
{% block portico_content %}
{% include 'zerver/landing_nav.html' %}
<div class="portico-landing why-page">
<div class="hero small-hero">
<h1 class="center">{% trans %}Privacy policy{% endtrans %}</h1>
</div>
<div class="main">
<div class="padded-content">
<div class="inner-content markdown">
{% if privacy_policy %}
{{ render_markdown_path(privacy_policy, pure_markdown=True) }}
{% else %}
{% trans %}
This installation of Zulip does not have a configured privacy policy.
Contact this <a href="mailto:{{ support_email }}">server's administrator</a>
if you have any questions.
{% endtrans %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,35 +0,0 @@
{% extends "zerver/portico.html" %}
{% set entrypoint = "landing-page" %}
{# Terms of Service. #}
{% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %}
{% block portico_content %}
{% include 'zerver/landing_nav.html' %}
<div class="portico-landing why-page">
<div class="hero small-hero">
<h1 class="center">{% trans %}Terms of Service{% endtrans %}</h1>
</div>
<div class="main">
<div class="padded-content">
<div class="inner-content markdown">
{% if terms_of_service %}
{{ render_markdown_path(terms_of_service, pure_markdown=True) }}
{% else %}
{% trans %}
This installation of Zulip does not have a configured terms of service.
Contact this <a href="mailto:{{ support_email }}">server's administrator</a>
if you have any questions.
{% endtrans %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -81,6 +81,11 @@ def get_apps_page_url() -> str:
return "https://zulip.com/apps/"
def is_isolated_page(request: HttpRequest) -> bool:
"""Accept a GET param `?nav=no` to render an isolated, navless page."""
return request.GET.get("nav") == "no"
def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
"""Context available to all Zulip Jinja2 templates that have a request
passed in. Designed to provide the long list of variables at the
@ -145,8 +150,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
"custom_logo_url": settings.CUSTOM_LOGO_URL,
"register_link_disabled": register_link_disabled,
"login_link_disabled": login_link_disabled,
"terms_of_service": settings.TERMS_OF_SERVICE,
"privacy_policy": settings.PRIVACY_POLICY,
"terms_of_service": settings.TERMS_OF_SERVICE_VERSION is not None,
"login_url": settings.HOME_NOT_LOGGED_IN,
"only_sso": settings.ONLY_SSO,
"external_host": settings.EXTERNAL_HOST,
@ -172,6 +176,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
"platform": RequestNotes.get_notes(request).client_name,
"allow_search_engine_indexing": allow_search_engine_indexing,
"landing_page_navbar_message": settings.LANDING_PAGE_NAVBAR_MESSAGE,
"is_isolated_page": is_isolated_page(request),
"default_page_params": default_page_params,
}

View File

@ -126,7 +126,7 @@ class RegistrationForm(forms.Form):
del kwargs["realm_creation"]
super().__init__(*args, **kwargs)
if settings.TERMS_OF_SERVICE:
if settings.TERMS_OF_SERVICE_VERSION is not None:
self.fields["terms"] = forms.BooleanField(required=True)
self.fields["realm_name"] = forms.CharField(
max_length=Realm.MAX_REALM_NAME_LENGTH, required=self.realm_creation

View File

@ -1416,7 +1416,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
self.assertFalse(user_profile.has_usable_password())
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_registration(self) -> None:
"""If the user doesn't exist yet, social auth can be used to register an account"""
email = "newuser@zulip.com"
@ -1431,7 +1431,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
result, realm, subdomain, email, name, name, self.BACKEND_CLASS.full_name_validated
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_mobile_registration(self) -> None:
email = "newuser@zulip.com"
name = "Full Name"
@ -1458,7 +1458,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
mobile_flow_otp=mobile_flow_otp,
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_desktop_registration(self) -> None:
email = "newuser@zulip.com"
name = "Full Name"
@ -1485,7 +1485,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
desktop_flow_otp=desktop_flow_otp,
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_registration_invitation_exists(self) -> None:
"""
This tests the registration flow in the case where an invitation for the user
@ -1507,7 +1507,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
result, realm, subdomain, email, name, name, self.BACKEND_CLASS.full_name_validated
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_with_invalid_multiuse_invite(self) -> None:
email = "newuser@zulip.com"
name = "Full Name"
@ -1528,7 +1528,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
self.assertEqual(result.status_code, 404)
self.assert_in_response("Whoops. The confirmation link is malformed.", result)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_registration_using_multiuse_invite(self) -> None:
"""If the user doesn't exist yet, social auth can be used to register an account"""
email = "newuser@zulip.com"
@ -1628,7 +1628,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
result,
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_with_ldap_populate_registration_from_confirmation(self) -> None:
self.init_default_ldap_database()
email = "newuser@zulip.com"
@ -1691,7 +1691,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
log_warn.output, [f"WARNING:root:New account email {email} could not be found in LDAP"]
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_with_ldap_auth_registration_from_confirmation(self) -> None:
"""
This test checks that in configurations that use the LDAP authentication backend
@ -1784,7 +1784,7 @@ class SocialAuthBase(DesktopFlowTestingLib, ZulipTestCase):
self.assertEqual(result.status_code, 302)
self.assertIn("login", result.url)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_invited_as_admin_but_expired(self) -> None:
iago = self.example_user("iago")
email = self.nonreg_email("alice")
@ -2167,7 +2167,7 @@ class SAMLAuthBackendTest(SocialAuthBase):
result,
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_registration_auto_signup(self) -> None:
"""
Verify that with SAML auto signup enabled, a user coming from the /login page
@ -3335,7 +3335,7 @@ class GenericOpenIdConnectTest(SocialAuthBase):
family_name=name.split(" ")[1],
)
@override_settings(TERMS_OF_SERVICE=None)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_social_auth_registration_auto_signup(self) -> None:
"""
The analogue of the auto_signup test for SAML.

View File

@ -561,49 +561,54 @@ class AppsPageTest(ZulipTestCase):
class PrivacyTermsTest(ZulipTestCase):
def test_custom_tos_template(self) -> None:
response = self.client_get("/terms/")
self.assert_in_success_response(
[
'Thanks for using our products and services ("Services"). ',
"By using our Services, you are agreeing to these terms",
],
response,
)
def test_terms_and_policies_index(self) -> None:
with self.settings(POLICIES_DIRECTORY="corporate/policies"):
response = self.client_get("/policies/")
self.assert_in_success_response(["Terms and policies"], response)
def test_custom_terms_of_service_template(self) -> None:
not_configured_message = (
"This installation of Zulip does not have a configured terms of service"
)
with self.settings(TERMS_OF_SERVICE=None):
response = self.client_get("/terms/")
self.assert_in_success_response([not_configured_message], response)
with self.settings(TERMS_OF_SERVICE="zerver/tests/markdown/test_markdown.md"):
response = self.client_get("/terms/")
self.assert_in_success_response(["This is some <em>bold text</em>."], response)
self.assert_not_in_success_response([not_configured_message], response)
not_configured_message = "This server is an installation"
with self.settings(POLICIES_DIRECTORY="zerver/policies_absent"):
response = self.client_get("/policies/terms")
self.assert_in_response(not_configured_message, response)
with self.settings(POLICIES_DIRECTORY="corporate/policies"):
response = self.client_get("/policies/terms")
self.assert_in_success_response(["Kandra Labs"], response)
def test_custom_privacy_policy_template(self) -> None:
not_configured_message = (
"This installation of Zulip does not have a configured privacy policy"
)
with self.settings(PRIVACY_POLICY=None):
response = self.client_get("/privacy/")
self.assert_in_success_response([not_configured_message], response)
with self.settings(PRIVACY_POLICY="zerver/tests/markdown/test_markdown.md"):
response = self.client_get("/privacy/")
self.assert_in_success_response(["This is some <em>bold text</em>."], response)
self.assert_not_in_success_response([not_configured_message], response)
not_configured_message = "This server is an installation"
with self.settings(POLICIES_DIRECTORY="zerver/policies_absent"):
response = self.client_get("/policies/privacy")
self.assert_in_response(not_configured_message, response)
with self.settings(POLICIES_DIRECTORY="corporate/policies"):
response = self.client_get("/policies/privacy")
self.assert_in_success_response(["Kandra Labs"], response)
def test_custom_privacy_policy_template_with_absolute_url(self) -> None:
"""Verify that using our recommended production default of an absolute path
like /etc/zulip/policies/ works."""
current_dir = os.path.dirname(os.path.abspath(__file__))
abs_path = os.path.join(
current_dir, "..", "..", "templates/zerver/tests/markdown/test_markdown.md"
abs_path = os.path.abspath(
os.path.join(current_dir, "..", "..", "templates/corporate/policies")
)
with self.settings(PRIVACY_POLICY=abs_path):
response = self.client_get("/privacy/")
self.assert_in_success_response(["This is some <em>bold text</em>."], response)
with self.settings(POLICIES_DIRECTORY=abs_path):
response = self.client_get("/policies/privacy")
self.assert_in_success_response(["Kandra Labs"], response)
with self.settings(POLICIES_DIRECTORY=abs_path):
response = self.client_get("/policies/nonexistent")
self.assert_in_response("No such page", response)
def test_redirects_from_older_urls(self) -> None:
with self.settings(POLICIES_DIRECTORY="corporate/policies"):
result = self.client_get("/privacy/", follow=True)
self.assert_in_success_response(["Kandra Labs"], result)
with self.settings(POLICIES_DIRECTORY="corporate/policies"):
result = self.client_get("/terms/", follow=True)
self.assert_in_success_response(["Kandra Labs"], result)
def test_no_nav(self) -> None:
# Test that our ?nav=0 feature of /privacy and /terms,
@ -611,11 +616,15 @@ class PrivacyTermsTest(ZulipTestCase):
# policies that ToS/Privacy pages linked from an iOS app have
# no links to the rest of the site if there's pricing
# information for anything elsewhere on the site.
response = self.client_get("/terms/")
self.assert_in_success_response(["Plans"], response)
response = self.client_get("/terms/", {"nav": "no"})
self.assert_not_in_success_response(["Plans"], response)
# We don't have this link at all on these pages; this first
# line of the test would change if we were to adjust the
# design.
response = self.client_get("/policies/terms")
self.assert_not_in_success_response(["Back to Zulip"], response)
response = self.client_get("/privacy/", {"nav": "no"})
self.assert_not_in_success_response(["Plans"], response)
response = self.client_get("/policies/terms", {"nav": "no"})
self.assert_not_in_success_response(["Back to Zulip"], response)
response = self.client_get("/policies/privacy", {"nav": "no"})
self.assert_not_in_success_response(["Back to Zulip"], response)

View File

@ -387,6 +387,7 @@ class HomeTest(ZulipTestCase):
# Should be successful after calling 2fa login function.
self.check_rendered_logged_in_app(result)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_num_queries_for_realm_admin(self) -> None:
# Verify number of queries for Realm admin isn't much higher than for normal users.
self.login("iago")
@ -457,10 +458,7 @@ class HomeTest(ZulipTestCase):
user.tos_version = user_tos_version
user.save()
with self.settings(TERMS_OF_SERVICE="whatever"), self.settings(
TERMS_OF_SERVICE_VERSION="99.99"
):
with self.settings(TERMS_OF_SERVICE_VERSION="99.99"):
result = self.client_get("/", dict(stream="Denmark"))
html = result.content.decode()

View File

@ -5127,7 +5127,7 @@ class UserSignUpTest(InviteUserBase):
LDAP_APPEND_DOMAIN="zulip.com",
AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map,
AUTHENTICATION_BACKENDS=("zproject.backends.ZulipLDAPAuthBackend",),
TERMS_OF_SERVICE=False,
TERMS_OF_SERVICE_VERSION=1.0,
):
result = self.client_get(confirmation_url)
self.assertEqual(result.status_code, 200)
@ -5259,7 +5259,7 @@ class UserSignUpTest(InviteUserBase):
"AssertionError: Mirror dummy user is already active!" in error_log.output[0]
)
@override_settings(TERMS_OF_SERVICE=False)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_dev_user_registration(self) -> None:
"""Verify that /devtools/register_user creates a new user, logs them
in, and redirects to the logged-in app."""
@ -5275,7 +5275,7 @@ class UserSignUpTest(InviteUserBase):
self.assertEqual(result["Location"], "http://zulip.testserver/")
self.assert_logged_in_user_id(user_profile.id)
@override_settings(TERMS_OF_SERVICE=False)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_dev_user_registration_create_realm(self) -> None:
count = UserProfile.objects.count()
string_id = f"realm-{count}"
@ -5293,7 +5293,7 @@ class UserSignUpTest(InviteUserBase):
assert user_profile is not None
self.assert_logged_in_user_id(user_profile.id)
@override_settings(TERMS_OF_SERVICE=False)
@override_settings(TERMS_OF_SERVICE_VERSION=None)
def test_dev_user_registration_create_demo_realm(self) -> None:
result = self.client_post("/devtools/register_demo_realm/")
self.assertEqual(result.status_code, 302)

View File

@ -69,6 +69,7 @@ class ApiURLView(TemplateView):
class MarkdownDirectoryView(ApiURLView):
path_template = ""
policies_view = False
def get_path(self, article: str) -> DocumentationArticle:
http_status = 200
@ -87,10 +88,25 @@ class MarkdownDirectoryView(ApiURLView):
endpoint_name = None
endpoint_method = None
if self.policies_view and self.path_template.startswith("/"):
# This block is required because neither the Django
# template loader nor the article_path logic below support
# settings.POLICIES_DIRECTORY being an absolute path.
if not os.path.exists(path):
article = "missing"
http_status = 404
path = self.path_template % (article,)
return DocumentationArticle(
article_path=path,
article_http_status=http_status,
endpoint_path=None,
endpoint_method=None,
)
# The following is a somewhat hacky approach to extract titles from articles.
# Hack: `context["article"] has a leading `/`, so we use + to add directories.
article_path = os.path.join(settings.DEPLOY_ROOT, "templates") + path
if (not os.path.exists(article_path)) and self.path_template == "/zerver/api/%s.md":
try:
endpoint_name, endpoint_method = get_endpoint_from_operationid(article)
@ -102,6 +118,7 @@ class MarkdownDirectoryView(ApiURLView):
endpoint_path=None,
endpoint_method=None,
)
try:
loader.get_template(path)
return DocumentationArticle(
@ -124,6 +141,20 @@ class MarkdownDirectoryView(ApiURLView):
documentation_article = self.get_path(article)
context["article"] = documentation_article.article_path
if documentation_article.article_path.startswith("/") and os.path.exists(
documentation_article.article_path
):
# Absolute path case
article_path = documentation_article.article_path
elif documentation_article.article_path.startswith("/"):
# Hack: `context["article"] has a leading `/`, so we use + to add directories.
article_path = (
os.path.join(settings.DEPLOY_ROOT, "templates") + documentation_article.article_path
)
else:
article_path = os.path.join(
settings.DEPLOY_ROOT, "templates", documentation_article.article_path
)
# For disabling the "Back to home" on the homepage
context["not_index_page"] = not context["article"].endswith("/index.md")
@ -134,6 +165,13 @@ class MarkdownDirectoryView(ApiURLView):
sidebar_article = self.get_path("include/sidebar_index")
sidebar_index = sidebar_article.article_path
title_base = "Zulip Help Center"
elif self.path_template == f"{settings.POLICIES_DIRECTORY}/%s.md":
context["page_is_policy_center"] = True
context["doc_root"] = "/policies/"
context["doc_root_title"] = "Terms and policies"
sidebar_article = self.get_path("sidebar_index")
sidebar_index = sidebar_article.article_path
title_base = "Zulip terms and policies"
else:
context["page_is_api_center"] = True
context["doc_root"] = "/api/"
@ -143,8 +181,6 @@ class MarkdownDirectoryView(ApiURLView):
title_base = "Zulip API documentation"
# The following is a somewhat hacky approach to extract titles from articles.
# Hack: `context["article"] has a leading `/`, so we use + to add directories.
article_path = os.path.join(settings.DEPLOY_ROOT, "templates") + context["article"]
endpoint_name = None
endpoint_method = None
if os.path.exists(article_path):
@ -188,6 +224,12 @@ class MarkdownDirectoryView(ApiURLView):
return context
def get(self, request: HttpRequest, article: str = "") -> HttpResponse:
# Hack: It's hard to reinitialize urls.py from tests, and so
# we want to defer the use of settings.POLICIES_DIRECTORY to
# runtime.
if self.policies_view:
self.path_template = f"{settings.POLICIES_DIRECTORY}/%s.md"
documentation_article = self.get_path(article)
http_status = documentation_article.article_http_status
result = super().get(self, article=article)

View File

@ -26,9 +26,6 @@ def need_accept_tos(user_profile: Optional[UserProfile]) -> bool:
if user_profile is None:
return False
if settings.TERMS_OF_SERVICE is None: # nocoverage
return False
if settings.TERMS_OF_SERVICE_VERSION is None:
return False

View File

@ -95,11 +95,6 @@ def team_view(request: HttpRequest) -> HttpResponse:
)
def get_isolated_page(request: HttpRequest) -> bool:
"""Accept a GET param `?nav=no` to render an isolated, navless page."""
return request.GET.get("nav") == "no"
@add_google_analytics
def landing_view(request: HttpRequest, template_name: str) -> HttpResponse:
return TemplateResponse(request, template_name)
@ -108,21 +103,3 @@ def landing_view(request: HttpRequest, template_name: str) -> HttpResponse:
@add_google_analytics
def hello_view(request: HttpRequest) -> HttpResponse:
return TemplateResponse(request, "zerver/hello.html", latest_info_context())
@add_google_analytics
def terms_view(request: HttpRequest) -> HttpResponse:
return TemplateResponse(
request,
"zerver/terms.html",
context={"isolated_page": get_isolated_page(request)},
)
@add_google_analytics
def privacy_view(request: HttpRequest) -> HttpResponse:
return TemplateResponse(
request,
"zerver/privacy.html",
context={"isolated_page": get_isolated_page(request)},
)

View File

@ -177,8 +177,7 @@ TORNADO_PORTS: List[int] = []
USING_TORNADO = True
# ToS/Privacy templates
PRIVACY_POLICY: Optional[str] = None
TERMS_OF_SERVICE: Optional[str] = None
POLICIES_DIRECTORY: str = "zerver/policies_absent"
# Security
ENABLE_FILE_LINKS = False

View File

@ -77,8 +77,9 @@ OPEN_REALM_CREATION = True
WEB_PUBLIC_STREAMS_ENABLED = True
INVITES_MIN_USER_AGE_DAYS = 0
TERMS_OF_SERVICE = "corporate/terms.md"
PRIVACY_POLICY = "corporate/privacy.md"
# For development convenience, configure the ToS/Privacy Policies
POLICIES_DIRECTORY = "corporate/policies"
TERMS_OF_SERVICE_VERSION = "1.0"
EMBEDDED_BOTS_ENABLED = True
@ -166,9 +167,6 @@ SEARCH_PILLS_ENABLED = bool(os.getenv("SEARCH_PILLS_ENABLED", False))
BILLING_ENABLED = True
LANDING_PAGE_NAVBAR_MESSAGE: Optional[str] = None
# Test custom TOS template rendering
TERMS_OF_SERVICE = "corporate/terms.md"
# Our run-dev.py proxy uses X-Forwarded-Port to communicate to Django
# that the request is actually on port 9991, not port 9992 (the Django
# server's own port); this setting tells Django to read that HTTP

View File

@ -760,9 +760,11 @@ CAMO_URI = "/external_content/"
## together into one bucket when applying rate-limiting.
# RATE_LIMIT_TOR_TOGETHER = False
## If you want to set a Terms of Service for your server, set the path
## to your Markdown file, and uncomment the following line.
# TERMS_OF_SERVICE = '/etc/zulip/terms.md'
## Configuration for Terms of Service and Privacy Policy for the
## server. If unset, Zulip will never prompt users to accept Terms of
## Service. Users will be prompted to accept the terms during account
## registration, and during login if this value has changed.
# TERMS_OF_SERVICE_VERSION = "1.0"
## Similarly if you want to set a Privacy Policy.
# PRIVACY_POLICY = '/etc/zulip/privacy.md'
## Directory containing Markdown files for the server's policies.
# POLICIES_DIRECTORY = "/etc/zulip/policies/"

View File

@ -83,9 +83,7 @@ from zerver.views.portico import (
hello_view,
landing_view,
plans_view,
privacy_view,
team_view,
terms_view,
)
from zerver.views.presence import (
get_presence_backend,
@ -607,6 +605,9 @@ i18n_urls = [
path("apps/", apps_view),
path("apps/download/<platform>", app_download_link_redirect),
path("apps/<platform>", apps_view),
path(
"developer-community/", RedirectView.as_view(url="/development-community/", permanent=True)
),
path(
"development-community/",
landing_view,
@ -641,9 +642,6 @@ i18n_urls = [
RedirectView.as_view(url="/for/communities/", permanent=True),
),
path("security/", landing_view, {"template_name": "zerver/security.html"}),
# Terms of Service and privacy pages.
path("terms/", terms_view),
path("privacy/", privacy_view),
]
# Make a copy of i18n_urls so that they appear without prefix for english
@ -805,6 +803,10 @@ help_documentation_view = MarkdownDirectoryView.as_view(
api_documentation_view = MarkdownDirectoryView.as_view(
template_name="zerver/documentation_main.html", path_template="/zerver/api/%s.md"
)
policy_documentation_view = MarkdownDirectoryView.as_view(
template_name="zerver/documentation_main.html",
policies_view=True,
)
urls += [
# Redirects due to us having moved the docs:
path(
@ -883,6 +885,16 @@ urls += [
path("help/<path:article>", help_documentation_view),
path("api/", api_documentation_view),
path("api/<slug:article>", api_documentation_view),
path("policies/", policy_documentation_view),
path("policies/<slug:article>", policy_documentation_view),
path(
"privacy/",
RedirectView.as_view(url="/policies/privacy"),
),
path(
"terms/",
RedirectView.as_view(url="/policies/terms"),
),
]
# Two-factor URLs