mirror of https://github.com/zulip/zulip.git
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:
parent
95854d9d94
commit
ee77c6365a
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Terms and policies
|
||||
|
||||
* [Terms of Service](/policies/terms)
|
||||
* [Privacy Policy](/policies/privacy)
|
||||
* [Rules of Use](/policies/rules)
|
|
@ -0,0 +1 @@
|
|||
No such page.
|
|
@ -0,0 +1,2 @@
|
|||
## [Terms of Service](/policies/terms)
|
||||
## [Privacy Policy](/policies/privacy)
|
|
@ -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 />
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
## No policies configured
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)},
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue