def clean_username(self): # type: () -> str email = self.cleaned_data['username'] try: user_profile = get_user_profile_by_email(email) except UserProfile.DoesNotExist: return email if user_profile.realm.deactivated: error_msg = u"""Sorry for the trouble, but %s has been deactivated. Please contact %s to reactivate this group.""" % ( user_profile.realm.name, FromAddress.SUPPORT) raise ValidationError(mark_safe(error_msg)) if not user_profile.is_active and not user_profile.is_mirror_dummy: error_msg = (u"Sorry for the trouble, but your account has been " u"deactivated. Please contact %s to reactivate " u"it.") % (FromAddress.SUPPORT,) raise ValidationError(mark_safe(error_msg)) if not check_subdomain(get_subdomain(self.request), user_profile.realm.subdomain): logging.warning("User %s attempted to password login to wrong subdomain %s" % (user_profile.email, get_subdomain(self.request))) raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR)) return email
def api_fetch_api_key(request, username=REQ(), password=REQ()): # type: (HttpRequest, str, str) -> HttpResponse return_data = {} # type: Dict[str, bool] if username == "google-oauth2-token": user_profile = authenticate(google_oauth2_token=password, realm_subdomain=get_subdomain(request), return_data=return_data) else: user_profile = authenticate(username=username, password=password, realm_subdomain=get_subdomain(request), return_data=return_data) if return_data.get("inactive_user") == True: return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if return_data.get("inactive_realm") == True: return json_error(_("Your realm has been deactivated."), data={"reason": "realm deactivated"}, status=403) if return_data.get("password_auth_disabled") == True: return json_error(_("Password auth is disabled in your team."), data={"reason": "password auth disabled"}, status=403) if user_profile is None: if return_data.get("valid_attestation") == True: # We can leak that the user is unregistered iff they present a valid authentication string for the user. return json_error(_("This user is not registered; do so from a browser."), data={"reason": "unregistered"}, status=403) return json_error(_("Your username or password is incorrect."), data={"reason": "incorrect_creds"}, status=403) return json_success({"api_key": user_profile.api_key, "email": user_profile.email})
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 _wrapped_func_arguments(request, api_key=REQ(), *args, **kwargs): # type: (HttpRequest, Text, *Any, **Any) -> HttpResponse try: user_profile = UserProfile.objects.get(api_key=api_key) except UserProfile.DoesNotExist: raise JsonableError(_("Invalid API key")) if not user_profile.is_active: raise JsonableError(_("Account not active")) if user_profile.realm.deactivated: raise JsonableError(_("Realm for account has been deactivated")) if not check_subdomain(get_subdomain(request), user_profile.realm.subdomain): logging.warning("User %s attempted to access webhook API on wrong subdomain %s" % ( user_profile.email, get_subdomain(request))) raise JsonableError(_("Account is not associated with this subdomain")) request.user = user_profile request._email = user_profile.email webhook_client_name = "Zulip{}Webhook".format(client_name) process_client(request, user_profile, client_name=webhook_client_name) if settings.RATE_LIMITING: rate_limit_user(request, user_profile, domain='all') try: return view_func(request, user_profile, *args, **kwargs) except Exception as err: if request.content_type == 'application/json': try: request_body = ujson.dumps(ujson.loads(request.body), indent=4) except ValueError: request_body = str(request.body) else: request_body = str(request.body) message = """ user: {email} ({realm}) client: {client_name} URL: {path_info} content_type: {content_type} body: {body} """.format( email=user_profile.email, realm=user_profile.realm.string_id, client_name=webhook_client_name, body=request_body, path_info=request.META.get('PATH_INFO', None), content_type=request.content_type, ) webhook_logger.exception(message) raise err
def validate_api_key(request, role, api_key, is_webhook=False): # type: (HttpRequest, Text, Text, bool) -> Union[UserProfile, RemoteZulipServer] # Remove whitespace to protect users from trivial errors. role, api_key = role.strip(), api_key.strip() if not is_remote_server(role): try: profile = get_user_profile_by_email(role) # type: Union[UserProfile, RemoteZulipServer] except UserProfile.DoesNotExist: raise JsonableError(_("Invalid user: %s") % (role,)) else: try: profile = get_remote_server_by_uuid(role) except RemoteZulipServer.DoesNotExist: raise JsonableError(_("Invalid Zulip server: %s") % (role,)) if api_key != profile.api_key: if len(api_key) != 32: reason = _("Incorrect API key length (keys should be 32 " "characters long) for role '%s'") else: reason = _("Invalid API key for role '%s'") raise JsonableError(reason % (role,)) # early exit for RemoteZulipServer instances if settings.ZILENCER_ENABLED and isinstance(profile, RemoteZulipServer): if not check_subdomain(get_subdomain(request), ""): raise JsonableError(_("This API key only works on the root subdomain")) return profile profile = cast(UserProfile, profile) # is UserProfile if not profile.is_active: raise JsonableError(_("Account not active")) if profile.is_incoming_webhook and not is_webhook: raise JsonableError(_("Account is not valid to post webhook messages")) if profile.realm.deactivated: raise JsonableError(_("Realm for account has been deactivated")) if (not check_subdomain(get_subdomain(request), profile.realm.subdomain) and # Allow access to localhost for Tornado not (settings.RUNNING_INSIDE_TORNADO and request.META["SERVER_NAME"] == "127.0.0.1" and request.META["REMOTE_ADDR"] == "127.0.0.1")): logging.warning("User %s attempted to access API on wrong subdomain %s" % ( profile.email, get_subdomain(request))) raise JsonableError(_("Account is not associated with this subdomain")) return profile
def do_auth(self, *args, **kwargs): # type: (*Any, **Any) -> Optional[HttpResponse] kwargs["return_data"] = {} request = self.strategy.request kwargs["realm_subdomain"] = get_subdomain(request) user_profile = None team_id = settings.SOCIAL_AUTH_GITHUB_TEAM_ID org_name = settings.SOCIAL_AUTH_GITHUB_ORG_NAME if team_id is None and org_name is None: user_profile = GithubOAuth2.do_auth(self, *args, **kwargs) elif team_id: backend = GithubTeamOAuth2(self.strategy, self.redirect_uri) try: user_profile = backend.do_auth(*args, **kwargs) except AuthFailed: logging.info("User profile not member of team.") user_profile = None elif org_name: backend = GithubOrganizationOAuth2(self.strategy, self.redirect_uri) try: user_profile = backend.do_auth(*args, **kwargs) except AuthFailed: logging.info("User profile not member of organisation.") user_profile = None return self.process_do_auth(user_profile, *args, **kwargs)
def json_fetch_api_key(request, user_profile, password=REQ(default='')): # type: (HttpRequest, UserProfile, str) -> HttpResponse if password_auth_enabled(user_profile.realm): if not authenticate(username=user_profile.email, password=password, realm_subdomain=get_subdomain(request)): return json_error(_("Your username or password is incorrect.")) return json_success({"api_key": user_profile.api_key})
def send_mail(self, subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None): # type: (str, str, Dict[str, Any], str, str, str) -> None """ Currently we don't support accounts in multiple subdomains using a single email addresss. We override this function so that we do not send a reset link to an email address if the reset attempt is done on the subdomain which does not match user.realm.subdomain. Once we start supporting accounts with the same email in multiple subdomains, we may be able to delete or refactor this function. """ user_realm = get_user_profile_by_email(to_email).realm attempted_subdomain = get_subdomain(getattr(self, 'request')) context['attempted_realm'] = False if not check_subdomain(user_realm.subdomain, attempted_subdomain): context['attempted_realm'] = get_realm(attempted_subdomain) super(ZulipPasswordResetForm, self).send_mail( subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=html_email_template_name )
def authenticate_log_and_execute_json(request, view_func, *args, **kwargs): # type: (HttpRequest, Callable[..., HttpResponse], *Any, **Any) -> HttpResponse if not request.user.is_authenticated(): return json_error(_("Not logged in"), status=401) user_profile = request.user if not user_profile.is_active: raise JsonableError(_("Account not active")) if user_profile.realm.deactivated: raise JsonableError(_("Realm for account has been deactivated")) if user_profile.is_incoming_webhook: raise JsonableError(_("Webhook bots can only access webhooks")) if ( not check_subdomain(get_subdomain(request), user_profile.realm.subdomain) and # Exclude the SOCKET requests from this filter; they were # checked when the original websocket request reached Tornado not (request.method == "SOCKET" and request.META["SERVER_NAME"] == "127.0.0.1") ): logging.warning( "User %s attempted to access JSON API on wrong subdomain %s" % (user_profile.email, get_subdomain(request)) ) raise JsonableError(_("Account is not associated with this subdomain")) process_client(request, user_profile, True) request._email = user_profile.email return view_func(request, user_profile, *args, **kwargs)
def get_auth_backends_data(request): # type: (HttpRequest) -> Dict[str, Any] """Returns which authentication methods are enabled on the server""" if settings.REALMS_HAVE_SUBDOMAINS: subdomain = get_subdomain(request) try: realm = Realm.objects.get(string_id=subdomain) except Realm.DoesNotExist: # If not the root subdomain, this is an error if subdomain != "": raise JsonableError(_("Invalid subdomain")) # With the root subdomain, it's an error or not depending # whether SUBDOMAINS_HOMEPAGE (which indicates whether # there are some realms without subdomains on this server) # is set. if settings.SUBDOMAINS_HOMEPAGE: raise JsonableError(_("Subdomain required")) else: realm = None else: # Without subdomains, we just have to report what the server # supports, since we don't know the realm. realm = None return {"password": password_auth_enabled(realm), "dev": dev_auth_enabled(realm), "github": github_auth_enabled(realm), "google": google_auth_enabled(realm)}
def api_dev_fetch_api_key(request, username=REQ()): # type: (HttpRequest, str) -> 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. """ if not dev_auth_enabled() or settings.PRODUCTION: return json_error(_("Dev environment not enabled.")) # 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) return_data = {} # type: Dict[str, bool] user_profile = authenticate(username=username, realm_subdomain=get_subdomain(request), return_data=return_data) if return_data.get("inactive_realm"): return json_error(_("Your realm 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) login(request, user_profile) return json_success({"api_key": user_profile.api_key, "email": user_profile.email})
def log_into_subdomain(request): # type: (HttpRequest) -> HttpResponse try: # Discard state if older than 15 seconds state = request.get_signed_cookie('subdomain.signature', salt='zerver.views.auth', max_age=15) except KeyError: logging.warning('Missing subdomain signature cookie.') return HttpResponse(status=400) except signing.BadSignature: logging.warning('Subdomain cookie has bad signature.') return HttpResponse(status=400) data = ujson.loads(state) if data['subdomain'] != get_subdomain(request): logging.warning('Login attemp on invalid subdomain') return HttpResponse(status=400) email_address = data['email'] full_name = data['name'] is_signup = data['is_signup'] if is_signup: # If we are signing up, user_profile should be None. In case # email_address already exists, user will get an error message. user_profile = None return_data = {} # type: Dict[str, Any] else: user_profile, return_data = authenticate_remote_user(request, email_address) invalid_subdomain = bool(return_data.get('invalid_subdomain')) return login_or_register_remote_user(request, email_address, user_profile, full_name, invalid_subdomain=invalid_subdomain, is_signup=is_signup)
def do_auth(self, *args, **kwargs): # type: (*Any, **Any) -> Optional[UserProfile] kwargs['return_data'] = {} request = self.strategy.request # type: ignore # This comes from Python Social Auth. kwargs['realm_subdomain'] = get_subdomain(request) user_profile = None team_id = settings.SOCIAL_AUTH_GITHUB_TEAM_ID org_name = settings.SOCIAL_AUTH_GITHUB_ORG_NAME if (team_id is None and org_name is None): user_profile = GithubOAuth2.do_auth(self, *args, **kwargs) elif (team_id): backend = GithubTeamOAuth2(self.strategy, self.redirect_uri) try: user_profile = backend.do_auth(*args, **kwargs) except AuthFailed: logging.info("User profile not member of team.") user_profile = None elif (org_name): backend = GithubOrganizationOAuth2(self.strategy, self.redirect_uri) try: user_profile = backend.do_auth(*args, **kwargs) except AuthFailed: logging.info("User profile not member of organisation.") user_profile = None return self.process_do_auth(user_profile, *args, **kwargs)
def send_mail(self, subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None): # type: (str, str, Dict[str, Any], str, str, str) -> None """ Currently we don't support accounts in multiple subdomains using a single email address. We override this function so that we do not send a reset link to an email address if the reset attempt is done on the subdomain which does not match user.realm.subdomain. Once we start supporting accounts with the same email in multiple subdomains, we may be able to refactor this function. A second reason we override this function is so that we can send the mail through the functions in zerver.lib.send_email, to match how we send all other mail in the codebase. """ user = get_user_profile_by_email(to_email) attempted_subdomain = get_subdomain(getattr(self, 'request')) context['attempted_realm'] = False if not check_subdomain(user.realm.subdomain, attempted_subdomain): context['attempted_realm'] = get_realm(attempted_subdomain) send_email('zerver/emails/password_reset', to_user_id=user.id, from_name="Zulip Account Security", from_address=FromAddress.NOREPLY, context=context)
def log_into_subdomain(request): # type: (HttpRequest) -> HttpResponse try: # Discard state if older than 15 seconds state = request.get_signed_cookie('subdomain.signature', salt='zerver.views.auth', max_age=15) except KeyError: logging.warning('Missing subdomain signature cookie.') return HttpResponse(status=400) except signing.BadSignature: logging.warning('Subdomain cookie has bad signature.') return HttpResponse(status=400) data = ujson.loads(state) if data['subdomain'] != get_subdomain(request): logging.warning('Login attemp on invalid subdomain') return HttpResponse(status=400) email_address = data['email'] full_name = data['name'] user_profile, return_data = authenticate_remote_user(request, email_address) invalid_subdomain = bool(return_data.get('invalid_subdomain')) return login_or_register_remote_user(request, email_address, user_profile, full_name, invalid_subdomain)
def get_realm_from_request(request): # type: (HttpRequest) -> Realm if settings.REALMS_HAVE_SUBDOMAINS: realm_str = get_subdomain(request) else: realm_str = request.session.get("realm_str") return get_realm(realm_str)
def authenticate_remote_user(request, email_address): # type: (HttpRequest, str) -> Tuple[UserProfile, Dict[str, Any]] return_data = {} # type: Dict[str, bool] user_profile = authenticate(username=email_address, realm_subdomain=get_subdomain(request), use_dummy_backend=True, return_data=return_data) return user_profile, return_data
def remote_user_sso(request): # type: (HttpRequest) -> HttpResponse try: remote_user = request.META["REMOTE_USER"] except KeyError: raise JsonableError(_("No REMOTE_USER set.")) user_profile = authenticate(remote_user=remote_user, realm_subdomain=get_subdomain(request)) return login_or_register_remote_user(request, remote_user, user_profile)
def get_realm_from_request(request): # type: (HttpRequest) -> Optional[Realm] if hasattr(request, "user") and hasattr(request.user, "realm"): return request.user.realm elif settings.REALMS_HAVE_SUBDOMAINS: subdomain = get_subdomain(request) return get_realm(subdomain) # This will return None if there is no unique, open realm. return get_unique_non_system_realm()
def redirect_to_main_site(request, url): # type: (HttpRequest, Text) -> HttpResponse main_site_uri = ''.join(( settings.EXTERNAL_URI_SCHEME, settings.EXTERNAL_HOST, url, )) params = {'subdomain': get_subdomain(request)} return redirect(main_site_uri + '?' + urllib.parse.urlencode(params))
def start_google_oauth2(request): # type: (HttpRequest) -> HttpResponse main_site_uri = ''.join(( settings.EXTERNAL_URI_SCHEME, settings.EXTERNAL_HOST, reverse('zerver.views.auth.send_oauth_request_to_google'), )) params = {'subdomain': get_subdomain(request)} return redirect(main_site_uri + '?' + urllib.parse.urlencode(params))
def logged_in_and_active(request): # type: (HttpRequest) -> bool if not request.user.is_authenticated(): return False if not request.user.is_active: return False if request.user.realm.deactivated: return False return check_subdomain(get_subdomain(request), request.user.realm.subdomain)
def process_response(self, request, response): # type: (HttpRequest, HttpResponse) -> HttpResponse if settings.REALMS_HAVE_SUBDOMAINS: if (not request.path.startswith("/static/") and not request.path.startswith("/api/") and not request.path.startswith("/json/")): subdomain = get_subdomain(request) if (request.get_host() == "127.0.0.1:9991" or request.get_host() == "localhost:9991"): return redirect("%s%s" % (settings.EXTERNAL_URI_SCHEME, settings.EXTERNAL_HOST)) if subdomain != "": realm = resolve_subdomain_to_realm(subdomain) if (realm is None): return render_to_response("zerver/invalid_realm.html") """ If request.session was modified, or if the configuration is to save the session every time, save the changes and set a session cookie. """ try: accessed = request.session.accessed modified = request.session.modified except AttributeError: pass else: if accessed: patch_vary_headers(response, ('Cookie',)) if modified or settings.SESSION_SAVE_EVERY_REQUEST: if request.session.get_expire_at_browser_close(): max_age = None expires = None else: max_age = request.session.get_expiry_age() expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. # Skip session save for 500 responses, refs #3881. if response.status_code != 500: request.session.save() host = request.get_host().split(':')[0] session_cookie_domain = settings.SESSION_COOKIE_DOMAIN # The subdomains feature overrides the # SESSION_COOKIE_DOMAIN setting, since the setting # is a fixed value and with subdomains enabled, # the session cookie domain has to vary with the # subdomain. if settings.REALMS_HAVE_SUBDOMAINS: session_cookie_domain = host response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=session_cookie_domain, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response
def api_fetch_api_key(request, username=REQ(), password=REQ()): # type: (HttpRequest, str, str) -> HttpResponse return_data = {} # type: Dict[str, bool] if username == "google-oauth2-token": user_profile = authenticate(google_oauth2_token=password, realm_subdomain=get_subdomain(request), return_data=return_data) else: if not ldap_auth_enabled(realm=get_realm_from_request(request)): # In case we don't authenticate against LDAP, check for a valid # email. LDAP backend can authenticate against a non-email. validate_login_email(username) user_profile = authenticate(username=username, password=password, realm_subdomain=get_subdomain(request), return_data=return_data) if return_data.get("inactive_user"): return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if return_data.get("inactive_realm"): return json_error(_("Your realm has been deactivated."), data={"reason": "realm deactivated"}, status=403) if return_data.get("password_auth_disabled"): return json_error(_("Password auth is disabled in your team."), data={"reason": "password auth disabled"}, status=403) if user_profile is None: if return_data.get("valid_attestation"): # We can leak that the user is unregistered iff they present a valid authentication string for the user. return json_error(_("This user is not registered; do so from a browser."), data={"reason": "unregistered"}, status=403) return json_error(_("Your username or password is incorrect."), data={"reason": "incorrect_creds"}, status=403) # Maybe sending 'user_logged_in' signal is the better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) # Not doing this only because over here we don't add the user information # in the session. If the signal receiver assumes that we do then that # would cause problems. email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) return json_success({"api_key": user_profile.api_key, "email": user_profile.email})
def clean_username(self): # type: () -> str email = self.cleaned_data['username'] try: user_profile = get_user_profile_by_email(email) except UserProfile.DoesNotExist: return email if user_profile.realm.deactivated: error_msg = u"""Sorry for the trouble, but %s has been deactivated. Please contact %s to reactivate this group.""" % ( user_profile.realm.name, settings.ZULIP_ADMINISTRATOR) raise ValidationError(mark_safe(error_msg)) if not check_subdomain(get_subdomain(self.request), user_profile.realm.subdomain): logging.warning("User %s attempted to password login to wrong subdomain %s" % (user_profile.email, get_subdomain(self.request))) raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR)) return email
def validate_api_key(request, role, api_key, is_webhook=False): # type: (HttpRequest, text_type, text_type, bool) -> Union[UserProfile, Deployment] # Remove whitespace to protect users from trivial errors. role, api_key = role.strip(), api_key.strip() try: profile = get_deployment_or_userprofile(role) except UserProfile.DoesNotExist: raise JsonableError(_("Invalid user: %s") % (role,)) except Deployment.DoesNotExist: raise JsonableError(_("Invalid deployment: %s") % (role,)) if api_key != profile.api_key: if len(api_key) != 32: reason = _("Incorrect API key length (keys should be 32 " "characters long) for role '%s'") else: reason = _("Invalid API key for role '%s'") raise JsonableError(reason % (role,)) if not profile.is_active: raise JsonableError(_("Account not active")) if profile.is_incoming_webhook and not is_webhook: raise JsonableError(_("Account is not valid to post webhook messages")) try: if profile.realm.deactivated: raise JsonableError(_("Realm for account has been deactivated")) except AttributeError: # Deployment objects don't have realms pass if (not check_subdomain(get_subdomain(request), profile.realm.subdomain) # Allow access to localhost for Tornado and not (settings.RUNNING_INSIDE_TORNADO and request.META["SERVER_NAME"] == "127.0.0.1" and request.META["REMOTE_ADDR"] == "127.0.0.1")): logging.warning("User %s attempted to access API on wrong subdomain %s" % ( profile.email, get_subdomain(request))) raise JsonableError(_("Account is not associated with this subdomain")) return profile
def home(request): # type: (HttpRequest) -> HttpResponse if not settings.SUBDOMAINS_HOMEPAGE: return home_real(request) # If settings.SUBDOMAINS_HOMEPAGE, sends the user the landing # page, not the login form, on the root domain subdomain = get_subdomain(request) if subdomain != "": return home_real(request) return render_to_response('zerver/hello.html', request=request)
def _wrapped_func_arguments(request, api_key=REQ(), *args, **kwargs): # type: (HttpRequest, text_type, *Any, **Any) -> HttpResponse try: user_profile = UserProfile.objects.get(api_key=api_key) except UserProfile.DoesNotExist: raise JsonableError(_("Invalid API key")) if not user_profile.is_active: raise JsonableError(_("Account not active")) if user_profile.realm.deactivated: raise JsonableError(_("Realm for account has been deactivated")) if not check_subdomain(get_subdomain(request), user_profile.realm.subdomain): logging.warning("User %s attempted to access webhook API on wrong subdomain %s" % ( user_profile.email, get_subdomain(request))) raise JsonableError(_("Account is not associated with this subdomain")) request.user = user_profile request._email = user_profile.email webhook_client_name = "Zulip{}Webhook".format(client_name) process_client(request, user_profile, client_name=webhook_client_name) if settings.RATE_LIMITING: rate_limit_user(request, user_profile, domain='all') return view_func(request, user_profile, request.client, *args, **kwargs)
def remote_user_sso(request): # type: (HttpRequest) -> HttpResponse try: remote_user = request.META["REMOTE_USER"] except KeyError: raise JsonableError(_("No REMOTE_USER set.")) # 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(remote_user_to_email(remote_user)) user_profile = authenticate(remote_user=remote_user, realm_subdomain=get_subdomain(request)) return login_or_register_remote_user(request, remote_user, user_profile)
def dev_direct_login(request, **kwargs): # type: (HttpRequest, **Any) -> 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. raise Exception('Direct login not supported.') email = request.POST['direct_email'] user_profile = authenticate(username=email, realm_subdomain=get_subdomain(request)) if user_profile is None: raise Exception("User cannot login") login(request, user_profile) if settings.REALMS_HAVE_SUBDOMAINS and user_profile.realm.subdomain is not None: return HttpResponseRedirect(user_profile.realm.uri) return HttpResponseRedirect("%s%s" % (settings.EXTERNAL_URI_SCHEME, request.get_host()))
def do_auth(self, *args, **kwargs): # type: (*Any, **Any) -> Optional[HttpResponse] kwargs['return_data'] = {} request = self.strategy.request kwargs['realm_subdomain'] = get_subdomain(request) user_profile = None team_id = settings.SOCIAL_AUTH_GITHUB_TEAM_ID org_name = settings.SOCIAL_AUTH_GITHUB_ORG_NAME if (team_id is None and org_name is None): try: user_profile = GithubOAuth2.do_auth(self, *args, **kwargs) except AuthFailed: logging.info("User authentication failed.") user_profile = None elif (team_id): backend = GithubTeamOAuth2(self.strategy, self.redirect_uri) try: user_profile = backend.do_auth(*args, **kwargs) except AuthFailed: logging.info("User is not member of GitHub team.") user_profile = None elif (org_name): backend = GithubOrganizationOAuth2(self.strategy, self.redirect_uri) try: user_profile = backend.do_auth(*args, **kwargs) except AuthFailed: logging.info("User is not member of GitHub organization.") user_profile = None return self.process_do_auth(user_profile, *args, **kwargs)
def log_into_subdomain(request): # type: (HttpRequest) -> HttpResponse try: # Discard state if older than 15 seconds state = request.get_signed_cookie('subdomain.signature', salt='zerver.views.auth', max_age=15) except KeyError: logging.warning('Missing subdomain signature cookie.') return HttpResponse(status=400) except signing.BadSignature: logging.warning('Subdomain cookie has bad signature.') return HttpResponse(status=400) data = ujson.loads(state) if data['subdomain'] != get_subdomain(request): logging.warning('Login attemp on invalid subdomain') return HttpResponse(status=400) email_address = data['email'] full_name = data['name'] is_signup = data['is_signup'] if is_signup: # If we are signing up, user_profile should be None. In case # email_address already exists, user will get an error message. user_profile = None return_data = {} # type: Dict[str, Any] else: user_profile, return_data = authenticate_remote_user( request, email_address) invalid_subdomain = bool(return_data.get('invalid_subdomain')) return login_or_register_remote_user(request, email_address, user_profile, full_name, invalid_subdomain=invalid_subdomain, is_signup=is_signup)
def api_dev_fetch_api_key(request, username=REQ()): # type: (HttpRequest, str) -> 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. """ if not dev_auth_enabled() or settings.PRODUCTION: return json_error(_("Dev environment not enabled.")) # 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) return_data = {} # type: Dict[str, bool] user_profile = authenticate(username=username, realm_subdomain=get_subdomain(request), return_data=return_data) if return_data.get("inactive_realm"): return json_error(_("Your realm 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) return json_success({ "api_key": user_profile.api_key, "email": user_profile.email })
def get_auth_backends_data(request): # type: (HttpRequest) -> Dict[str, Any] """Returns which authentication methods are enabled on the server""" subdomain = get_subdomain(request) try: realm = Realm.objects.get(string_id=subdomain) except Realm.DoesNotExist: # If not the root subdomain, this is an error if subdomain != "": raise JsonableError(_("Invalid subdomain")) # With the root subdomain, it's an error or not depending # whether ROOT_DOMAIN_LANDING_PAGE (which indicates whether # there are some realms without subdomains on this server) # is set. if settings.ROOT_DOMAIN_LANDING_PAGE: raise JsonableError(_("Subdomain required")) else: realm = None return { "password": password_auth_enabled(realm), "dev": dev_auth_enabled(realm), "github": github_auth_enabled(realm), "google": google_auth_enabled(realm) }
def process_response(self, request, response): # type: (HttpRequest, HttpResponse) -> HttpResponse try: request.get_host() except DisallowedHost: # If we get a DisallowedHost exception trying to access # the host, (1) the request is failed anyway and so the # below code will do nothing, and (2) the below will # trigger a recursive exception, breaking things, so we # just return here. return response if settings.REALMS_HAVE_SUBDOMAINS: if (not request.path.startswith("/static/") and not request.path.startswith("/api/") and not request.path.startswith("/json/")): subdomain = get_subdomain(request) if (request.get_host() == "127.0.0.1:9991" or request.get_host() == "localhost:9991"): return redirect( "%s%s" % (settings.EXTERNAL_URI_SCHEME, settings.EXTERNAL_HOST)) if subdomain != "": realm = get_realm(subdomain) if (realm is None): return render(request, "zerver/invalid_realm.html") """ If request.session was modified, or if the configuration is to save the session every time, save the changes and set a session cookie. """ try: accessed = request.session.accessed modified = request.session.modified except AttributeError: pass else: if accessed: patch_vary_headers(response, ('Cookie', )) if modified or settings.SESSION_SAVE_EVERY_REQUEST: if request.session.get_expire_at_browser_close(): max_age = None expires = None else: max_age = request.session.get_expiry_age() expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. # Skip session save for 500 responses, refs #3881. if response.status_code != 500: request.session.save() host = request.get_host().split(':')[0] session_cookie_domain = settings.SESSION_COOKIE_DOMAIN # The subdomains feature overrides the # SESSION_COOKIE_DOMAIN setting, since the setting # is a fixed value and with subdomains enabled, # the session cookie domain has to vary with the # subdomain. if settings.REALMS_HAVE_SUBDOMAINS: session_cookie_domain = host response.set_cookie( settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=session_cookie_domain, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response
def zulip_default_context(request): # type: (HttpRequest) -> Dict[str, Any] """Context available to all Zulip Jinja2 templates that have a request passed in. Designed to provide the long list of variables at the bottom of this function in a wide range of situations: logged-in or logged-out, subdomains or not, etc. The main variable in the below is whether we know what realm the user is trying to interact with. """ realm = get_realm_from_request(request) if realm is None: realm_uri = settings.ROOT_DOMAIN_URI realm_name = None realm_icon = None realm_description = None realm_invite_required = False else: realm_uri = realm.uri realm_name = realm.name realm_icon = get_realm_icon_url(realm) realm_description_raw = realm.description or "The coolest place in the universe." realm_description = convert(realm_description_raw, message_realm=realm) realm_invite_required = realm.invite_required register_link_disabled = settings.REGISTER_LINK_DISABLED login_link_disabled = settings.LOGIN_LINK_DISABLED about_link_disabled = settings.ABOUT_LINK_DISABLED find_team_link_disabled = settings.FIND_TEAM_LINK_DISABLED if settings.ROOT_DOMAIN_LANDING_PAGE and get_subdomain(request) == "": register_link_disabled = True login_link_disabled = True about_link_disabled = True find_team_link_disabled = False apps_page_url = 'https://zulipchat.com/apps/' if settings.ZILENCER_ENABLED: apps_page_url = '/apps/' user_is_authenticated = False if hasattr(request, 'user') and hasattr(request.user, 'is_authenticated'): user_is_authenticated = request.user.is_authenticated.value if settings.DEVELOPMENT: secrets_path = "zproject/dev-secrets.conf" settings_path = "zproject/dev_settings.py" settings_comments_path = "zproject/prod_settings_template.py" else: secrets_path = "/etc/zulip/zulip-secrets.conf" settings_path = "/etc/zulip/settings.py" settings_comments_path = "/etc/zulip/settings.py" if hasattr(request, "client") and request.client.name == "ZulipElectron": platform = "ZulipElectron" # nocoverage else: platform = "ZulipWeb" return { 'root_domain_landing_page': settings.ROOT_DOMAIN_LANDING_PAGE, 'custom_logo_url': settings.CUSTOM_LOGO_URL, 'register_link_disabled': register_link_disabled, 'login_link_disabled': login_link_disabled, 'about_link_disabled': about_link_disabled, 'terms_of_service': settings.TERMS_OF_SERVICE, 'privacy_policy': settings.PRIVACY_POLICY, 'login_url': settings.HOME_NOT_LOGGED_IN, 'only_sso': settings.ONLY_SSO, 'external_api_path': settings.EXTERNAL_API_PATH, 'external_api_uri': settings.EXTERNAL_API_URI, 'external_host': settings.EXTERNAL_HOST, 'external_uri_scheme': settings.EXTERNAL_URI_SCHEME, 'realm_invite_required': realm_invite_required, 'realm_uri': realm_uri, 'realm_name': realm_name, 'realm_icon': realm_icon, 'realm_description': realm_description, 'root_domain_uri': settings.ROOT_DOMAIN_URI, 'api_site_required': settings.EXTERNAL_API_PATH != "api.zulip.com", 'email_gateway_example': settings.EMAIL_GATEWAY_EXAMPLE, 'apps_page_url': apps_page_url, 'open_realm_creation': settings.OPEN_REALM_CREATION, 'password_auth_enabled': password_auth_enabled(realm), 'dev_auth_enabled': dev_auth_enabled(realm), 'google_auth_enabled': google_auth_enabled(realm), 'github_auth_enabled': github_auth_enabled(realm), 'email_auth_enabled': email_auth_enabled(realm), 'require_email_format_usernames': require_email_format_usernames(realm), 'any_oauth_backend_enabled': any_oauth_backend_enabled(realm), 'no_auth_enabled': not auth_enabled_helper(list(AUTH_BACKEND_NAME_MAP.keys()), realm), 'development_environment': settings.DEVELOPMENT, 'support_email': FromAddress.SUPPORT, 'find_team_link_disabled': find_team_link_disabled, 'password_min_length': settings.PASSWORD_MIN_LENGTH, 'password_min_guesses': settings.PASSWORD_MIN_GUESSES, 'zulip_version': ZULIP_VERSION, 'user_is_authenticated': user_is_authenticated, 'settings_path': settings_path, 'secrets_path': secrets_path, 'settings_comments_path': settings_comments_path, 'platform': platform, }
def get_realm_from_request(request): # type: (HttpRequest) -> Optional[Realm] if hasattr(request, "user") and hasattr(request.user, "realm"): return request.user.realm subdomain = get_subdomain(request) return get_realm(subdomain)
def zulip_default_context(request): # type: (HttpRequest) -> Dict[str, Any] """Context available to all Zulip Jinja2 templates that have a request passed in. Designed to provide the long list of variables at the bottom of this function in a wide range of situations: logged-in or logged-out, subdomains or not, etc. The main variable in the below is whether we know the realm, which is the case if there is only one realm, or we're on a REALMS_HAVE_SUBDOMAINS subdomain, or the user is logged in. """ realm = get_realm_from_request(request) if realm is not None: realm_uri = realm.uri realm_name = realm.name realm_icon = get_realm_icon_url(realm) realm_description_raw = realm.description or "The coolest place in the universe." realm_description = convert(realm_description_raw, message_realm=realm) else: realm_uri = settings.SERVER_URI realm_name = None realm_icon = None realm_description = None register_link_disabled = settings.REGISTER_LINK_DISABLED login_link_disabled = settings.LOGIN_LINK_DISABLED about_link_disabled = settings.ABOUT_LINK_DISABLED find_team_link_disabled = settings.FIND_TEAM_LINK_DISABLED if settings.SUBDOMAINS_HOMEPAGE and get_subdomain(request) == "": register_link_disabled = True login_link_disabled = True about_link_disabled = True find_team_link_disabled = False return { 'realms_have_subdomains': settings.REALMS_HAVE_SUBDOMAINS, 'custom_logo_url': settings.CUSTOM_LOGO_URL, 'register_link_disabled': register_link_disabled, 'login_link_disabled': login_link_disabled, 'about_link_disabled': about_link_disabled, 'show_oss_announcement': settings.SHOW_OSS_ANNOUNCEMENT, 'zulip_admin': settings.ZULIP_ADMINISTRATOR, 'terms_of_service': settings.TERMS_OF_SERVICE, 'privacy_policy': settings.PRIVACY_POLICY, 'login_url': settings.HOME_NOT_LOGGED_IN, 'only_sso': settings.ONLY_SSO, 'external_api_path': settings.EXTERNAL_API_PATH, 'external_api_uri': settings.EXTERNAL_API_URI, 'external_host': settings.EXTERNAL_HOST, 'external_uri_scheme': settings.EXTERNAL_URI_SCHEME, 'realm_uri': realm_uri, 'realm_name': realm_name, 'realm_icon': realm_icon, 'realm_description': realm_description, 'server_uri': settings.SERVER_URI, 'api_site_required': settings.EXTERNAL_API_PATH != "api.zulip.com", 'email_gateway_example': settings.EMAIL_GATEWAY_EXAMPLE, 'open_realm_creation': settings.OPEN_REALM_CREATION, 'password_auth_enabled': password_auth_enabled(realm), 'dev_auth_enabled': dev_auth_enabled(realm), 'google_auth_enabled': google_auth_enabled(realm), 'github_auth_enabled': github_auth_enabled(realm), 'any_oauth_backend_enabled': any_oauth_backend_enabled(realm), 'no_auth_enabled': not auth_enabled_helper(list(AUTH_BACKEND_NAME_MAP.keys()), realm), 'development_environment': settings.DEVELOPMENT, 'support_email': settings.ZULIP_ADMINISTRATOR, 'find_team_link_disabled': find_team_link_disabled, 'password_min_length': settings.PASSWORD_MIN_LENGTH, 'password_min_quality': settings.PASSWORD_MIN_ZXCVBN_QUALITY, 'zulip_version': ZULIP_VERSION, }
def finish_google_oauth2(request): # type: (HttpRequest) -> HttpResponse error = request.GET.get('error') if error == 'access_denied': return redirect('/') elif error is not None: logging.warning('Error from google oauth2 login: %s' % (request.GET.get("error"), )) return HttpResponse(status=400) csrf_state = request.GET.get('state') if csrf_state is None or len(csrf_state.split(':')) != 2: logging.warning('Missing Google oauth2 CSRF state') return HttpResponse(status=400) value, hmac_value = csrf_state.split(':') if hmac_value != google_oauth2_csrf(request, value): logging.warning('Google oauth2 CSRF error') return HttpResponse(status=400) resp = requests.post( 'https://www.googleapis.com/oauth2/v3/token', data={ 'code': request.GET.get('code'), 'client_id': settings.GOOGLE_OAUTH2_CLIENT_ID, 'client_secret': settings.GOOGLE_OAUTH2_CLIENT_SECRET, 'redirect_uri': ''.join(( settings.EXTERNAL_URI_SCHEME, request.get_host(), reverse('zerver.views.auth.finish_google_oauth2'), )), 'grant_type': 'authorization_code', }, ) if resp.status_code == 400: logging.warning( 'User error converting Google oauth2 login to token: %s' % (resp.text, )) return HttpResponse(status=400) elif resp.status_code != 200: logging.error( 'Could not convert google oauth2 code to access_token: %s' % (resp.text, )) return HttpResponse(status=400) access_token = resp.json()['access_token'] resp = requests.get('https://www.googleapis.com/plus/v1/people/me', params={'access_token': access_token}) if resp.status_code == 400: logging.warning('Google login failed making info API call: %s' % (resp.text, )) return HttpResponse(status=400) elif resp.status_code != 200: logging.error('Google login failed making API call: %s' % (resp.text, )) return HttpResponse(status=400) body = resp.json() try: full_name = body['name']['formatted'] except KeyError: # Only google+ users have a formated name. I am ignoring i18n here. full_name = u'{} {}'.format(body['name']['givenName'], body['name']['familyName']) for email in body['emails']: if email['type'] == 'account': break else: logging.error('Google oauth2 account email not found: %s' % (body, )) return HttpResponse(status=400) email_address = email['value'] return_data = {} # type: Dict[str, bool] user_profile = authenticate(username=email_address, realm_subdomain=get_subdomain(request), use_dummy_backend=True, return_data=return_data) invalid_subdomain = bool(return_data.get('invalid_subdomain')) return login_or_register_remote_user(request, email_address, user_profile, full_name, invalid_subdomain)
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 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 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. realm = prereg_user.realm elif realm_creation: # For creating a new realm, there is no existing realm or domain realm = None elif settings.REALMS_HAVE_SUBDOMAINS: realm = get_realm(get_subdomain(request)) else: realm = get_realm_by_email_domain(email) if realm and not email_allowed_for_realm(email, realm): return render(request, "zerver/closed_realm.html", context={"closed_domain_name": realm.name}) if realm and realm.deactivated: # The user is trying to register for a deactivated realm. Advise them to # contact support. return render(request, "zerver/deactivated.html", context={ "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: # 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 "" }) 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: string_id = form.cleaned_data['realm_subdomain'] realm_name = form.cleaned_data['realm_name'] org_type = int(form.cleaned_data['realm_org_type']) realm = do_create_realm(string_id, 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: user_profile = existing_user_profile do_activate_user(user_profile) do_change_password(user_profile, password) do_change_full_name(user_profile, full_name) 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.home')) return render( request, 'zerver/register.html', 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, 'realms_have_subdomains': settings.REALMS_HAVE_SUBDOMAINS, 'password_auth_enabled': password_auth_enabled(realm), '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), 'MAX_REALM_SUBDOMAIN_LENGTH': str(Realm.MAX_REALM_SUBDOMAIN_LENGTH) })