import os from django.conf import settings from django.conf.urls import include from django.conf.urls.i18n import i18n_patterns from django.contrib.auth.views import ( LoginView, PasswordResetCompleteView, PasswordResetConfirmView, PasswordResetDoneView, ) from django.urls import path from django.utils.module_loading import import_string from django.views.generic import RedirectView, TemplateView from zerver.forms import LoggingSetPasswordForm from zerver.lib.integrations import WEBHOOK_INTEGRATIONS from zerver.lib.rest import rest_path from zerver.tornado.views import cleanup_event_queue, get_events, get_events_internal, notify from zerver.views.alert_words import add_alert_words, list_alert_words, remove_alert_words from zerver.views.archive import archive, get_web_public_topics_backend from zerver.views.attachments import list_by_user, remove from zerver.views.auth import ( api_dev_fetch_api_key, api_dev_list_users, api_fetch_api_key, api_fetch_google_client_id, api_get_server_settings, dev_direct_login, json_fetch_api_key, log_into_subdomain, login_page, logout_then_login, password_reset, remote_user_jwt, remote_user_sso, saml_sp_metadata, show_deactivation_notice, start_remote_user_sso, start_social_login, start_social_signup, ) from zerver.views.camo import handle_camo_url from zerver.views.compatibility import check_global_compatibility from zerver.views.custom_profile_fields import ( create_realm_custom_profile_field, delete_realm_custom_profile_field, list_realm_custom_profile_fields, remove_user_custom_profile_data, reorder_realm_custom_profile_fields, update_realm_custom_profile_field, update_user_custom_profile_data, ) from zerver.views.digest import digest_page from zerver.views.documentation import IntegrationView, MarkdownDirectoryView, integration_doc from zerver.views.drafts import create_drafts, delete_draft, edit_draft, fetch_drafts from zerver.views.email_mirror import email_mirror_message from zerver.views.events_register import events_register_backend from zerver.views.home import accounts_accept_terms, desktop_home, home from zerver.views.hotspots import mark_hotspot_as_read from zerver.views.invite import ( generate_multiuse_invite_backend, get_user_invites, invite_users_backend, resend_user_invite_email, revoke_multiuse_invite, revoke_user_invite, ) from zerver.views.message_edit import ( delete_message_backend, get_message_edit_history, json_fetch_raw_message, update_message_backend, ) from zerver.views.message_fetch import get_messages_backend, messages_in_narrow_backend from zerver.views.message_flags import ( mark_all_as_read, mark_stream_as_read, mark_topic_as_read, update_message_flags, ) from zerver.views.message_send import render_message_backend, send_message_backend, zcommand_backend from zerver.views.muting import update_muted_topic from zerver.views.portico import ( app_download_link_redirect, apps_view, hello_view, landing_view, plans_view, privacy_view, team_view, terms_view, ) from zerver.views.presence import ( get_presence_backend, get_statuses_for_realm, update_active_status_backend, update_user_status_backend, ) from zerver.views.push_notifications import ( add_android_reg_id, add_apns_device_token, remove_android_reg_id, remove_apns_device_token, ) from zerver.views.reactions import add_reaction, remove_reaction from zerver.views.realm import ( check_subdomain_available, deactivate_realm, realm_reactivation, update_realm, ) from zerver.views.realm_domains import ( create_realm_domain, delete_realm_domain, list_realm_domains, patch_realm_domain, ) from zerver.views.realm_emoji import delete_emoji, list_emoji, upload_emoji from zerver.views.realm_export import delete_realm_export, export_realm, get_realm_exports from zerver.views.realm_filters import create_filter, delete_filter, list_filters from zerver.views.realm_icon import delete_icon_backend, get_icon_backend, upload_icon from zerver.views.realm_logo import delete_logo_backend, get_logo_backend, upload_logo from zerver.views.registration import ( accounts_home, accounts_home_from_multiuse_invite, accounts_register, check_prereg_key_and_redirect, create_realm, find_account, generate_204, realm_redirect, ) from zerver.views.report import ( report_csp_violations, report_error, report_narrow_times, report_send_times, report_unnarrow_times, ) from zerver.views.storage import get_storage, remove_storage, update_storage from zerver.views.streams import ( add_default_stream, add_subscriptions_backend, create_default_stream_group, deactivate_stream_backend, delete_in_topic, get_streams_backend, get_subscribers_backend, get_topics_backend, json_get_stream_id, list_subscriptions_backend, remove_default_stream, remove_default_stream_group, remove_subscriptions_backend, update_default_stream_group_info, update_default_stream_group_streams, update_stream_backend, update_subscription_properties_backend, update_subscriptions_backend, update_subscriptions_property, ) from zerver.views.submessage import process_submessage from zerver.views.thumbnail import backend_serve_thumbnail from zerver.views.tutorial import set_tutorial_status from zerver.views.typing import send_notification_backend from zerver.views.unsubscribe import email_unsubscribe from zerver.views.upload import ( serve_file_backend, serve_file_url_backend, serve_local_file_unauthed, upload_file_backend, ) from zerver.views.user_groups import ( add_user_group, delete_user_group, edit_user_group, get_user_group, update_user_group_backend, ) from zerver.views.user_settings import ( change_enter_sends, confirm_email_change, delete_avatar_backend, json_change_notify_settings, json_change_settings, regenerate_api_key, set_avatar_backend, update_display_settings_backend, ) from zerver.views.users import ( add_bot_backend, avatar, create_user_backend, deactivate_bot_backend, deactivate_user_backend, deactivate_user_own_backend, get_bots_backend, get_members_backend, get_profile_backend, get_subscription_backend, patch_bot_backend, reactivate_user_backend, regenerate_bot_api_key, update_user_backend, ) from zerver.views.video_calls import ( complete_zoom_user, deauthorize_zoom_user, get_bigbluebutton_url, join_bigbluebutton, make_zoom_video_call, register_zoom_user, ) from zerver.views.zephyr import webathena_kerberos_login from zproject import dev_urls from zproject.legacy_urls import legacy_urls if settings.TWO_FACTOR_AUTHENTICATION_ENABLED: from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls from two_factor.urls import urlpatterns as tf_urls # NB: There are several other pieces of code which route requests by URL: # # - legacy_urls.py contains API endpoint written before the redesign # and should not be added to. # # - runtornado.py has its own URL list for Tornado views. See the # invocation of web.Application in that file. # # - The Nginx config knows which URLs to route to Django or Tornado. # # - Likewise for the local dev server in tools/run-dev.py. # These endpoints constitute the currently designed API (V1), which uses: # * REST verbs # * Basic auth (username:password is email:apiKey) # * Take and return json-formatted data # # If you're adding a new endpoint to the code that requires authentication, # please add it here. # See rest_dispatch in zerver.lib.rest for an explanation of auth methods used # # All of these paths are accessed by either a /json or /api/v1 prefix; # e.g. `PATCH /json/realm` or `PATCH /api/v1/realm`. v1_api_and_json_patterns = [ # realm-level calls rest_path('realm', PATCH=update_realm), # Returns a 204, used by desktop app to verify connectivity status path('generate_204', generate_204), path('realm/subdomain/', check_subdomain_available), # realm/domains -> zerver.views.realm_domains rest_path('realm/domains', GET=list_realm_domains, POST=create_realm_domain), rest_path('realm/domains/', PATCH=patch_realm_domain, DELETE=delete_realm_domain), # realm/emoji -> zerver.views.realm_emoji rest_path('realm/emoji', GET=list_emoji), rest_path('realm/emoji/', POST=upload_emoji, DELETE=(delete_emoji, {"intentionally_undocumented"})), # this endpoint throws a status code 400 JsonableError when it should be a 404. # realm/icon -> zerver.views.realm_icon rest_path('realm/icon', POST=upload_icon, DELETE=delete_icon_backend, GET=get_icon_backend), # realm/logo -> zerver.views.realm_logo rest_path('realm/logo', POST=upload_logo, DELETE=delete_logo_backend, GET=get_logo_backend), # realm/filters -> zerver.views.realm_filters rest_path('realm/filters', GET=list_filters, POST=create_filter), rest_path('realm/filters/', DELETE=delete_filter), # realm/profile_fields -> zerver.views.custom_profile_fields rest_path('realm/profile_fields', GET=list_realm_custom_profile_fields, PATCH=reorder_realm_custom_profile_fields, POST=create_realm_custom_profile_field), rest_path('realm/profile_fields/', PATCH=update_realm_custom_profile_field, DELETE=delete_realm_custom_profile_field), # realm/deactivate -> zerver.views.deactivate_realm rest_path('realm/deactivate', POST=deactivate_realm), # users -> zerver.views.users rest_path('users', GET=get_members_backend, POST=create_user_backend), rest_path('users/me', GET=get_profile_backend, DELETE=deactivate_user_own_backend), rest_path('users//reactivate', POST=reactivate_user_backend), rest_path('users/', GET=get_members_backend, PATCH=update_user_backend, DELETE=deactivate_user_backend), rest_path('users//subscriptions/', GET=get_subscription_backend), rest_path('bots', GET=get_bots_backend, POST=add_bot_backend), rest_path('bots//api_key/regenerate', POST=regenerate_bot_api_key), rest_path('bots/', PATCH=patch_bot_backend, DELETE=deactivate_bot_backend), # invites -> zerver.views.invite rest_path('invites', GET=get_user_invites, POST=invite_users_backend), rest_path('invites/', DELETE=revoke_user_invite), rest_path('invites//resend', POST=resend_user_invite_email), # invites/multiuse -> zerver.views.invite rest_path('invites/multiuse', POST=generate_multiuse_invite_backend), # invites/multiuse -> zerver.views.invite rest_path('invites/multiuse/', DELETE=revoke_multiuse_invite), # mark messages as read (in bulk) rest_path('mark_all_as_read', POST=mark_all_as_read), rest_path('mark_stream_as_read', POST=mark_stream_as_read), rest_path('mark_topic_as_read', POST=mark_topic_as_read), rest_path('zcommand', POST=zcommand_backend), # Endpoints for syncing drafts. rest_path('drafts', GET=(fetch_drafts, {'intentionally_undocumented'}), POST=(create_drafts, {'intentionally_undocumented'})), rest_path('drafts/', PATCH=(edit_draft, {'intentionally_undocumented'}), DELETE=(delete_draft, {'intentionally_undocumented'})), # messages -> zerver.views.message* # GET returns messages, possibly filtered, POST sends a message rest_path('messages', GET=(get_messages_backend, {'allow_anonymous_user_web'}), POST=(send_message_backend, {'allow_incoming_webhooks'})), rest_path('messages/', GET=json_fetch_raw_message, PATCH=update_message_backend, DELETE=delete_message_backend), rest_path('messages/render', POST=render_message_backend), rest_path('messages/flags', POST=update_message_flags), rest_path('messages//history', GET=get_message_edit_history), rest_path('messages/matches_narrow', GET=messages_in_narrow_backend), rest_path('users/me/subscriptions/properties', POST=update_subscription_properties_backend), rest_path('users/me/subscriptions/', PATCH=update_subscriptions_property), rest_path('submessage', POST=process_submessage), # New endpoint for handling reactions. # reactions -> zerver.view.reactions # POST adds a reaction to a message # DELETE removes a reaction from a message rest_path('messages//reactions', POST=add_reaction, DELETE=remove_reaction), # attachments -> zerver.views.attachments rest_path('attachments', GET=list_by_user), rest_path('attachments/', DELETE=remove), # typing -> zerver.views.typing # POST sends a typing notification event to recipients rest_path('typing', POST=send_notification_backend), # user_uploads -> zerver.views.upload rest_path('user_uploads', POST=upload_file_backend), rest_path('user_uploads//', GET=(serve_file_url_backend, {'override_api_url_scheme'})), # bot_storage -> zerver.views.storage rest_path('bot_storage', PUT=update_storage, GET=get_storage, DELETE=remove_storage), # Endpoint used by mobile devices to register their push # notification credentials rest_path('users/me/apns_device_token', POST=add_apns_device_token, DELETE=remove_apns_device_token), rest_path('users/me/android_gcm_reg_id', POST=add_android_reg_id, DELETE=remove_android_reg_id), # users/*/presnece => zerver.views.presence. rest_path('users/me/presence', POST=update_active_status_backend), # It's important that this sit after users/me/presence so that # Django's URL resolution order doesn't break the # /users/me/presence endpoint. rest_path('users//presence', GET=get_presence_backend), rest_path('realm/presence', GET=get_statuses_for_realm), rest_path('users/me/status', POST=update_user_status_backend), # user_groups -> zerver.views.user_groups rest_path('user_groups', GET=get_user_group), rest_path('user_groups/create', POST=add_user_group), rest_path('user_groups/', PATCH=edit_user_group, DELETE=delete_user_group), rest_path('user_groups//members', POST=update_user_group_backend), # users/me -> zerver.views.user_settings rest_path('users/me/api_key/regenerate', POST=regenerate_api_key), rest_path('users/me/enter-sends', POST=(change_enter_sends, # This endpoint should be folded into user settings {'intentionally_undocumented'})), rest_path('users/me/avatar', POST=set_avatar_backend, DELETE=delete_avatar_backend), # users/me/hotspots -> zerver.views.hotspots rest_path('users/me/hotspots', POST=(mark_hotspot_as_read, # This endpoint is low priority for documentation as # it is part of the webapp-specific tutorial. {'intentionally_undocumented'})), # users/me/tutorial_status -> zerver.views.tutorial rest_path('users/me/tutorial_status', POST=(set_tutorial_status, # This is a relic of an old Zulip tutorial model and # should be deleted. {'intentionally_undocumented'})), # settings -> zerver.views.user_settings rest_path('settings', PATCH=json_change_settings), rest_path('settings/display', PATCH=update_display_settings_backend), rest_path('settings/notifications', PATCH=json_change_notify_settings), # users/me/alert_words -> zerver.views.alert_words rest_path('users/me/alert_words', GET=list_alert_words, POST=add_alert_words, DELETE=remove_alert_words), # users/me/custom_profile_data -> zerver.views.custom_profile_data rest_path('users/me/profile_data', PATCH=update_user_custom_profile_data, DELETE=remove_user_custom_profile_data), rest_path('users/me//topics', GET=(get_topics_backend, {'allow_anonymous_user_web'})), # streams -> zerver.views.streams # (this API is only used externally) rest_path('streams', GET=get_streams_backend), # GET returns `stream_id`, stream name should be encoded in the URL query (in `stream` param) rest_path('get_stream_id', GET=json_get_stream_id), # GET returns "stream info" (undefined currently?), HEAD returns whether stream exists (200 or 404) rest_path('streams//members', GET=get_subscribers_backend), rest_path('streams/', PATCH=update_stream_backend, DELETE=deactivate_stream_backend), # Delete topic in stream rest_path('streams//delete_topic', POST=delete_in_topic), rest_path('default_streams', POST=add_default_stream, DELETE=remove_default_stream), rest_path('default_stream_groups/create', POST=create_default_stream_group), rest_path('default_stream_groups/', PATCH=update_default_stream_group_info, DELETE=remove_default_stream_group), rest_path('default_stream_groups//streams', PATCH=update_default_stream_group_streams), # GET lists your streams, POST bulk adds, PATCH bulk modifies/removes rest_path('users/me/subscriptions', GET=list_subscriptions_backend, POST=add_subscriptions_backend, PATCH=update_subscriptions_backend, DELETE=remove_subscriptions_backend), # muting -> zerver.views.muting rest_path('users/me/subscriptions/muted_topics', PATCH=update_muted_topic), # used to register for an event queue in tornado rest_path('register', POST=events_register_backend), # events -> zerver.tornado.views rest_path('events', GET=get_events, DELETE=cleanup_event_queue), # report -> zerver.views.report # # These endpoints are for internal error/performance reporting # from the browser to the webapp, and we don't expect to ever # include in our API documentation. rest_path('report/error', # Logged-out browsers can hit this endpoint, for portico page JS exceptions. POST=(report_error, {'allow_anonymous_user_web', 'intentionally_undocumented'})), rest_path('report/send_times', POST=(report_send_times, {'intentionally_undocumented'})), rest_path('report/narrow_times', POST=(report_narrow_times, {'allow_anonymous_user_web', 'intentionally_undocumented'})), rest_path('report/unnarrow_times', POST=(report_unnarrow_times, {'allow_anonymous_user_web', 'intentionally_undocumented'})), # Used to generate a Zoom video call URL rest_path('calls/zoom/create', POST=make_zoom_video_call), # Used to generate a Big Blue Button video call URL rest_path('calls/bigbluebutton/create', GET=get_bigbluebutton_url), # export/realm -> zerver.views.realm_export rest_path('export/realm', POST=export_realm, GET=get_realm_exports), rest_path('export/realm/', DELETE=delete_realm_export), ] integrations_view = IntegrationView.as_view() # These views serve pages (HTML). As such, their internationalization # must depend on the URL. # # If you're adding a new page to the website (as opposed to a new # endpoint for use by code), you should add it here. i18n_urls = [ path('', home, name='home'), # We have a desktop-specific landing page in case we change our / # to not log in in the future. We don't want to require a new # desktop app build for everyone in that case path('desktop_home/', desktop_home), # Backwards-compatibility (legacy) Google auth URL for the mobile # apps; see https://github.com/zulip/zulip/issues/13081 for # background. We can remove this once older versions of the # mobile app are no longer present in the wild. path('accounts/login/google/', start_social_login, {"backend": "google"}), path('accounts/login/start/sso/', start_remote_user_sso, name='start-login-sso'), path('accounts/login/sso/', remote_user_sso, name='login-sso'), path('accounts/login/jwt/', remote_user_jwt), path('accounts/login/social/', start_social_login, name='login-social'), path('accounts/login/social//', start_social_login, name='login-social'), path('accounts/register/social/', start_social_signup, name='signup-social'), path('accounts/register/social//', start_social_signup, name='signup-social'), path('accounts/login/subdomain/', log_into_subdomain), path('accounts/login/local/', dev_direct_login, name='login-local'), # We have two entries for accounts/login; only the first one is # used for URL resolution. The second here is to allow # reverse("login") in templates to # return `/accounts/login/`. path('accounts/login/', login_page, {'template_name': 'zerver/login.html'}, name='login_page'), path('accounts/login/', LoginView.as_view(template_name='zerver/login.html'), name='login'), path('accounts/logout/', logout_then_login), path('accounts/webathena_kerberos_login/', webathena_kerberos_login), path('accounts/password/reset/', password_reset, name='password_reset'), path('accounts/password/reset/done/', PasswordResetDoneView.as_view(template_name='zerver/reset_emailed.html')), path('accounts/password/reset///', PasswordResetConfirmView.as_view(success_url='/accounts/password/done/', template_name='zerver/reset_confirm.html', form_class=LoggingSetPasswordForm), name='password_reset_confirm'), path('accounts/password/done/', PasswordResetCompleteView.as_view(template_name='zerver/reset_done.html')), path('accounts/deactivated/', show_deactivation_notice), # Displays digest email content in browser. path('digest/', digest_page), # Registration views, require a confirmation ID. path('accounts/home/', accounts_home), path('accounts/send_confirm/', TemplateView.as_view( template_name='zerver/accounts_send_confirm.html'), name='signup_send_confirm'), path('accounts/new/send_confirm/', TemplateView.as_view( template_name='zerver/accounts_send_confirm.html'), {'realm_creation': True}, name='new_realm_send_confirm'), path('accounts/register/', accounts_register, name='accounts_register'), path('accounts/do_confirm/', check_prereg_key_and_redirect, name='check_prereg_key_and_redirect'), path('accounts/confirm_new_email/', confirm_email_change, name='confirm_email_change'), # Email unsubscription endpoint. Allows for unsubscribing from various types of emails, # including the welcome emails (day 1 & 2), missed PMs, etc. path('accounts/unsubscribe//', email_unsubscribe, name='unsubscribe'), # Portico-styled page used to provide email confirmation of terms acceptance. path('accounts/accept_terms/', accounts_accept_terms, name='accept_terms'), # Find your account path('accounts/find/', find_account, name='find_account'), # Go to organization subdomain path('accounts/go/', realm_redirect, name='realm_redirect'), # Realm creation path('new/', create_realm), path('new/', create_realm, name='create_realm'), # Realm reactivation path('reactivate/', realm_reactivation, name='realm_reactivation'), # Global public streams (Zulip's way of doing archives) path('archive/streams//topics/', archive), path('archive/streams//topics', get_web_public_topics_backend), # Login/registration path('register/', accounts_home, name='register'), path('login/', login_page, {'template_name': 'zerver/login.html'}, name='login_page'), path('join//', accounts_home_from_multiuse_invite, name='join'), # Used to generate a Zoom video call URL path('calls/zoom/register', register_zoom_user), path('calls/zoom/complete', complete_zoom_user), path('calls/zoom/deauthorize', deauthorize_zoom_user), # Used to join a Big Blue Button video call path('calls/bigbluebutton/join', join_bigbluebutton), # API and integrations documentation path('integrations/doc-html/', integration_doc), path('integrations/', integrations_view), path('integrations/', integrations_view), # Landing page, features pages, signup form, etc. path('hello/', hello_view), path('new-user/', RedirectView.as_view(url='/hello', permanent=True)), path('features/', landing_view, {'template_name': 'zerver/features.html'}), path('plans/', plans_view, name='plans'), path('apps/', apps_view), path('apps/download/', app_download_link_redirect), path('apps/', apps_view), path('team/', team_view), path('history/', landing_view, {'template_name': 'zerver/history.html'}), path('why-zulip/', landing_view, {'template_name': 'zerver/why-zulip.html'}), path('for/open-source/', landing_view, {'template_name': 'zerver/for-open-source.html'}), path('for/research/', landing_view, {'template_name': 'zerver/for-research.html'}), path('for/companies/', landing_view, {'template_name': 'zerver/for-companies.html'}), path('for/working-groups-and-communities/', landing_view, {'template_name': 'zerver/for-working-groups-and-communities.html'}), path('security/', landing_view, {'template_name': 'zerver/security.html'}), # Terms of Service and privacy pages. path('terms/', terms_view), path('privacy/', privacy_view), ] # Make a copy of i18n_urls so that they appear without prefix for english urls = list(i18n_urls) # Include the dual-use patterns twice urls += [ path('api/v1/', include(v1_api_and_json_patterns)), path('json/', include(v1_api_and_json_patterns)), ] # user_uploads -> zerver.views.upload.serve_file_backend # # This URL is an exception to the URL naming schemes for endpoints. It # supports both API and session cookie authentication, using a single # URL for both (not 'api/v1/' or 'json/' prefix). This is required to # easily support the mobile apps fetching uploaded files without # having to rewrite URLs, and is implemented using the # 'override_api_url_scheme' flag passed to rest_dispatch urls += [ path('user_uploads/temporary//', serve_local_file_unauthed, name='local_file_unauthed'), rest_path('user_uploads//', GET=(serve_file_backend, {'override_api_url_scheme'})), # This endpoint serves thumbnailed versions of images using thumbor; # it requires an exception for the same reason. rest_path('thumbnail', GET=(backend_serve_thumbnail, {'override_api_url_scheme'})), # Avatars have the same constraint because their URLs are included # in API data structures used by both the mobile and web clients. rest_path('avatar/', GET=(avatar, {'override_api_url_scheme'})), rest_path('avatar//medium', {'medium': True}, GET=(avatar, {'override_api_url_scheme'})), ] # This URL serves as a way to receive CSP violation reports from the users. # We use this endpoint to just log these reports. urls += [ path('report/csp_violations', report_csp_violations), ] # This URL serves as a way to provide backward compatibility to messages # rendered at the time Zulip used camo for doing http -> https conversion for # such links with images previews. Now thumbor can be used for serving such # images. urls += [ path('external_content//', handle_camo_url), ] # Incoming webhook URLs # We don't create URLs for particular Git integrations here # because of generic one below for incoming_webhook in WEBHOOK_INTEGRATIONS: if incoming_webhook.url_object: urls.append(incoming_webhook.url_object) # Desktop-specific authentication URLs urls += [ rest_path('json/fetch_api_key', POST=json_fetch_api_key), ] # Mobile-specific authentication URLs urls += [ # Used as a global check by all mobile clients, which currently send # requests to https://zulip.com/compatibility almost immediately after # starting up. path('compatibility', check_global_compatibility), ] v1_api_mobile_patterns = [ # This json format view used by the mobile apps lists which # authentication backends the server allows as well as details # like the requested subdomains'd realm icon (if known) and # server-specific compatibility. path('server_settings', api_get_server_settings), # This json format view used by the mobile apps accepts a username # password/pair and returns an API key. path('fetch_api_key', api_fetch_api_key), # This is for the signing in through the devAuthBackEnd on mobile apps. path('dev_fetch_api_key', api_dev_fetch_api_key), # This is for fetching the emails of the admins and the users. path('dev_list_users', api_dev_list_users), # Used to present the GOOGLE_CLIENT_ID to mobile apps path('fetch_google_client_id', api_fetch_google_client_id), ] urls += [ path('api/v1/', include(v1_api_mobile_patterns)), ] # View for uploading messages from email mirror urls += [ path('email_mirror_message', email_mirror_message), ] # Include URL configuration files for site-specified extra installed # Django apps for app_name in settings.EXTRA_INSTALLED_APPS: app_dir = os.path.join(settings.DEPLOY_ROOT, app_name) if os.path.exists(os.path.join(app_dir, 'urls.py')): urls += [path('', include(f'{app_name}.urls'))] i18n_urls += import_string(f"{app_name}.urls.i18n_urlpatterns") # Tornado views urls += [ # Used internally for communication between Django and Tornado processes # # Since these views don't use rest_dispatch, they cannot have # asynchronous Tornado behavior. path('notify_tornado', notify), path('api/v1/events/internal', get_events_internal), ] # Python Social Auth urls += [path('', include('social_django.urls', namespace='social'))] urls += [path('saml/metadata.xml', saml_sp_metadata)] # User documentation site help_documentation_view = MarkdownDirectoryView.as_view( template_name='zerver/documentation_main.html', path_template='/zerver/help/%s.md' ) api_documentation_view = MarkdownDirectoryView.as_view( template_name='zerver/documentation_main.html', path_template='/zerver/api/%s.md' ) urls += [ path('help/', help_documentation_view), path('help/', help_documentation_view), path('api/', api_documentation_view), path('api/', api_documentation_view), ] # Two-factor URLs if settings.TWO_FACTOR_AUTHENTICATION_ENABLED: urls += [path('', include(tf_urls)), path('', include(tf_twilio_urls))] if settings.DEVELOPMENT: urls += dev_urls.urls i18n_urls += dev_urls.i18n_urls # The sequence is important; if i18n URLs don't come first then # reverse URL mapping points to i18n URLs which causes the frontend # tests to fail urlpatterns = i18n_patterns(*i18n_urls) + urls + legacy_urls