mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	This commit fixes a bug which occurs when a user tries to bypass the correct registration url for the completely open domains.
		
			
				
	
	
		
			714 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			714 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding: utf-8 -*-
 | 
						|
from __future__ import absolute_import
 | 
						|
from typing import Any, List, Dict, Optional
 | 
						|
 | 
						|
from django.utils import translation
 | 
						|
from django.utils.translation import ugettext as _
 | 
						|
from django.conf import settings
 | 
						|
from django.contrib.auth import authenticate, login, get_backends
 | 
						|
from django.core.urlresolvers import reverse
 | 
						|
from django.http import HttpResponseRedirect, HttpResponseForbidden, HttpResponse, HttpRequest
 | 
						|
from django.shortcuts import redirect
 | 
						|
from django.template import RequestContext, loader
 | 
						|
from django.utils.timezone import now
 | 
						|
from django.utils.cache import patch_cache_control
 | 
						|
from django.core.exceptions import ValidationError
 | 
						|
from django.core import validators
 | 
						|
from django.core.mail import send_mail
 | 
						|
from zerver.models import Message, UserProfile, Stream, Subscription, Huddle, \
 | 
						|
    Recipient, Realm, UserMessage, DefaultStream, RealmEmoji, RealmAlias, \
 | 
						|
    RealmFilter, \
 | 
						|
    PreregistrationUser, get_client, UserActivity, \
 | 
						|
    get_stream, UserPresence, get_recipient, name_changes_disabled, \
 | 
						|
    split_email_to_domain, resolve_email_to_domain, email_to_username, get_realm, \
 | 
						|
    completely_open, get_unique_open_realm, email_allowed_for_realm, \
 | 
						|
    get_cross_realm_users, resolve_subdomain_to_realm, list_of_domains_for_realm
 | 
						|
from zerver.lib.actions import do_change_password, do_change_full_name, do_change_is_admin, \
 | 
						|
    do_activate_user, do_create_user, do_create_realm, set_default_streams, \
 | 
						|
    update_user_presence, do_events_register, \
 | 
						|
    do_change_tos_version, \
 | 
						|
    user_email_is_unique, \
 | 
						|
    compute_mit_user_fullname, do_set_muted_topics, \
 | 
						|
    do_update_pointer, realm_user_count
 | 
						|
from zerver.lib.push_notifications import num_push_devices_for_user
 | 
						|
from zerver.forms import RegistrationForm, HomepageForm, RealmCreationForm, ToSForm, \
 | 
						|
    CreateUserForm
 | 
						|
from zerver.lib.actions import is_inactive
 | 
						|
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
 | 
						|
from zerver.lib.validator import check_string, check_list
 | 
						|
from zerver.decorator import require_post, authenticated_json_post_view, \
 | 
						|
    has_request_variables, \
 | 
						|
    JsonableError, get_user_profile_by_email, REQ, \
 | 
						|
    zulip_login_required
 | 
						|
from zerver.lib.avatar import avatar_url
 | 
						|
from zerver.lib.i18n import get_language_list, get_language_name, \
 | 
						|
    get_language_list_for_templates
 | 
						|
from zerver.lib.response import json_success, json_error
 | 
						|
from zerver.lib.utils import statsd, get_subdomain
 | 
						|
from version import ZULIP_VERSION
 | 
						|
from zproject.backends import password_auth_enabled
 | 
						|
 | 
						|
from confirmation.models import Confirmation, RealmCreationKey, check_key_is_valid
 | 
						|
 | 
						|
import requests
 | 
						|
import ujson
 | 
						|
 | 
						|
import calendar
 | 
						|
import datetime
 | 
						|
import simplejson
 | 
						|
import re
 | 
						|
from six import text_type
 | 
						|
from six.moves import urllib, zip_longest, zip, range
 | 
						|
import time
 | 
						|
import logging
 | 
						|
 | 
						|
from zproject.jinja2 import render_to_response
 | 
						|
 | 
						|
def redirect_and_log_into_subdomain(realm, full_name, email_address):
 | 
						|
    # type: (Realm, text_type, text_type) -> HttpResponse
 | 
						|
    subdomain_login_uri = ''.join([
 | 
						|
        realm.uri,
 | 
						|
        reverse('zerver.views.auth.log_into_subdomain')
 | 
						|
    ])
 | 
						|
 | 
						|
    domain = '.' + settings.EXTERNAL_HOST.split(':')[0]
 | 
						|
    response = redirect(subdomain_login_uri)
 | 
						|
 | 
						|
    data = {'name': full_name, 'email': email_address, 'subdomain': realm.subdomain}
 | 
						|
    # Creating a singed cookie so that it cannot be tampered with.
 | 
						|
    # Cookie and the signature expire in 15 seconds.
 | 
						|
    response.set_signed_cookie('subdomain.signature',
 | 
						|
                               ujson.dumps(data),
 | 
						|
                               expires=15,
 | 
						|
                               domain=domain,
 | 
						|
                               salt='zerver.views.auth')
 | 
						|
    return response
 | 
						|
 | 
						|
@require_post
 | 
						|
