mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
home: Allow logged out user through home.
We allow user to load webapp without log-in. This is only be enabled for developed purposes now. Production setups will see no changes.
This commit is contained in:
@@ -317,7 +317,7 @@ exports.initialize = function () {
|
||||
|
||||
exports.cleanup_event_queue = function cleanup_event_queue() {
|
||||
// Submit a request to the server to cleanup our event queue
|
||||
if (page_params.event_queue_expired === true) {
|
||||
if (page_params.event_queue_expired === true || page_params.no_event_queue === true) {
|
||||
return;
|
||||
}
|
||||
blueslip.log("Cleaning up our event queue");
|
||||
|
||||
@@ -65,8 +65,6 @@ not_yet_fully_covered = [
|
||||
'zerver/lib/markdown/__init__.py',
|
||||
'zerver/lib/cache.py',
|
||||
'zerver/lib/cache_helpers.py',
|
||||
# events.py temporarily removed due to logged-out user unfinished code path.
|
||||
'zerver/lib/events.py',
|
||||
'zerver/lib/i18n.py',
|
||||
'zerver/lib/email_notifications.py',
|
||||
'zerver/lib/send_email.py',
|
||||
|
||||
@@ -341,7 +341,9 @@ def user_passes_test(test_func: Callable[[HttpResponse], bool], login_url: Optio
|
||||
(not login_netloc or login_netloc == current_netloc)):
|
||||
path = request.get_full_path()
|
||||
|
||||
if path == "/":
|
||||
# TODO: Restore testing for this case; it was removed when
|
||||
# we enabled web-public stream testing on /.
|
||||
if path == "/": # nocoverage
|
||||
# Don't add ?next=/, to keep our URLs clean
|
||||
return HttpResponseRedirect(resolved_login_url)
|
||||
return redirect_to_login(
|
||||
@@ -417,6 +419,28 @@ def zulip_login_required(
|
||||
return actual_decorator(function)
|
||||
return actual_decorator # nocoverage # We don't use this without a function
|
||||
|
||||
def web_public_view(
|
||||
view_func: ViewFuncT,
|
||||
redirect_field_name: str=REDIRECT_FIELD_NAME,
|
||||
login_url: str=settings.HOME_NOT_LOGGED_IN,
|
||||
) -> Union[Callable[[ViewFuncT], ViewFuncT], ViewFuncT]:
|
||||
"""
|
||||
This wrapper adds client info for unauthenticated users but
|
||||
forces authenticated users to go through 2fa.
|
||||
|
||||
NOTE: This function == zulip_login_required in a production environment as
|
||||
web_public_view path has only been enabled for development purposes
|
||||
currently.
|
||||
"""
|
||||
if not settings.DEVELOPMENT:
|
||||
# Coverage disabled because DEVELOPMENT is always true in development.
|
||||
return zulip_login_required(view_func, redirect_field_name, login_url) # nocoverage
|
||||
|
||||
actual_decorator = lambda view_func: zulip_otp_required(
|
||||
redirect_field_name=redirect_field_name, login_url=login_url)(add_logging_data(view_func))
|
||||
|
||||
return actual_decorator(view_func)
|
||||
|
||||
def require_server_admin(view_func: ViewFuncT) -> ViewFuncT:
|
||||
@zulip_login_required
|
||||
@wraps(view_func)
|
||||
@@ -779,6 +803,8 @@ def zulip_otp_required(
|
||||
|
||||
# This request is unauthenticated (logged-out) access; 2FA is
|
||||
# not required or possible.
|
||||
#
|
||||
# TODO: Add a test for 2FA-enabled with web-public views.
|
||||
if not user.is_authenticated: # nocoverage
|
||||
return True
|
||||
|
||||
|
||||
@@ -364,7 +364,11 @@ def fetch_initial_state_data(
|
||||
if user_profile is not None:
|
||||
state['streams'] = do_get_streams(user_profile)
|
||||
else:
|
||||
state['streams'] = get_web_public_streams(realm)
|
||||
# TODO: This line isn't used by the webapp because it
|
||||
# gets these data via the `subscriptions` key; it will
|
||||
# be used when the mobile apps support logged-out
|
||||
# access.
|
||||
state['streams'] = get_web_public_streams(realm) # nocoverage
|
||||
state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH
|
||||
state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH
|
||||
if want('default_streams'):
|
||||
|
||||
@@ -48,7 +48,7 @@ def get_furthest_read_time(user_profile: Optional[UserProfile]) -> Optional[floa
|
||||
|
||||
def get_bot_types(user_profile: Optional[UserProfile]) -> List[Dict[str, object]]:
|
||||
bot_types: List[Dict[str, object]] = []
|
||||
if user_profile is None: # nocoverage
|
||||
if user_profile is None:
|
||||
return bot_types
|
||||
|
||||
for type_id, name in UserProfile.BOT_TYPES.items():
|
||||
@@ -91,7 +91,7 @@ def get_user_permission_info(user_profile: Optional[UserProfile]) -> UserPermiss
|
||||
is_realm_admin=user_profile.is_realm_admin,
|
||||
show_webathena=user_profile.realm.webathena_enabled,
|
||||
)
|
||||
else: # nocoverage
|
||||
else:
|
||||
return UserPermissionInfo(
|
||||
color_scheme=UserProfile.COLOR_SCHEME_AUTOMATIC,
|
||||
is_guest=False,
|
||||
@@ -103,7 +103,7 @@ def get_user_permission_info(user_profile: Optional[UserProfile]) -> UserPermiss
|
||||
|
||||
def build_page_params_for_home_page_load(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
user_profile: Optional[UserProfile],
|
||||
realm: Realm,
|
||||
insecure_desktop_app: bool,
|
||||
has_mobile_devices: bool,
|
||||
@@ -125,6 +125,7 @@ def build_page_params_for_home_page_load(
|
||||
"user_avatar_url_field_optional": True,
|
||||
}
|
||||
|
||||
if user_profile is not None:
|
||||
register_ret = do_events_register(
|
||||
user_profile,
|
||||
request.client,
|
||||
@@ -135,6 +136,22 @@ def build_page_params_for_home_page_load(
|
||||
narrow=narrow,
|
||||
include_streams=False,
|
||||
)
|
||||
else:
|
||||
# Since events for web_public_visitor is not implemented, we only fetch the data
|
||||
# at the time of request and don't register for any events.
|
||||
# TODO: Implement events for web_public_visitor.
|
||||
from zerver.lib.events import fetch_initial_state_data, post_process_state
|
||||
register_ret = fetch_initial_state_data(user_profile,
|
||||
event_types=None,
|
||||
queue_id=None,
|
||||
client_gravatar=False,
|
||||
user_avatar_url_field_optional=client_capabilities['user_avatar_url_field_optional'],
|
||||
realm=realm,
|
||||
slim_presence=False,
|
||||
include_subscribers=False,
|
||||
include_streams=False)
|
||||
|
||||
post_process_state(user_profile, register_ret, False)
|
||||
|
||||
furthest_read_time = get_furthest_read_time(user_profile)
|
||||
|
||||
@@ -179,6 +196,9 @@ def build_page_params_for_home_page_load(
|
||||
# 2FA is not enabled.
|
||||
two_fa_enabled_user=two_fa_enabled and bool(default_device(user_profile)),
|
||||
is_web_public_visitor=user_profile is None,
|
||||
# There is no event queue for web_public_visitors since
|
||||
# events support for web_public_visitors is not implemented yet.
|
||||
no_event_queue=user_profile is None,
|
||||
)
|
||||
|
||||
for field_name in register_ret.keys():
|
||||
|
||||
@@ -888,7 +888,7 @@ def extract_unread_data_from_um_rows(
|
||||
)
|
||||
|
||||
if user_profile is None:
|
||||
return raw_unread_messages # nocoverage
|
||||
return raw_unread_messages
|
||||
|
||||
muted_stream_ids = get_muted_stream_ids(user_profile)
|
||||
raw_unread_messages['muted_stream_ids'] = muted_stream_ids
|
||||
|
||||
@@ -464,6 +464,15 @@ Output:
|
||||
# not as a web-public visitor.
|
||||
self.assertEqual(page_params['is_web_public_visitor'], False)
|
||||
|
||||
def check_rendered_web_public_visitor(self, result: HttpResponse) -> None:
|
||||
"""Verifies that a visit of / was a 200 that rendered page_params
|
||||
for a logged-out web-public visitor."""
|
||||
self.assertEqual(result.status_code, 200)
|
||||
page_params = self._get_page_params(result)
|
||||
# It is important to check `is_web_public_visitor` to verify
|
||||
# that we treated this request to render for a `web_public_visitor`
|
||||
self.assertEqual(page_params['is_web_public_visitor'], True)
|
||||
|
||||
def login_with_return(self, email: str, password: Optional[str]=None,
|
||||
**kwargs: Any) -> HttpResponse:
|
||||
if password is None:
|
||||
|
||||
@@ -103,6 +103,7 @@ class HomeTest(ZulipTestCase):
|
||||
"narrow_stream",
|
||||
"needs_tutorial",
|
||||
"never_subscribed",
|
||||
"no_event_queue",
|
||||
"notification_sound",
|
||||
"password_min_guesses",
|
||||
"password_min_length",
|
||||
@@ -233,10 +234,6 @@ class HomeTest(ZulipTestCase):
|
||||
'data-params',
|
||||
]
|
||||
|
||||
# Verify fails if logged-out
|
||||
result = self.client_get('/')
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
self.login('hamlet')
|
||||
|
||||
# Create bot for realm_bots testing. Must be done before fetching home_page.
|
||||
@@ -290,6 +287,20 @@ class HomeTest(ZulipTestCase):
|
||||
realm_bots_actual_keys = sorted(str(key) for key in page_params['realm_bots'][0].keys())
|
||||
self.assertEqual(realm_bots_actual_keys, realm_bots_expected_keys)
|
||||
|
||||
def test_logged_out_home(self) -> None:
|
||||
result = self.client_get('/')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
page_params = self._get_page_params(result)
|
||||
actual_keys = sorted(str(k) for k in page_params.keys())
|
||||
removed_keys = [
|
||||
'last_event_id',
|
||||
'narrow',
|
||||
'narrow_stream',
|
||||
]
|
||||
expected_keys = [i for i in self.expected_page_params_keys if i not in removed_keys]
|
||||
self.assertEqual(actual_keys, expected_keys)
|
||||
|
||||
def test_home_under_2fa_without_otp_device(self) -> None:
|
||||
with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
|
||||
self.login('iago')
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestSessions(ZulipTestCase):
|
||||
action()
|
||||
if expected_result:
|
||||
result = self.client_get('/', subdomain=realm.subdomain)
|
||||
self.assertEqual('/login/', result.url)
|
||||
self.check_rendered_web_public_visitor(result)
|
||||
else:
|
||||
self.assertIn('_auth_user_id', self.client.session)
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestSessions(ZulipTestCase):
|
||||
for session in user_sessions(user_profile):
|
||||
delete_session(session)
|
||||
result = self.client_get("/")
|
||||
self.assertEqual('/login/', result.url)
|
||||
self.check_rendered_web_public_visitor(result)
|
||||
|
||||
def test_delete_user_sessions(self) -> None:
|
||||
user_profile = self.example_user('hamlet')
|
||||
@@ -75,7 +75,7 @@ class TestSessions(ZulipTestCase):
|
||||
self.client_post('/accounts/logout/')
|
||||
delete_all_deactivated_user_sessions()
|
||||
result = self.client_get("/")
|
||||
self.assertEqual('/login/', result.url)
|
||||
self.check_rendered_web_public_visitor(result)
|
||||
|
||||
# Test nothing happens to an active user's session
|
||||
self.login('othello')
|
||||
@@ -95,7 +95,7 @@ class TestSessions(ZulipTestCase):
|
||||
'INFO:root:Deactivating session for deactivated user 8'
|
||||
])
|
||||
result = self.client_get("/")
|
||||
self.assertEqual('/login/', result.url)
|
||||
self.check_rendered_web_public_visitor(result)
|
||||
|
||||
class TestExpirableSessionVars(ZulipTestCase):
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -35,8 +35,7 @@ class PublicURLTest(ZulipTestCase):
|
||||
get_urls = {200: ["/accounts/home/", "/accounts/login/",
|
||||
"/en/accounts/home/", "/ru/accounts/home/",
|
||||
"/en/accounts/login/", "/ru/accounts/login/",
|
||||
"/help/"],
|
||||
302: ["/", "/en/", "/ru/"],
|
||||
"/help/", "/", "/en/", "/ru/"],
|
||||
400: ["/json/messages",
|
||||
],
|
||||
401: [f"/json/streams/{denmark_stream_id}/members",
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.urls import reverse
|
||||
from django.utils.cache import patch_cache_control
|
||||
|
||||
from zerver.context_processors import get_valid_realm_from_request
|
||||
from zerver.decorator import zulip_login_required
|
||||
from zerver.decorator import web_public_view, zulip_login_required
|
||||
from zerver.forms import ToSForm
|
||||
from zerver.lib.actions import do_change_tos_version, realm_user_count
|
||||
from zerver.lib.home import (
|
||||
@@ -28,7 +28,7 @@ from zerver.views.portico import hello_view
|
||||
|
||||
|
||||
def need_accept_tos(user_profile: Optional[UserProfile]) -> bool:
|
||||
if user_profile is None: # nocoverage
|
||||
if user_profile is None:
|
||||
return False
|
||||
|
||||
if settings.TERMS_OF_SERVICE is None: # nocoverage
|
||||
@@ -67,7 +67,7 @@ def detect_narrowed_window(request: HttpRequest,
|
||||
Optional[str]]:
|
||||
"""This function implements Zulip's support for a mini Zulip window
|
||||
that just handles messages from a single narrow"""
|
||||
if user_profile is None: # nocoverage
|
||||
if user_profile is None:
|
||||
return [], None, None
|
||||
|
||||
narrow: List[List[str]] = []
|
||||
@@ -91,7 +91,7 @@ def update_last_reminder(user_profile: Optional[UserProfile]) -> None:
|
||||
"""Reset our don't-spam-users-with-email counter since the
|
||||
user has since logged in
|
||||
"""
|
||||
if user_profile is None: # nocoverage
|
||||
if user_profile is None:
|
||||
return
|
||||
|
||||
if user_profile.last_reminder is not None: # nocoverage
|
||||
@@ -120,7 +120,7 @@ def home(request: HttpRequest) -> HttpResponse:
|
||||
|
||||
return hello_view(request)
|
||||
|
||||
@zulip_login_required
|
||||
@web_public_view
|
||||
def home_real(request: HttpRequest) -> HttpResponse:
|
||||
# Before we do any real work, check if the app is banned.
|
||||
client_user_agent = request.META.get("HTTP_USER_AGENT", "")
|
||||
@@ -152,7 +152,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
||||
if request.user.is_authenticated:
|
||||
user_profile = request.user
|
||||
realm = user_profile.realm
|
||||
else: # nocoverage
|
||||
else:
|
||||
# user_profile=None corresponds to the logged-out "web_public" visitor case.
|
||||
user_profile = None
|
||||
realm = get_valid_realm_from_request(request)
|
||||
@@ -177,7 +177,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
||||
)
|
||||
needs_tutorial = user_profile.tutorial_status == UserProfile.TUTORIAL_WAITING
|
||||
|
||||
else: # nocoverage
|
||||
else:
|
||||
first_in_realm = False
|
||||
prompt_for_invites = False
|
||||
# The current tutorial doesn't super make sense for logged-out users.
|
||||
|
||||
Reference in New Issue
Block a user