mirror of https://github.com/zulip/zulip.git
remote_billing: Make "plan management" always available.
Just shows a config error page if the bouncer is not enabled. Uses a new endpoint for this so that it can work nicely for both browser and desktop app clients. It's necessary, because the desktop app expects to get a json response with either an error or billing_access_url to redirect to. Showing a nice config error page can't be done via the json error mechanism, so instead we just serve a redirect to the new error page, which the app will open in the browser in a new window or tab.
This commit is contained in:
parent
d5fb3f3176
commit
e39f400f94
|
@ -199,12 +199,41 @@ class SelfHostedBillingEndpointBasicTest(RemoteRealmBillingTestCase):
|
||||||
self_hosted_billing_json_url = "/json/self-hosted-billing"
|
self_hosted_billing_json_url = "/json/self-hosted-billing"
|
||||||
|
|
||||||
with self.settings(PUSH_NOTIFICATION_BOUNCER_URL=None):
|
with self.settings(PUSH_NOTIFICATION_BOUNCER_URL=None):
|
||||||
result = self.client_get(self_hosted_billing_url)
|
with self.settings(CORPORATE_ENABLED=True):
|
||||||
self.assertEqual(result.status_code, 404)
|
result = self.client_get(self_hosted_billing_url)
|
||||||
self.assert_in_response("Page not found (404)", result)
|
self.assertEqual(result.status_code, 404)
|
||||||
|
self.assert_in_response("Page not found (404)", result)
|
||||||
|
|
||||||
result = self.client_get(self_hosted_billing_json_url)
|
with self.settings(CORPORATE_ENABLED=False):
|
||||||
self.assert_json_error(result, "Server doesn't use the push notification service", 404)
|
result = self.client_get(self_hosted_billing_url)
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
redirect_url = result["Location"]
|
||||||
|
self.assertEqual(redirect_url, "/self-hosted-billing/not-configured/")
|
||||||
|
|
||||||
|
with self.assertLogs("django.request"):
|
||||||
|
result = self.client_get(redirect_url)
|
||||||
|
self.assert_in_response(
|
||||||
|
"This server is not configured to use push notifications.", result
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.settings(CORPORATE_ENABLED=True):
|
||||||
|
result = self.client_get(self_hosted_billing_json_url)
|
||||||
|
self.assert_json_error(
|
||||||
|
result, "Server doesn't use the push notification service", 404
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.settings(CORPORATE_ENABLED=False):
|
||||||
|
result = self.client_get(self_hosted_billing_json_url)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
redirect_url = result.json()["billing_access_url"]
|
||||||
|
self.assertEqual(redirect_url, "/self-hosted-billing/not-configured/")
|
||||||
|
|
||||||
|
with self.assertLogs("django.request"):
|
||||||
|
result = self.client_get(redirect_url)
|
||||||
|
self.assert_in_response(
|
||||||
|
"This server is not configured to use push notifications.", result
|
||||||
|
)
|
||||||
|
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"zerver.views.push_notifications.send_to_push_bouncer",
|
"zerver.views.push_notifications.send_to_push_bouncer",
|
||||||
|
@ -240,6 +269,26 @@ class SelfHostedBillingEndpointBasicTest(RemoteRealmBillingTestCase):
|
||||||
|
|
||||||
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
|
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
|
||||||
class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
|
class RemoteBillingAuthenticationTest(RemoteRealmBillingTestCase):
|
||||||
|
def test_self_hosted_config_error_page(self) -> None:
|
||||||
|
with self.settings(
|
||||||
|
CORPORATE_ENABLED=False, PUSH_NOTIFICATION_BOUNCER_URL=None
|
||||||
|
), self.assertLogs("django.request"):
|
||||||
|
result = self.client_get("/self-hosted-billing/not-configured/")
|
||||||
|
self.assertEqual(result.status_code, 500)
|
||||||
|
self.assert_in_response(
|
||||||
|
"This server is not configured to use push notifications.", result
|
||||||
|
)
|
||||||
|
|
||||||
|
# The page doesn't make sense if PUSH_NOTIFICATION_BOUNCER_URL is configured.
|
||||||
|
with self.settings(CORPORATE_ENABLED=False):
|
||||||
|
result = self.client_get("/self-hosted-billing/not-configured/")
|
||||||
|
self.assertEqual(result.status_code, 404)
|
||||||
|
|
||||||
|
# Also doesn't make sense on zulipchat.com (where CORPORATE_ENABLED is True).
|
||||||
|
with self.settings(CORPORATE_ENABLED=True, PUSH_NOTIFICATION_BOUNCER_URL=None):
|
||||||
|
result = self.client_get("/self-hosted-billing/not-configured/")
|
||||||
|
self.assertEqual(result.status_code, 404)
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_remote_billing_authentication_flow(self) -> None:
|
def test_remote_billing_authentication_flow(self) -> None:
|
||||||
self.login("desdemona")
|
self.login("desdemona")
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "zerver/config_error/container.html" %}
|
||||||
|
|
||||||
|
{% block error_content %}
|
||||||
|
{% trans doc_url="https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html" %}
|
||||||
|
This server is not configured to use push notifications. For instructions on how to
|
||||||
|
configure push notifications, please see the
|
||||||
|
<a href="{{ doc_url }}">documentation</a>.
|
||||||
|
{% endtrans %}
|
||||||
|
{% endblock %}
|
|
@ -16,7 +16,6 @@ from zerver.lib.i18n import (
|
||||||
get_language_translation_data,
|
get_language_translation_data,
|
||||||
)
|
)
|
||||||
from zerver.lib.narrow_helpers import NarrowTerm
|
from zerver.lib.narrow_helpers import NarrowTerm
|
||||||
from zerver.lib.push_notifications import uses_notification_bouncer
|
|
||||||
from zerver.lib.realm_description import get_realm_rendered_description
|
from zerver.lib.realm_description import get_realm_rendered_description
|
||||||
from zerver.lib.request import RequestNotes
|
from zerver.lib.request import RequestNotes
|
||||||
from zerver.models import Message, Realm, Stream, UserProfile
|
from zerver.models import Message, Realm, Stream, UserProfile
|
||||||
|
@ -82,8 +81,13 @@ def get_billing_info(user_profile: Optional[UserProfile]) -> BillingInfo:
|
||||||
show_billing = False
|
show_billing = False
|
||||||
show_plans = False
|
show_plans = False
|
||||||
sponsorship_pending = False
|
sponsorship_pending = False
|
||||||
|
|
||||||
|
# We want to always show the remote billing option as long as the user is authorized,
|
||||||
|
# except on zulipchat.com where it's not applicable.
|
||||||
show_remote_billing = (
|
show_remote_billing = (
|
||||||
user_profile is not None and user_profile.has_billing_access and uses_notification_bouncer()
|
(not settings.CORPORATE_ENABLED)
|
||||||
|
and user_profile is not None
|
||||||
|
and user_profile.has_billing_access
|
||||||
)
|
)
|
||||||
|
|
||||||
# This query runs on home page load, so we want to avoid
|
# This query runs on home page load, so we want to avoid
|
||||||
|
|
|
@ -528,6 +528,9 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N
|
||||||
# TODO: This endpoint and the rest of its system are a work in progress,
|
# TODO: This endpoint and the rest of its system are a work in progress,
|
||||||
# we are not testing it yet.
|
# we are not testing it yet.
|
||||||
"self-hosted-billing/",
|
"self-hosted-billing/",
|
||||||
|
# This endpoint only returns 500 and 404 codes, so it doesn't get picked up
|
||||||
|
# by find_pattern above and therefore needs to be exempt.
|
||||||
|
"self-hosted-billing/not-configured/",
|
||||||
*(webhook.url for webhook in WEBHOOK_INTEGRATIONS if not include_webhooks),
|
*(webhook.url for webhook in WEBHOOK_INTEGRATIONS if not include_webhooks),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -973,7 +973,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertFalse(billing_info.show_billing)
|
self.assertFalse(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# realm owner, with inactive CustomerPlan and realm plan_type SELF_HOSTED -> show only billing link
|
# realm owner, with inactive CustomerPlan and realm plan_type SELF_HOSTED -> show only billing link
|
||||||
customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id")
|
customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id")
|
||||||
|
@ -990,7 +990,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertTrue(billing_info.show_billing)
|
self.assertTrue(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# realm owner, with inactive CustomerPlan and realm plan_type LIMITED -> show billing link and plans
|
# realm owner, with inactive CustomerPlan and realm plan_type LIMITED -> show billing link and plans
|
||||||
do_change_realm_plan_type(user.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None)
|
do_change_realm_plan_type(user.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None)
|
||||||
|
@ -999,7 +999,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertTrue(billing_info.show_billing)
|
self.assertTrue(billing_info.show_billing)
|
||||||
self.assertTrue(billing_info.show_plans)
|
self.assertTrue(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# Always false without CORPORATE_ENABLED
|
# Always false without CORPORATE_ENABLED
|
||||||
with self.settings(CORPORATE_ENABLED=False):
|
with self.settings(CORPORATE_ENABLED=False):
|
||||||
|
@ -1029,6 +1029,12 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertFalse(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
|
# Self-hosted servers show remote billing, but not for a user without
|
||||||
|
# billing access permission.
|
||||||
|
with self.settings(CORPORATE_ENABLED=False):
|
||||||
|
billing_info = get_billing_info(user)
|
||||||
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# billing admin, with CustomerPlan and realm plan_type STANDARD -> show only billing link
|
# billing admin, with CustomerPlan and realm plan_type STANDARD -> show only billing link
|
||||||
user.role = UserProfile.ROLE_MEMBER
|
user.role = UserProfile.ROLE_MEMBER
|
||||||
user.is_billing_admin = True
|
user.is_billing_admin = True
|
||||||
|
@ -1039,6 +1045,11 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertTrue(billing_info.show_billing)
|
self.assertTrue(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
|
# Self-hosted servers show remote billing for billing admins.
|
||||||
|
with self.settings(CORPORATE_ENABLED=False):
|
||||||
|
billing_info = get_billing_info(user)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertTrue(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# billing admin, with CustomerPlan and realm plan_type PLUS -> show only billing link
|
# billing admin, with CustomerPlan and realm plan_type PLUS -> show only billing link
|
||||||
|
@ -1049,7 +1060,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertTrue(billing_info.show_billing)
|
self.assertTrue(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# member, with CustomerPlan and realm plan_type STANDARD -> neither billing link or plans
|
# member, with CustomerPlan and realm plan_type STANDARD -> neither billing link or plans
|
||||||
do_change_realm_plan_type(user.realm, Realm.PLAN_TYPE_STANDARD, acting_user=None)
|
do_change_realm_plan_type(user.realm, Realm.PLAN_TYPE_STANDARD, acting_user=None)
|
||||||
|
@ -1083,7 +1094,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertFalse(billing_info.show_billing)
|
self.assertFalse(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# billing admin, with sponsorship pending and realm plan_type SELF_HOSTED -> show only sponsorship pending link
|
# billing admin, with sponsorship pending and realm plan_type SELF_HOSTED -> show only sponsorship pending link
|
||||||
customer.sponsorship_pending = True
|
customer.sponsorship_pending = True
|
||||||
|
@ -1093,7 +1104,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertFalse(billing_info.show_billing)
|
self.assertFalse(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertTrue(billing_info.sponsorship_pending)
|
self.assertTrue(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# billing admin, no customer object and realm plan_type SELF_HOSTED -> no links
|
# billing admin, no customer object and realm plan_type SELF_HOSTED -> no links
|
||||||
customer.delete()
|
customer.delete()
|
||||||
|
@ -1102,13 +1113,14 @@ class HomeTest(ZulipTestCase):
|
||||||
self.assertFalse(billing_info.show_billing)
|
self.assertFalse(billing_info.show_billing)
|
||||||
self.assertFalse(billing_info.show_plans)
|
self.assertFalse(billing_info.show_plans)
|
||||||
self.assertFalse(billing_info.sponsorship_pending)
|
self.assertFalse(billing_info.sponsorship_pending)
|
||||||
self.assertTrue(billing_info.show_remote_billing)
|
self.assertFalse(billing_info.show_remote_billing)
|
||||||
|
|
||||||
# If the server doesn't have the push bouncer configured,
|
# If the server doesn't have the push bouncer configured,
|
||||||
# don't show remote billing.
|
# remote billing should be shown anyway, as the billing endpoint
|
||||||
with self.settings(PUSH_NOTIFICATION_BOUNCER_URL=None):
|
# is supposed show a useful error page.
|
||||||
|
with self.settings(PUSH_NOTIFICATION_BOUNCER_URL=None, CORPORATE_ENABLED=False):
|
||||||
billing_info = get_billing_info(user)
|
billing_info = get_billing_info(user)
|
||||||
self.assertFalse(billing_info.show_remote_billing)
|
self.assertTrue(billing_info.show_remote_billing)
|
||||||
|
|
||||||
def test_promote_sponsoring_zulip_in_realm(self) -> None:
|
def test_promote_sponsoring_zulip_in_realm(self) -> None:
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
|
|
|
@ -4,6 +4,7 @@ from typing import Optional
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.decorator import human_users_only, zulip_login_required
|
from zerver.decorator import human_users_only, zulip_login_required
|
||||||
|
@ -33,6 +34,7 @@ from zerver.lib.response import json_success
|
||||||
from zerver.lib.typed_endpoint import typed_endpoint
|
from zerver.lib.typed_endpoint import typed_endpoint
|
||||||
from zerver.lib.validator import check_string
|
from zerver.lib.validator import check_string
|
||||||
from zerver.models import PushDeviceToken, UserProfile
|
from zerver.models import PushDeviceToken, UserProfile
|
||||||
|
from zerver.views.errors import config_error
|
||||||
|
|
||||||
|
|
||||||
def check_app_id(var_name: str, val: object) -> str:
|
def check_app_id(var_name: str, val: object) -> str:
|
||||||
|
@ -123,7 +125,11 @@ def self_hosting_auth_view_common(
|
||||||
request: HttpRequest, user_profile: UserProfile, next_page: Optional[str] = None
|
request: HttpRequest, user_profile: UserProfile, next_page: Optional[str] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
if not uses_notification_bouncer():
|
if not uses_notification_bouncer():
|
||||||
raise ResourceNotFoundError(_("Server doesn't use the push notification service"))
|
if settings.CORPORATE_ENABLED:
|
||||||
|
# This endpoint makes no sense on zulipchat.com, so just 404.
|
||||||
|
raise ResourceNotFoundError(_("Server doesn't use the push notification service"))
|
||||||
|
else:
|
||||||
|
return reverse(self_hosting_auth_not_configured)
|
||||||
|
|
||||||
if not user_profile.has_billing_access: # nocoverage
|
if not user_profile.has_billing_access: # nocoverage
|
||||||
# We may want to replace this with an html error page at some point,
|
# We may want to replace this with an html error page at some point,
|
||||||
|
@ -210,3 +216,12 @@ def self_hosting_auth_json_endpoint(
|
||||||
redirect_url = self_hosting_auth_view_common(request, user_profile, next_page)
|
redirect_url = self_hosting_auth_view_common(request, user_profile, next_page)
|
||||||
|
|
||||||
return json_success(request, data={"billing_access_url": redirect_url})
|
return json_success(request, data={"billing_access_url": redirect_url})
|
||||||
|
|
||||||
|
|
||||||
|
def self_hosting_auth_not_configured(request: HttpRequest) -> HttpResponse:
|
||||||
|
if settings.CORPORATE_ENABLED or uses_notification_bouncer():
|
||||||
|
# This error page should only be available if the config error
|
||||||
|
# is actually real.
|
||||||
|
return render(request, "404.html", status=404)
|
||||||
|
|
||||||
|
return config_error(request, "remote_billing_bouncer_not_configured")
|
||||||
|
|
|
@ -99,6 +99,7 @@ from zerver.views.push_notifications import (
|
||||||
remove_android_reg_id,
|
remove_android_reg_id,
|
||||||
remove_apns_device_token,
|
remove_apns_device_token,
|
||||||
self_hosting_auth_json_endpoint,
|
self_hosting_auth_json_endpoint,
|
||||||
|
self_hosting_auth_not_configured,
|
||||||
self_hosting_auth_redirect_endpoint,
|
self_hosting_auth_redirect_endpoint,
|
||||||
send_test_push_notification_api,
|
send_test_push_notification_api,
|
||||||
)
|
)
|
||||||
|
@ -828,6 +829,10 @@ urls += [
|
||||||
self_hosting_auth_redirect_endpoint,
|
self_hosting_auth_redirect_endpoint,
|
||||||
name="self_hosting_auth_redirect_endpoint",
|
name="self_hosting_auth_redirect_endpoint",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"self-hosted-billing/not-configured/",
|
||||||
|
self_hosting_auth_not_configured,
|
||||||
|
),
|
||||||
rest_path(
|
rest_path(
|
||||||
"json/self-hosted-billing",
|
"json/self-hosted-billing",
|
||||||
GET=self_hosting_auth_json_endpoint,
|
GET=self_hosting_auth_json_endpoint,
|
||||||
|
|
Loading…
Reference in New Issue