def accounts_register(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    key = request.POST['key']
 | 
						|
    confirmation = Confirmation.objects.get(confirmation_key=key)
 | 
						|
    prereg_user = confirmation.content_object
 | 
						|
    email = prereg_user.email
 | 
						|
    realm_creation = prereg_user.realm_creation
 | 
						|
    try:
 | 
						|
        existing_user_profile = get_user_profile_by_email(email)
 | 
						|
    except UserProfile.DoesNotExist:
 | 
						|
        existing_user_profile = None
 | 
						|
 | 
						|
    validators.validate_email(email)
 | 
						|
    # If OPEN_REALM_CREATION is enabled all user sign ups should go through the
 | 
						|
    # special URL with domain name so that REALM can be identified if multiple realms exist
 | 
						|
    unique_open_realm = get_unique_open_realm()
 | 
						|
    if unique_open_realm is not None:
 | 
						|
        realm = unique_open_realm
 | 
						|
        domain = realm.domain
 | 
						|
    elif prereg_user.referred_by:
 | 
						|
        # If someone invited you, you are joining their realm regardless
 | 
						|
        # of your e-mail address.
 | 
						|
        realm = prereg_user.referred_by.realm
 | 
						|
        domain = realm.domain
 | 
						|
        if not email_allowed_for_realm(email, realm):
 | 
						|
            return render_to_response("zerver/closed_realm.html", {"closed_domain_name": realm.name})
 | 
						|
    elif prereg_user.realm:
 | 
						|
        # You have a realm set, even though nobody referred you. This
 | 
						|
        # happens if you sign up through a special URL for an open
 | 
						|
        # realm.
 | 
						|
        domain = prereg_user.realm.domain
 | 
						|
        realm = get_realm(domain)
 | 
						|
    else:
 | 
						|
        subdomain_realm = resolve_subdomain_to_realm(get_subdomain(request))
 | 
						|
        domain = resolve_email_to_domain(email)
 | 
						|
        domain = subdomain_realm.domain if subdomain_realm else domain
 | 
						|
        if (not realm_creation and completely_open(domain)):
 | 
						|
            # When subdomains are enabled and the user is registering into a
 | 
						|
            # completely open subdomain without going through the correct url
 | 
						|
            # for the completely open domains.
 | 
						|
            # NOTE: When the user comes through the correct url then
 | 
						|
            # `prereg_user.realm` will have the correct value and this branch
 | 
						|
            # will not run.
 | 
						|
            path = reverse('zerver.views.accounts_home_with_domain',
 | 
						|
                           kwargs={'domain': subdomain_realm.domain})
 | 
						|
            ctx = {"link": "%s%s" % (subdomain_realm.uri, path)}
 | 
						|
            return render_to_response("zerver/completely_open_link.html", ctx)
 | 
						|
        else:
 | 
						|
            realm = get_realm(domain)
 | 
						|
 | 
						|
 | 
						|
    if realm and realm.deactivated:
 | 
						|
        # The user is trying to register for a deactivated realm. Advise them to
 | 
						|
        # contact support.
 | 
						|
        return render_to_response("zerver/deactivated.html",
 | 
						|
                                  {"deactivated_domain_name": realm.name,
 | 
						|
                                   "zulip_administrator": settings.ZULIP_ADMINISTRATOR})
 | 
						|
 | 
						|
    try:
 | 
						|
        if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
 | 
						|
            # Mirror dummy users to be activated must be inactive
 | 
						|
            is_inactive(email)
 | 
						|
        else:
 | 
						|
            # Other users should not already exist at all.
 | 
						|
            user_email_is_unique(email)
 | 
						|
    except ValidationError:
 | 
						|
        return HttpResponseRedirect(reverse('django.contrib.auth.views.login') + '?email=' +
 | 
						|
                                    urllib.parse.quote_plus(email))
 | 
						|
 | 
						|
    name_validated = False
 | 
						|
    full_name = None
 | 
						|
 | 
						|
    if request.POST.get('from_confirmation'):
 | 
						|
        try:
 | 
						|
            del request.session['authenticated_full_name']
 | 
						|
        except KeyError:
 | 
						|
            pass
 | 
						|
        if realm is not None and realm.is_zephyr_mirror_realm and domain == "mit.edu":
 | 
						|
            # for MIT users, we can get an authoritative name from Hesiod
 | 
						|
            hesiod_name = compute_mit_user_fullname(email)
 | 
						|
            form = RegistrationForm(
 | 
						|
                    initial={'full_name': hesiod_name if "@" not in hesiod_name else ""})
 | 
						|
            name_validated = True
 | 
						|
        elif settings.POPULATE_PROFILE_VIA_LDAP:
 | 
						|
            for backend in get_backends():
 | 
						|
                if isinstance(backend, LDAPBackend):
 | 
						|
                    ldap_attrs = _LDAPUser(backend, backend.django_to_ldap_username(email)).attrs
 | 
						|
                    try:
 | 
						|
                        ldap_full_name = ldap_attrs[settings.AUTH_LDAP_USER_ATTR_MAP['full_name']][0]
 | 
						|
                        request.session['authenticated_full_name'] = ldap_full_name
 | 
						|
                        name_validated = True
 | 
						|
                        # We don't use initial= here, because if the form is
 | 
						|
                        # complete (that is, no additional fields need to be
 | 
						|
                        # filled out by the user) we want the form to validate,
 | 
						|
                        # so they can be directly registered without having to
 | 
						|
                        # go through this interstitial.
 | 
						|
                        form = RegistrationForm({'full_name': ldap_full_name})
 | 
						|
                        # FIXME: This will result in the user getting
 | 
						|
                        # validation errors if they have to enter a password.
 | 
						|
                        # Not relevant for ONLY_SSO, though.
 | 
						|
                        break
 | 
						|
                    except TypeError:
 | 
						|
                        # Let the user fill out a name and/or try another backend
 | 
						|
                        form = RegistrationForm()
 | 
						|
        elif 'full_name' in request.POST:
 | 
						|
            form = RegistrationForm(
 | 
						|
                initial={'full_name': request.POST.get('full_name')}
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            form = RegistrationForm()
 | 
						|
    else:
 | 
						|
        postdata = request.POST.copy()
 | 
						|
        if name_changes_disabled(realm):
 | 
						|
            # If we populate profile information via LDAP and we have a
 | 
						|
            # verified name from you on file, use that. Otherwise, fall
 | 
						|
            # back to the full name in the request.
 | 
						|
            try:
 | 
						|
                postdata.update({'full_name': request.session['authenticated_full_name']})
 | 
						|
                name_validated = True
 | 
						|
            except KeyError:
 | 
						|
                pass
 | 
						|
        form = RegistrationForm(postdata)
 | 
						|
        if not password_auth_enabled(realm):
 | 
						|
            form['password'].field.required = False
 | 
						|
 | 
						|
    if form.is_valid():
 | 
						|
        if password_auth_enabled(realm):
 | 
						|
            password = form.cleaned_data['password']
 | 
						|
        else:
 | 
						|
            # SSO users don't need no passwords
 | 
						|
            password = None
 | 
						|
 | 
						|
        if realm_creation:
 | 
						|
            domain = split_email_to_domain(email)
 | 
						|
            realm_name = form.cleaned_data['realm_name']
 | 
						|
            org_type = int(form.cleaned_data['realm_org_type'])
 | 
						|
            if settings.REALMS_HAVE_SUBDOMAINS:
 | 
						|
                realm = do_create_realm(domain, realm_name, org_type=org_type,
 | 
						|
                                        subdomain=form.cleaned_data['realm_subdomain'])[0]
 | 
						|
            else:
 | 
						|
                realm = do_create_realm(domain, realm_name, org_type=org_type)[0]
 | 
						|
 | 
						|
            set_default_streams(realm, settings.DEFAULT_NEW_REALM_STREAMS)
 | 
						|
 | 
						|
        full_name = form.cleaned_data['full_name']
 | 
						|
        short_name = email_to_username(email)
 | 
						|
        first_in_realm = len(UserProfile.objects.filter(realm=realm, is_bot=False)) == 0
 | 
						|
 | 
						|
        # FIXME: sanitize email addresses and fullname
 | 
						|
        if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
 | 
						|
            try:
 | 
						|
                user_profile = existing_user_profile
 | 
						|
                do_activate_user(user_profile)
 | 
						|
                do_change_password(user_profile, password)
 | 
						|
                do_change_full_name(user_profile, full_name)
 | 
						|
            except UserProfile.DoesNotExist:
 | 
						|
                user_profile = do_create_user(email, password, realm, full_name, short_name,
 | 
						|
                                              prereg_user=prereg_user,
 | 
						|
                                              tos_version=settings.TOS_VERSION,
 | 
						|
                                              newsletter_data={"IP": request.META['REMOTE_ADDR']})
 | 
						|
        else:
 | 
						|
            user_profile = do_create_user(email, password, realm, full_name, short_name,
 | 
						|
                                          prereg_user=prereg_user,
 | 
						|
                                          tos_version=settings.TOS_VERSION,
 | 
						|
                                          newsletter_data={"IP": request.META['REMOTE_ADDR']})
 | 
						|
 | 
						|
        if first_in_realm:
 | 
						|
            do_change_is_admin(user_profile, True)
 | 
						|
 | 
						|
        if realm_creation and settings.REALMS_HAVE_SUBDOMAINS:
 | 
						|
            # Because for realm creation, registration happens on the
 | 
						|
            # root domain, we need to log them into the subdomain for
 | 
						|
            # their new realm.
 | 
						|
            return redirect_and_log_into_subdomain(realm, full_name, email)
 | 
						|
 | 
						|
        # This dummy_backend check below confirms the user is
 | 
						|
        # authenticating to the correct subdomain.
 | 
						|
        return_data = {} # type: Dict[str, bool]
 | 
						|
        auth_result = authenticate(username=user_profile.email,
 | 
						|
                                   realm_subdomain=realm.subdomain,
 | 
						|
                                   return_data=return_data,
 | 
						|
                                   use_dummy_backend=True)
 | 
						|
        if return_data.get('invalid_subdomain'):
 | 
						|
            # By construction, this should never happen.
 | 
						|
            logging.error("Subdomain mismatch in registration %s: %s" % (
 | 
						|
                realm.subdomain, user_profile.email,))
 | 
						|
            return redirect('/')
 | 
						|
        login(request, auth_result)
 | 
						|
        return HttpResponseRedirect(realm.uri + reverse('zerver.views.home'))
 | 
						|
 | 
						|
    return render_to_response('zerver/register.html',
 | 
						|
            {'form': form,
 | 
						|
             'company_name': domain,
 | 
						|
             'email': email,
 | 
						|
             'key': key,
 | 
						|
             'full_name': request.session.get('authenticated_full_name', None),
 | 
						|
             'lock_name': name_validated and name_changes_disabled(realm),
 | 
						|
             # password_auth_enabled is normally set via our context processor,
 | 
						|
             # but for the registration form, there is no logged in user yet, so
 | 
						|
             # we have to set it here.
 | 
						|
             'creating_new_team': realm_creation,
 | 
						|
             'realms_have_subdomains': settings.REALMS_HAVE_SUBDOMAINS,
 | 
						|
             'password_auth_enabled': password_auth_enabled(realm),
 | 
						|
            },
 | 
						|
        request=request)
 | 
						|
 | 
						|
@zulip_login_required
 | 
						|
def accounts_accept_terms(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    if request.method == "POST":
 | 
						|
        form = ToSForm(request.POST)
 | 
						|
        if form.is_valid():
 | 
						|
            do_change_tos_version(request.user, settings.TOS_VERSION)
 | 
						|
            return redirect(home)
 | 
						|
    else:
 | 
						|
        form = ToSForm()
 | 
						|
 | 
						|
    email = request.user.email
 | 
						|
    domain = resolve_email_to_domain(email)
 | 
						|
    special_message_template = None
 | 
						|
    if request.user.tos_version is None and settings.FIRST_TIME_TOS_TEMPLATE is not None:
 | 
						|
        special_message_template = 'zerver/' + settings.FIRST_TIME_TOS_TEMPLATE
 | 
						|
    return render_to_response('zerver/accounts_accept_terms.html',
 | 
						|
        { 'form': form, 'company_name': domain, 'email': email, \
 | 
						|
          'special_message_template' : special_message_template },
 | 
						|
        request=request)
 | 
						|
 | 
						|
def create_homepage_form(request, user_info=None):
 | 
						|
    # type: (HttpRequest, Optional[Dict[str, Any]]) -> HomepageForm
 | 
						|
    if user_info:
 | 
						|
        return HomepageForm(user_info, domain=request.session.get("domain"),
 | 
						|
                            subdomain=get_subdomain(request))
 | 
						|
    # An empty fields dict is not treated the same way as not
 | 
						|
    # providing it.
 | 
						|
    return HomepageForm(domain=request.session.get("domain"), subdomain=get_subdomain(request))
 | 
						|
 | 
						|
def create_preregistration_user(email, request, realm_creation=False):
 | 
						|
    # type: (text_type, HttpRequest, bool) -> HttpResponse
 | 
						|
    domain = request.session.get("domain")
 | 
						|
    if completely_open(domain):
 | 
						|
        # Clear the "domain" from the session object; it's no longer needed
 | 
						|
        request.session["domain"] = None
 | 
						|
 | 
						|
        # The user is trying to sign up for a completely open realm,
 | 
						|
        # so create them a PreregistrationUser for that realm
 | 
						|
        return PreregistrationUser.objects.create(email=email,
 | 
						|
                                                  realm=get_realm(domain),
 | 
						|
                                                  realm_creation=realm_creation)
 | 
						|
 | 
						|
    return PreregistrationUser.objects.create(email=email, realm_creation=realm_creation)
 | 
						|
 | 
						|
def accounts_home_with_domain(request, domain):
 | 
						|
    # type: (HttpRequest, str) -> HttpResponse
 | 
						|
    if completely_open(domain):
 | 
						|
        # You can sign up for a completely open realm through a
 | 
						|
        # special registration path that contains the domain in the
 | 
						|
        # URL. We store this information in the session rather than
 | 
						|
        # elsewhere because we don't have control over URL or form
 | 
						|
        # data for folks registering through OpenID.
 | 
						|
        request.session["domain"] = domain
 | 
						|
        return accounts_home(request)
 | 
						|
    else:
 | 
						|
        return HttpResponseRedirect(reverse('zerver.views.accounts_home'))
 | 
						|
 | 
						|
def send_registration_completion_email(email, request, realm_creation=False):
 | 
						|
    # type: (str, HttpRequest, bool) -> Confirmation
 | 
						|
    """
 | 
						|
    Send an email with a confirmation link to the provided e-mail so the user
 | 
						|
    can complete their registration.
 | 
						|
    """
 | 
						|
    prereg_user = create_preregistration_user(email, request, realm_creation)
 | 
						|
    context = {'support_email': settings.ZULIP_ADMINISTRATOR,
 | 
						|
               'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS}
 | 
						|
    return Confirmation.objects.send_confirmation(prereg_user, email,
 | 
						|
                                                  additional_context=context,
 | 
						|
                                                  host=request.get_host())
 | 
						|
 | 
						|
def redirect_to_email_login_url(email):
 | 
						|
    # type: (str) -> HttpResponseRedirect
 | 
						|
    login_url = reverse('django.contrib.auth.views.login')
 | 
						|
    redirect_url = login_url + '?email=' + urllib.parse.quote_plus(email)
 | 
						|
    return HttpResponseRedirect(redirect_url)
 | 
						|
 | 
						|
"""
 | 
						|
When settings.OPEN_REALM_CREATION is enabled public users can create new realm. For creating the realm the user should
 | 
						|
not be the member of any current realm. The realm is created with domain same as the that of the user's email.
 | 
						|
When there is no unique_open_realm user registrations are made by visiting /register/domain_of_the_realm.
 | 
						|
"""
 | 
						|
def create_realm(request, creation_key=None):
 | 
						|
    # type: (HttpRequest, Optional[text_type]) -> HttpResponse
 | 
						|
    if not settings.OPEN_REALM_CREATION:
 | 
						|
        if creation_key is None:
 | 
						|
            return render_to_response("zerver/realm_creation_failed.html",
 | 
						|
                                      {'message': _('New organization creation disabled.')})
 | 
						|
        elif not check_key_is_valid(creation_key):
 | 
						|
            return render_to_response("zerver/realm_creation_failed.html",
 | 
						|
                                      {'message': _('The organization creation link has been expired'
 | 
						|
                                                    ' or is not valid.')})
 | 
						|
 | 
						|
    if request.method == 'POST':
 | 
						|
        form = RealmCreationForm(request.POST, domain=request.session.get("domain"))
 | 
						|
        if form.is_valid():
 | 
						|
            email = form.cleaned_data['email']
 | 
						|
            confirmation_key = send_registration_completion_email(email, request, realm_creation=True).confirmation_key
 | 
						|
            if settings.DEVELOPMENT:
 | 
						|
                 request.session['confirmation_key'] = {'confirmation_key': confirmation_key}
 | 
						|
            if (creation_key is not None and check_key_is_valid(creation_key)):
 | 
						|
                RealmCreationKey.objects.get(creation_key=creation_key).delete()
 | 
						|
            return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))
 | 
						|
        try:
 | 
						|
            email = request.POST['email']
 | 
						|
            user_email_is_unique(email)
 | 
						|
        except ValidationError:
 | 
						|
            # if the user user is already registered he can't create a new realm as a realm
 | 
						|
            # with the same domain as user's email already exists
 | 
						|
            return redirect_to_email_login_url(email)
 | 
						|
    else:
 | 
						|
        form = RealmCreationForm(domain=request.session.get("domain"))
 | 
						|
    return render_to_response('zerver/create_realm.html',
 | 
						|
                              {'form': form, 'current_url': request.get_full_path},
 | 
						|
                              request=request)
 | 
						|
 | 
						|
def confirmation_key(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    return json_success(request.session.get('confirmation_key'))
 | 
						|
 | 
						|
def accounts_home(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    if request.method == 'POST':
 | 
						|
        form = create_homepage_form(request, user_info=request.POST)
 | 
						|
        if form.is_valid():
 | 
						|
            email = form.cleaned_data['email']
 | 
						|
            send_registration_completion_email(email, request)
 | 
						|
            return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))
 | 
						|
        try:
 | 
						|
            email = request.POST['email']
 | 
						|
            # Note: We don't check for uniqueness
 | 
						|
            is_inactive(email)
 | 
						|
        except ValidationError:
 | 
						|
            return redirect_to_email_login_url(email)
 | 
						|
    else:
 | 
						|
        form = create_homepage_form(request)
 | 
						|
    return render_to_response('zerver/accounts_home.html',
 | 
						|
                              {'form': form, 'current_url': request.get_full_path},
 | 
						|
                              request=request)
 | 
						|
 | 
						|
def approximate_unread_count(user_profile):
 | 
						|
    # type: (UserProfile) -> int
 | 
						|
    not_in_home_view_recipients = [sub.recipient.id for sub in \
 | 
						|
                                       Subscription.objects.filter(
 | 
						|
            user_profile=user_profile, in_home_view=False)]
 | 
						|
 | 
						|
    # TODO: We may want to exclude muted messages from this count.
 | 
						|
    #       It was attempted in the past, but the original attempt
 | 
						|
    #       was broken.  When we re-architect muting, we may
 | 
						|
    #       want to to revisit this (see git issue #1019).
 | 
						|
    return UserMessage.objects.filter(
 | 
						|
        user_profile=user_profile, message_id__gt=user_profile.pointer).exclude(
 | 
						|
        message__recipient__type=Recipient.STREAM,
 | 
						|
        message__recipient__id__in=not_in_home_view_recipients).exclude(
 | 
						|
        flags=UserMessage.flags.read).count()
 | 
						|
 | 
						|
def sent_time_in_epoch_seconds(user_message):
 | 
						|
    # type: (UserMessage) -> float
 | 
						|
    # user_message is a UserMessage object.
 | 
						|
    if not user_message:
 | 
						|
        return None
 | 
						|
    # We have USE_TZ = True, so our datetime objects are timezone-aware.
 | 
						|
    # Return the epoch seconds in UTC.
 | 
						|
    return calendar.timegm(user_message.message.pub_date.utctimetuple())
 | 
						|
 | 
						|
@zulip_login_required
 | 
						|
def home(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    # We need to modify the session object every two weeks or it will expire.
 | 
						|
    # This line makes reloading the page a sufficient action to keep the
 | 
						|
    # session alive.
 | 
						|
    request.session.modified = True
 | 
						|
 | 
						|
    user_profile = request.user
 | 
						|
    request._email = request.user.email
 | 
						|
    request.client = get_client("website")
 | 
						|
 | 
						|
    # If a user hasn't signed the current Terms of Service, send them there
 | 
						|
    if settings.TERMS_OF_SERVICE is not None and settings.TOS_VERSION is not None and \
 | 
						|
       int(settings.TOS_VERSION.split('.')[0]) > user_profile.major_tos_version():
 | 
						|
        return accounts_accept_terms(request)
 | 
						|
 | 
						|
    narrow = [] # type: List[List[text_type]]
 | 
						|
    narrow_stream = None
 | 
						|
    narrow_topic = request.GET.get("topic")
 | 
						|
    if request.GET.get("stream"):
 | 
						|
        try:
 | 
						|
            narrow_stream = get_stream(request.GET.get("stream"), user_profile.realm)
 | 
						|
            assert(narrow_stream is not None)
 | 
						|
            assert(narrow_stream.is_public())
 | 
						|
            narrow = [["stream", narrow_stream.name]]
 | 
						|
        except Exception:
 | 
						|
            logging.exception("Narrow parsing")
 | 
						|
        if narrow_topic is not None:
 | 
						|
            narrow.append(["topic", narrow_topic])
 | 
						|
 | 
						|
    register_ret = do_events_register(user_profile, request.client,
 | 
						|
                                      apply_markdown=True, narrow=narrow)
 | 
						|
    user_has_messages = (register_ret['max_message_id'] != -1)
 | 
						|
 | 
						|
    # Reset our don't-spam-users-with-email counter since the
 | 
						|
    # user has since logged in
 | 
						|
    if not user_profile.last_reminder is None:
 | 
						|
        user_profile.last_reminder = None
 | 
						|
        user_profile.save(update_fields=["last_reminder"])
 | 
						|
 | 
						|
    # Brand new users get the tutorial
 | 
						|
    needs_tutorial = settings.TUTORIAL_ENABLED and \
 | 
						|
        user_profile.tutorial_status != UserProfile.TUTORIAL_FINISHED
 | 
						|
 | 
						|
    first_in_realm = realm_user_count(user_profile.realm) == 1
 | 
						|
    # If you are the only person in the realm and you didn't invite
 | 
						|
    # anyone, we'll continue to encourage you to do so on the frontend.
 | 
						|
    prompt_for_invites = first_in_realm and \
 | 
						|
        not PreregistrationUser.objects.filter(referred_by=user_profile).count()
 | 
						|
 | 
						|
    if user_profile.pointer == -1 and user_has_messages:
 | 
						|
        # Put the new user's pointer at the bottom
 | 
						|
        #
 | 
						|
        # This improves performance, because we limit backfilling of messages
 | 
						|
        # before the pointer.  It's also likely that someone joining an
 | 
						|
        # organization is interested in recent messages more than the very
 | 
						|
        # first messages on the system.
 | 
						|
 | 
						|
        register_ret['pointer'] = register_ret['max_message_id']
 | 
						|
        user_profile.last_pointer_updater = request.session.session_key
 | 
						|
 | 
						|
    if user_profile.pointer == -1:
 | 
						|
        latest_read = None
 | 
						|
    else:
 | 
						|
        try:
 | 
						|
            latest_read = UserMessage.objects.get(user_profile=user_profile,
 | 
						|
                                                  message__id=user_profile.pointer)
 | 
						|
        except UserMessage.DoesNotExist:
 | 
						|
            # Don't completely fail if your saved pointer ID is invalid
 | 
						|
            logging.warning("%s has invalid pointer %s" % (user_profile.email, user_profile.pointer))
 | 
						|
            latest_read = None
 | 
						|
 | 
						|
    desktop_notifications_enabled = user_profile.enable_desktop_notifications
 | 
						|
    if narrow_stream is not None:
 | 
						|
        desktop_notifications_enabled = False
 | 
						|
 | 
						|
    if user_profile.realm.notifications_stream:
 | 
						|
        notifications_stream = user_profile.realm.notifications_stream.name
 | 
						|
    else:
 | 
						|
        notifications_stream = ""
 | 
						|
 | 
						|
    # Set default language and make it persist
 | 
						|
    default_language = register_ret['default_language']
 | 
						|
    url_lang = '/{}'.format(request.LANGUAGE_CODE)
 | 
						|
    if not request.path.startswith(url_lang):
 | 
						|
        translation.activate(default_language)
 | 
						|
 | 
						|
    request.session[translation.LANGUAGE_SESSION_KEY] = default_language
 | 
						|
 | 
						|
    # Pass parameters to the client-side JavaScript code.
 | 
						|
    # These end up in a global JavaScript Object named 'page_params'.
 | 
						|
    page_params = dict(
 | 
						|
        zulip_version         = ZULIP_VERSION,
 | 
						|
        share_the_love        = settings.SHARE_THE_LOVE,
 | 
						|
        development_environment = settings.DEVELOPMENT,
 | 
						|
        debug_mode            = settings.DEBUG,
 | 
						|
        test_suite            = settings.TEST_SUITE,
 | 
						|
        poll_timeout          = settings.POLL_TIMEOUT,
 | 
						|
        login_page            = settings.HOME_NOT_LOGGED_IN,
 | 
						|
        server_uri            = settings.SERVER_URI,
 | 
						|
        realm_uri             = user_profile.realm.uri,
 | 
						|
        maxfilesize           = settings.MAX_FILE_UPLOAD_SIZE,
 | 
						|
        server_generation     = settings.SERVER_GENERATION,
 | 
						|
        password_auth_enabled = password_auth_enabled(user_profile.realm),
 | 
						|
        have_initial_messages = user_has_messages,
 | 
						|
        subbed_info           = register_ret['subscriptions'],
 | 
						|
        unsubbed_info         = register_ret['unsubscribed'],
 | 
						|
        neversubbed_info      = register_ret['never_subscribed'],
 | 
						|
        email_dict            = register_ret['email_dict'],
 | 
						|
        people_list           = register_ret['realm_users'],
 | 
						|
        bot_list              = register_ret['realm_bots'],
 | 
						|
        initial_pointer       = register_ret['pointer'],
 | 
						|
        initial_presences     = register_ret['presences'],
 | 
						|
        initial_servertime    = time.time(), # Used for calculating relative presence age
 | 
						|
        fullname              = user_profile.full_name,
 | 
						|
        email                 = user_profile.email,
 | 
						|
        domain                = user_profile.realm.domain,
 | 
						|
        domains               = list_of_domains_for_realm(user_profile.realm),
 | 
						|
        realm_name            = register_ret['realm_name'],
 | 
						|
        realm_invite_required = register_ret['realm_invite_required'],
 | 
						|
        realm_invite_by_admins_only = register_ret['realm_invite_by_admins_only'],
 | 
						|
        realm_create_stream_by_admins_only = register_ret['realm_create_stream_by_admins_only'],
 | 
						|
        realm_allow_message_editing = register_ret['realm_allow_message_editing'],
 | 
						|
        realm_message_content_edit_limit_seconds = register_ret['realm_message_content_edit_limit_seconds'],
 | 
						|
        realm_restricted_to_domain = register_ret['realm_restricted_to_domain'],
 | 
						|
        realm_default_language = register_ret['realm_default_language'],
 | 
						|
        enter_sends           = user_profile.enter_sends,
 | 
						|
        user_id               = user_profile.id,
 | 
						|
        left_side_userlist    = register_ret['left_side_userlist'],
 | 
						|
        default_language      = register_ret['default_language'],
 | 
						|
        default_language_name = get_language_name(register_ret['default_language']),
 | 
						|
        language_list_dbl_col = get_language_list_for_templates(register_ret['default_language']),
 | 
						|
        language_list         = get_language_list(),
 | 
						|
        referrals             = register_ret['referrals'],
 | 
						|
        realm_emoji           = register_ret['realm_emoji'],
 | 
						|
        needs_tutorial        = needs_tutorial,
 | 
						|
        first_in_realm        = first_in_realm,
 | 
						|
        prompt_for_invites    = prompt_for_invites,
 | 
						|
        notifications_stream  = notifications_stream,
 | 
						|
        cross_realm_user_emails = list(get_cross_realm_users()),
 | 
						|
 | 
						|
        # Stream message notification settings:
 | 
						|
        stream_desktop_notifications_enabled =
 | 
						|
            user_profile.enable_stream_desktop_notifications,
 | 
						|
        stream_sounds_enabled = user_profile.enable_stream_sounds,
 | 
						|
 | 
						|
        # Private message and @-mention notification settings:
 | 
						|
        desktop_notifications_enabled = desktop_notifications_enabled,
 | 
						|
        sounds_enabled =
 | 
						|
            user_profile.enable_sounds,
 | 
						|
        enable_offline_email_notifications =
 | 
						|
            user_profile.enable_offline_email_notifications,
 | 
						|
        enable_offline_push_notifications =
 | 
						|
            user_profile.enable_offline_push_notifications,
 | 
						|
        enable_online_push_notifications =
 | 
						|
            user_profile.enable_online_push_notifications,
 | 
						|
        twenty_four_hour_time = register_ret['twenty_four_hour_time'],
 | 
						|
 | 
						|
        enable_digest_emails  = user_profile.enable_digest_emails,
 | 
						|
        event_queue_id        = register_ret['queue_id'],
 | 
						|
        last_event_id         = register_ret['last_event_id'],
 | 
						|
        max_message_id        = register_ret['max_message_id'],
 | 
						|
        unread_count          = approximate_unread_count(user_profile),
 | 
						|
        furthest_read_time    = sent_time_in_epoch_seconds(latest_read),
 | 
						|
        save_stacktraces      = settings.SAVE_FRONTEND_STACKTRACES,
 | 
						|
        alert_words           = register_ret['alert_words'],
 | 
						|
        muted_topics          = register_ret['muted_topics'],
 | 
						|
        realm_filters         = register_ret['realm_filters'],
 | 
						|
        realm_default_streams = register_ret['realm_default_streams'],
 | 
						|
        is_admin              = user_profile.is_realm_admin,
 | 
						|
        can_create_streams    = user_profile.can_create_streams(),
 | 
						|
        name_changes_disabled = name_changes_disabled(user_profile.realm),
 | 
						|
        has_mobile_devices    = num_push_devices_for_user(user_profile) > 0,
 | 
						|
        autoscroll_forever = user_profile.autoscroll_forever,
 | 
						|
        default_desktop_notifications = user_profile.default_desktop_notifications,
 | 
						|
        avatar_url            = avatar_url(user_profile),
 | 
						|
        avatar_url_medium     = avatar_url(user_profile, medium=True),
 | 
						|
        mandatory_topics      = user_profile.realm.mandatory_topics,
 | 
						|
        show_digest_email     = user_profile.realm.show_digest_email,
 | 
						|
        presence_disabled     = user_profile.realm.presence_disabled,
 | 
						|
        is_zephyr_mirror_realm = user_profile.realm.is_zephyr_mirror_realm,
 | 
						|
    )
 | 
						|
 | 
						|
    if narrow_stream is not None:
 | 
						|
        # In narrow_stream context, initial pointer is just latest message
 | 
						|
        recipient = get_recipient(Recipient.STREAM, narrow_stream.id)
 | 
						|
        try:
 | 
						|
            initial_pointer = Message.objects.filter(recipient=recipient).order_by('id').reverse()[0].id
 | 
						|
        except IndexError:
 | 
						|
            initial_pointer = -1
 | 
						|
        page_params["narrow_stream"] = narrow_stream.name
 | 
						|
        if narrow_topic is not None:
 | 
						|
            page_params["narrow_topic"] = narrow_topic
 | 
						|
        page_params["narrow"] = [dict(operator=term[0], operand=term[1]) for term in narrow]
 | 
						|
        page_params["max_message_id"] = initial_pointer
 | 
						|
        page_params["initial_pointer"] = initial_pointer
 | 
						|
        page_params["have_initial_messages"] = (initial_pointer != -1)
 | 
						|
 | 
						|
    statsd.incr('views.home')
 | 
						|
    show_invites = True
 | 
						|
 | 
						|
    # Some realms only allow admins to invite users
 | 
						|
    if user_profile.realm.invite_by_admins_only and not user_profile.is_realm_admin:
 | 
						|
        show_invites = False
 | 
						|
 | 
						|
    product_name = "Zulip"
 | 
						|
    page_params['product_name'] = product_name
 | 
						|
    request._log_data['extra'] = "[%s]" % (register_ret["queue_id"],)
 | 
						|
    response = render_to_response('zerver/index.html',
 | 
						|
                                  {'user_profile': user_profile,
 | 
						|
                                   'page_params' : simplejson.encoder.JSONEncoderForHTML().encode(page_params),
 | 
						|
                                   'nofontface': is_buggy_ua(request.META.get("HTTP_USER_AGENT", "Unspecified")),
 | 
						|
                                   'avatar_url': avatar_url(user_profile),
 | 
						|
                                   'show_debug':
 | 
						|
                                       settings.DEBUG and ('show_debug' in request.GET),
 | 
						|
                                   'pipeline': settings.PIPELINE_ENABLED,
 | 
						|
                                   'show_invites': show_invites,
 | 
						|
                                   'is_admin': user_profile.is_realm_admin,
 | 
						|
                                   'show_webathena': user_profile.realm.webathena_enabled,
 | 
						|
                                   'enable_feedback': settings.ENABLE_FEEDBACK,
 | 
						|
                                   'embedded': narrow_stream is not None,
 | 
						|
                                   'product_name': product_name
 | 
						|
                                   },
 | 
						|
                                  request=request)
 | 
						|
    patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True)
 | 
						|
    return response
 | 
						|
 | 
						|
@zulip_login_required
 | 
						|
def desktop_home(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    return HttpResponseRedirect(reverse('zerver.views.home'))
 | 
						|
 | 
						|
def is_buggy_ua(agent):
 | 
						|
    # type: (str) -> bool
 | 
						|
    """Discrimiate CSS served to clients based on User Agent
 | 
						|
 | 
						|
    Due to QTBUG-3467, @font-face is not supported in QtWebKit.
 | 
						|
    This may get fixed in the future, but for right now we can
 | 
						|
    just serve the more conservative CSS to all our desktop apps.
 | 
						|
    """
 | 
						|
    return ("Humbug Desktop/" in agent or "Zulip Desktop/" in agent or "ZulipDesktop/" in agent) and \
 | 
						|
        "Mac" not in agent
 | 
						|
 | 
						|
@authenticated_json_post_view
 | 
						|
@has_request_variables
 | 
						|
def json_set_muted_topics(request, user_profile,
 | 
						|
                         muted_topics=REQ(validator=check_list(check_list(check_string, length=2)), default=[])):
 | 
						|
    # type: (HttpRequest, UserProfile, List[List[text_type]]) -> HttpResponse
 | 
						|
    do_set_muted_topics(user_profile, muted_topics)
 | 
						|
    return json_success()
 | 
						|
 | 
						|
def generate_204(request):
 | 
						|
    # type: (HttpRequest) -> HttpResponse
 | 
						|
    return HttpResponse(content=None, status=204)
 |