mirror of
https://github.com/zulip/zulip.git
synced 2025-11-12 18:06:44 +00:00
JsonableError has two major benefits over json_error: * It can be raised from anywhere in the codebase, rather than being a return value, which is much more convenient for refactoring, as one doesn't potentially need to change error handling style when extracting a bit of view code to a function. * It is guaranteed to contain the `code` property, which is helpful for API consistency. Various stragglers are not updated because JsonableError requires subclassing in order to specify custom data or HTTP status codes.
147 lines
5.8 KiB
Python
147 lines
5.8 KiB
Python
from typing import Any, Dict, List, Optional
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import authenticate
|
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
|
from django.utils.translation import gettext as _
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
from zerver.context_processors import get_realm_from_request
|
|
from zerver.decorator import do_login, require_post
|
|
from zerver.lib.request import REQ, JsonableError, has_request_variables
|
|
from zerver.lib.response import json_error, json_success
|
|
from zerver.lib.subdomains import get_subdomain
|
|
from zerver.lib.users import get_api_key
|
|
from zerver.lib.validator import validate_login_email
|
|
from zerver.models import Realm, UserProfile, get_realm
|
|
from zerver.views.auth import config_error, get_safe_redirect_to
|
|
from zproject.backends import dev_auth_enabled
|
|
|
|
|
|
def get_dev_users(realm: Optional[Realm] = None, extra_users_count: int = 10) -> List[UserProfile]:
|
|
# Development environments usually have only a few users, but
|
|
# it still makes sense to limit how many extra users we render to
|
|
# support performance testing with DevAuthBackend.
|
|
if realm is not None:
|
|
users_query = UserProfile.objects.select_related().filter(
|
|
is_bot=False, is_active=True, realm=realm
|
|
)
|
|
else:
|
|
users_query = UserProfile.objects.select_related().filter(is_bot=False, is_active=True)
|
|
|
|
shakespearian_users = users_query.exclude(email__startswith="extrauser").order_by("email")
|
|
extra_users = users_query.filter(email__startswith="extrauser").order_by("email")
|
|
# Limit the number of extra users we offer by default
|
|
extra_users = extra_users[0:extra_users_count]
|
|
users = list(shakespearian_users) + list(extra_users)
|
|
return users
|
|
|
|
|
|
def add_dev_login_context(realm: Optional[Realm], context: Dict[str, Any]) -> None:
|
|
users = get_dev_users(realm)
|
|
context["current_realm"] = realm
|
|
context["all_realms"] = Realm.objects.all()
|
|
|
|
def sort(lst: List[UserProfile]) -> List[UserProfile]:
|
|
return sorted(lst, key=lambda u: u.delivery_email)
|
|
|
|
context["direct_owners"] = sort([u for u in users if u.is_realm_owner])
|
|
context["direct_admins"] = sort([u for u in users if u.is_realm_admin and not u.is_realm_owner])
|
|
context["guest_users"] = sort([u for u in users if u.is_guest])
|
|
context["direct_moderators"] = sort([u for u in users if u.is_moderator])
|
|
context["direct_users"] = sort(
|
|
[u for u in users if not (u.is_realm_admin or u.is_guest or u.is_moderator)]
|
|
)
|
|
|
|
|
|
@csrf_exempt
|
|
@has_request_variables
|
|
def dev_direct_login(
|
|
request: HttpRequest,
|
|
next: str = REQ(default="/"),
|
|
) -> HttpResponse:
|
|
# This function allows logging in without a password and should only be called
|
|
# in development environments. It may be called if the DevAuthBackend is included
|
|
# in settings.AUTHENTICATION_BACKENDS
|
|
if (not dev_auth_enabled()) or settings.PRODUCTION:
|
|
# This check is probably not required, since authenticate would fail without
|
|
# an enabled DevAuthBackend.
|
|
return config_error(request, "dev")
|
|
email = request.POST["direct_email"]
|
|
subdomain = get_subdomain(request)
|
|
realm = get_realm(subdomain)
|
|
user_profile = authenticate(dev_auth_username=email, realm=realm)
|
|
if user_profile is None:
|
|
return config_error(request, "dev")
|
|
do_login(request, user_profile)
|
|
|
|
redirect_to = get_safe_redirect_to(next, user_profile.realm.uri)
|
|
return HttpResponseRedirect(redirect_to)
|
|
|
|
|
|
def check_dev_auth_backend() -> None:
|
|
if settings.PRODUCTION:
|
|
raise JsonableError(_("Endpoint not available in production."))
|
|
if not dev_auth_enabled():
|
|
raise JsonableError(_("DevAuthBackend not enabled."))
|
|
|
|
|
|
@csrf_exempt
|
|
@require_post
|
|
@has_request_variables
|
|
def api_dev_fetch_api_key(request: HttpRequest, username: str = REQ()) -> HttpResponse:
|
|
"""This function allows logging in without a password on the Zulip
|
|
mobile apps when connecting to a Zulip development environment. It
|
|
requires DevAuthBackend to be included in settings.AUTHENTICATION_BACKENDS.
|
|
"""
|
|
check_dev_auth_backend()
|
|
|
|
# Django invokes authenticate methods by matching arguments, and this
|
|
# authentication flow will not invoke LDAP authentication because of
|
|
# this condition of Django so no need to check if LDAP backend is
|
|
# enabled.
|
|
validate_login_email(username)
|
|
realm = get_realm_from_request(request)
|
|
if realm is None:
|
|
raise JsonableError(_("Invalid subdomain"))
|
|
return_data: Dict[str, bool] = {}
|
|
user_profile = authenticate(dev_auth_username=username, realm=realm, return_data=return_data)
|
|
if return_data.get("inactive_realm"):
|
|
return json_error(
|
|
_("This organization has been deactivated."),
|
|
data={"reason": "realm deactivated"},
|
|
status=403,
|
|
)
|
|
if return_data.get("inactive_user"):
|
|
return json_error(
|
|
_("Your account has been disabled."), data={"reason": "user disable"}, status=403
|
|
)
|
|
if user_profile is None:
|
|
return json_error(
|
|
_("This user is not registered."), data={"reason": "unregistered"}, status=403
|
|
)
|
|
do_login(request, user_profile)
|
|
api_key = get_api_key(user_profile)
|
|
return json_success({"api_key": api_key, "email": user_profile.delivery_email})
|
|
|
|
|
|
@csrf_exempt
|
|
def api_dev_list_users(request: HttpRequest) -> HttpResponse:
|
|
check_dev_auth_backend()
|
|
|
|
users = get_dev_users()
|
|
return json_success(
|
|
dict(
|
|
direct_admins=[
|
|
dict(email=u.delivery_email, realm_uri=u.realm.uri)
|
|
for u in users
|
|
if u.is_realm_admin
|
|
],
|
|
direct_users=[
|
|
dict(email=u.delivery_email, realm_uri=u.realm.uri)
|
|
for u in users
|
|
if not u.is_realm_admin
|
|
],
|
|
)
|
|
)
|