2020-06-11 00:54:34 +02:00
|
|
|
import logging
|
|
|
|
import smtplib
|
|
|
|
import urllib
|
|
|
|
from typing import Dict, List, Optional
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
from django.conf import settings
|
2017-08-25 01:11:30 +02:00
|
|
|
from django.contrib.auth import authenticate, get_backends
|
2017-01-07 21:46:03 +01:00
|
|
|
from django.core import validators
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-08-01 15:25:54 +02:00
|
|
|
from django.db.models import Q
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
|
|
|
from django.shortcuts import redirect, render
|
|
|
|
from django.urls import reverse
|
|
|
|
from django.utils.translation import ugettext as _
|
2017-01-07 21:46:03 +01:00
|
|
|
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
|
2020-06-11 00:54:34 +02:00
|
|
|
|
|
|
|
from confirmation import settings as confirmation_settings
|
|
|
|
from confirmation.models import (
|
|
|
|
Confirmation,
|
|
|
|
ConfirmationKeyException,
|
|
|
|
RealmCreationKey,
|
|
|
|
create_confirmation_link,
|
|
|
|
get_object_from_key,
|
|
|
|
render_confirmation_key_error,
|
|
|
|
validate_key,
|
|
|
|
)
|
|
|
|
from zerver.context_processors import get_realm_from_request, login_context
|
|
|
|
from zerver.decorator import do_login, require_post
|
|
|
|
from zerver.forms import (
|
|
|
|
FindMyTeamForm,
|
|
|
|
HomepageForm,
|
|
|
|
RealmCreationForm,
|
|
|
|
RealmRedirectForm,
|
|
|
|
RegistrationForm,
|
|
|
|
)
|
|
|
|
from zerver.lib.actions import (
|
|
|
|
bulk_add_subscriptions,
|
|
|
|
do_activate_user,
|
|
|
|
do_change_full_name,
|
|
|
|
do_change_password,
|
|
|
|
do_create_realm,
|
|
|
|
do_create_user,
|
|
|
|
do_set_user_display_setting,
|
|
|
|
lookup_default_stream_groups,
|
|
|
|
)
|
2020-06-03 01:11:36 +02:00
|
|
|
from zerver.lib.create_user import get_role_for_new_user
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm
|
2019-02-23 23:38:54 +01:00
|
|
|
from zerver.lib.onboarding import send_initial_realm_messages, setup_realm_internal_bots
|
2019-12-20 00:00:45 +01:00
|
|
|
from zerver.lib.pysa import mark_sanitized
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.send_email import FromAddress, send_email
|
|
|
|
from zerver.lib.sessions import get_expirable_session_var
|
2017-10-19 07:42:03 +02:00
|
|
|
from zerver.lib.subdomains import get_subdomain, is_root_domain_available
|
2017-05-04 15:19:50 +02:00
|
|
|
from zerver.lib.timezone import get_all_timezones
|
2020-03-02 19:38:16 +01:00
|
|
|
from zerver.lib.url_encoding import add_query_to_redirect_url
|
2018-06-19 10:55:56 +02:00
|
|
|
from zerver.lib.users import get_accounts_for_email
|
2018-08-11 16:26:46 +02:00
|
|
|
from zerver.lib.zephyr import compute_mit_user_fullname
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import (
|
|
|
|
DisposableEmailError,
|
|
|
|
DomainNotAllowedForRealmError,
|
|
|
|
EmailContainsPlusError,
|
|
|
|
MultiuseInvite,
|
|
|
|
Realm,
|
|
|
|
Stream,
|
|
|
|
UserProfile,
|
|
|
|
get_default_stream_groups,
|
|
|
|
get_realm,
|
|
|
|
get_source_profile,
|
|
|
|
get_user_by_delivery_email,
|
|
|
|
name_changes_disabled,
|
|
|
|
)
|
|
|
|
from zerver.views.auth import (
|
|
|
|
create_preregistration_user,
|
|
|
|
finish_desktop_flow,
|
|
|
|
finish_mobile_flow,
|
|
|
|
get_safe_redirect_to,
|
|
|
|
redirect_and_log_into_subdomain,
|
|
|
|
redirect_to_deactivation_notice,
|
|
|
|
)
|
|
|
|
from zproject.backends import (
|
|
|
|
ExternalAuthResult,
|
|
|
|
ZulipLDAPAuthBackend,
|
|
|
|
ZulipLDAPExceptionNoMatchingLDAPUser,
|
|
|
|
any_social_backend_enabled,
|
|
|
|
email_auth_enabled,
|
|
|
|
email_belongs_to_ldap,
|
|
|
|
ldap_auth_enabled,
|
|
|
|
password_auth_enabled,
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
|
2017-11-30 05:07:18 +01:00
|
|
|
def check_prereg_key_and_redirect(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
|
|
|
confirmation = Confirmation.objects.filter(confirmation_key=confirmation_key).first()
|
|
|
|
if confirmation is None or confirmation.type not in [
|
|
|
|
Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION]:
|
|
|
|
return render_confirmation_key_error(
|
|
|
|
request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST))
|
2020-05-01 01:52:45 +02:00
|
|
|
|
|
|
|
prereg_user = confirmation.content_object
|
|
|
|
if prereg_user.status == confirmation_settings.STATUS_REVOKED:
|
|
|
|
return render(request, "zerver/confirmation_link_expired_error.html")
|
|
|
|
|
2017-11-30 05:07:18 +01:00
|
|
|
try:
|
2020-02-15 07:06:48 +01:00
|
|
|
get_object_from_key(confirmation_key, confirmation.type, activate_object=False)
|
2017-11-30 05:07:18 +01:00
|
|
|
except ConfirmationKeyException as exception:
|
|
|
|
return render_confirmation_key_error(request, exception)
|
|
|
|
|
|
|
|
# confirm_preregistrationuser.html just extracts the confirmation_key
|
|
|
|
# (and GET parameters) and redirects to /accounts/register, so that the
|
|
|
|
# user can enter their information on a cleaner URL.
|
|
|
|
return render(request, 'confirmation/confirm_preregistrationuser.html',
|
|
|
|
context={
|
|
|
|
'key': confirmation_key,
|
|
|
|
'full_name': request.GET.get("full_name", None)})
|
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
@require_post
|
2017-11-27 09:28:57 +01:00
|
|
|
def accounts_register(request: HttpRequest) -> HttpResponse:
|
2020-05-02 00:23:19 +02:00
|
|
|
try:
|
|
|
|
key = request.POST.get('key', default='')
|
|
|
|
confirmation = Confirmation.objects.get(confirmation_key=key)
|
|
|
|
except Confirmation.DoesNotExist:
|
|
|
|
return render(request, "zerver/confirmation_link_expired_error.html")
|
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
prereg_user = confirmation.content_object
|
2020-05-01 01:52:45 +02:00
|
|
|
if prereg_user.status == confirmation_settings.STATUS_REVOKED:
|
|
|
|
return render(request, "zerver/confirmation_link_expired_error.html")
|
2017-01-07 21:46:03 +01:00
|
|
|
email = prereg_user.email
|
|
|
|
realm_creation = prereg_user.realm_creation
|
2017-08-04 08:09:25 +02:00
|
|
|
password_required = prereg_user.password_required
|
2020-06-03 01:11:36 +02:00
|
|
|
|
|
|
|
role = get_role_for_new_user(prereg_user.invited_as, realm_creation)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2018-04-06 19:33:02 +02:00
|
|
|
try:
|
|
|
|
validators.validate_email(email)
|
|
|
|
except ValidationError:
|
|
|
|
return render(request, "zerver/invalid_email.html", context={"invalid_email": True})
|
|
|
|
|
2017-11-08 22:02:59 +01:00
|
|
|
if realm_creation:
|
2017-01-07 21:46:03 +01:00
|
|
|
# For creating a new realm, there is no existing realm or domain
|
|
|
|
realm = None
|
|
|
|
else:
|
2019-05-04 04:47:44 +02:00
|
|
|
if get_subdomain(request) != prereg_user.realm.string_id:
|
2017-12-02 01:07:52 +01:00
|
|
|
return render_confirmation_key_error(
|
|
|
|
request, ConfirmationKeyException(ConfirmationKeyException.DOES_NOT_EXIST))
|
2019-05-04 04:47:44 +02:00
|
|
|
realm = prereg_user.realm
|
2018-03-14 12:54:05 +01:00
|
|
|
try:
|
|
|
|
email_allowed_for_realm(email, realm)
|
|
|
|
except DomainNotAllowedForRealmError:
|
2018-03-05 20:19:07 +01:00
|
|
|
return render(request, "zerver/invalid_email.html",
|
|
|
|
context={"realm_name": realm.name, "closed_domain": True})
|
2018-03-14 13:25:26 +01:00
|
|
|
except DisposableEmailError:
|
2018-03-05 20:19:07 +01:00
|
|
|
return render(request, "zerver/invalid_email.html",
|
|
|
|
context={"realm_name": realm.name, "disposable_emails_not_allowed": True})
|
2018-06-20 13:08:07 +02:00
|
|
|
except EmailContainsPlusError:
|
|
|
|
return render(request, "zerver/invalid_email.html",
|
|
|
|
context={"realm_name": realm.name, "email_contains_plus": True})
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-12-02 01:13:17 +01:00
|
|
|
if realm.deactivated:
|
|
|
|
# The user is trying to register for a deactivated realm. Advise them to
|
|
|
|
# contact support.
|
|
|
|
return redirect_to_deactivation_notice()
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-22 20:22:11 +01:00
|
|
|
try:
|
2020-03-02 13:24:50 +01:00
|
|
|
validate_email_not_already_in_realm(realm, email)
|
2019-11-03 23:02:37 +01:00
|
|
|
except ValidationError:
|
2020-09-22 02:54:44 +02:00
|
|
|
view_url = reverse('login')
|
2020-03-02 19:38:16 +01:00
|
|
|
redirect_url = add_query_to_redirect_url(view_url, 'email=' + urllib.parse.quote_plus(email))
|
|
|
|
return HttpResponseRedirect(redirect_url)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
name_validated = False
|
|
|
|
full_name = None
|
2019-01-16 09:56:17 +01:00
|
|
|
require_ldap_password = False
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
if request.POST.get('from_confirmation'):
|
|
|
|
try:
|
|
|
|
del request.session['authenticated_full_name']
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2019-11-09 07:01:05 +01:00
|
|
|
|
|
|
|
ldap_full_name = None
|
|
|
|
if settings.POPULATE_PROFILE_VIA_LDAP:
|
|
|
|
# If the user can be found in LDAP, we'll take the full name from the directory,
|
|
|
|
# and further down create a form pre-filled with it.
|
2017-01-07 21:46:03 +01:00
|
|
|
for backend in get_backends():
|
|
|
|
if isinstance(backend, LDAPBackend):
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
try:
|
2018-05-31 23:04:47 +02:00
|
|
|
ldap_username = backend.django_to_ldap_username(email)
|
2019-10-25 02:26:05 +02:00
|
|
|
except ZulipLDAPExceptionNoMatchingLDAPUser:
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.warning("New account email %s could not be found in LDAP", email)
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
break
|
|
|
|
|
2019-11-09 00:27:18 +01:00
|
|
|
# Note that this `ldap_user` object is not a
|
|
|
|
# `ZulipLDAPUser` with a `Realm` attached, so
|
|
|
|
# calling `.populate_user()` on it will crash.
|
|
|
|
# This is OK, since we're just accessing this user
|
|
|
|
# to extract its name.
|
|
|
|
#
|
|
|
|
# TODO: We should potentially be accessing this
|
|
|
|
# user to sync its initial avatar and custom
|
|
|
|
# profile fields as well, if we indeed end up
|
|
|
|
# creating a user account through this flow,
|
|
|
|
# rather than waiting until `manage.py
|
|
|
|
# sync_ldap_user_data` runs to populate it.
|
2019-01-16 09:08:52 +01:00
|
|
|
ldap_user = _LDAPUser(backend, ldap_username)
|
2018-05-31 23:04:47 +02:00
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
try:
|
2020-07-17 20:22:10 +02:00
|
|
|
ldap_full_name = backend.get_mapped_name(ldap_user)
|
2017-01-07 21:46:03 +01:00
|
|
|
except TypeError:
|
2019-11-09 07:01:05 +01:00
|
|
|
break
|
|
|
|
|
|
|
|
# Check whether this is ZulipLDAPAuthBackend,
|
|
|
|
# which is responsible for authentication and
|
|
|
|
# requires that LDAP accounts enter their LDAP
|
|
|
|
# password to register, or ZulipLDAPUserPopulator,
|
|
|
|
# which just populates UserProfile fields (no auth).
|
|
|
|
require_ldap_password = isinstance(backend, ZulipLDAPAuthBackend)
|
|
|
|
break
|
|
|
|
|
|
|
|
if ldap_full_name:
|
|
|
|
# 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},
|
|
|
|
realm_creation=realm_creation)
|
|
|
|
request.session['authenticated_full_name'] = ldap_full_name
|
|
|
|
name_validated = True
|
|
|
|
elif realm is not None and realm.is_zephyr_mirror_realm:
|
|
|
|
# For MIT users, we can get an authoritative name from Hesiod.
|
|
|
|
# Technically we should check that this is actually an MIT
|
|
|
|
# realm, but we can cross that bridge if we ever get a non-MIT
|
|
|
|
# zephyr mirroring realm.
|
|
|
|
hesiod_name = compute_mit_user_fullname(email)
|
|
|
|
form = RegistrationForm(
|
|
|
|
initial={'full_name': hesiod_name if "@" not in hesiod_name else ""},
|
|
|
|
realm_creation=realm_creation)
|
|
|
|
name_validated = True
|
2019-11-01 00:00:36 +01:00
|
|
|
elif prereg_user.full_name:
|
|
|
|
if prereg_user.full_name_validated:
|
|
|
|
request.session['authenticated_full_name'] = prereg_user.full_name
|
|
|
|
name_validated = True
|
|
|
|
form = RegistrationForm({'full_name': prereg_user.full_name},
|
|
|
|
realm_creation=realm_creation)
|
|
|
|
else:
|
|
|
|
form = RegistrationForm(initial={'full_name': prereg_user.full_name},
|
|
|
|
realm_creation=realm_creation)
|
2017-01-07 21:46:03 +01:00
|
|
|
elif 'full_name' in request.POST:
|
|
|
|
form = RegistrationForm(
|
2017-06-15 19:24:38 +02:00
|
|
|
initial={'full_name': request.POST.get('full_name')},
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
realm_creation=realm_creation,
|
2017-01-07 21:46:03 +01:00
|
|
|
)
|
|
|
|
else:
|
2017-06-15 19:24:38 +02:00
|
|
|
form = RegistrationForm(realm_creation=realm_creation)
|
2017-01-07 21:46:03 +01:00
|
|
|
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:
|
2020-09-03 05:32:15 +02:00
|
|
|
postdata.update(full_name=request.session['authenticated_full_name'])
|
2017-01-07 21:46:03 +01:00
|
|
|
name_validated = True
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2017-06-15 19:24:38 +02:00
|
|
|
form = RegistrationForm(postdata, realm_creation=realm_creation)
|
2019-10-30 20:06:46 +01:00
|
|
|
|
|
|
|
if not (password_auth_enabled(realm) and password_required):
|
|
|
|
form['password'].field.required = False
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
if form.is_valid():
|
2019-11-18 07:57:36 +01:00
|
|
|
if password_auth_enabled(realm) and form['password'].field.required:
|
2017-01-07 21:46:03 +01:00
|
|
|
password = form.cleaned_data['password']
|
|
|
|
else:
|
2019-11-18 07:57:36 +01:00
|
|
|
# If the user wasn't prompted for a password when
|
|
|
|
# completing the authentication form (because they're
|
|
|
|
# signing up with SSO and no password is required), set
|
|
|
|
# the password field to `None` (Which causes Django to
|
|
|
|
# create an unusable password).
|
2017-01-07 21:46:03 +01:00
|
|
|
password = None
|
|
|
|
|
|
|
|
if realm_creation:
|
|
|
|
string_id = form.cleaned_data['realm_subdomain']
|
|
|
|
realm_name = form.cleaned_data['realm_name']
|
2017-08-24 04:52:34 +02:00
|
|
|
realm = do_create_realm(string_id, realm_name)
|
2018-01-12 09:10:43 +01:00
|
|
|
setup_realm_internal_bots(realm)
|
2017-05-25 18:22:28 +02:00
|
|
|
assert(realm is not None)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
full_name = form.cleaned_data['full_name']
|
2017-10-26 20:31:43 +02:00
|
|
|
default_stream_group_names = request.POST.getlist('default_stream_group')
|
|
|
|
default_stream_groups = lookup_default_stream_groups(default_stream_group_names, realm)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-04 05:23:22 +01:00
|
|
|
timezone = ""
|
2017-05-04 15:19:50 +02:00
|
|
|
if 'timezone' in request.POST and request.POST['timezone'] in get_all_timezones():
|
|
|
|
timezone = request.POST['timezone']
|
|
|
|
|
2018-05-22 18:13:51 +02:00
|
|
|
if 'source_realm' in request.POST and request.POST["source_realm"] != "on":
|
|
|
|
source_profile = get_source_profile(email, request.POST["source_realm"])
|
|
|
|
else:
|
|
|
|
source_profile = None
|
|
|
|
|
2017-11-28 03:01:44 +01:00
|
|
|
if not realm_creation:
|
|
|
|
try:
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
existing_user_profile: Optional[UserProfile] = get_user_by_delivery_email(email, realm)
|
2017-11-28 03:01:44 +01:00
|
|
|
except UserProfile.DoesNotExist:
|
|
|
|
existing_user_profile = None
|
|
|
|
else:
|
2017-08-25 07:05:27 +02:00
|
|
|
existing_user_profile = None
|
|
|
|
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
user_profile: Optional[UserProfile] = None
|
|
|
|
return_data: Dict[str, bool] = {}
|
2017-10-24 19:38:31 +02:00
|
|
|
if ldap_auth_enabled(realm):
|
|
|
|
# If the user was authenticated using an external SSO
|
|
|
|
# mechanism like Google or GitHub auth, then authentication
|
|
|
|
# will have already been done before creating the
|
|
|
|
# PreregistrationUser object with password_required=False, and
|
|
|
|
# so we don't need to worry about passwords.
|
|
|
|
#
|
|
|
|
# If instead the realm is using EmailAuthBackend, we will
|
|
|
|
# set their password above.
|
|
|
|
#
|
|
|
|
# But if the realm is using LDAPAuthBackend, we need to verify
|
|
|
|
# their LDAP password (which will, as a side effect, create
|
|
|
|
# the user account) here using authenticate.
|
2019-11-07 05:13:08 +01:00
|
|
|
# pregeg_user.realm_creation carries the information about whether
|
|
|
|
# we're in realm creation mode, and the ldap flow will handle
|
|
|
|
# that and create the user with the appropriate parameters.
|
2019-08-01 14:08:06 +02:00
|
|
|
user_profile = authenticate(request=request,
|
2019-11-07 04:35:15 +01:00
|
|
|
username=email,
|
|
|
|
password=password,
|
|
|
|
realm=realm,
|
|
|
|
prereg_user=prereg_user,
|
|
|
|
return_data=return_data)
|
|
|
|
if user_profile is None:
|
2019-11-21 14:48:49 +01:00
|
|
|
can_use_different_backend = email_auth_enabled(realm) or any_social_backend_enabled(realm)
|
2019-11-23 18:17:41 +01:00
|
|
|
if settings.LDAP_APPEND_DOMAIN:
|
2020-10-23 02:43:28 +02:00
|
|
|
# In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-LDAP account
|
2019-11-23 18:17:41 +01:00
|
|
|
# if the email matches the ldap domain.
|
|
|
|
can_use_different_backend = can_use_different_backend and (
|
|
|
|
not email_belongs_to_ldap(realm, email))
|
|
|
|
if return_data.get("no_matching_ldap_user") and can_use_different_backend:
|
2019-11-21 14:48:49 +01:00
|
|
|
# If both the LDAP and Email or Social auth backends are
|
2019-11-07 04:26:46 +01:00
|
|
|
# enabled, and there's no matching user in the LDAP
|
|
|
|
# directory then the intent is to create a user in the
|
|
|
|
# realm with their email outside the LDAP organization
|
|
|
|
# (with e.g. a password stored in the Zulip database,
|
|
|
|
# not LDAP). So we fall through and create the new
|
|
|
|
# account.
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
# TODO: This probably isn't going to give a
|
|
|
|
# user-friendly error message, but it doesn't
|
|
|
|
# particularly matter, because the registration form
|
|
|
|
# is hidden for most users.
|
2020-09-22 02:54:44 +02:00
|
|
|
view_url = reverse('login')
|
2020-03-02 19:38:16 +01:00
|
|
|
query = 'email=' + urllib.parse.quote_plus(email)
|
|
|
|
redirect_url = add_query_to_redirect_url(view_url, query)
|
|
|
|
return HttpResponseRedirect(redirect_url)
|
2019-11-07 05:13:08 +01:00
|
|
|
elif not realm_creation:
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
# Since we'll have created a user, we now just log them in.
|
2019-11-07 04:35:15 +01:00
|
|
|
return login_and_go_to_home(request, user_profile)
|
2019-11-07 05:13:08 +01:00
|
|
|
else:
|
|
|
|
# With realm_creation=True, we're going to return further down,
|
|
|
|
# after finishing up the creation process.
|
|
|
|
pass
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
|
|
|
|
if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
|
2017-03-19 01:42:40 +01:00
|
|
|
user_profile = existing_user_profile
|
2020-06-29 13:12:17 +02:00
|
|
|
do_activate_user(user_profile, acting_user=user_profile)
|
2017-03-19 01:42:40 +01:00
|
|
|
do_change_password(user_profile, password)
|
2017-04-07 07:28:28 +02:00
|
|
|
do_change_full_name(user_profile, full_name, user_profile)
|
2017-05-04 15:19:50 +02:00
|
|
|
do_set_user_display_setting(user_profile, 'timezone', timezone)
|
2017-10-27 20:22:19 +02:00
|
|
|
# TODO: When we clean up the `do_activate_user` code path,
|
|
|
|
# make it respect invited_as_admin / is_realm_admin.
|
2019-11-07 05:13:08 +01:00
|
|
|
|
|
|
|
if user_profile is None:
|
2020-07-16 14:10:43 +02:00
|
|
|
user_profile = do_create_user(email, password, realm, full_name,
|
2018-12-30 11:08:07 +01:00
|
|
|
prereg_user=prereg_user,
|
2020-06-03 01:11:36 +02:00
|
|
|
role=role,
|
2017-01-07 21:46:03 +01:00
|
|
|
tos_version=settings.TOS_VERSION,
|
2017-05-04 15:19:50 +02:00
|
|
|
timezone=timezone,
|
2017-10-12 19:36:14 +02:00
|
|
|
newsletter_data={"IP": request.META['REMOTE_ADDR']},
|
2018-05-22 18:13:51 +02:00
|
|
|
default_stream_groups=default_stream_groups,
|
2018-06-29 09:31:00 +02:00
|
|
|
source_profile=source_profile,
|
2020-06-29 13:12:17 +02:00
|
|
|
realm_creation=realm_creation,
|
|
|
|
acting_user=None)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-08-05 08:48:20 +02:00
|
|
|
if realm_creation:
|
2020-10-13 15:16:27 +02:00
|
|
|
bulk_add_subscriptions(realm, [realm.signup_notifications_stream], [user_profile])
|
2017-07-17 06:21:53 +02:00
|
|
|
send_initial_realm_messages(realm)
|
2017-06-14 19:55:07 +02:00
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
# Because for realm creation, registration happens on the
|
|
|
|
# root domain, we need to log them into the subdomain for
|
|
|
|
# their new realm.
|
2020-05-22 15:42:46 +02:00
|
|
|
return redirect_and_log_into_subdomain(ExternalAuthResult(user_profile=user_profile,
|
|
|
|
data_dict={'is_realm_creation': True}))
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
# This dummy_backend check below confirms the user is
|
|
|
|
# authenticating to the correct subdomain.
|
2018-12-06 23:17:46 +01:00
|
|
|
auth_result = authenticate(username=user_profile.delivery_email,
|
2017-10-03 02:29:20 +02:00
|
|
|
realm=realm,
|
2017-01-07 21:46:03 +01:00
|
|
|
return_data=return_data,
|
|
|
|
use_dummy_backend=True)
|
|
|
|
if return_data.get('invalid_subdomain'):
|
|
|
|
# By construction, this should never happen.
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.error(
|
|
|
|
"Subdomain mismatch in registration %s: %s",
|
|
|
|
realm.subdomain, user_profile.delivery_email,
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
return redirect('/')
|
2017-08-23 01:14:45 +02:00
|
|
|
|
2017-10-24 19:37:14 +02:00
|
|
|
return login_and_go_to_home(request, auth_result)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-03-16 14:28:08 +01:00
|
|
|
return render(
|
|
|
|
request,
|
2017-01-07 21:46:03 +01:00
|
|
|
'zerver/register.html',
|
2017-03-16 14:28:08 +01:00
|
|
|
context={'form': form,
|
|
|
|
'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,
|
2017-08-09 22:09:38 +02:00
|
|
|
'password_required': password_auth_enabled(realm) and password_required,
|
2019-01-16 09:56:17 +01:00
|
|
|
'require_ldap_password': require_ldap_password,
|
2017-03-16 14:28:08 +01:00
|
|
|
'password_auth_enabled': password_auth_enabled(realm),
|
2017-10-19 07:42:03 +02:00
|
|
|
'root_domain_available': is_root_domain_available(),
|
2020-07-05 02:55:15 +02:00
|
|
|
'default_stream_groups': [] if realm is None else get_default_stream_groups(realm),
|
2018-06-19 10:55:56 +02:00
|
|
|
'accounts': get_accounts_for_email(email),
|
2017-03-23 00:15:06 +01:00
|
|
|
'MAX_REALM_NAME_LENGTH': str(Realm.MAX_REALM_NAME_LENGTH),
|
|
|
|
'MAX_NAME_LENGTH': str(UserProfile.MAX_NAME_LENGTH),
|
|
|
|
'MAX_PASSWORD_LENGTH': str(form.MAX_PASSWORD_LENGTH),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
'MAX_REALM_SUBDOMAIN_LENGTH': str(Realm.MAX_REALM_SUBDOMAIN_LENGTH),
|
|
|
|
},
|
2017-03-16 14:28:08 +01:00
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def login_and_go_to_home(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
2020-02-06 18:27:10 +01:00
|
|
|
mobile_flow_otp = get_expirable_session_var(request.session, 'registration_mobile_flow_otp',
|
|
|
|
delete=True)
|
|
|
|
desktop_flow_otp = get_expirable_session_var(request.session, 'registration_desktop_flow_otp',
|
|
|
|
delete=True)
|
|
|
|
if mobile_flow_otp is not None:
|
|
|
|
return finish_mobile_flow(request, user_profile, mobile_flow_otp)
|
|
|
|
elif desktop_flow_otp is not None:
|
2020-02-22 15:55:32 +01:00
|
|
|
return finish_desktop_flow(request, user_profile, desktop_flow_otp)
|
2020-02-06 18:27:10 +01:00
|
|
|
|
2017-10-24 19:37:14 +02:00
|
|
|
do_login(request, user_profile)
|
2019-12-20 00:00:45 +01:00
|
|
|
# Using 'mark_sanitized' to work around false positive where Pysa thinks
|
|
|
|
# that 'user_profile' is user-controlled
|
2020-09-22 02:54:44 +02:00
|
|
|
return HttpResponseRedirect(mark_sanitized(user_profile.realm.uri) + reverse('home'))
|
2017-10-24 19:37:14 +02:00
|
|
|
|
2018-01-26 20:50:22 +01:00
|
|
|
def prepare_activation_url(email: str, request: HttpRequest,
|
|
|
|
realm_creation: bool=False,
|
2019-02-06 22:57:14 +01:00
|
|
|
streams: Optional[List[Stream]]=None,
|
|
|
|
invited_as: Optional[int]=None) -> str:
|
2017-01-07 21:46:03 +01:00
|
|
|
"""
|
|
|
|
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)
|
2017-08-10 22:33:14 +02:00
|
|
|
|
|
|
|
if streams is not None:
|
2018-01-31 08:22:07 +01:00
|
|
|
prereg_user.streams.set(streams)
|
2017-08-10 22:33:14 +02:00
|
|
|
|
2019-02-06 22:57:14 +01:00
|
|
|
if invited_as is not None:
|
|
|
|
prereg_user.invited_as = invited_as
|
|
|
|
prereg_user.save()
|
|
|
|
|
2017-11-30 01:06:25 +01:00
|
|
|
confirmation_type = Confirmation.USER_REGISTRATION
|
|
|
|
if realm_creation:
|
|
|
|
confirmation_type = Confirmation.REALM_CREATION
|
|
|
|
|
2020-06-14 01:36:12 +02:00
|
|
|
activation_url = create_confirmation_link(prereg_user, confirmation_type)
|
2017-06-11 00:58:00 +02:00
|
|
|
if settings.DEVELOPMENT and realm_creation:
|
2017-06-11 00:39:58 +02:00
|
|
|
request.session['confirmation_key'] = {'confirmation_key': activation_url.split('/')[-1]}
|
2018-01-26 20:50:22 +01:00
|
|
|
return activation_url
|
|
|
|
|
2020-06-14 13:32:38 +02:00
|
|
|
def send_confirm_registration_email(email: str, activation_url: str, language: str,
|
|
|
|
realm: Optional[Realm]=None) -> None:
|
2018-12-03 23:26:51 +01:00
|
|
|
send_email('zerver/emails/confirm_registration', to_emails=[email],
|
2018-06-11 08:18:16 +02:00
|
|
|
from_address=FromAddress.tokenized_no_reply_address(),
|
2020-06-14 13:32:38 +02:00
|
|
|
language=language, context={'activate_url': activation_url},
|
|
|
|
realm=realm)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def redirect_to_email_login_url(email: str) -> HttpResponseRedirect:
|
2020-09-22 02:54:44 +02:00
|
|
|
login_url = reverse('login')
|
2017-08-28 08:13:15 +02:00
|
|
|
email = urllib.parse.quote_plus(email)
|
2020-06-10 22:31:13 +02:00
|
|
|
redirect_url = add_query_to_redirect_url(login_url, 'already_registered=' + email)
|
2017-01-07 21:46:03 +01:00
|
|
|
return HttpResponseRedirect(redirect_url)
|
|
|
|
|
2018-04-24 03:47:28 +02:00
|
|
|
def create_realm(request: HttpRequest, creation_key: Optional[str]=None) -> HttpResponse:
|
2018-01-29 20:54:49 +01:00
|
|
|
try:
|
|
|
|
key_record = validate_key(creation_key)
|
|
|
|
except RealmCreationKey.Invalid:
|
2018-01-29 20:06:55 +01:00
|
|
|
return render(request, "zerver/realm_creation_failed.html",
|
|
|
|
context={'message': _('The organization creation link has expired'
|
|
|
|
' or is not valid.')})
|
2017-01-07 21:46:03 +01:00
|
|
|
if not settings.OPEN_REALM_CREATION:
|
2018-01-29 20:54:49 +01:00
|
|
|
if key_record is None:
|
2017-03-16 14:28:08 +01:00
|
|
|
return render(request, "zerver/realm_creation_failed.html",
|
2018-08-10 02:16:49 +02:00
|
|
|
context={'message': _('New organization creation disabled')})
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
# When settings.OPEN_REALM_CREATION is enabled, anyone can create a new realm,
|
2018-11-09 18:51:12 +01:00
|
|
|
# with a few restrictions on their email address.
|
2017-01-07 21:46:03 +01:00
|
|
|
if request.method == 'POST':
|
|
|
|
form = RealmCreationForm(request.POST)
|
|
|
|
if form.is_valid():
|
|
|
|
email = form.cleaned_data['email']
|
2018-01-26 20:50:22 +01:00
|
|
|
activation_url = prepare_activation_url(email, request, realm_creation=True)
|
2018-01-26 21:12:58 +01:00
|
|
|
if key_record is not None and key_record.presume_email_valid:
|
|
|
|
# The user has a token created from the server command line;
|
|
|
|
# skip confirming the email is theirs, taking their word for it.
|
|
|
|
# This is essential on first install if the admin hasn't stopped
|
|
|
|
# to configure outbound email up front, or it isn't working yet.
|
|
|
|
key_record.delete()
|
|
|
|
return HttpResponseRedirect(activation_url)
|
|
|
|
|
2017-08-17 19:59:17 +02:00
|
|
|
try:
|
2018-12-14 08:41:42 +01:00
|
|
|
send_confirm_registration_email(email, activation_url, request.LANGUAGE_CODE)
|
2017-08-17 19:59:17 +02:00
|
|
|
except smtplib.SMTPException as e:
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.error('Error in create_realm: %s', str(e))
|
2017-08-17 19:59:17 +02:00
|
|
|
return HttpResponseRedirect("/config-error/smtp")
|
|
|
|
|
2018-01-29 20:54:49 +01:00
|
|
|
if key_record is not None:
|
|
|
|
key_record.delete()
|
2018-08-24 10:01:42 +02:00
|
|
|
return HttpResponseRedirect(reverse('new_realm_send_confirm', kwargs={'email': email}))
|
2017-01-07 21:46:03 +01:00
|
|
|
else:
|
|
|
|
form = RealmCreationForm()
|
2017-03-16 14:28:08 +01:00
|
|
|
return render(request,
|
|
|
|
'zerver/create_realm.html',
|
|
|
|
context={'form': form, 'current_url': request.get_full_path},
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2020-06-13 01:57:21 +02:00
|
|
|
def accounts_home(request: HttpRequest, multiuse_object_key: str="",
|
2019-02-08 17:09:25 +01:00
|
|
|
multiuse_object: Optional[MultiuseInvite]=None) -> HttpResponse:
|
2019-05-04 04:47:44 +02:00
|
|
|
try:
|
|
|
|
realm = get_realm(get_subdomain(request))
|
|
|
|
except Realm.DoesNotExist:
|
2020-09-22 02:54:44 +02:00
|
|
|
return HttpResponseRedirect(reverse(find_account))
|
2017-11-23 02:59:51 +01:00
|
|
|
if realm.deactivated:
|
2017-08-24 09:58:44 +02:00
|
|
|
return redirect_to_deactivation_notice()
|
|
|
|
|
2017-08-10 22:34:17 +02:00
|
|
|
from_multiuse_invite = False
|
|
|
|
streams_to_subscribe = None
|
2019-02-06 22:57:14 +01:00
|
|
|
invited_as = None
|
2017-08-10 22:34:17 +02:00
|
|
|
|
|
|
|
if multiuse_object:
|
|
|
|
realm = multiuse_object.realm
|
|
|
|
streams_to_subscribe = multiuse_object.streams.all()
|
|
|
|
from_multiuse_invite = True
|
2019-02-06 22:57:14 +01:00
|
|
|
invited_as = multiuse_object.invited_as
|
2017-08-10 22:34:17 +02:00
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
if request.method == 'POST':
|
2017-08-10 22:34:17 +02:00
|
|
|
form = HomepageForm(request.POST, realm=realm, from_multiuse_invite=from_multiuse_invite)
|
2017-01-07 21:46:03 +01:00
|
|
|
if form.is_valid():
|
|
|
|
email = form.cleaned_data['email']
|
2019-02-06 22:57:14 +01:00
|
|
|
activation_url = prepare_activation_url(email, request, streams=streams_to_subscribe,
|
|
|
|
invited_as=invited_as)
|
2017-08-17 18:28:21 +02:00
|
|
|
try:
|
2020-06-14 13:32:38 +02:00
|
|
|
send_confirm_registration_email(email, activation_url, request.LANGUAGE_CODE, realm=realm)
|
2017-08-17 18:28:21 +02:00
|
|
|
except smtplib.SMTPException as e:
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.error('Error in accounts_home: %s', str(e))
|
2017-08-17 18:28:21 +02:00
|
|
|
return HttpResponseRedirect("/config-error/smtp")
|
|
|
|
|
2018-08-24 10:01:42 +02:00
|
|
|
return HttpResponseRedirect(reverse('signup_send_confirm', kwargs={'email': email}))
|
2017-08-25 07:05:27 +02:00
|
|
|
|
|
|
|
email = request.POST['email']
|
2017-01-07 21:46:03 +01:00
|
|
|
try:
|
2020-03-02 13:24:50 +01:00
|
|
|
validate_email_not_already_in_realm(realm, email)
|
2017-01-07 21:46:03 +01:00
|
|
|
except ValidationError:
|
|
|
|
return redirect_to_email_login_url(email)
|
|
|
|
else:
|
|
|
|
form = HomepageForm(realm=realm)
|
2019-03-20 13:13:44 +01:00
|
|
|
context = login_context(request)
|
2020-09-03 05:32:15 +02:00
|
|
|
context.update(form=form, current_url=request.get_full_path,
|
|
|
|
multiuse_object_key=multiuse_object_key,
|
|
|
|
from_multiuse_invite=from_multiuse_invite)
|
2019-03-20 13:13:44 +01:00
|
|
|
return render(request, 'zerver/accounts_home.html', context=context)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def accounts_home_from_multiuse_invite(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
2017-08-10 22:34:17 +02:00
|
|
|
multiuse_object = None
|
|
|
|
try:
|
2017-11-01 21:07:39 +01:00
|
|
|
multiuse_object = get_object_from_key(confirmation_key, Confirmation.MULTIUSE_INVITE)
|
2017-09-27 03:34:58 +02:00
|
|
|
# Required for oAuth2
|
2017-08-10 22:34:17 +02:00
|
|
|
except ConfirmationKeyException as exception:
|
|
|
|
realm = get_realm_from_request(request)
|
|
|
|
if realm is None or realm.invite_required:
|
|
|
|
return render_confirmation_key_error(request, exception)
|
2019-02-08 17:09:25 +01:00
|
|
|
return accounts_home(request, multiuse_object_key=confirmation_key,
|
|
|
|
multiuse_object=multiuse_object)
|
2017-08-10 22:34:17 +02:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def generate_204(request: HttpRequest) -> HttpResponse:
|
2017-01-07 21:46:03 +01:00
|
|
|
return HttpResponse(content=None, status=204)
|
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def find_account(request: HttpRequest) -> HttpResponse:
|
2018-12-19 20:53:14 +01:00
|
|
|
from zerver.context_processors import common_context
|
2020-09-22 02:54:44 +02:00
|
|
|
url = reverse('find_account')
|
2017-01-07 21:46:03 +01:00
|
|
|
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
emails: List[str] = []
|
2017-01-07 21:46:03 +01:00
|
|
|
if request.method == 'POST':
|
|
|
|
form = FindMyTeamForm(request.POST)
|
|
|
|
if form.is_valid():
|
|
|
|
emails = form.cleaned_data['emails']
|
2020-08-01 15:25:54 +02:00
|
|
|
|
|
|
|
# Django doesn't support __iexact__in lookup with EmailField, so we have
|
|
|
|
# to use Qs to get around that without needing to do multiple queries.
|
|
|
|
emails_q = Q()
|
|
|
|
for email in emails:
|
|
|
|
emails_q |= Q(delivery_email__iexact=email)
|
|
|
|
|
2018-12-19 20:53:14 +01:00
|
|
|
for user in UserProfile.objects.filter(
|
2020-08-01 15:25:54 +02:00
|
|
|
emails_q, is_active=True, is_bot=False,
|
2019-11-16 01:56:43 +01:00
|
|
|
realm__deactivated=False):
|
2018-12-19 20:53:14 +01:00
|
|
|
context = common_context(user)
|
2020-09-03 05:32:15 +02:00
|
|
|
context.update(
|
|
|
|
email=user.delivery_email,
|
|
|
|
)
|
2020-01-09 06:55:00 +01:00
|
|
|
send_email('zerver/emails/find_team', to_user_ids=[user.id], context=context,
|
|
|
|
from_address=FromAddress.SUPPORT)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
# Note: Show all the emails in the result otherwise this
|
|
|
|
# feature can be used to ascertain which email addresses
|
|
|
|
# are associated with Zulip.
|
|
|
|
data = urllib.parse.urlencode({'emails': ','.join(emails)})
|
2020-03-02 19:38:16 +01:00
|
|
|
return redirect(add_query_to_redirect_url(url, data))
|
2017-01-07 21:46:03 +01:00
|
|
|
else:
|
|
|
|
form = FindMyTeamForm()
|
|
|
|
result = request.GET.get('emails')
|
2017-08-25 08:30:33 +02:00
|
|
|
# The below validation is perhaps unnecessary, in that we
|
|
|
|
# shouldn't get able to get here with an invalid email unless
|
|
|
|
# the user hand-edits the URLs.
|
2017-01-07 21:46:03 +01:00
|
|
|
if result:
|
|
|
|
for email in result.split(','):
|
|
|
|
try:
|
|
|
|
validators.validate_email(email)
|
|
|
|
emails.append(email)
|
|
|
|
except ValidationError:
|
|
|
|
pass
|
|
|
|
|
2017-03-16 14:28:08 +01:00
|
|
|
return render(request,
|
2017-08-28 23:27:16 +02:00
|
|
|
'zerver/find_account.html',
|
2017-03-16 14:28:08 +01:00
|
|
|
context={'form': form, 'current_url': lambda: url,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
'emails': emails})
|
2018-08-25 14:06:17 +02:00
|
|
|
|
|
|
|
def realm_redirect(request: HttpRequest) -> HttpResponse:
|
|
|
|
if request.method == 'POST':
|
|
|
|
form = RealmRedirectForm(request.POST)
|
|
|
|
if form.is_valid():
|
|
|
|
subdomain = form.cleaned_data['subdomain']
|
|
|
|
realm = get_realm(subdomain)
|
|
|
|
redirect_to = get_safe_redirect_to(request.GET.get("next", ""), realm.uri)
|
|
|
|
return HttpResponseRedirect(redirect_to)
|
|
|
|
else:
|
|
|
|
form = RealmRedirectForm()
|
|
|
|
|
|
|
|
return render(request, 'zerver/realm_redirect.html', context={'form': form})
|