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:
Aman Agrawal
2020-09-27 10:19:16 +05:30
committed by Tim Abbott
parent 13f95dfc2b
commit 87cdd8433d
11 changed files with 103 additions and 36 deletions

View File

@@ -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");

View File

@@ -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',

View File

@@ -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

View File

@@ -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'):

View File

@@ -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():

View File

@@ -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

View File

@@ -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:

View File

@@ -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')

View File

@@ -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:

View File

@@ -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",

View File

@@ -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.